{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "agent-checklist",
  "title": "Agent Checklist",
  "description": "A task list whose items change state as work proceeds, announcing only what changed.",
  "dependencies": [
    "lucide-react@^1.31.0"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/default/agent-checklist/agent-checklist.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Check, Circle, Loader, Minus, TriangleAlert } from \"lucide-react\"\nimport { cn } from \"@/lib/utils\"\n\nexport type ChecklistItemStatus =\n  \"pending\" | \"active\" | \"done\" | \"error\" | \"skipped\"\n\nexport type AgentChecklistItem = {\n  id: string\n  label: React.ReactNode\n  status: ChecklistItemStatus\n  detail?: React.ReactNode\n}\n\nexport type AgentChecklistProps = Omit<\n  React.HTMLAttributes<HTMLElement>,\n  \"children\"\n> & {\n  items: AgentChecklistItem[]\n  title?: React.ReactNode\n  announce?: boolean\n  showProgress?: boolean\n}\n\nconst statusText: Record<ChecklistItemStatus, string> = {\n  pending: \"waiting\",\n  active: \"in progress\",\n  done: \"done\",\n  error: \"failed\",\n  skipped: \"skipped\",\n}\n\nfunction ItemIcon({ status }: { status: ChecklistItemStatus }) {\n  if (status === \"active\") {\n    return (\n      <Loader\n        aria-hidden=\"true\"\n        size={15}\n        className=\"text-foreground animate-spin motion-reduce:animate-none\"\n      />\n    )\n  }\n\n  if (status === \"done\") {\n    return <Check aria-hidden=\"true\" size={15} className=\"text-accent\" />\n  }\n\n  if (status === \"error\") {\n    return (\n      <TriangleAlert\n        aria-hidden=\"true\"\n        size={15}\n        className=\"text-destructive\"\n      />\n    )\n  }\n\n  if (status === \"skipped\") {\n    return (\n      <Minus aria-hidden=\"true\" size={15} className=\"text-muted-foreground\" />\n    )\n  }\n\n  return (\n    <Circle aria-hidden=\"true\" size={15} className=\"text-muted-foreground/50\" />\n  )\n}\n\nfunction textOf(label: React.ReactNode): string {\n  if (typeof label === \"string\" || typeof label === \"number\")\n    return String(label)\n  return \"\"\n}\n\nexport function AgentChecklist({\n  items,\n  title,\n  announce = true,\n  showProgress = true,\n  className,\n  ...rootProps\n}: AgentChecklistProps) {\n  const settled = items.filter(\n    (item) => item.status === \"done\" || item.status === \"skipped\"\n  ).length\n\n  const [previous, setPrevious] = React.useState(items)\n  const [message, setMessage] = React.useState(\"\")\n\n  if (previous !== items) {\n    setPrevious(items)\n\n    if (announce) {\n      const changed = items.flatMap((item, index) => {\n        const before = previous.find((entry) => entry.id === item.id)?.status\n\n        if (before === undefined || before === item.status) return []\n\n        const name = textOf(item.label) || `Step ${index + 1}`\n        return [`${name} ${statusText[item.status]}`]\n      })\n\n      if (changed.length > 0) {\n        setMessage(\n          `${changed.join(\". \")}. ${settled} of ${items.length} complete.`\n        )\n      }\n    }\n  }\n\n  return (\n    <section\n      data-slot=\"agent-checklist\"\n      className={cn(\n        \"border-border bg-card text-card-foreground rounded-[var(--radius)] border\",\n        className\n      )}\n      {...rootProps}\n    >\n      {title || showProgress ? (\n        <div\n          data-slot=\"agent-checklist-header\"\n          className=\"border-border flex items-center gap-3 border-b px-3.5 py-2.5\"\n        >\n          {title ? (\n            <h3\n              data-slot=\"agent-checklist-title\"\n              className=\"text-sm font-semibold\"\n            >\n              {title}\n            </h3>\n          ) : null}\n\n          {showProgress ? (\n            <span\n              data-slot=\"agent-checklist-progress\"\n              className=\"text-muted-foreground ml-auto font-[family-name:var(--font-mono),monospace] text-xs tabular-nums\"\n            >\n              {settled}/{items.length}\n            </span>\n          ) : null}\n        </div>\n      ) : null}\n\n      <ol data-slot=\"agent-checklist-list\" className=\"grid gap-0.5 p-2\">\n        {items.map((item) => (\n          <li\n            key={item.id}\n            data-slot=\"agent-checklist-item\"\n            data-status={item.status}\n            className={cn(\n              \"flex items-start gap-2.5 rounded-[calc(var(--radius)-0.25rem)] px-1.5 py-1.5 text-sm transition-colors duration-200 motion-reduce:transition-none\",\n              item.status === \"active\" && \"bg-muted/60\"\n            )}\n          >\n            <span className=\"mt-0.5 shrink-0\">\n              <ItemIcon status={item.status} />\n            </span>\n\n            <span className=\"min-w-0\">\n              <span\n                className={cn(\n                  \"leading-snug\",\n                  item.status === \"pending\" && \"text-muted-foreground\",\n                  item.status === \"skipped\" &&\n                    \"text-muted-foreground line-through\",\n                  item.status === \"active\" && \"font-semibold\"\n                )}\n              >\n                {item.label}\n              </span>\n              <span className=\"sr-only\">, {statusText[item.status]}</span>\n\n              {item.detail ? (\n                <span className=\"text-muted-foreground mt-0.5 block text-xs leading-relaxed\">\n                  {item.detail}\n                </span>\n              ) : null}\n            </span>\n          </li>\n        ))}\n      </ol>\n\n      {announce ? (\n        <span aria-live=\"polite\" className=\"sr-only\">\n          {message}\n        </span>\n      ) : null}\n    </section>\n  )\n}\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "ai",
    "list",
    "progress",
    "agent"
  ],
  "type": "registry:ui"
}