Installation
npx shadcn@latest add @pixeldosa/activity-trailRequires `cn` at `@/lib/utils`. Write `action` as the consequence ("Emailed the summary to 3 people"), never as a function name.
Usage
"use client";
import { ActivityTrail } from "@/components/ui/activity-trail";
<ActivityTrail entries={entries} onUndo={undo} onExport={exportTrail} />
Why it exists
Audit logging is becoming a legal requirement for high-risk AI, and regulated industries now expect months of retention. But those records are built for auditors. The person whose inbox the agent used needs a different thing: one readable line per action, and the answer to "who let it do that?".
Features
- The authority is a field. "You approved it", "Autonomy: acts on low-risk changes", "Schedule set by Dana" — the line every log leaves out.
- Actions read as consequences, not function names.
- Undo where it still applies, driven by your
undoableflag, because only you know what's still reversible. - One array, two audiences.
onExporthands the same records to your compliance process, so the readable view and the audit record can't drift apart. - Oldest first, the way a story is read, so causation runs forwards.
Props
| Prop | Type | Notes |
|---|---|---|
entries | { id, time, actor, actorName?, action, target?, authority?, result?, undoable? }[] | actor is agent, person or system. time is formatted by you. |
onUndo | (id: string) => void | Shown on entries you mark undoable. |
onExport / exportLabel | () => void / string | |
emptyLabel | string | |
title | string | Defaults to "What happened". |
headingLevel | 2 | 3 | 4 | 5 | 6 | Defaults to 3. |
Accessibility
- An ordered list inside a labelled
<section>. - Every field is text: actor, authority and result are read in order.
- Nothing animates, so the record never looks like it's still being written.
When not to use
- A developer event log. Raw tool calls belong in
Tool Call Card. - A chat transcript. This is what was done, not what was said.
- As your only compliance store. Export to a system built for retention.
why it's built this way
Engineering Notes
Entries are newest last, the order a story is read in, rather than the newest-first order of a log — this surface is meant to be reconstructed rather than monitored, and reversing it makes causation read backwards. `authority` is a required-shaped optional: nothing enforces it, but its absence in a row is visible, which pushes teams to record what permitted an action at the moment they render it rather than reconstructing it later from three systems. Undo is per entry and driven by the caller's `undoable` flag, because only the caller knows whether a specific action is still reversible; the component never infers reversibility from the action's words. Export exists so the human-readable trail and the compliance record come from one array instead of two implementations that disagree.
how it moves, and when it doesn't
Motion Notes
Nothing animates. Every row describes something that already happened, and this system reserves motion for a machine that is still busy; an entry that slid or faded in would imply the record was still being written. Only the standard instant colour change on Undo, dropped under `prefers-reduced-motion`.