{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"agent-plan","type":"registry:ui","title":"Agent Plan","description":"The checklist an agent writes for itself and then edits mid-run — steps added, skipped and re-ordered, without the list jumping under the reader.","author":"Scrim UI (https://scrimui.dev)","categories":["agents"],"docs":"https://scrimui.dev/components/agent-plan","dependencies":[],"registryDependencies":[],"files":[{"path":"src/showcase/agent-plan/agent-plan.tsx","content":"\"use client\";\n\nimport * as React from \"react\";\n\n/**\n * The checklist an agent writes for itself, and then edits while you watch.\n *\n * A static list of steps is a five-minute component. What makes this one\n * worth its own file is that **the plan changes mid-run**, and every naive\n * rendering of that change is worse than not showing the plan at all:\n *\n * **Completed steps must never move.** The model re-emits its plan and the\n * new list is not the old list — a step was inserted at position two, another\n * was dropped. Rendering the new array wholesale slides everything the reader\n * has already read to a new position, and they lose their place in a list\n * they were using precisely to keep it. So finished steps hold their order,\n * and new work arrives underneath.\n *\n * **A dropped step is not a deleted step.** Something the agent said it would\n * do and then decided not to is the most interesting line in the plan. It\n * becomes `skipped`, with the reason, rather than vanishing — a plan that\n * silently loses items is a plan you cannot audit afterwards.\n *\n * **A newly added step should announce itself.** `added` marks work that was\n * not in the original plan, because \"the agent decided to do three more\n * things\" is a fact the reader wants before it finishes doing them.\n *\n * **A revision counter, not a diff.** Full before/after diffing of a plan is\n * a lot of machinery for a reader who mostly wants to know *that* it changed.\n * `revision` says how many times, and the per-step marks say where.\n */\n\nexport type PlanStepState = \"pending\" | \"active\" | \"done\" | \"skipped\" | \"failed\";\n\nexport type PlanStep = {\n  id: string;\n  text: string;\n  state: PlanStepState;\n  /** Why it was skipped, or what it produced. One line. */\n  note?: string;\n  /** True for a step the agent added after the first plan. */\n  added?: boolean;\n};\n\nexport type AgentPlanProps = {\n  steps: PlanStep[];\n  /** 0 = the original plan. Anything higher is shown next to the title. */\n  revision?: number;\n  /** The plan itself is still being written. */\n  planning?: boolean;\n  title?: string;\n  className?: string;\n};\n\n/* ------------------------------------------------------------------ */\n/* Step marks                                                          */\n/* ------------------------------------------------------------------ */\n\nfunction Mark({ state }: { state: PlanStepState }) {\n  const base = \"mt-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded-full border\";\n  switch (state) {\n    case \"done\":\n      return (\n        <span className={`${base} border-emerald-600 bg-emerald-600 text-white dark:border-emerald-500 dark:bg-emerald-500`}>\n          <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"3.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" width=\"9\" height=\"9\">\n            <path d=\"M20 6 9 17l-5-5\" />\n          </svg>\n        </span>\n      );\n    case \"active\":\n      return (\n        <span className={`${base} border-blue-500 text-blue-500`}>\n          <span className=\"h-1.5 w-1.5 animate-pulse rounded-full bg-current\" />\n        </span>\n      );\n    case \"failed\":\n      return (\n        <span className={`${base} border-red-500 bg-red-500 text-white`}>\n          <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"3.5\" strokeLinecap=\"round\" width=\"9\" height=\"9\">\n            <path d=\"M18 6 6 18M6 6l12 12\" />\n          </svg>\n        </span>\n      );\n    case \"skipped\":\n      return (\n        <span className={`${base} border-zinc-300 text-zinc-400 dark:border-zinc-600 dark:text-zinc-500`}>\n          <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"3\" strokeLinecap=\"round\" width=\"9\" height=\"9\">\n            <path d=\"M5 12h14\" />\n          </svg>\n        </span>\n      );\n    default:\n      return <span className={`${base} border-zinc-300 dark:border-zinc-600`} />;\n  }\n}\n\n/* ------------------------------------------------------------------ */\n/* AgentPlan                                                           */\n/* ------------------------------------------------------------------ */\n\nexport function AgentPlan({\n  steps,\n  revision = 0,\n  planning = false,\n  title = \"Plan\",\n  className = \"\",\n}: AgentPlanProps) {\n  const done = steps.filter((s) => s.state === \"done\").length;\n  const settled = steps.filter((s) => s.state !== \"pending\" && s.state !== \"active\").length;\n\n  return (\n    <div className={`rounded-xl border border-zinc-200 bg-white dark:border-zinc-800 dark:bg-zinc-900 ${className}`}>\n      <div className=\"flex flex-wrap items-baseline gap-x-2 gap-y-1 border-b border-zinc-100 px-3.5 py-2.5 dark:border-zinc-800\">\n        <span className=\"text-[13px] font-medium text-zinc-900 dark:text-zinc-100\">{title}</span>\n        <span className=\"tabular-nums text-[11px] text-zinc-500 dark:text-zinc-400\">\n          {done}/{steps.length} done\n        </span>\n        {revision > 0 && (\n          /* The reader's mental model of \"the plan\" is now out of date, and\n             saying so costs one word. */\n          <span className=\"rounded-full bg-amber-100 px-2 py-0.5 text-[10px] font-medium text-amber-800 dark:bg-amber-900/40 dark:text-amber-300\">\n            revised {revision}×\n          </span>\n        )}\n        {planning && (\n          <span className=\"ml-auto inline-flex items-center gap-1.5 text-[11px] text-zinc-500 dark:text-zinc-400\">\n            <span className=\"h-1.5 w-1.5 animate-pulse rounded-full bg-current\" />\n            still planning\n          </span>\n        )}\n      </div>\n\n      <ol className=\"px-3.5 py-3\">\n        {steps.map((step, i) => (\n          <li key={step.id} className=\"relative flex gap-2.5 pb-3 last:pb-0\">\n            {/* Rail, drawn between marks rather than behind them, so a\n                skipped step reads as part of the sequence it was cut from. */}\n            {i < steps.length - 1 && (\n              <span className=\"absolute left-2 top-5 h-[calc(100%-1.25rem)] w-px bg-zinc-200 dark:bg-zinc-700\" aria-hidden />\n            )}\n            <Mark state={step.state} />\n            <div className=\"min-w-0 flex-1\">\n              <div className=\"flex flex-wrap items-baseline gap-x-2 gap-y-0.5\">\n                <span\n                  className={`text-[13px] leading-5 ${\n                    step.state === \"skipped\"\n                      ? \"text-zinc-400 line-through decoration-zinc-300 dark:text-zinc-500 dark:decoration-zinc-600\"\n                      : step.state === \"done\"\n                        ? \"text-zinc-500 dark:text-zinc-400\"\n                        : \"text-zinc-800 dark:text-zinc-100\"\n                  }`}\n                >\n                  {step.text}\n                </span>\n                {step.added && (\n                  <span className=\"shrink-0 rounded-md bg-blue-100 px-1.5 py-px text-[10px] font-medium text-blue-700 dark:bg-blue-900/40 dark:text-blue-300\">\n                    added\n                  </span>\n                )}\n              </div>\n              {step.note && (\n                <p className=\"mt-0.5 text-[11px] leading-4 text-zinc-500 dark:text-zinc-400\">\n                  {step.note}\n                </p>\n              )}\n            </div>\n          </li>\n        ))}\n      </ol>\n\n      {settled > 0 && settled === steps.length && (\n        <p className=\"border-t border-zinc-100 px-3.5 py-2 text-[11px] text-zinc-500 dark:border-zinc-800 dark:text-zinc-400\">\n          Plan complete — {done} done, {steps.length - done} skipped or failed.\n        </p>\n      )}\n    </div>\n  );\n}\n","type":"registry:ui","target":"components/ui/agent-plan.tsx"}]}