{
  "schemaVersion": 1,
  "target": "react",
  "name": "step-list",
  "version": "0.2.0",
  "category": "agent",
  "title": "Step List",
  "description": "A compact checklist of an agent's plan on a connected rail, with queued, running, succeeded, failed, and skipped steps.",
  "tags": [
    "agent",
    "plan",
    "steps",
    "checklist",
    "progress"
  ],
  "requires": {
    "projectTypes": [
      "next",
      "vite-react"
    ],
    "styling": "tailwind-v4"
  },
  "npmDependencies": {},
  "registryDependencies": [
    {
      "name": "agent-run",
      "version": "0.1.1"
    }
  ],
  "docsPath": "/components/step-list",
  "props": [
    {
      "name": "steps",
      "type": "{ id, label, status, detail?, meta? }[]",
      "description": "Steps in order. detail shows a result or the error; meta is right-aligned metadata such as a time and duration."
    },
    {
      "name": "StepStatusIcon",
      "type": "({ status }) => JSX",
      "description": "The status glyph on its own, also used by Run Timeline and the graph components."
    },
    {
      "name": "StatusGlyph",
      "type": "({ kind, size? }) => JSX",
      "description": "The shared stroke glyphs: queued, running, success, failed, waiting, skipped. size is 16 or 20."
    }
  ],
  "accessibility": [
    "Each step has a visible text status and a screen-reader status after its label; glyphs are decorative.",
    "Skipped steps are struck through and labeled, not only dimmed.",
    "With reduced motion the spinner holds still and new steps, glyph swaps, and the check draw appear instantly."
  ],
  "states": [
    "queued",
    "running",
    "succeeded",
    "failed",
    "skipped"
  ],
  "files": [
    {
      "path": "agent/step-list.tsx",
      "content": "import type { ComponentProps, ReactNode } from \"react\";\nimport type { StepStatus } from \"./agent-run\";\n\nconst labels: Record<StepStatus, string> = {\n  queued: \"Queued\",\n  running: \"Running\",\n  succeeded: \"Done\",\n  failed: \"Failed\",\n  skipped: \"Skipped\",\n};\n\n// Only running and failed rows color their label; the glyph already carries the rest.\nconst labelTone: Record<StepStatus, string> = {\n  queued: \"text-zinc-500\",\n  running: \"text-sky-600 dark:text-sky-400\",\n  succeeded: \"text-zinc-500\",\n  failed: \"text-red-600 dark:text-red-400\",\n  skipped: \"text-zinc-500\",\n};\n\nexport interface StepListItem {\n  id: string;\n  label: ReactNode;\n  status: StepStatus;\n  /** Secondary line, e.g. a result summary or the error. */\n  detail?: ReactNode;\n  /** Optional right-aligned metadata on the second line, e.g. a start time and duration. */\n  meta?: ReactNode;\n}\n\nexport interface StepListProps extends Omit<ComponentProps<\"ol\">, \"children\"> {\n  steps: StepListItem[];\n}\n\n/**\n * A compact checklist of an agent's plan. Steps sit on a connected rail; each shows a\n * status glyph and a text status, and the rail turns green below finished steps.\n */\nexport function StepList({ steps, className, ...props }: StepListProps) {\n  return (\n    <ol className={[\"grid\", className].filter(Boolean).join(\" \")} {...props}>\n      {steps.map((step, index) => (\n        <li\n          key={step.id}\n          data-status={step.status}\n          className=\"relative grid grid-cols-[1rem_minmax(0,1fr)] gap-x-3 pb-4 transition-[opacity,translate] duration-200 ease-[cubic-bezier(0.23,1,0.32,1)] last:pb-0 starting:translate-y-1 starting:opacity-0 motion-reduce:transition-none\"\n        >\n          {index < steps.length - 1 ? (\n            <span\n              aria-hidden=\"true\"\n              className={[\n                \"absolute top-[1.375rem] bottom-0.5 left-[7.5px] w-px rounded-full transition-colors duration-200 motion-reduce:transition-none\",\n                step.status === \"succeeded\" ? \"bg-emerald-500/45\" : \"bg-zinc-950/10 dark:bg-white/12\",\n              ].join(\" \")}\n            />\n          ) : null}\n          <StepStatusIcon key={step.status} status={step.status} className=\"mt-0.5\" />\n          <div className=\"min-w-0\">\n            <div className=\"flex items-baseline justify-between gap-3\">\n              <p\n                className={[\n                  \"min-w-0 text-sm break-words\",\n                  step.status === \"skipped\"\n                    ? \"text-zinc-500 line-through decoration-zinc-500/60\"\n                    : step.status === \"queued\"\n                      ? \"text-zinc-600 dark:text-zinc-400\"\n                      : \"text-zinc-950 dark:text-white\",\n                  step.status === \"running\" ? \"font-medium\" : \"\",\n                ].join(\" \")}\n              >\n                {step.label}\n                <span className=\"sr-only\">, {labels[step.status]}</span>\n              </p>\n              <span\n                aria-hidden=\"true\"\n                className={`shrink-0 text-xs transition-colors duration-200 motion-reduce:transition-none ${labelTone[step.status]}`}\n              >\n                {labels[step.status]}\n              </span>\n            </div>\n            {step.detail != null || step.meta != null ? (\n              <div className=\"mt-0.5 flex items-baseline justify-between gap-3\">\n                {step.detail != null ? (\n                  <p className={step.status === \"failed\" ? \"min-w-0 text-xs text-red-600 dark:text-red-400\" : \"min-w-0 text-xs text-zinc-500\"}>\n                    {step.detail}\n                  </p>\n                ) : (\n                  <span />\n                )}\n                {step.meta != null ? <span className=\"shrink-0 font-mono text-[11px] text-zinc-500 tabular-nums\">{step.meta}</span> : null}\n              </div>\n            ) : null}\n          </div>\n        </li>\n      ))}\n    </ol>\n  );\n}\n\n/** The shared status shapes. Each one is paired with a text label wherever it is used. */\nexport type StatusGlyphKind = \"queued\" | \"running\" | \"success\" | \"failed\" | \"waiting\" | \"skipped\";\n\nconst glyphTone: Record<StatusGlyphKind, string> = {\n  queued: \"text-zinc-500\",\n  running: \"text-sky-600 dark:text-sky-400\",\n  success: \"text-emerald-600 dark:text-emerald-400\",\n  failed: \"text-red-600 dark:text-red-400\",\n  waiting: \"text-amber-600 dark:text-amber-400\",\n  skipped: \"text-zinc-500\",\n};\n\nconst stepGlyph: Record<StepStatus, StatusGlyphKind> = {\n  queued: \"queued\",\n  running: \"running\",\n  succeeded: \"success\",\n  failed: \"failed\",\n  skipped: \"skipped\",\n};\n\n/**\n * A 16px or 20px stroke glyph for an agent state. Remount it with `key={kind}` so the new\n * shape scales in (and the success check draws); both are instant with reduced motion.\n * The running arc is the only loop, and it holds still with reduced motion.\n */\nexport function StatusGlyph({ kind, size = 16, className = \"\" }: { kind: StatusGlyphKind; size?: 16 | 20; className?: string }) {\n  return (\n    <svg\n      aria-hidden=\"true\"\n      viewBox=\"0 0 16 16\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      // 1.2 in a 16 unit box drawn at 20px is a 1.5px stroke, matching the 16px glyph.\n      strokeWidth={size === 20 ? 1.2 : 1.5}\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      data-glyph={kind}\n      className={[\n        size === 20 ? \"size-5\" : \"size-4\",\n        \"shrink-0 transition-[opacity,scale] duration-200 ease-[cubic-bezier(0.23,1,0.32,1)] starting:scale-90 starting:opacity-0 motion-reduce:transition-none\",\n        glyphTone[kind],\n        className,\n      ].join(\" \")}\n    >\n      {kind === \"queued\" ? (\n        <circle cx=\"8\" cy=\"8\" r=\"6.25\" strokeDasharray=\"2.15 2.75\" />\n      ) : kind === \"running\" ? (\n        <>\n          <circle cx=\"8\" cy=\"8\" r=\"6.25\" strokeOpacity=\"0.2\" />\n          <path d=\"M8 1.75A6.25 6.25 0 1 1 1.75 8\" className=\"origin-center motion-safe:animate-spin\" />\n        </>\n      ) : (\n        <>\n          <circle cx=\"8\" cy=\"8\" r=\"6.25\" />\n          {kind === \"success\" ? (\n            <path\n              d=\"m5.35 8.25 1.75 1.75 3.55-3.75\"\n              pathLength={1}\n              strokeDasharray=\"1\"\n              className=\"[stroke-dashoffset:0] transition-[stroke-dashoffset] delay-75 duration-300 ease-[cubic-bezier(0.23,1,0.32,1)] starting:[stroke-dashoffset:1] motion-reduce:transition-none\"\n            />\n          ) : kind === \"failed\" ? (\n            <path d=\"m6.1 6.1 3.8 3.8m0-3.8-3.8 3.8\" />\n          ) : kind === \"waiting\" ? (\n            <path d=\"M6.6 5.9v4.2M9.4 5.9v4.2\" />\n          ) : (\n            <path d=\"m5.6 10.4 4.8-4.8\" />\n          )}\n        </>\n      )}\n    </svg>\n  );\n}\n\n/** Status glyph for steps; remount with `key={status}` to play its entry transition. */\nexport function StepStatusIcon({ status, className = \"\" }: { status: StepStatus; className?: string }) {\n  return <StatusGlyph kind={stepGlyph[status]} className={className} />;\n}\n"
    }
  ],
  "example": {
    "content": "import { StepList } from \"@/components/seamless/agent/step-list\";\n\nexport default function StepListExample() {\n  return (\n    <StepList\n      className=\"w-full max-w-sm\"\n      steps={[\n        { id: \"1\", label: \"Search the docs\", status: \"succeeded\", detail: \"Found 3 pages\" },\n        { id: \"2\", label: \"Edit the limits table\", status: \"running\" },\n        { id: \"3\", label: \"Check links\", status: \"failed\", detail: \"2 links returned 404\" },\n        { id: \"4\", label: \"Notify the team\", status: \"skipped\" },\n        { id: \"5\", label: \"Open a pull request\", status: \"queued\" },\n      ]}\n    />\n  );\n}\n"
  }
}
