Trace Waterfall
betaSpan tree on a shared time axis for agent runs: nested agent, LLM, tool and retrieval spans, live bars, a details row and keyboard tree navigation.
A finished turn
minimal
Trace4 spans14.2s29.4k tok$0.14
Kuraclaude-opus-5-514.2s
Plan the fix6.4s
read_filesrc/auth/client.ts400ms
Write the patch7.2s
Replay of a run
The 12-minute auth-client migration replayed on a controlled clock: subagents, a failing pnpm test (selected, with its error), the fix, review and PR. The axis rescales as the run grows.
full
Trace14 spans4m 40s230k tok$0.86
Kuraacme/web · kura/auth-client4m 40s
Plan the migration9.8s
read_filesrc/app/api/session/route.ts400ms
search_codefetchUser(1.7s
explorersubagent49.5s
Draft the client patch37.0s
edit_filesrc/auth/client.ts700ms
edit_filesrc/middleware.ts600ms
test-writersubagent2m 28s
Write session tests2m 19s
edit_filesrc/auth/login.test.ts1.0s
Live, from a wall clock
A subagent that is still running: `startedAt` makes running bars grow in real time.
state
test-writer6 spans25.2s34.1k tok$0.14
test-writersubagent25.2s
read_filesrc/auth/login.test.ts500ms
search_coderefreshSession1.6s
Write session tests21.3s
edit_filesrc/auth/session.test.ts700ms
bashpnpm vitest session0ms
Custom details
`renderDetails` shows the edit a tool made as a CodeBlock.
composition
Tool calls4 spans9.3s76.3k tok$0.29
Fix token refresh9.3s
Diagnose the failure7.9s
edit_filesrc/auth/client.ts400ms
src/auth/client.tsts
bashpnpm test login800ms
Installation
npx shadcn@latest add https://kuro.abstergo.fyi/r/trace-waterfall.jsonCopies the source into components/kura/trace-waterfall with its dependencies (kura-theme, kura-format, status-glyph, use-controllable-state, use-elapsed, utils). Tokens come from the kura-theme item.
When to use it
Use it for
- Show where the time (and tokens, and money) went in an agent run: which model calls, tool calls and subagents ran, nested, and when.
- Debug a slow or failed run: spot the long LLM call, the failed `pnpm test`, the subagent that ran in parallel.
- Watch a run live: running spans grow against the axis and a 'now' line moves (pass `startedAt` or drive `now`).
- Inspect one span's input, output, error, model, tokens and cost inline by selecting it.
Reach for something else when
- The agent's plan or todo list (what it intends to do) — use task-list.
- A narrative of steps with tool calls attached — use agent-timeline.
- Parallel subagents as a live roster with activity and progress — use agent-fleet.
- A single tool invocation — use tool-call.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| spans* | readonly TraceSpan[] | — | Root spans (usually one agent span). TraceSpan = { id, name, kind: "agent" | "llm" | "tool" | "retrieval", start (ms since run start), end? (omit while running), status: AgentStatus, meta? (mono hint: path, command), model?, tokens? { input, output }, cost? (USD), input?, output?, error?, children? }. |
| title | React.ReactNode | "Trace" | Header title. |
| actions | React.ReactNode | — | Controls on the right of the header (e.g. a replay or open-in-LangSmith button). |
| now | number | — | Current run time in ms since start (a controlled clock). Running spans end here and the 'now' line sits here. Use it for replays and server-driven clocks. |
| startedAt | Date | number | — | Wall-clock start of the run. When given (and `now` isn't), running spans grow live, ticking every 250ms. Without either, running spans end at the latest known time. |
| selectedId | string | null | — | Selected span (controlled). Its details row opens under it. |
| defaultSelectedId | string | null | null | Initially selected span when uncontrolled. May name a span that doesn't exist yet; it opens when the span appears. |
| onSelectedIdChange | (id: string | null) => void | — | Called when a span is selected, or with null when the selection is cleared. |
| defaultCollapsedIds | readonly string[] | — | Spans whose subtrees start collapsed (e.g. noisy subagents). |
| renderDetails | (span: TraceSpan) => React.ReactNode | — | Replace the details row body for a span (e.g. a CodeBlock with the diff a tool wrote). Return null to use the default stats + input/output. |
| empty | React.ReactNode | — | Rendered when there are no spans. Default: 'No spans yet'. |
| label | string | "Trace" | Accessible name of the tree. |
All native attributes of the root element, className and ref pass through.
Anatomy
- TraceWaterfallRoot panel, a container-query context. `data-state="running" | "idle"`; contains a polite status region.
data-slot="trace-waterfall" - Headerh-10 header: title, summary, actions.
data-slot="trace-waterfall-header" - SummaryMono: span count · total duration · tokens · cost. Count/tokens/cost hide on narrow containers.
data-slot="trace-waterfall-summary" - AxisSticky row with 'Span / Duration' over the tree and nice tick labels (0, 2m, 4m…) over the bars.
data-slot="trace-waterfall-axis" - BodyScroll container. Give the root a height (e.g. `h-[480px]`) to scroll long traces under the sticky axis.
data-slot="trace-waterfall-body" - Spanrole="treeitem" with `data-kind`, `data-status`, `data-selected`; wraps the row and, when selected, its details.
data-slot="trace-waterfall-span" - Rowh-8 grid row: indented tree cell (chevron, kind icon, name, mono meta, glyph for running/failed/waiting, duration) and the bar cell.
data-slot="trace-waterfall-row" - ToggleChevron that collapses a subtree (pointer only; ArrowLeft/Right on the keyboard).
data-slot="trace-waterfall-toggle" - BarPositioned on the shared axis. Neutral greys by kind (LLM darkest), thin for agent spans; blue while running, red failed, amber waiting, ink when selected.
data-slot="trace-waterfall-bar" - NowHairline at the current time while any span is running.
data-slot="trace-waterfall-now" - DetailsSunken row under the selected span: kind, start, duration, model, tokens, cost, then input and output/error.
data-slot="trace-waterfall-details" - DurationMono, tabular, width-reserved; ticks while the span runs.
data-slot="trace-waterfall-duration" - EmptyRendered when there are no spans.
data-slot="trace-waterfall-empty"
Accessibility
- Role:
tree - A WAI-ARIA tree: each span is a treeitem with aria-level/posinset/setsize, aria-expanded for subtrees and aria-selected; one roving tab stop.
- Each treeitem is labelled “bash pnpm test, tool, 38.4s, failed” — status is spoken, never colour-only. The selected span is described by its details row.
- A polite status region announces failures and “Trace complete, 11m 52s” — ticking durations are not announced.
- The axis, grid lines, bars and now-line are decorative (aria-hidden or inside labelled items).
- Reduced motion: Bars and the now-line jump to their new positions instead of easing; the chevron turns instantly; rows and details only fade.
| Keys | Action |
|---|---|
| ArrowDown / ArrowUp | Moves focus to the next / previous visible span. |
| ArrowRight | Expands a collapsed span; on an expanded span moves to its first child. |
| ArrowLeft | Collapses an expanded span; otherwise moves to the parent span. |
| Home / End | First / last visible span. |
| Enter / Space | Selects the focused span (opens details); again to close. |
| Escape | Clears the selection. |
Notes for agents
- Times are ms relative to the run start, not timestamps: convert with `span.start = +new Date(event.ts) - runStart`.
- Omit `end` (and set status running) for unfinished spans; pass `startedAt` (wall clock) or `now` so they grow.
- Put tokens/cost on leaf spans only. If every root span carries totals, the header uses the roots instead of summing.
- Ids must be unique across the whole tree; they drive selection, collapse state and focus.
- Give the root a fixed height for long traces; the axis stays sticky while the body scrolls.