{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "diff-review",
  "type": "registry:ui",
  "title": "Diff review",
  "description": "Reviews agent-proposed field edits with explicit per-change decisions.",
  "dependencies": [
    "motion"
  ],
  "categories": [
    "agent"
  ],
  "docs": "https://motion-lexicon.pages.dev/en/components/diff-review/",
  "meta": {
    "engines": [
      "motion"
    ],
    "runtimeCost": "light",
    "signature": "Diff decisions resolve directly inside each row"
  },
  "files": [
    {
      "path": "src/registry/components/diff-review.tsx",
      "type": "registry:ui",
      "target": "components/motion-lexicon/diff-review.tsx",
      "content": "\"use client\";\n\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\";\nimport { useState } from \"react\";\n\nexport type DiffChange = {\n  id: string;\n  field: string;\n  before: string;\n  after: string;\n};\nexport type DiffReviewProps = {\n  changes: readonly DiffChange[];\n  title?: string;\n  acceptLabel?: string;\n  rejectLabel?: string;\n  editsLabel?: (count: number) => string;\n  acceptAllLabel?: string;\n  fieldLabel?: string;\n  beforeLabel?: string;\n  afterLabel?: string;\n  decisionLabel?: string;\n  acceptedLabel?: string;\n  rejectedLabel?: string;\n  reviewedLabel?: (decided: number, total: number) => string;\n  className?: string;\n};\n\nexport function DiffReview({\n  changes,\n  title = \"Proposed changes\",\n  acceptLabel = \"Accept\",\n  rejectLabel = \"Reject\",\n  editsLabel = (count) => `${count} edits`,\n  acceptAllLabel = \"Accept all\",\n  fieldLabel = \"Field\",\n  beforeLabel = \"Before\",\n  afterLabel = \"After\",\n  decisionLabel = \"Decision\",\n  acceptedLabel = \"Accepted\",\n  rejectedLabel = \"Rejected\",\n  reviewedLabel = (decided, total) => `${decided}/${total} reviewed`,\n  className = \"\",\n}: DiffReviewProps) {\n  const [decisions, setDecisions] = useState<\n    Record<string, \"accepted\" | \"rejected\">\n  >({});\n  const reduced = useReducedMotion();\n  const decided = Object.keys(decisions).length;\n  return (\n    <section\n      className={`w-full overflow-hidden rounded-[10px] border border-neutral-200 bg-white text-neutral-950 dark:border-white/10 dark:bg-[#1b1b1b] dark:text-neutral-50 ${className}`}\n    >\n      <header className=\"flex min-h-12 items-center gap-3 border-b border-neutral-100 px-4 dark:border-white/8\">\n        <strong className=\"text-[12px]\">{title}</strong>\n        <span className=\"text-[9px] text-neutral-400\">\n          {editsLabel(changes.length)}\n        </span>\n        <button\n          type=\"button\"\n          onClick={() =>\n            setDecisions(\n              Object.fromEntries(\n                changes.map((change) => [change.id, \"accepted\"]),\n              ),\n            )\n          }\n          className=\"ml-auto min-h-11 rounded-lg bg-neutral-950 px-3 text-[9px] font-semibold text-white dark:bg-neutral-50 dark:text-neutral-950\"\n        >\n          {acceptAllLabel}\n        </button>\n      </header>\n      <div className=\"overflow-x-auto\">\n        <table className=\"w-full min-w-[460px] border-collapse text-left\">\n          <thead>\n            <tr className=\"text-[9px] uppercase tracking-[.12em] text-neutral-400\">\n              <th className=\"px-4 py-3 font-medium\">{fieldLabel}</th>\n              <th className=\"px-3 py-3 font-medium\">{beforeLabel}</th>\n              <th className=\"px-3 py-3 font-medium\">{afterLabel}</th>\n              <th className=\"px-3 py-3\">\n                <span className=\"sr-only\">{decisionLabel}</span>\n              </th>\n            </tr>\n          </thead>\n          <tbody>\n            {changes.map((change) => (\n              <motion.tr\n                layout={!reduced}\n                key={change.id}\n                className=\"border-t border-neutral-100 text-[10px] dark:border-white/8\"\n              >\n                <th className=\"px-4 py-3 font-medium\">{change.field}</th>\n                <td className=\"px-3 py-3\">\n                  <span className=\"rounded-md bg-red-50 px-2 py-1 text-red-600 line-through decoration-red-300 dark:bg-red-500/10 dark:text-red-300\">\n                    {change.before}\n                  </span>\n                </td>\n                <td className=\"px-3 py-3\">\n                  <span className=\"rounded-md bg-emerald-50 px-2 py-1 text-emerald-700 dark:bg-emerald-500/10 dark:text-emerald-300\">\n                    {change.after}\n                  </span>\n                </td>\n                <td className=\"px-3 py-3\">\n                  <AnimatePresence mode=\"wait\">\n                    {decisions[change.id] ? (\n                      <motion.span\n                        key=\"done\"\n                        initial={{ scale: 0.8, opacity: 0 }}\n                        animate={{ scale: 1, opacity: 1 }}\n                        className={`text-[9px] ${decisions[change.id] === \"accepted\" ? \"text-emerald-600\" : \"text-red-500\"}`}\n                      >\n                        {decisions[change.id] === \"accepted\"\n                          ? acceptedLabel\n                          : rejectedLabel}\n                      </motion.span>\n                    ) : (\n                      <motion.div key=\"actions\" className=\"flex gap-1\">\n                        <button\n                          type=\"button\"\n                          aria-label={`${acceptLabel} ${change.field}`}\n                          onClick={() =>\n                            setDecisions((current) => ({\n                              ...current,\n                              [change.id]: \"accepted\",\n                            }))\n                          }\n                          className=\"grid size-11 place-items-center rounded-lg hover:bg-emerald-50 hover:text-emerald-600\"\n                        >\n                          ✓\n                        </button>\n                        <button\n                          type=\"button\"\n                          aria-label={`${rejectLabel} ${change.field}`}\n                          onClick={() =>\n                            setDecisions((current) => ({\n                              ...current,\n                              [change.id]: \"rejected\",\n                            }))\n                          }\n                          className=\"grid size-11 place-items-center rounded-lg hover:bg-red-50 hover:text-red-600\"\n                        >\n                          ×\n                        </button>\n                      </motion.div>\n                    )}\n                  </AnimatePresence>\n                </td>\n              </motion.tr>\n            ))}\n          </tbody>\n        </table>\n      </div>\n      <footer className=\"border-t border-neutral-100 px-4 py-2 text-[9px] text-neutral-400 dark:border-white/8\">\n        {reviewedLabel(decided, changes.length)}\n      </footer>\n    </section>\n  );\n}\n"
    }
  ]
}
