{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "elements-reviewable-diff",
  "type": "registry:component",
  "title": "Reviewable diff",
  "description": "The same diff, but each hunk is a decision: keep it, discard it, apply what survived.",
  "registryDependencies": [
    "https://r.assistant-ui.com/elements-surfaces.json",
    "https://r.assistant-ui.com/elements-code-diff.json"
  ],
  "dependencies": [
    "lucide-react"
  ],
  "files": [
    {
      "type": "registry:component",
      "path": "components/assistant-ui/elements/reviewable-diff.tsx",
      "content": "\"use client\";\n\nimport type { ComponentProps } from \"react\";\nimport { CheckIcon, XIcon } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\nimport { codeScroll, codeSurface, inkButton, mono, paper } from \"./surfaces\";\nimport type { DiffLine } from \"./code-diff\";\n\nexport type HunkDecision = \"pending\" | \"kept\" | \"discarded\";\n\nexport interface DiffHunk {\n  id: string;\n  range: string;\n  decision: HunkDecision;\n  lines: readonly DiffLine[];\n}\n\nconst GUTTER = { context: \"\", added: \"+\", removed: \"−\" } as const;\n\nexport function ReviewableDiff({\n  filename,\n  hunks,\n  onKeep,\n  onDiscard,\n  onApply,\n  className,\n  ...props\n}: Omit<\n  ComponentProps<\"div\">,\n  \"children\" | \"filename\" | \"hunks\" | \"onKeep\" | \"onDiscard\" | \"onApply\"\n> & {\n  filename: string;\n  hunks: readonly DiffHunk[];\n  onKeep?: (id: string) => void;\n  onDiscard?: (id: string) => void;\n  onApply?: () => void;\n}) {\n  const kept = hunks.filter((hunk) => hunk.decision === \"kept\").length;\n  const pending = hunks.filter((hunk) => hunk.decision === \"pending\").length;\n\n  return (\n    <div\n      data-slot=\"reviewable-diff\"\n      className={cn(\n        paper,\n        \"flex w-full max-w-md flex-col overflow-hidden rounded-2xl\",\n        className,\n      )}\n\n      {...props}\n    >\n      <div className=\"flex items-center justify-between px-4 pt-3 pb-2\">\n        <span className=\"font-mono text-xs\">{filename}</span>\n        <span className={cn(mono, \"text-foreground/35 tabular-nums\")}>\n          {kept} of {hunks.length} kept\n        </span>\n      </div>\n\n      <div className=\"flex flex-col\">\n        {hunks.map((hunk) => (\n          <div\n            key={hunk.id}\n            className={cn(\n              \"border-foreground/[0.06] border-t transition-opacity duration-300\",\n              hunk.decision === \"discarded\" && \"opacity-40\",\n            )}\n          >\n            <div className=\"flex items-center gap-2 px-4 py-1.5\">\n              <span className={cn(mono, \"text-foreground/30\")}>\n                {hunk.range}\n              </span>\n              <span className=\"ms-auto flex items-center gap-1\">\n                {hunk.decision === \"pending\" ? (\n                  <>\n                    <button\n                      type=\"button\"\n                      aria-label={`Discard hunk ${hunk.range}`}\n                      onClick={() => onDiscard?.(hunk.id)}\n                      className=\"text-foreground/45 hover:bg-foreground/[0.06] hover:text-foreground/90 flex h-6 items-center gap-1 rounded-full px-2 text-[11px] font-medium transition-[background-color,color,scale] duration-150 active:scale-[0.96]\"\n                    >\n                      <XIcon className=\"size-3\" />\n                      Discard\n                    </button>\n                    <button\n                      type=\"button\"\n                      aria-label={`Keep hunk ${hunk.range}`}\n                      onClick={() => onKeep?.(hunk.id)}\n                      className=\"flex h-6 items-center gap-1 rounded-full bg-emerald-500/12 px-2 text-[11px] font-medium text-emerald-700 transition-[background-color,scale] duration-150 hover:bg-emerald-500/20 active:scale-[0.96] dark:text-emerald-300\"\n                    >\n                      <CheckIcon className=\"size-3\" />\n                      Keep\n                    </button>\n                  </>\n                ) : (\n                  <span\n                    className={cn(\n                      mono,\n                      \"fade-in animate-in duration-300\",\n                      hunk.decision === \"kept\"\n                        ? \"text-emerald-600 dark:text-emerald-400\"\n                        : \"text-foreground/35\",\n                    )}\n                  >\n                    {hunk.decision}\n                  </span>\n                )}\n              </span>\n            </div>\n            <div className={cn(codeScroll, \"pb-1.5 font-mono text-xs\")}>\n              <div className={codeSurface}>\n                {hunk.lines.map((line, i) => (\n                  <div\n                    key={`${hunk.id}-${i}`}\n                    className={cn(\n                      \"flex px-4 py-0.5 leading-relaxed whitespace-pre\",\n                      line.kind === \"context\" && \"text-foreground/40\",\n                      line.kind === \"added\" &&\n                        \"bg-emerald-500/10 text-emerald-700 dark:text-emerald-300\",\n                      line.kind === \"removed\" &&\n                        \"bg-red-500/10 text-red-700 dark:text-red-300\",\n                    )}\n                  >\n                    <span className=\"w-4 shrink-0 select-none\">\n                      {GUTTER[line.kind]}\n                    </span>\n                    <span>{line.text}</span>\n                  </div>\n                ))}\n              </div>\n            </div>\n          </div>\n        ))}\n      </div>\n\n      <div className=\"border-foreground/[0.06] flex items-center justify-between border-t px-4 py-2.5\">\n        <span className={cn(mono, \"text-foreground/35\")}>\n          {pending > 0 ? `${pending} left to review` : \"All reviewed\"}\n        </span>\n        <button\n          type=\"button\"\n          disabled={pending > 0}\n          onClick={onApply}\n          className={cn(\n            inkButton,\n            \"flex h-7 items-center rounded-full px-3 text-xs font-medium disabled:pointer-events-none disabled:opacity-30\",\n          )}\n        >\n          Apply {kept}\n        </button>\n      </div>\n    </div>\n  );\n}\n"
    }
  ]
}