{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "checkpoint-timeline",
  "title": "CheckpointTimeline",
  "description": "Checkpoint history rail with git sha, snapshot indicator, project id, restore with busy state, and empty state.",
  "registryDependencies": [
    "utils",
    "https://ui.neon.com/r/neon-tokens.json",
    "https://ui.neon.com/r/button.json",
    "https://ui.neon.com/r/empty-state.json"
  ],
  "files": [
    {
      "path": "src/components/checkpoint-timeline/checkpoint-timeline.tsx",
      "content": "\"use client\";\n\nimport type { ComponentProps, ReactNode } from \"react\";\n\nimport { EmptyState } from \"@/components/empty-state/empty-state\";\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface Checkpoint {\n  /** Stable id the restore action reports. */\n  id: string;\n  /** Human label, e.g. \"Added billing page\". */\n  label: string;\n  /** Display-ready timestamp, e.g. \"2m ago\". */\n  createdAt: string;\n  /** Short git sha, e.g. \"f39ac2d\". */\n  sha?: string;\n  /** Whether a database snapshot rides with this checkpoint. */\n  snapshot?: boolean;\n  /** Tenant Neon project id, e.g. \"damp-forest-123456\". */\n  projectId?: string;\n}\n\nexport type CheckpointTimelineProps = Omit<\n  ComponentProps<\"div\">,\n  \"children\"\n> & {\n  /** Newest first — the top row reads as \"now\". */\n  checkpoints: Checkpoint[];\n  /** Renders the restore action on every row. */\n  onRestore?: (id: string) => void;\n  /** The checkpoint currently restoring; locks every other action. */\n  restoringId?: string | null;\n  /** Override the built-in empty state. */\n  empty?: ReactNode;\n};\n\n/* ─────────────────────────────────────────────────────────\n * TIMELINE STORYBOARD\n *\n *  entrance  rows rise 8px one at a time, 60ms apart —\n *            history introduces itself newest first\n *  rest      one continuous hairline rail threads square\n *            markers (the StatusBadge vocabulary); the\n *            newest holds primary and wears a mono\n *            \"current\" tag, the past stays muted; each row\n *            speaks twice — label + timestamp, then one\n *            quiet mono line: sha · ■ snapshot · project\n *  engage    hovering or focusing a row wakes its restore\n *            action — it slides out from behind the row\n *            (8px, 200ms ease-out) as it fades in; the\n *            space is always reserved, the row itself\n *            never moves\n *  restore   the acting row holds: its marker breathes\n *            primary and the action shimmers \"Restoring…\";\n *            the rest of history recedes to half voice\n *            until the flight lands\n *  empty     EmptyState holds the space — history hasn't\n *            started, it isn't missing\n * ───────────────────────────────────────────────────────── */\nconst ROW_STAGGER_MS = 60;\n\n/* Restore action visibility, one state at a time:\n * hidden (slid 8px behind the row) → revealed on row hover/focus →\n * held visible while restoring → fully absent while another row acts. */\nconst ACTION_BASE =\n  \"h-6 shrink-0 rounded-full border border-border/60 px-2.5 text-muted-foreground text-xs transition-[opacity,translate,color,border-color] duration-200 ease-out hover:border-border hover:bg-transparent hover:text-foreground active:scale-[0.98] motion-reduce:translate-x-0 motion-reduce:transition-none\";\nconst ACTION_HIDDEN =\n  \"-translate-x-2 opacity-0 focus-visible:translate-x-0 focus-visible:opacity-100 group-focus-within:translate-x-0 group-focus-within:opacity-100 group-hover:translate-x-0 group-hover:opacity-100\";\nconst ACTION_RESTORING = \"translate-x-0 opacity-100\";\nconst ACTION_LOCKED = \"disabled:opacity-0\";\n\nconst actionClasses = (isRestoring: boolean, locked: boolean) => {\n  if (isRestoring) {\n    return cn(ACTION_BASE, ACTION_RESTORING);\n  }\n  if (locked) {\n    return cn(ACTION_BASE, ACTION_LOCKED);\n  }\n\n  return cn(ACTION_BASE, ACTION_HIDDEN);\n};\n\nconst RISE =\n  \"fill-mode-backwards fade-in-0 slide-in-from-bottom-2 animate-in duration-500 motion-reduce:animate-none\";\n\n/** One quiet mono meta line: sha · ■ snapshot · project id. */\nconst MetaLine = ({ checkpoint }: { checkpoint: Checkpoint }) => {\n  const parts: { key: string; node: ReactNode }[] = [];\n\n  if (checkpoint.sha) {\n    parts.push({ key: \"sha\", node: checkpoint.sha });\n  }\n\n  if (checkpoint.snapshot) {\n    parts.push({\n      key: \"snapshot\",\n      node: (\n        <span className=\"inline-flex items-center gap-1\">\n          <span aria-hidden=\"true\" className=\"size-1 bg-primary\" />\n          snapshot\n        </span>\n      ),\n    });\n  }\n\n  if (checkpoint.projectId) {\n    parts.push({\n      key: \"project\",\n      node: (\n        <span className=\"truncate\" title={checkpoint.projectId}>\n          {checkpoint.projectId}\n        </span>\n      ),\n    });\n  }\n\n  if (parts.length === 0) {\n    return null;\n  }\n\n  return (\n    <p className=\"flex min-w-0 items-center gap-2 font-mono text-[10px] text-muted-foreground/70\">\n      {parts.map((part, index) => (\n        <span className=\"flex min-w-0 items-center gap-2\" key={part.key}>\n          {index > 0 ? <span aria-hidden=\"true\">·</span> : null}\n          {part.node}\n        </span>\n      ))}\n    </p>\n  );\n};\n\nconst CheckpointRow = ({\n  checkpoint,\n  index,\n  isLatest,\n  isRestoring,\n  locked,\n  onRestore,\n}: {\n  checkpoint: Checkpoint;\n  index: number;\n  isLatest: boolean;\n  isRestoring: boolean;\n  locked: boolean;\n  onRestore?: (id: string) => void;\n}) => (\n  <li\n    className={cn(\n      \"group relative flex items-start gap-3 pb-7 pl-6 transition-opacity duration-300 last:pb-0\",\n      locked && \"opacity-50\",\n      RISE\n    )}\n    data-restoring={isRestoring || undefined}\n    data-slot=\"checkpoint-row\"\n    style={{ animationDelay: `${index * ROW_STAGGER_MS}ms` }}\n  >\n    {/* The marker, punched on the continuous rail. */}\n    <span\n      aria-hidden=\"true\"\n      className={cn(\n        \"absolute top-[7px] left-[-2.5px] size-1.5 transition-colors duration-300\",\n        isLatest ? \"bg-primary\" : \"bg-muted-foreground/50\",\n        isRestoring &&\n          \"neon-status-breathe bg-primary text-primary motion-reduce:animate-none\"\n      )}\n      data-slot=\"checkpoint-marker\"\n    />\n\n    <div className=\"flex min-w-0 flex-1 flex-col gap-1\">\n      <div className=\"flex items-baseline gap-2.5\">\n        <p\n          className={cn(\n            \"truncate font-medium text-sm transition-colors duration-200\",\n            isLatest || isRestoring\n              ? \"text-foreground\"\n              : \"text-foreground/80 group-focus-within:text-foreground group-hover:text-foreground\"\n          )}\n          title={checkpoint.label}\n        >\n          {checkpoint.label}\n        </p>\n        {isLatest ? (\n          <span className=\"shrink-0 font-mono text-[10px] text-primary\">\n            current\n          </span>\n        ) : null}\n        <span className=\"ml-auto shrink-0 font-mono text-[10px] text-muted-foreground/70 tabular-nums\">\n          {checkpoint.createdAt}\n        </span>\n      </div>\n      <MetaLine checkpoint={checkpoint} />\n    </div>\n\n    {onRestore ? (\n      <Button\n        className={actionClasses(isRestoring, locked)}\n        disabled={locked}\n        onClick={() => onRestore(checkpoint.id)}\n        size=\"sm\"\n        variant=\"ghost\"\n      >\n        {/* The shimmer needs a muted base (it can only brighten) and\n            its own span (animate-in owns the animation shorthand). */}\n        <span\n          className=\"fade-in-0 animate-in duration-300\"\n          key={String(isRestoring)}\n        >\n          <span\n            className={cn(\"block\", {\n              \"shimmer shimmer-duration-2400\": isRestoring,\n            })}\n          >\n            {isRestoring ? \"Restoring…\" : \"Restore\"}\n          </span>\n        </span>\n      </Button>\n    ) : null}\n  </li>\n);\n\nexport const CheckpointTimeline = ({\n  checkpoints,\n  className,\n  empty,\n  onRestore,\n  restoringId = null,\n  ...props\n}: CheckpointTimelineProps) => (\n  <div\n    className={cn(\"w-full\", className)}\n    data-slot=\"checkpoint-timeline\"\n    {...props}\n  >\n    {checkpoints.length === 0 ? (\n      (empty ?? (\n        <EmptyState\n          description=\"Checkpoints capture your app and database together as the agent works.\"\n          title=\"No checkpoints yet\"\n        />\n      ))\n    ) : (\n      <ol className=\"relative flex flex-col\">\n        {/* One continuous rail behind every marker. */}\n        <span\n          aria-hidden=\"true\"\n          className=\"absolute top-2 bottom-3 left-0 w-px bg-border/60\"\n        />\n        {checkpoints.map((checkpoint, index) => (\n          <CheckpointRow\n            checkpoint={checkpoint}\n            index={index}\n            isLatest={index === 0}\n            isRestoring={restoringId === checkpoint.id}\n            key={checkpoint.id}\n            locked={restoringId !== null && restoringId !== checkpoint.id}\n            onRestore={onRestore}\n          />\n        ))}\n      </ol>\n    )}\n  </div>\n);\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:component"
}