UpNext / Activity Feed

Activity Feed

<ActivityFeed />Stable

The athlete activity thread — one component for every place the app narrates history: the manage-athlete panel, board side sheets, list detail. A Badge states, a Chip acts, an activity event narrates. Pure presentation over an events array — no fetching, no ordering, no state. Each event kind wears one outfit (a soft tone tile — the tone at ~20% + the ink-colored registry glyph, a couple notches deeper than the standard soft so the small glyph grounds; scoped to the feed) via the ACTIVITY_KIND map — yellow for lists, gold for smart lists — both wearing the ListCard's own drawn list glyph, the corner sparkle marking smart, purple for boards, and semantic tones for milestones — actor events lead with the Avatar primitive, and every entity reference — a list, a board, a school, a coach — is a REAL Link control with its underline visible at rest, never a hover-revealed affordance. University mentions carry a small crest via the base Avatar. Every color resolves through tokens; dark mode ships free.

import { ActivityFeed, type ActivityEvent } from "@/components/upnext";

Manage-athlete thread — live replica

The shipped Activity view, 1:1: status changes, portal entry, board/list adds, custom tags, transfers, and coach notes + assignments in one thread. Every bold mention is a Link control — click one and the readout below names it. The page owns the events array, timestamp formatting, and what each mention does; the feed just narrates.

  • Status changed to

    May 31, 2026

  • Entered the

    May 30, 2026

  • CV

    May 28, 2026

    OV scheduled 6/14. Bringing both parents.

  • SH

    assigned to evaluate player

    May 28, 2026

  • Added to the

    May 25, 2026

  • Auto-added to smart list — matched its criteria

    May 24, 2026

  • Added to list

    May 23, 2026

  • CD

    May 20, 2026

    Spoke with his QB coach — confirmed he wants a place he can start Week 1. Tape vs ranked opponents is legit.

  • Custom tagged

    May 20, 2026

  • CD

    May 18, 2026

    Family prioritizes academics + NIL. Set up a visit for the first week of June.

  • CD

    started a conversation

    May 17, 2026

  • Transferred from to

    May 15, 2026

Click any bold mention (list, board, school, coach) — they're Link controls.

Inspector

Edit one prop at a time and see the token chain resolve. The preview re-renders, the code panel updates, and the resolves-to column shows every Tailwind class → CSS variable → final value.

Preview

Code

import { ActivityFeed } from "@/components/upnext";
<ActivityFeed />

Shapes

Three shapes cover the thread: an icon event (kind → tile + glyph), an actor line (assignments, 'started a conversation'), and an actor note (name and timestamp share the header, the body sits under it). Mentions are Link controls — href renders an anchor, onSelect a button, neither plain bold text; the underline is visible at rest so the affordance never hides.

  • Status changed to Offered

    May 31, 2026

<ActivityFeed events={[{
id: "s",
kind: "status", // → violet tile + status glyph
timestamp: "May 31, 2026",
parts: ["Status changed to ", { label: "Offered", href: "/status" }],
}]} />

Composition

The kind taxonomy — 17 event kinds, each a fixed tone + glyph in ACTIVITY_KIND (the documented convention; every tile is a feed-scoped deeper-soft fill — the tone at ~20% — so all colors are token-driven). Two rules hold at once. (1) Status / milestone events STICK TO OUR SEMANTIC CONVENTION — committed=success, decommitted=error, flagged=warning, offered=info — because they carry a judgement. (2) Categorical kinds are spread ACROSS the hue wheel so no two neighbours read alike, with stable pairings: yellow=list, gold+sparkle=smart list (its AI/dynamic sibling), purple=board, orange=tag, teal=transfer, cocoa=evaluation, magenta=film, pink=visit. A run of activity is a rainbow, and the valence still reads by color. And one kind covers a whole LIFECYCLE — created, added, auto-added are narration verbs on the same outfit (see List lifecycle): the tile names the object family, the words name the moment, and creation stays a kind-tile event so the standard vs smart tell never hides.

// 17 kinds → 17 tones + glyphs, one map (ACTIVITY_KIND).
ACTIVITY_KINDS.map((kind) => ({ id: kind, kind, timestamp, parts }))
// milestone: status·committed·offered·signed·decommitted·flagged
// movement: portal·transfer · organization: list·smartList·board·tag
// scouting: evaluation·film·visit · communication: call·message

Source

A domain component composed from atoms: Avatar (actor identity + university crests), the Icon registry (one glyph per kind via ACTIVITY_KIND; the list pair wears the ListCard's drawn ListTypeGlyph so lists look identical everywhere), and Link (interactive mentions). No fetching or state — the page supplies the events and wires each mention.