{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "trace-waterfall",
  "type": "registry:ui",
  "title": "Trace Waterfall",
  "description": "Span 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.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "https://kuro.abstergo.fyi/r/kura-theme.json",
    "https://kuro.abstergo.fyi/r/kura-format.json",
    "https://kuro.abstergo.fyi/r/status-glyph.json",
    "https://kuro.abstergo.fyi/r/use-controllable-state.json",
    "https://kuro.abstergo.fyi/r/use-elapsed.json",
    "utils"
  ],
  "files": [
    {
      "path": "registry/kura/trace-waterfall/index.ts",
      "type": "registry:ui",
      "target": "components/kura/trace-waterfall/index.ts",
      "content": "export {\n  formatTick,\n  niceStep,\n  type TraceSpan,\n  type TraceSpanKind,\n  TraceWaterfall,\n  type TraceWaterfallProps,\n} from \"./trace-waterfall\";\n"
    },
    {
      "path": "registry/kura/trace-waterfall/trace-waterfall.tsx",
      "type": "registry:ui",
      "target": "components/kura/trace-waterfall/trace-waterfall.tsx",
      "content": "\"use client\";\n\nimport { Bot, BrainCircuit, ChevronRight, FileSearch, Waypoints, Wrench } from \"lucide-react\";\nimport * as React from \"react\";\nimport { useControllableState } from \"@/hooks/kura/use-controllable-state\";\nimport { useElapsed } from \"@/hooks/kura/use-elapsed\";\nimport { cn } from \"@/lib/utils\";\nimport { formatCost, formatDuration, formatTokens } from \"@/lib/kura/format\";\nimport { type AgentStatus, StatusGlyph } from \"@/components/kura/status-glyph\";\n\nexport type TraceSpanKind = \"agent\" | \"llm\" | \"tool\" | \"retrieval\";\n\nexport interface TraceSpan {\n  /** Stable, unique across the whole tree. */\n  id: string;\n  /** What ran: \"explorer\", \"Plan the migration\", \"bash\". */\n  name: string;\n  kind: TraceSpanKind;\n  /** Start, in ms since the run started. */\n  start: number;\n  /** End, in ms since the run started. Omit while running: the bar grows to \"now\". */\n  end?: number;\n  status: AgentStatus;\n  /** Short machine hint after the name, in mono: a path, a command, a model. */\n  meta?: string;\n  /** Model used by an llm span. */\n  model?: string;\n  tokens?: { input: number; output: number };\n  /** USD. */\n  cost?: number;\n  /** Shown in the details row. Strings render as mono, preserving whitespace. */\n  input?: React.ReactNode;\n  output?: React.ReactNode;\n  /** Shown in the details row in red. */\n  error?: React.ReactNode;\n  children?: readonly TraceSpan[];\n}\n\nexport interface TraceWaterfallProps\n  extends Omit<React.ComponentProps<\"div\">, \"title\" | \"children\"> {\n  /** Root spans (usually one agent span). Times are ms since the run started. */\n  spans: readonly TraceSpan[];\n  /** Header title. Default \"Trace\". */\n  title?: React.ReactNode;\n  /** Controls on the right of the header. */\n  actions?: React.ReactNode;\n  /** The run's current time in ms since start (a controlled clock). Running spans end here. */\n  now?: number;\n  /** Wall-clock start of the run. When given (and `now` isn't), running spans grow live. */\n  startedAt?: Date | number;\n  /** Selected span (controlled). Its details row opens under it. */\n  selectedId?: string | null;\n  /** Initially selected span when uncontrolled. */\n  defaultSelectedId?: string | null;\n  onSelectedIdChange?: (id: string | null) => void;\n  /** Spans whose children start collapsed. */\n  defaultCollapsedIds?: readonly string[];\n  /** Replace the details row's body for a span (return null to fall back to the default). */\n  renderDetails?: (span: TraceSpan) => React.ReactNode;\n  /** Rendered when there are no spans. */\n  empty?: React.ReactNode;\n  /** Accessible name of the tree. Default \"Trace\". */\n  label?: string;\n}\n\ninterface Row {\n  span: TraceSpan;\n  depth: number;\n  parentId: string | null;\n  posinset: number;\n  setsize: number;\n}\n\nconst KIND_ICON: Record<TraceSpanKind, typeof Bot> = {\n  agent: Bot,\n  llm: BrainCircuit,\n  tool: Wrench,\n  retrieval: FileSearch,\n};\n\nconst KIND_LABEL: Record<TraceSpanKind, string> = {\n  agent: \"agent\",\n  llm: \"LLM call\",\n  tool: \"tool\",\n  retrieval: \"retrieval\",\n};\n\nconst STATUS_WORD: Record<AgentStatus, string> = {\n  queued: \"queued\",\n  running: \"running\",\n  waiting: \"waiting for you\",\n  done: \"done\",\n  failed: \"failed\",\n  skipped: \"skipped\",\n  paused: \"paused\",\n};\n\nconst STEPS = [\n  50, 100, 200, 250, 500, 1000, 2000, 5000, 10_000, 15_000, 30_000, 60_000, 120_000, 300_000,\n  600_000, 900_000, 1_800_000, 3_600_000, 7_200_000,\n];\n\n/** A tick step that yields about four to seven ticks across `total`. */\nexport function niceStep(total: number): number {\n  const raw = total / 6;\n  return STEPS.find((s) => s >= raw) ?? STEPS[STEPS.length - 1]!;\n}\n\n/** Axis label: 0, 250ms, 1.5s, 30s, 2m, 2m 30s, 1h. */\nexport function formatTick(ms: number): string {\n  if (ms === 0) return \"0\";\n  if (ms < 1000) return `${ms}ms`;\n  if (ms < 60_000) return `${+(ms / 1000).toFixed(1)}s`;\n  if (ms < 3_600_000) {\n    const m = Math.floor(ms / 60_000);\n    const s = Math.round((ms % 60_000) / 1000);\n    return s ? `${m}m ${s}s` : `${m}m`;\n  }\n  const h = Math.floor(ms / 3_600_000);\n  const m = Math.round((ms % 3_600_000) / 60_000);\n  return m ? `${h}h ${m}m` : `${h}h`;\n}\n\nfunction walk(spans: readonly TraceSpan[], fn: (s: TraceSpan) => void) {\n  for (const s of spans) {\n    fn(s);\n    if (s.children) walk(s.children, fn);\n  }\n}\n\nfunction flatten(\n  spans: readonly TraceSpan[],\n  collapsed: ReadonlySet<string>,\n  depth = 0,\n  parentId: string | null = null,\n  out: Row[] = [],\n): Row[] {\n  spans.forEach((span, i) => {\n    out.push({ span, depth, parentId, posinset: i + 1, setsize: spans.length });\n    if (span.children?.length && !collapsed.has(span.id))\n      flatten(span.children, collapsed, depth + 1, span.id, out);\n  });\n  return out;\n}\n\nfunction barTone(span: TraceSpan, selected: boolean): string {\n  switch (span.status) {\n    case \"running\":\n      return \"bg-kura-accent\";\n    case \"failed\":\n      return \"bg-kura-danger\";\n    case \"waiting\":\n      return \"bg-kura-warning\";\n    case \"queued\":\n    case \"skipped\":\n      return \"bg-transparent shadow-[inset_0_0_0_1px_var(--kura-border-strong)]\";\n    case \"paused\":\n      return \"bg-kura-gray-7\";\n  }\n  if (selected) return \"bg-kura-fg\";\n  if (span.kind === \"agent\") return \"bg-kura-gray-8\";\n  if (span.kind === \"llm\") return \"bg-kura-gray-11\";\n  return \"bg-kura-gray-9\";\n}\n\nconst TRACK = \"absolute inset-y-0 left-3 right-5\";\nconst GRID =\n  \"grid grid-cols-[minmax(0,58%)_1fr] @sm:grid-cols-[minmax(150px,54%)_1fr] @lg:grid-cols-[minmax(176px,46%)_1fr] @3xl:grid-cols-[minmax(300px,40%)_1fr]\";\n\nfunction Text({ children, tone }: { children: React.ReactNode; tone?: \"danger\" }) {\n  return (\n    <div\n      className={cn(\n        \"max-h-44 overflow-auto whitespace-pre-wrap break-words rounded-kura-md bg-kura-surface px-2.5 py-2 font-kura-mono text-[12px] leading-[18px] shadow-kura-xs\",\n        tone === \"danger\" ? \"text-kura-danger-text\" : \"text-kura-fg-muted\",\n      )}\n    >\n      {children}\n    </div>\n  );\n}\n\nfunction Stat({ label, children }: { label: string; children: React.ReactNode }) {\n  return (\n    <div className=\"flex min-w-0 flex-col gap-0.5\">\n      <dt className=\"text-[11.5px] text-kura-fg-subtle\">{label}</dt>\n      <dd className=\"truncate font-kura-mono text-[12.5px] text-kura-fg tabular-nums\">\n        {children}\n      </dd>\n    </div>\n  );\n}\n\nfunction Details({ span, end }: { span: TraceSpan; end: number }) {\n  const running = span.status === \"running\";\n  return (\n    <div className=\"flex flex-col gap-3\">\n      <dl className=\"flex flex-wrap gap-x-8 gap-y-2\">\n        <Stat label=\"Kind\">{span.kind}</Stat>\n        <Stat label=\"Start\">+{formatDuration(span.start)}</Stat>\n        <Stat label=\"Duration\">\n          {formatDuration(end - span.start)}\n          {running && <span className=\"text-kura-fg-subtle\"> so far</span>}\n        </Stat>\n        {span.model && <Stat label=\"Model\">{span.model}</Stat>}\n        {span.tokens && (\n          <Stat label=\"Tokens\">\n            {formatTokens(span.tokens.input)}\n            <span className=\"text-kura-fg-subtle\"> in · </span>\n            {formatTokens(span.tokens.output)}\n            <span className=\"text-kura-fg-subtle\"> out</span>\n          </Stat>\n        )}\n        {span.cost != null && <Stat label=\"Cost\">{formatCost(span.cost)}</Stat>}\n      </dl>\n      {(span.input != null || span.output != null || span.error != null) && (\n        <div className=\"grid gap-3 @xl:grid-cols-2\">\n          {span.input != null && (\n            <div className=\"flex min-w-0 flex-col gap-1.5\">\n              <span className=\"text-[11.5px] text-kura-fg-subtle\">Input</span>\n              <Text>{span.input}</Text>\n            </div>\n          )}\n          {(span.output != null || span.error != null) && (\n            <div className=\"flex min-w-0 flex-col gap-1.5\">\n              <span className=\"text-[11.5px] text-kura-fg-subtle\">\n                {span.error != null ? \"Error\" : \"Output\"}\n              </span>\n              <Text tone={span.error != null ? \"danger\" : undefined}>\n                {span.error ?? span.output}\n              </Text>\n            </div>\n          )}\n        </div>\n      )}\n    </div>\n  );\n}\n\n/**\n * A span tree on a shared time axis, LangSmith/Honeycomb style: the left column is an indented tree\n * (kind icon, name, mono hint, duration), the right column draws each span as a bar against common\n * ticks. Running spans grow live; selecting a span opens a details row (timing, tokens, cost, input\n * and output); subtrees collapse; the tree is fully keyboard navigable.\n */\nexport function TraceWaterfall({\n  spans,\n  title = \"Trace\",\n  actions,\n  now: nowProp,\n  startedAt,\n  selectedId: selectedProp,\n  defaultSelectedId = null,\n  onSelectedIdChange,\n  defaultCollapsedIds,\n  renderDetails,\n  empty,\n  label = \"Trace\",\n  className,\n  ...props\n}: TraceWaterfallProps) {\n  const [selectedId, setSelectedId] = useControllableState<string | null>({\n    value: selectedProp,\n    defaultValue: defaultSelectedId,\n    onChange: onSelectedIdChange,\n  });\n  const [collapsed, setCollapsed] = React.useState<ReadonlySet<string>>(\n    () => new Set(defaultCollapsedIds),\n  );\n  const [focusedId, setFocusedId] = React.useState<string | null>(null);\n  const itemRefs = React.useRef(new Map<string, HTMLDivElement>());\n  const focusOnUpdate = React.useRef(false);\n  const detailsId = React.useId();\n\n  // Aggregate facts over the whole tree.\n  let anyRunning = false;\n  let count = 0;\n  let maxKnown = 0;\n  let tokens = 0;\n  let cost = 0;\n  let hasTokens = false;\n  let hasCost = false;\n  let failed = 0;\n  walk(spans, (s) => {\n    count++;\n    if (s.status === \"running\") anyRunning = true;\n    if (s.status === \"failed\") failed++;\n    maxKnown = Math.max(maxKnown, s.end ?? s.start, s.start);\n    if (s.tokens) {\n      hasTokens = true;\n      tokens += s.tokens.input + s.tokens.output;\n    }\n    if (s.cost != null) {\n      hasCost = true;\n      cost += s.cost;\n    }\n  });\n  // Cost and tokens live on leaves; avoid double counting when parents also report totals.\n  const rootTotals = spans.every((s) => s.tokens || s.cost != null);\n  if (rootTotals) {\n    tokens = spans.reduce((a, s) => a + (s.tokens ? s.tokens.input + s.tokens.output : 0), 0);\n    cost = spans.reduce((a, s) => a + (s.cost ?? 0), 0);\n  }\n\n  const live = useElapsed(nowProp == null ? startedAt : null, anyRunning, 250);\n  const now = nowProp ?? live ?? maxKnown;\n  const endOf = (s: TraceSpan) => s.end ?? Math.max(now, s.start);\n\n  let total = 0;\n  walk(spans, (s) => {\n    total = Math.max(total, endOf(s));\n  });\n  total = Math.max(total, 1);\n  const step = niceStep(total);\n  const axis = Math.max(step, Math.ceil((total * 1.04) / step) * step);\n  const ticks: number[] = [];\n  for (let t = 0; t <= axis + 1e-6; t += step) ticks.push(t);\n  const pct = (ms: number) => `${Math.min(100, Math.max(0, (ms / axis) * 100))}%`;\n\n  const rows = flatten(spans, collapsed);\n  const rowIndex = new Map(rows.map((r, i) => [r.span.id, i] as const));\n  const activeFocus =\n    focusedId != null && rowIndex.has(focusedId)\n      ? focusedId\n      : selectedId != null && rowIndex.has(selectedId)\n        ? selectedId\n        : (rows[0]?.span.id ?? null);\n\n  React.useEffect(() => {\n    if (!focusOnUpdate.current || activeFocus == null) return;\n    focusOnUpdate.current = false;\n    itemRefs.current.get(activeFocus)?.focus();\n  });\n\n  const moveFocus = (id: string | undefined) => {\n    if (id == null) return;\n    focusOnUpdate.current = true;\n    setFocusedId(id);\n  };\n  const toggle = (id: string, open?: boolean) =>\n    setCollapsed((prev) => {\n      const next = new Set(prev);\n      const isOpen = !prev.has(id);\n      if ((open ?? !isOpen) === true) next.delete(id);\n      else next.add(id);\n      return next;\n    });\n  const select = (id: string) => setSelectedId((cur) => (cur === id ? null : id));\n\n  const onItemKeyDown = (e: React.KeyboardEvent<HTMLDivElement>, row: Row) => {\n    if (e.target !== e.currentTarget) return;\n    const i = rowIndex.get(row.span.id) ?? 0;\n    const hasKids = !!row.span.children?.length;\n    const open = hasKids && !collapsed.has(row.span.id);\n    let handled = true;\n    switch (e.key) {\n      case \"ArrowDown\":\n        moveFocus(rows[i + 1]?.span.id);\n        break;\n      case \"ArrowUp\":\n        moveFocus(rows[i - 1]?.span.id);\n        break;\n      case \"Home\":\n        moveFocus(rows[0]?.span.id);\n        break;\n      case \"End\":\n        moveFocus(rows[rows.length - 1]?.span.id);\n        break;\n      case \"ArrowRight\":\n        if (hasKids && !open) toggle(row.span.id, true);\n        else if (open) moveFocus(rows[i + 1]?.span.id);\n        break;\n      case \"ArrowLeft\":\n        if (open) toggle(row.span.id, false);\n        else if (row.parentId) moveFocus(row.parentId);\n        break;\n      case \"Enter\":\n      case \" \":\n        select(row.span.id);\n        break;\n      case \"Escape\":\n        if (selectedId != null) setSelectedId(null);\n        else handled = false;\n        break;\n      default:\n        handled = false;\n    }\n    if (handled) e.preventDefault();\n  };\n\n  const runningNow = anyRunning ? now : null;\n  const announcement = failed\n    ? `${failed} span${failed > 1 ? \"s\" : \"\"} failed`\n    : count > 0 && !anyRunning\n      ? `Trace complete, ${formatDuration(total)}`\n      : \"\";\n\n  return (\n    <div\n      data-slot=\"trace-waterfall\"\n      data-state={anyRunning ? \"running\" : \"idle\"}\n      className={cn(\n        \"@container flex min-w-0 flex-col overflow-hidden rounded-kura-xl bg-kura-surface font-kura-sans text-kura-fg shadow-kura-sm\",\n        className,\n      )}\n      {...props}\n    >\n      <div\n        data-slot=\"trace-waterfall-header\"\n        className=\"flex h-10 shrink-0 items-center gap-3 border-kura-border-subtle border-b px-3.5\"\n      >\n        <Waypoints aria-hidden strokeWidth={1.75} className=\"size-3.5 text-kura-fg-subtle\" />\n        <span className=\"font-medium text-[13px] tracking-[-0.005em]\">{title}</span>\n        <span className=\"flex-1\" />\n        {count > 0 && (\n          <span\n            data-slot=\"trace-waterfall-summary\"\n            className=\"flex items-center gap-1.5 whitespace-nowrap font-kura-mono text-[12px] text-kura-fg-subtle tabular-nums\"\n          >\n            <span className=\"hidden @md:inline\">{count} spans</span>\n            <span aria-hidden className=\"hidden @md:inline\">\n              ·\n            </span>\n            <span className={cn(\"min-w-[6ch] text-right\", anyRunning && \"text-kura-fg-muted\")}>\n              {formatDuration(total)}\n            </span>\n            {hasTokens && (\n              <>\n                <span aria-hidden className=\"hidden @lg:inline\">\n                  ·\n                </span>\n                <span className=\"hidden @lg:inline\">{formatTokens(tokens)} tok</span>\n              </>\n            )}\n            {hasCost && (\n              <>\n                <span aria-hidden className=\"hidden @lg:inline\">\n                  ·\n                </span>\n                <span className=\"hidden @lg:inline\">{formatCost(cost)}</span>\n              </>\n            )}\n          </span>\n        )}\n        {actions}\n      </div>\n\n      {count === 0 ? (\n        <div\n          data-slot=\"trace-waterfall-empty\"\n          className=\"flex flex-col items-center gap-1 px-6 py-10 text-center\"\n        >\n          {empty ?? (\n            <>\n              <span className=\"font-medium text-[13px]\">No spans yet</span>\n              <span className=\"text-[12px] text-kura-fg-subtle\">\n                Spans appear here as the agent calls models and tools.\n              </span>\n            </>\n          )}\n        </div>\n      ) : (\n        <div data-slot=\"trace-waterfall-body\" className=\"relative min-h-0 flex-1 overflow-auto\">\n          {/* Axis */}\n          <div\n            data-slot=\"trace-waterfall-axis\"\n            aria-hidden\n            className={cn(\n              GRID,\n              \"sticky top-0 z-20 h-7 border-kura-border-subtle border-b bg-kura-surface text-[11px] text-kura-fg-subtle\",\n            )}\n          >\n            <div className=\"flex items-center justify-between border-kura-border-subtle border-r px-3.5\">\n              <span>Span</span>\n              <span className=\"hidden @md:inline\">Duration</span>\n            </div>\n            <div className=\"relative\">\n              <div className={TRACK}>\n                {ticks.map((t, i) => (\n                  <span\n                    key={t}\n                    className={cn(\n                      \"absolute top-1/2 -translate-y-1/2 whitespace-nowrap font-kura-mono tabular-nums\",\n                      i === 0 ? \"\" : \"-translate-x-1/2\",\n                      // Narrow containers keep only the first and last labels.\n                      i > 0 && i < ticks.length - 1 && \"hidden @lg:inline\",\n                      // Keep the outermost labels inside the track.\n                      i === ticks.length - 1 && \"-translate-x-full\",\n                    )}\n                    style={{ left: pct(t) }}\n                  >\n                    {formatTick(t)}\n                  </span>\n                ))}\n                {anyRunning && (\n                  <span\n                    className=\"absolute bottom-0 h-2 w-px bg-kura-accent transition-[left] duration-(--kura-duration-base) ease-linear motion-reduce:transition-none\"\n                    style={{ left: pct(now) }}\n                  />\n                )}\n              </div>\n            </div>\n          </div>\n\n          <div className=\"relative\">\n            {/* Grid lines and the \"now\" marker, under the rows. */}\n            <div aria-hidden className={cn(GRID, \"pointer-events-none absolute inset-0 z-0\")}>\n              <div className=\"border-kura-border-subtle border-r\" />\n              <div className=\"relative\">\n                <div className={TRACK}>\n                  {ticks.map((t) => (\n                    <span\n                      key={t}\n                      className=\"absolute inset-y-0 w-px bg-kura-border-subtle\"\n                      style={{ left: pct(t) }}\n                    />\n                  ))}\n                  {runningNow != null && (\n                    <span\n                      data-slot=\"trace-waterfall-now\"\n                      className=\"absolute inset-y-0 w-px bg-kura-accent transition-[left] duration-(--kura-duration-base) ease-linear motion-reduce:transition-none\"\n                      style={{ left: pct(runningNow) }}\n                    />\n                  )}\n                </div>\n              </div>\n            </div>\n\n            <div role=\"tree\" aria-label={label} className=\"relative z-10 py-1\">\n              {rows.map((row) => {\n                const { span, depth } = row;\n                const end = endOf(span);\n                const hasKids = !!span.children?.length;\n                const open = hasKids && !collapsed.has(span.id);\n                const selected = selectedId === span.id;\n                const Icon = KIND_ICON[span.kind];\n                const dur = end - span.start;\n                const showGlyph =\n                  span.status === \"running\" ||\n                  span.status === \"failed\" ||\n                  span.status === \"waiting\";\n                const details = selected\n                  ? (renderDetails?.(span) ?? <Details span={span} end={end} />)\n                  : null;\n                return (\n                  <div\n                    key={span.id}\n                    ref={(el) => {\n                      if (el) itemRefs.current.set(span.id, el);\n                      else itemRefs.current.delete(span.id);\n                    }}\n                    role=\"treeitem\"\n                    aria-level={depth + 1}\n                    aria-posinset={row.posinset}\n                    aria-setsize={row.setsize}\n                    aria-expanded={hasKids ? open : undefined}\n                    aria-selected={selected}\n                    aria-label={`${span.name}${span.meta ? ` ${span.meta}` : \"\"}, ${KIND_LABEL[span.kind]}, ${formatDuration(dur)}, ${STATUS_WORD[span.status]}`}\n                    aria-describedby={selected ? detailsId : undefined}\n                    tabIndex={activeFocus === span.id ? 0 : -1}\n                    data-slot=\"trace-waterfall-span\"\n                    data-kind={span.kind}\n                    data-status={span.status}\n                    data-selected={selected || undefined}\n                    onKeyDown={(e) => onItemKeyDown(e, row)}\n                    onFocus={(e) => {\n                      if (e.target === e.currentTarget) setFocusedId(span.id);\n                    }}\n                    className=\"group/item outline-none transition-opacity duration-(--kura-duration-base) ease-kura-out starting:opacity-0\"\n                  >\n                    {/* biome-ignore lint/a11y/noStaticElementInteractions: pointer shortcut; the treeitem owns keyboard interaction. */}\n                    {/* biome-ignore lint/a11y/useKeyWithClickEvents: Enter/Space are handled on the treeitem. */}\n                    <div\n                      data-slot=\"trace-waterfall-row\"\n                      onClick={() => {\n                        select(span.id);\n                        setFocusedId(span.id);\n                      }}\n                      className={cn(\n                        GRID,\n                        \"relative h-8 cursor-default transition-colors duration-(--kura-duration-instant)\",\n                        \"group-focus-visible/item:shadow-[inset_0_0_0_2px_var(--kura-ring)]\",\n                        selected\n                          ? \"bg-kura-active before:absolute before:inset-y-0 before:left-0 before:z-10 before:w-[2px] before:bg-kura-accent\"\n                          : \"hover:bg-kura-hover\",\n                      )}\n                    >\n                      <div\n                        className=\"flex min-w-0 items-center gap-1.5 pr-3.5\"\n                        style={{ paddingLeft: `${8 + depth * 16}px` }}\n                      >\n                        {hasKids ? (\n                          // biome-ignore lint/a11y/noStaticElementInteractions: mouse affordance; ArrowLeft/Right on the treeitem do the same.\n                          // biome-ignore lint/a11y/useKeyWithClickEvents: ArrowLeft/Right on the treeitem do the same.\n                          <span\n                            data-slot=\"trace-waterfall-toggle\"\n                            onClick={(e) => {\n                              e.stopPropagation();\n                              toggle(span.id);\n                              setFocusedId(span.id);\n                            }}\n                            className=\"-my-1 grid size-5 shrink-0 place-items-center rounded-kura-xs text-kura-fg-subtle hover:bg-kura-hover hover:text-kura-fg\"\n                          >\n                            <ChevronRight\n                              aria-hidden\n                              strokeWidth={2}\n                              className={cn(\n                                \"size-3.5 transition-transform duration-(--kura-duration-fast) ease-kura-out motion-reduce:transition-none\",\n                                open && \"rotate-90\",\n                              )}\n                            />\n                          </span>\n                        ) : (\n                          <span className=\"size-5 shrink-0\" />\n                        )}\n                        <Icon\n                          aria-hidden\n                          strokeWidth={1.75}\n                          className={cn(\n                            \"size-3.5 shrink-0\",\n                            span.kind === \"agent\" ? \"text-kura-fg-muted\" : \"text-kura-fg-subtle\",\n                          )}\n                        />\n                        <span\n                          className={cn(\n                            \"min-w-0 shrink truncate text-[13px]\",\n                            span.kind === \"agent\" ? \"font-medium\" : \"\",\n                            span.kind === \"tool\" && \"font-kura-mono text-[12.5px]\",\n                            span.status === \"queued\" && \"text-kura-fg-subtle\",\n                          )}\n                        >\n                          {span.name}\n                        </span>\n                        {span.meta && (\n                          <span className=\"hidden min-w-0 flex-1 truncate font-kura-mono text-[12px] text-kura-fg-subtle @md:inline\">\n                            {span.meta}\n                          </span>\n                        )}\n                        <span className={cn(\"flex-1\", span.meta && \"@md:hidden\")} />\n                        {showGlyph && <StatusGlyph status={span.status} size=\"sm\" label={null} />}\n                        <span\n                          data-slot=\"trace-waterfall-duration\"\n                          className={cn(\n                            \"hidden min-w-[6ch] shrink-0 text-right font-kura-mono text-[12px] tabular-nums @sm:inline\",\n                            span.status === \"running\"\n                              ? \"text-kura-fg-muted\"\n                              : \"text-kura-fg-subtle\",\n                          )}\n                        >\n                          {span.status === \"queued\" ? \"—\" : formatDuration(dur)}\n                        </span>\n                      </div>\n                      <div className=\"relative\">\n                        <div className={TRACK}>\n                          <span\n                            data-slot=\"trace-waterfall-bar\"\n                            className={cn(\n                              \"absolute top-1/2 -translate-y-1/2 rounded-[3px] transition-[left,width,background-color] duration-(--kura-duration-base) ease-kura-out motion-reduce:transition-none\",\n                              span.kind === \"agent\" && depth === 0 ? \"h-1\" : \"h-2.5\",\n                              span.kind === \"agent\" && depth > 0 && \"h-1.5\",\n                              barTone(span, selected),\n                            )}\n                            style={{\n                              left: pct(span.start),\n                              width: `max(3px, calc(${pct(end)} - ${pct(span.start)}))`,\n                            }}\n                          />\n                        </div>\n                      </div>\n                    </div>\n                    {details != null && (\n                      <div\n                        id={detailsId}\n                        data-slot=\"trace-waterfall-details\"\n                        className=\"relative border-kura-border-subtle border-y bg-kura-surface-sunken px-3.5 py-3 animate-kura-fade-in\"\n                        style={{ paddingLeft: `${36 + depth * 16}px` }}\n                      >\n                        {details}\n                      </div>\n                    )}\n                  </div>\n                );\n              })}\n            </div>\n          </div>\n        </div>\n      )}\n      <span role=\"status\" className=\"sr-only\">\n        {announcement}\n      </span>\n    </div>\n  );\n}\n"
    }
  ],
  "categories": [
    "agent",
    "agent",
    "trace",
    "observability",
    "spans"
  ],
  "docs": "Docs: https://kuro.abstergo.fyi/components/trace-waterfall · Markdown for agents: https://kuro.abstergo.fyi/components/trace-waterfall.md",
  "meta": {
    "kura": {
      "status": "beta",
      "whenToUse": [
        "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."
      ],
      "whenNotToUse": [
        "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."
      ]
    }
  }
}
