{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "diff-view",
  "title": "Diff View",
  "description": "Proposed file changes with per-file accept and reject.",
  "dependencies": [
    "lucide-react"
  ],
  "files": [
    {
      "path": "components/spectrumui/blocks/ai-assistants/diff-view.tsx",
      "content": "'use client';\n\nimport { useState } from 'react';\nimport { Check, FileCode2, X } from 'lucide-react';\nimport { cn } from '@/lib/utils';\n\nconst KEYFRAMES = `\n@keyframes su-pop { 0% { opacity: 0; transform: scale(0.85) } 100% { opacity: 1; transform: none } }\n`;\n\nexport interface DiffLine {\n  type: 'context' | 'add' | 'remove';\n  text: string;\n}\n\nexport interface FileDiff {\n  id: string;\n  path: string;\n  additions: number;\n  deletions: number;\n  lines: DiffLine[];\n}\n\nexport type DiffViewVariant = 'Default' | 'Summary';\n\nexport interface DiffViewProps {\n  diffs: FileDiff[];\n  onAccept?: (id: string) => void;\n  onReject?: (id: string) => void;\n  variant?: DiffViewVariant;\n  className?: string;\n}\n\nconst LINE_STYLES: Record<DiffLine['type'], string> = {\n  context: 'text-neutral-500 dark:text-neutral-500',\n  add: 'bg-emerald-500/[0.08] text-emerald-800 dark:text-emerald-300',\n  remove: 'bg-red-500/[0.07] text-red-700/80 dark:text-red-400/80',\n};\n\nconst LINE_SIGNS: Record<DiffLine['type'], string> = { context: ' ', add: '+', remove: '-' };\n\nexport function DiffView({ diffs, onAccept, onReject, variant = 'Default', className }: DiffViewProps) {\n  const [decided, setDecided] = useState<Record<string, 'accepted' | 'rejected'>>({});\n\n  function decide(id: string, decision: 'accepted' | 'rejected') {\n    setDecided((previous) => ({ ...previous, [id]: decision }));\n    (decision === 'accepted' ? onAccept : onReject)?.(id);\n  }\n\n  return (\n    <div className={cn('w-full max-w-[500px] space-y-2.5', className)}>\n      <style dangerouslySetInnerHTML={{ __html: KEYFRAMES }} />\n      {diffs.map((diff) => {\n        const decision = decided[diff.id];\n        return (\n          <div\n            key={diff.id}\n            className={cn(\n              'overflow-hidden rounded-xl border border-black/[0.07] bg-white transition-opacity duration-200 dark:border-white/[0.08] dark:bg-[#0B0B0D]',\n              decision === 'rejected' && 'opacity-45',\n            )}\n          >\n            <div className=\"flex items-center justify-between gap-3 border-b border-black/[0.06] px-3 py-2 dark:border-white/[0.07]\">\n              <span className=\"flex min-w-0 items-center gap-1.5\">\n                <FileCode2 className=\"size-3.5 shrink-0 text-neutral-400 dark:text-neutral-500\" />\n                <span className=\"truncate font-mono text-[11.5px] font-medium text-neutral-800 dark:text-neutral-200\">\n                  {diff.path}\n                </span>\n              </span>\n              <span className=\"flex shrink-0 items-center gap-2\">\n                <span className=\"font-mono text-[10.5px] tabular-nums\">\n                  <span className=\"text-emerald-600 dark:text-emerald-400\">+{diff.additions}</span>{' '}\n                  <span className=\"text-red-600/80 dark:text-red-400/80\">−{diff.deletions}</span>\n                </span>\n                {decision ? (\n                  <span\n                    className={cn(\n                      'rounded-full px-2 py-0.5 font-mono text-[9px] font-medium uppercase tracking-wide motion-safe:animate-[su-pop_200ms_cubic-bezier(0.23,1,0.32,1)_both]',\n                      decision === 'accepted'\n                        ? 'bg-emerald-500/10 text-emerald-700 dark:text-emerald-400'\n                        : 'bg-black/[0.05] text-neutral-500 dark:bg-white/[0.07] dark:text-neutral-400',\n                    )}\n                  >\n                    {decision}\n                  </span>\n                ) : (\n                  <span className=\"flex gap-1\">\n                    <button\n                      type=\"button\"\n                      aria-label={`Reject changes to ${diff.path}`}\n                      onClick={() => decide(diff.id, 'rejected')}\n                      className=\"grid size-6 place-items-center rounded-md text-neutral-400 transition-[color,background-color,transform] duration-150 hover:bg-black/[0.04] hover:text-neutral-700 active:scale-[0.9] focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-neutral-400 dark:hover:bg-white/[0.06] dark:hover:text-neutral-200\"\n                    >\n                      <X className=\"size-3\" />\n                    </button>\n                    <button\n                      type=\"button\"\n                      aria-label={`Accept changes to ${diff.path}`}\n                      onClick={() => decide(diff.id, 'accepted')}\n                      className=\"grid size-6 place-items-center rounded-md bg-neutral-900 text-white transition-transform duration-[160ms] ease-[cubic-bezier(0.23,1,0.32,1)] active:scale-[0.9] focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-neutral-400 dark:bg-neutral-100 dark:text-neutral-900\"\n                    >\n                      <Check className=\"size-3\" />\n                    </button>\n                  </span>\n                )}\n              </span>\n            </div>\n\n            {variant === 'Default' && (\n              <div className=\"overflow-x-auto py-1.5\">\n                {diff.lines.map((line, index) => (\n                  <div\n                    key={index}\n                    className={cn(\n                      'flex whitespace-nowrap px-3 font-mono text-[11px] leading-[1.8]',\n                      LINE_STYLES[line.type],\n                    )}\n                  >\n                    <span aria-hidden className=\"mr-2.5 w-3 select-none opacity-60\">\n                      {LINE_SIGNS[line.type]}\n                    </span>\n                    {line.text}\n                  </div>\n                ))}\n              </div>\n            )}\n          </div>\n        );\n      })}\n    </div>\n  );\n}\n\nexport default DiffView;\n",
      "type": "registry:block",
      "target": "components/spectrumui/blocks/ai-assistants/diff-view.tsx"
    }
  ],
  "type": "registry:block"
}
