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
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
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.