Patterns

Feed

Everything that happened to a record, in one chronological stream — audit and system events and conversation together, the generic pattern behind a record's History. Events are quick, plain notes on the rail; comments get their own flat, solid card. Every entry stamps its time with Time (relative, exact instant on hover), and events can carry a Badge inline.

A record's history

Events and comments interleaved oldest-to-newest, each with a timestamp — hover any time for the exact instant. Drop a comment at the bottom and it joins the stream.

Tom Becker was created by Dana Whitfield
Sam Cho
Pulled the NMLS record — the id checks out, but the branch assignment looks stale.
Marcus Reyes moved status to Review
Priya Anand edited the branch assignment
Dana Whitfield
Reassigned to the Provo branch — good to approve.
Status set to Active

Materiality

A single rail; the two entry kinds read differently by design. All tokens.

rail
A hairline at the gutter; each entry hangs an opaque node that breaks the line where it sits.
event
No card — just a dot, short fg-2 text, and a quiet Time. A note, not a surface.
comment
A flat, solid-fill card — author + Time up top, then the body. Content earns a card.
time
Every entry stamps through <Time> — relative by default, the exact instant on hover.

Accessibility

  • A list of items. The feed is role=list and each entry role=listitem, so a screen reader announces the count and walks the stream.
  • Times are machine-readable. Each Time is a <time> with the full ISO + an absolute-date tooltip, so "3 days ago" never costs the exact moment.
  • Events don't lean on colour. The action text carries the meaning; an inline Badge follows ADR-0017 (icon/label, not colour alone).

API

<Feed>
The rail + list container. Render Feed.Event / Feed.Comment children in the order you want them shown.
<Feed.Event at icon?>
A terse entry. `at` is an ISO timestamp; `icon` overrides the rail dot; children are the action (may embed a Badge).
<Feed.Comment author at avatar?>
A comment card. `author` + `at` head it; `avatar` (or `icon`) is the rail marker; children are the body.

Usage

A record's historytsx
import { Feed, Badge } from '@halo-compliance/ui'
import { SquarePen } from 'lucide-react'

<Feed>
  <Feed.Event at={officer.createdAt} icon={<UserPlus />}>
    {officer.createdBy} created this officer
  </Feed.Event>

  <Feed.Comment author="Sam Cho" at={comment.at}>
    Verify the NMLS id before we approve.
  </Feed.Comment>

  <Feed.Event at={event.at} icon={<SquarePen />}>
    Status set to <Badge tone="affirm" dot>Active</Badge>
  </Feed.Event>
</Feed>

When to use

  • A record's time-series. Edits, status changes, comments — anything that happened, in one chronological place.
  • Mixed kinds, one stream. When events and conversation belong together (the GitHub model), not split across a log and a comments tab.
  • Not a data table. If the rows are homogeneous and you need sort / filter / columns, that's a DataTable, not a Feed.

Anti-patterns

  • A separate log and comments tab. Splitting the stream is the very thing Feed exists to avoid — interleave them.
  • Boxing events in. An event is a quick note; wrapping it in a card makes it compete with the real comments and clutters the rail.
  • Absolute-only timestamps. A long feed reads faster relative ("2 hours ago"); the exact time stays on hover.
  • /record — the detail view whose History region this fills.
  • /time — the timestamp on every entry.
  • /badge — the status plaque an event carries inline.
  • /tabs — History is one of a record's tabbed sections.