{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "agent-timeline",
  "type": "registry:ui",
  "title": "Agent Timeline",
  "description": "Vertical plan tracker for multi-step agents: queued/active/done/failed/skipped steps, nested sub-steps, live durations and a connector that fills.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "https://kuro.abstergo.fyi/r/kura-theme.json",
    "https://kuro.abstergo.fyi/r/shimmer-text.json",
    "utils"
  ],
  "files": [
    {
      "path": "registry/kura/agent-timeline/agent-timeline.tsx",
      "type": "registry:ui",
      "target": "components/kura/agent-timeline/agent-timeline.tsx",
      "content": "\"use client\";\n\nimport { Check, Minus, X } from \"lucide-react\";\nimport * as React from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport { ShimmerText } from \"@/components/kura/shimmer-text\";\n\nexport type AgentStepStatus = \"queued\" | \"active\" | \"done\" | \"failed\" | \"skipped\";\n\nexport interface AgentTimelineStep {\n  /** Stable key. */\n  id: string;\n  /** Short imperative title, e.g. \"Run the test suite\". */\n  title: React.ReactNode;\n  status: AgentStepStatus;\n  /** Secondary line under the title (muted). */\n  description?: React.ReactNode;\n  /** Final duration in ms, shown on the right. */\n  duration?: number;\n  /** Start time of an active step; shows a live timer. Default: when the step became active. */\n  startedAt?: Date | number;\n  /** Nested sub-steps, rendered as a smaller timeline under this step. */\n  steps?: readonly AgentTimelineStep[];\n  /** Extra content under the step (a ToolCall, a CodeBlock, a link…). */\n  content?: React.ReactNode;\n}\n\nexport interface AgentTimelineProps extends Omit<React.ComponentProps<\"div\">, \"children\"> {\n  /** The plan. Order is display order. */\n  steps: readonly AgentTimelineStep[];\n  /** Accessible name of the list. Default “Agent progress”. */\n  label?: string;\n  /** `md` (default) for a plan panel; `sm` for dense sidebars. Nested sub-steps always render one size down. */\n  size?: \"sm\" | \"md\";\n}\n\nconst spoken: Record<AgentStepStatus, string> = {\n  queued: \"queued\",\n  active: \"in progress\",\n  done: \"done\",\n  failed: \"failed\",\n  skipped: \"skipped\",\n};\n\nexport function formatStepDuration(ms: number): string {\n  if (ms < 1000) return `${Math.round(ms)}ms`;\n  const s = ms / 1000;\n  if (s < 60) return `${s < 10 ? s.toFixed(1) : Math.round(s)}s`;\n  const r = Math.round(s);\n  return `${Math.floor(r / 60)}m ${String(r % 60).padStart(2, \"0\")}s`;\n}\n\nfunction useLiveDuration(active: boolean, startedAt?: Date | number): number | null {\n  const start = React.useRef<number | null>(null);\n  const [now, setNow] = React.useState<number | null>(null);\n  React.useEffect(() => {\n    if (!active) {\n      start.current = null;\n      setNow(null);\n      return;\n    }\n    start.current = startedAt != null ? +new Date(startedAt) : Date.now();\n    setNow(Date.now());\n    const id = setInterval(() => setNow(Date.now()), 200);\n    return () => clearInterval(id);\n  }, [active, startedAt]);\n  return active && start.current != null && now != null ? now - start.current : null;\n}\n\nfunction Node({ status, small }: { status: AgentStepStatus; small: boolean }) {\n  return (\n    <span\n      aria-hidden\n      data-slot=\"agent-timeline-node\"\n      data-status={status}\n      className={cn(\n        \"relative z-10 grid shrink-0 place-items-center rounded-full bg-kura-surface transition-[background-color,color,box-shadow] duration-(--kura-duration-base) ease-kura-out\",\n        small ? \"size-4 [&_svg]:size-2.5\" : \"size-5 [&_svg]:size-3\",\n        status === \"queued\" &&\n          \"text-kura-fg-subtle shadow-[inset_0_0_0_1.5px_var(--kura-border-strong)]\",\n        status === \"active\" && \"bg-kura-accent-subtle text-kura-accent-text\",\n        status === \"done\" &&\n          \"bg-kura-surface-sunken text-kura-success-text shadow-[inset_0_0_0_1px_var(--kura-border)]\",\n        status === \"failed\" && \"bg-kura-danger-subtle text-kura-danger-text\",\n        status === \"skipped\" && \"text-kura-fg-subtle shadow-[inset_0_0_0_1px_var(--kura-border)]\",\n      )}\n    >\n      {status === \"active\" && (\n        <>\n          <span className=\"absolute inset-0 animate-kura-pulse rounded-full shadow-[0_0_0_3px_var(--kura-accent-subtle)] motion-reduce:animate-none\" />\n          <span className={cn(\"rounded-full bg-current\", small ? \"size-1.5\" : \"size-2\")} />\n        </>\n      )}\n      {status === \"done\" && <Check strokeWidth={3} className=\"animate-kura-pop-in\" />}\n      {status === \"failed\" && <X strokeWidth={3} className=\"animate-kura-pop-in\" />}\n      {status === \"skipped\" && <Minus strokeWidth={3} />}\n    </span>\n  );\n}\n\nfunction Step({ step, last, small }: { step: AgentTimelineStep; last: boolean; small: boolean }) {\n  const active = step.status === \"active\";\n  const live = useLiveDuration(active, step.startedAt);\n  const ms = step.duration ?? live;\n  // The connector below a step fills once the agent has moved past it.\n  const filled = step.status === \"done\" || step.status === \"skipped\";\n  return (\n    <li\n      data-slot=\"agent-timeline-step\"\n      data-status={step.status}\n      aria-current={active ? \"step\" : undefined}\n      className={cn(\"relative flex gap-3\", !last && (small ? \"pb-3\" : \"pb-5\"))}\n    >\n      {!last && (\n        <span\n          aria-hidden\n          data-slot=\"agent-timeline-connector\"\n          data-filled={filled}\n          className={cn(\n            \"absolute w-px overflow-hidden rounded-full bg-kura-border\",\n            small ? \"top-[19px] bottom-[3px] left-[7.5px]\" : \"top-[23px] bottom-[3px] left-[9.5px]\",\n          )}\n        >\n          <span\n            className={cn(\n              \"absolute inset-0 origin-top transition-[scale,opacity] duration-(--kura-duration-slower) ease-kura-in-out motion-reduce:transition-opacity\",\n              \"bg-kura-fg-subtle\",\n              filled ? \"scale-y-100 opacity-100\" : \"scale-y-0 opacity-0 motion-reduce:scale-y-100\",\n            )}\n          />\n        </span>\n      )}\n      <span className={cn(\"flex shrink-0\", small ? \"pt-px\" : \"pt-0.5\")}>\n        <Node status={step.status} small={small} />\n      </span>\n      <div className=\"flex min-w-0 flex-1 flex-col gap-1\">\n        <div\n          className={cn(\n            \"flex items-baseline gap-3\",\n            small ? \"min-h-[18px] text-[13px]\" : \"min-h-6 text-sm\",\n          )}\n        >\n          <span\n            data-slot=\"agent-timeline-title\"\n            className={cn(\n              \"min-w-0 flex-1 transition-colors duration-(--kura-duration-base)\",\n              step.status === \"queued\" && \"text-kura-fg-muted\",\n              step.status === \"active\" && \"font-medium text-kura-fg\",\n              step.status === \"done\" && \"text-kura-fg\",\n              step.status === \"failed\" && \"text-kura-fg\",\n              step.status === \"skipped\" &&\n                \"text-kura-fg-subtle line-through decoration-kura-border-strong\",\n            )}\n          >\n            {active ? <ShimmerText duration={2.4}>{step.title}</ShimmerText> : step.title}\n            <span className=\"sr-only\"> ({spoken[step.status]})</span>\n          </span>\n          {ms != null && (\n            <span\n              data-slot=\"agent-timeline-duration\"\n              className=\"shrink-0 font-kura-mono text-kura-fg-subtle text-xs tabular-nums\"\n            >\n              {formatStepDuration(ms)}\n            </span>\n          )}\n        </div>\n        {step.description != null && (\n          <p\n            data-slot=\"agent-timeline-description\"\n            className={cn(\n              \"text-kura-fg-muted leading-relaxed\",\n              small ? \"text-xs\" : \"text-[13px]\",\n              step.status === \"failed\" && \"text-kura-danger-text\",\n            )}\n          >\n            {step.description}\n          </p>\n        )}\n        {step.content != null && (\n          <div data-slot=\"agent-timeline-content\" className=\"pt-1.5\">\n            {step.content}\n          </div>\n        )}\n        {step.steps && step.steps.length > 0 && (\n          <div className=\"pt-2\">\n            <StepList steps={step.steps} small label={undefined} />\n          </div>\n        )}\n      </div>\n    </li>\n  );\n}\n\nfunction StepList({\n  steps,\n  small,\n  label,\n}: {\n  steps: readonly AgentTimelineStep[];\n  small: boolean;\n  label: string | undefined;\n}) {\n  return (\n    <ol aria-label={label} data-slot=\"agent-timeline-list\" className=\"flex flex-col\">\n      {steps.map((step, i) => (\n        <Step key={step.id} step={step} last={i === steps.length - 1} small={small} />\n      ))}\n    </ol>\n  );\n}\n\n/**\n * Vertical plan/step tracker for multi-step agents. Steps are queued, active (jade pulse,\n * shimmering title, live timer), done, failed or skipped; sub-steps nest; the connector between\n * nodes fills as the agent moves past each step.\n */\nexport function AgentTimeline({\n  steps,\n  label = \"Agent progress\",\n  size = \"md\",\n  className,\n  ...props\n}: AgentTimelineProps) {\n  const total = steps.length;\n  const doneCount = steps.filter((s) => s.status === \"done\" || s.status === \"skipped\").length;\n  const activeIndex = steps.findIndex((s) => s.status === \"active\");\n  const failed = steps.find((s) => s.status === \"failed\");\n  const active = activeIndex >= 0 ? steps[activeIndex] : undefined;\n  const announcement = failed\n    ? \"A step failed\"\n    : active\n      ? `Step ${activeIndex + 1} of ${total}`\n      : doneCount === total\n        ? \"All steps complete\"\n        : \"\";\n\n  return (\n    <div\n      data-slot=\"agent-timeline\"\n      data-size={size}\n      data-progress={`${doneCount}/${total}`}\n      className={cn(\"font-kura-sans\", className)}\n      {...props}\n    >\n      <StepList steps={steps} small={size === \"sm\"} label={label} />\n      <span className=\"sr-only\" role=\"status\">\n        {announcement}\n      </span>\n    </div>\n  );\n}\n"
    },
    {
      "path": "registry/kura/agent-timeline/index.ts",
      "type": "registry:ui",
      "target": "components/kura/agent-timeline/index.ts",
      "content": "export {\n  type AgentStepStatus,\n  AgentTimeline,\n  type AgentTimelineProps,\n  type AgentTimelineStep,\n  formatStepDuration,\n} from \"./agent-timeline\";\n"
    }
  ],
  "categories": [
    "agent",
    "agent",
    "plan",
    "timeline",
    "steps"
  ],
  "docs": "Docs: https://kuro.abstergo.fyi/components/agent-timeline · Markdown for agents: https://kuro.abstergo.fyi/components/agent-timeline.md",
  "meta": {
    "kura": {
      "status": "beta",
      "whenToUse": [
        "Show an agent's plan and its progress (coding agents, research agents, deploy pipelines driven by a model).",
        "Show a todo list the model maintains (Claude Code / Cursor style task lists) with the current step highlighted.",
        "Nest sub-steps under a step, or attach a ToolCall / CodeBlock to a step via `content`."
      ],
      "whenNotToUse": [
        "A single tool invocation — use tool-call.",
        "Free-form model thinking — use reasoning.",
        "Generic (non-agent) wizards or checkout steppers — use a regular stepper; the active state here uses the AI signal colour."
      ]
    }
  }
}
