{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "trace-waterfall",
  "title": "Trace Waterfall",
  "description": "Nested spans on a shared timeline, so parallel work and slow steps show up at a glance.",
  "registryDependencies": [
    "https://spotcheckui.com/r/spotcheck-base.json"
  ],
  "files": [
    {
      "path": "registry/spotcheck/components/spotcheck/trace-waterfall.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst TICKS = [0, 0.5, 1]\nconst INDENT_REM = 0.9\nconst STATES = { ok: \"bg-ink\", running: \"bg-accent-ink\", error: \"bg-bad\" }\n\nexport type Span = {\n  id: string\n  /** The span this one ran inside. */\n  parent?: string\n  name: string\n  /** Start time in milliseconds from the beginning of the run. */\n  start: number\n  end: number\n  state?: keyof typeof STATES\n}\n\nexport type FlatSpan = { span: Span; depth: number; hasChildren: boolean }\n\n/** Depth-first rows, skipping anything inside a collapsed span. */\nexport function flattenSpans(spans: Span[], collapsed: ReadonlySet<string> = new Set()) {\n  const rows: FlatSpan[] = []\n  const visit = (parent: string | undefined, depth: number) => {\n    for (const span of spans.filter((item) => item.parent === parent).toSorted((a, b) => a.start - b.start)) {\n      const hasChildren = spans.some((item) => item.parent === span.id)\n      rows.push({ span, depth, hasChildren })\n      if (hasChildren && !collapsed.has(span.id)) visit(span.id, depth + 1)\n    }\n  }\n  visit(undefined, 0)\n  return rows\n}\n\nexport const formatMs = (ms: number) => (ms >= 1000 ? `${(ms / 1000).toFixed(2)} s` : `${Math.round(ms)} ms`)\n\ntype TraceWaterfallProps = {\n  /** Names the chart for screen readers. */\n  label: string\n  spans: Span[]\n  className?: string\n}\n\nexport function TraceWaterfall({ label, spans, className }: TraceWaterfallProps) {\n  const [collapsed, setCollapsed] = React.useState<ReadonlySet<string>>(new Set())\n  const total = Math.max(...spans.map((span) => span.end), 1)\n  const percent = (ms: number) => `${(ms / total) * 100}%`\n\n  const toggle = (id: string) =>\n    setCollapsed((current) => {\n      const next = new Set(current)\n      if (!next.delete(id)) next.add(id)\n      return next\n    })\n\n  return (\n    <div tabIndex={0} role=\"region\" aria-label={label} className={cn(\"spotcheck-focus relative overflow-x-auto font-(family-name:--f-body)\", className)}>\n      <div className=\"min-w-[520px] font-(family-name:--f-mono) text-xs\">\n        <div aria-hidden className=\"border-ink grid grid-cols-[13rem_minmax(0,1fr)_4.5rem] gap-2.5 border-b-(length:--rule) pb-1\">\n          <span className=\"spotcheck-label\">Span</span>\n          <div className=\"relative h-4\">\n            {TICKS.map((tick) => (\n              <span\n                key={tick}\n                data-tick\n                className=\"spotcheck-label absolute -translate-x-1/2 whitespace-nowrap first:translate-x-0 last:-translate-x-full\"\n                style={{ left: `${tick * 100}%` }}\n              >\n                {formatMs(total * tick)}\n              </span>\n            ))}\n          </div>\n          <span className=\"spotcheck-label text-right\">Time</span>\n        </div>\n        <ul className=\"m-0 list-none p-0\">\n          {flattenSpans(spans, collapsed).map(({ span, depth, hasChildren }) => {\n            const open = !collapsed.has(span.id)\n            return (\n              <li\n                key={span.id}\n                data-span={span.id}\n                className=\"grid grid-cols-[13rem_minmax(0,1fr)_4.5rem] items-center gap-2.5 border-b border-[color-mix(in_srgb,var(--ink)_18%,transparent)] py-1\"\n              >\n                <div className=\"min-w-0 truncate\" style={{ paddingLeft: `${depth * INDENT_REM}rem` }}>\n                  {hasChildren ? (\n                    <button\n                      type=\"button\"\n                      aria-expanded={open}\n                      onClick={() => toggle(span.id)}\n                      className=\"spotcheck-focus cursor-pointer font-semibold\"\n                    >\n                      <span aria-hidden>{open ? \"− \" : \"+ \"}</span>\n                      {span.name}\n                    </button>\n                  ) : (\n                    span.name\n                  )}\n                </div>\n                <div className=\"bg-wash relative h-3\">\n                  <i\n                    aria-hidden\n                    className={cn(\n                      \"absolute inset-y-0 min-w-0.5 origin-left motion-safe:[animation:spotcheck-grow_.5s_cubic-bezier(.2,.8,.2,1)_both]\",\n                      STATES[span.state ?? \"ok\"],\n                    )}\n                    style={{ left: percent(span.start), width: percent(span.end - span.start) }}\n                  />\n                </div>\n                <span className=\"text-right tabular-nums\">\n                  {formatMs(span.end - span.start)}\n                  <span className=\"sr-only\">, starting at {formatMs(span.start)}</span>\n                </span>\n              </li>\n            )\n          })}\n        </ul>\n      </div>\n    </div>\n  )\n}\n",
      "type": "registry:component"
    }
  ],
  "categories": [
    "ai"
  ],
  "type": "registry:component"
}