{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "code-diff",
  "type": "registry:ui",
  "title": "Code Diff",
  "description": "Unified or split diff from a git patch or a before/after pair, with word-level intra-line highlighting.",
  "dependencies": [
    "@tanstack/react-virtual@^3.14.9",
    "diff@^9.0.0"
  ],
  "registryDependencies": [
    "https://control-ui.dev/r/code.json",
    "https://control-ui.dev/r/core.json",
    "https://control-ui.dev/r/scroll-area.json"
  ],
  "files": [
    {
      "path": "src/registry/knob-contracts/code-diff-knobs.ts",
      "target": "@components/control-ui/knob-contracts/code-diff-knobs.ts",
      "type": "registry:component",
      "content": "// Generated from src/registry/sources/control-ui/recipes/code-diff.css by scripts/gen-knob-contracts.ts — run `bun run sync:knobs`.\nexport const codeDiffKnobs = [\n  \"--cui-code-diff-radius\",\n  \"--cui-code-diff-shadow\",\n  \"--cui-code-diff-background\",\n  \"--cui-code-diff-border-color\",\n  \"--cui-code-diff-foreground\",\n  \"--cui-code-diff-add-background\",\n  \"--cui-code-diff-del-background\",\n  \"--cui-code-diff-expand-button-radius\",\n  \"--cui-code-diff-expand-button-background\",\n  \"--cui-code-diff-expand-button-foreground\",\n  \"--cui-code-diff-expand-button-shadow\",\n  \"--cui-code-diff-expand-button-hover-background\",\n  \"--cui-code-diff-expand-button-hover-foreground\",\n] as const;\nexport type CodeDiffKnobStyle = Partial<Record<(typeof codeDiffKnobs)[number], string>>;\n"
    },
    {
      "path": "src/registry/lib/diff.ts",
      "target": "@components/control-ui/lib/diff.ts",
      "type": "registry:lib",
      "content": "import type { StructuredPatch, StructuredPatchHunk } from \"diff\";\nimport { diffChars, diffWordsWithSpace, parsePatch, structuredPatch } from \"diff\";\n\nexport type CodeDiffLineType = \"add\" | \"del\" | \"context\";\n\nexport type CodeLineType = CodeDiffLineType;\nexport type LineDiffType = \"word\" | \"char\" | \"none\";\nexport type DiffFileType = \"change\" | \"new\" | \"deleted\" | \"rename\";\n\n// one run of same-emphasis text inside changed line\nexport type DiffSegment = { text: string; emphasis: boolean };\n\nexport type DiffLine = {\n  type: CodeLineType;\n  // 1-based, only on side line exists on\n  oldNo?: number;\n  newNo?: number;\n  text: string;\n  // only on change lines paired across del/add boundary\n  segments?: DiffSegment[];\n};\n\nexport type DiffHunk = {\n  // scope trailing the @@ marker, when patch carries one\n  header?: string;\n  oldStart: number;\n  oldCount: number;\n  newStart: number;\n  newCount: number;\n  lines: DiffLine[];\n  // unchanged lines hidden immediately before this hunk — drives expand affordance\n  collapsedBefore: number;\n};\n\nexport type DiffFile = {\n  name: string;\n  oldName?: string;\n  type: DiffFileType;\n  lang?: string;\n  hunks: DiffHunk[];\n  // indexed 1:1 with line numbers so expand-context is range read; empty for isPartial files\n  oldLines: string[];\n  newLines: string[];\n  isPartial: boolean;\n  additions: number;\n  deletions: number;\n};\n\nexport type BuildDiffOptions = {\n  name?: string;\n  lang?: string;\n  // unchanged lines kept around each change\n  context?: number;\n  lineDiffType?: LineDiffType;\n  // skips intra-line diffing past old.length + new.length; 0 disables guard\n  maxLineDiffLength?: number;\n};\n\nconst DEFAULT_CONTEXT = 3;\nconst DEFAULT_MAX_LINE_DIFF_LENGTH = 2000;\n\nfunction splitLines(text: string): string[] {\n  // trailing newline yields phantom final \"\" — drop it so counts match file\n  if (text.length === 0) return [];\n  const lines = text.split(\"\\n\");\n  if (lines.length > 0 && lines[lines.length - 1] === \"\") lines.pop();\n  return lines;\n}\n\n// /dev/null and bare names pass through untouched\nfunction stripGitPrefix(name: string | undefined): string | undefined {\n  if (name === undefined) return undefined;\n  if (name === \"/dev/null\") return name;\n  return name.replace(/^[ab]\\//, \"\");\n}\n\n// [undefined, undefined] when diff is disabled or pair is too long\nexport function computeWordDiff(\n  oldText: string,\n  newText: string,\n  kind: LineDiffType,\n  maxLength: number,\n): [DiffSegment[] | undefined, DiffSegment[] | undefined] {\n  if (kind === \"none\") return [undefined, undefined];\n  if (maxLength > 0 && oldText.length + newText.length > maxLength) return [undefined, undefined];\n\n  const changes = kind === \"char\" ? diffChars(oldText, newText) : diffWordsWithSpace(oldText, newText);\n\n  const oldSegments: DiffSegment[] = [];\n  const newSegments: DiffSegment[] = [];\n  for (const change of changes) {\n    if (change.value.length === 0) continue;\n    if (change.added) {\n      newSegments.push({ text: change.value, emphasis: true });\n    } else if (change.removed) {\n      oldSegments.push({ text: change.value, emphasis: true });\n    } else {\n      oldSegments.push({ text: change.value, emphasis: false });\n      newSegments.push({ text: change.value, emphasis: false });\n    }\n  }\n  return [oldSegments, newSegments];\n}\n\nexport function diffRunEnd(lines: readonly DiffLine[], start: number, type: DiffLine[\"type\"]): number {\n  let end = start;\n  while (lines[end]?.type === type) end += 1;\n  return end;\n}\n\n// pairs each maximal [del…][add…] block across boundary\nfunction attachWordDiff(lines: DiffLine[], kind: LineDiffType, maxLength: number): void {\n  if (kind === \"none\") return;\n  let index = 0;\n  while (index < lines.length) {\n    if (lines[index]?.type !== \"del\") {\n      index += 1;\n      continue;\n    }\n    const delEnd = diffRunEnd(lines, index, \"del\");\n    const addEnd = diffRunEnd(lines, delEnd, \"add\");\n    const pairCount = Math.min(delEnd - index, addEnd - delEnd);\n    for (let pair = 0; pair < pairCount; pair += 1) {\n      const delLine = lines[index + pair];\n      const addLine = lines[delEnd + pair];\n      if (!delLine || !addLine) continue;\n      const [oldSegments, newSegments] = computeWordDiff(delLine.text, addLine.text, kind, maxLength);\n      delLine.segments = oldSegments;\n      addLine.segments = newSegments;\n    }\n    index = addEnd;\n  }\n}\n\n// patch prefixes: ' ' context, '+' add, '-' del, '\\' no-newline marker\nfunction hunkLines(hunk: StructuredPatchHunk): DiffLine[] {\n  const lines: DiffLine[] = [];\n  let oldNo = hunk.oldStart;\n  let newNo = hunk.newStart;\n  for (const raw of hunk.lines) {\n    if (raw.length === 0) continue;\n    const marker = raw[0];\n    const text = raw.slice(1);\n    if (marker === \"\\\\\") continue; // \"\\ No newline at end of file\"\n    if (marker === \"-\") {\n      lines.push({ type: \"del\", oldNo, text });\n      oldNo += 1;\n    } else if (marker === \"+\") {\n      lines.push({ type: \"add\", newNo, text });\n      newNo += 1;\n    } else {\n      lines.push({ type: \"context\", oldNo, newNo, text });\n      oldNo += 1;\n      newNo += 1;\n    }\n  }\n  return lines;\n}\n\n// git puts its scope hint after second \"@@\"\nfunction hunkHeaderScope(hunk: StructuredPatchHunk): string | undefined {\n  const raw = hunkRawHeader.get(hunk);\n  if (!raw) return undefined;\n  const trimmed = raw.trim();\n  return trimmed.length > 0 ? trimmed : undefined;\n}\n\n// jsdiff drops the @@-line scope, so it is recovered from raw patch\nconst hunkRawHeader = new WeakMap<StructuredPatchHunk, string>();\n\nfunction toDiffFile(\n  patch: StructuredPatch,\n  options: BuildDiffOptions,\n  oldLines: string[],\n  newLines: string[],\n  isPartial: boolean,\n): DiffFile {\n  const kind = options.lineDiffType ?? \"word\";\n  const maxLength = options.maxLineDiffLength ?? DEFAULT_MAX_LINE_DIFF_LENGTH;\n  const summary = summarizeHunks(patch.hunks, kind, maxLength);\n  const oldName = stripGitPrefix(patch.oldFileName);\n  const newName = stripGitPrefix(patch.newFileName);\n  const name = resolveDiffFileName(options.name, oldName, newName);\n  const type = resolveDiffFileType(oldName, newName, oldLines, newLines, isPartial);\n\n  return {\n    name,\n    oldName: type === \"rename\" ? oldName : undefined,\n    type,\n    lang: options.lang,\n    hunks: summary.hunks,\n    oldLines,\n    newLines,\n    isPartial,\n    additions: summary.additions,\n    deletions: summary.deletions,\n  };\n}\n\nfunction countLineChanges(lines: DiffLine[]): { additions: number; deletions: number } {\n  let additions = 0;\n  let deletions = 0;\n  for (const line of lines) {\n    if (line.type === \"add\") additions += 1;\n    if (line.type === \"del\") deletions += 1;\n  }\n  return { additions, deletions };\n}\n\nfunction summarizeHunks(\n  rawHunks: StructuredPatchHunk[],\n  kind: LineDiffType,\n  maxLength: number,\n): { hunks: DiffHunk[]; additions: number; deletions: number } {\n  const hunks: DiffHunk[] = [];\n  let additions = 0;\n  let deletions = 0;\n  let previousOldEnd = 0;\n\n  for (const raw of rawHunks) {\n    const lines = hunkLines(raw);\n    attachWordDiff(lines, kind, maxLength);\n    const changes = countLineChanges(lines);\n    additions += changes.additions;\n    deletions += changes.deletions;\n    hunks.push({\n      header: hunkHeaderScope(raw),\n      oldStart: raw.oldStart,\n      oldCount: raw.oldLines,\n      newStart: raw.newStart,\n      newCount: raw.newLines,\n      lines,\n      collapsedBefore: Math.max(0, raw.oldStart - 1 - previousOldEnd),\n    });\n    previousOldEnd = raw.oldStart - 1 + raw.oldLines;\n  }\n\n  return { hunks, additions, deletions };\n}\n\nfunction resolveDiffFileName(explicitName: string | undefined, oldName: string | undefined, newName: string | undefined): string {\n  if (explicitName !== undefined) return explicitName;\n  if (newName && newName !== \"/dev/null\") return newName;\n  return oldName ?? \"file\";\n}\n\nfunction resolveDiffFileType(\n  oldName: string | undefined,\n  newName: string | undefined,\n  oldLines: string[],\n  newLines: string[],\n  isPartial: boolean,\n): DiffFileType {\n  if (oldName === \"/dev/null\" || (!isPartial && oldLines.length === 0 && newLines.length > 0)) return \"new\";\n  if (newName === \"/dev/null\" || (!isPartial && newLines.length === 0 && oldLines.length > 0)) return \"deleted\";\n  if (oldName !== undefined && newName !== undefined && oldName !== newName) return \"rename\";\n  return \"change\";\n}\n\n// keeps both full line arrays, so result is never partial and renderer can expand hidden context\nexport function buildDiffFromFiles(oldText: string, newText: string, options: BuildDiffOptions = {}): DiffFile {\n  const name = options.name ?? \"file\";\n  const context = options.context ?? DEFAULT_CONTEXT;\n  const patch = structuredPatch(name, name, oldText, newText, \"\", \"\", { context });\n  return toDiffFile(patch, options, splitLines(oldText), splitLines(newText), false);\n}\n\n// one DiffFile per patch section; all are isPartial, so renderer disables expand-context\nexport function buildDiffFromPatch(patchText: string, options: BuildDiffOptions = {}): DiffFile[] {\n  let patches: StructuredPatch[];\n  try {\n    patches = parsePatch(patchText);\n    indexRawHunkHeaders(patchText, patches);\n  } catch {\n    // jsdiff throws when @@-header counts disagree with body — agent and hand-authored patches get this wrong often\n    patches = parsePatchLenient(patchText);\n  }\n  return patches.map((patch) => toDiffFile(patch, options, [], [], true));\n}\n\nfunction emptyStructuredPatch(): StructuredPatch {\n  return { oldFileName: undefined, newFileName: undefined, oldHeader: undefined, newHeader: undefined, hunks: [] };\n}\n\n// git appends tab-separated timestamp after path\nfunction patchFileName(rest: string): string {\n  return rest.split(\"\\t\", 1)[0]?.trim() ?? \"\";\n}\n\n// header numbers are not trusted — counted from body instead\nfunction reconcileHunkCounts(hunk: StructuredPatchHunk): void {\n  let oldCount = 0;\n  let newCount = 0;\n  for (const line of hunk.lines) {\n    const marker = line[0];\n    if (marker === \"+\") newCount += 1;\n    else if (marker === \"-\") oldCount += 1;\n    else if (marker === \"\\\\\")\n      continue; // \"\\ No newline at end of file\"\n    else {\n      oldCount += 1;\n      newCount += 1;\n    }\n  }\n  hunk.oldLines = oldCount;\n  hunk.newLines = newCount;\n}\n\ntype LenientPatchState = {\n  files: StructuredPatch[];\n  current?: StructuredPatch;\n  hunk?: StructuredPatchHunk;\n};\n\nfunction ensureCurrentFile(state: LenientPatchState): StructuredPatch {\n  state.current ??= emptyStructuredPatch();\n  return state.current;\n}\n\nfunction closeLenientHunk(state: LenientPatchState): void {\n  if (state.current && state.hunk) {\n    reconcileHunkCounts(state.hunk);\n    state.current.hunks.push(state.hunk);\n  }\n  state.hunk = undefined;\n}\n\nfunction closeLenientFile(state: LenientPatchState): void {\n  closeLenientHunk(state);\n  if (state.current) state.files.push(state.current);\n  state.current = undefined;\n}\n\nfunction lenientHunkHeader(line: string): { oldStart: number; newStart: number; scope: string } | undefined {\n  const match = line.match(/^@@ -(\\d+)(?:,\\d+)? \\+(\\d+)(?:,\\d+)? @@(.*)$/);\n  if (!match) return undefined;\n  return { oldStart: Number(match[1]), newStart: Number(match[2]), scope: match[3] ?? \"\" };\n}\n\nfunction isHunkBodyLine(line: string): boolean {\n  const marker = line[0];\n  return marker === \" \" || marker === \"+\" || marker === \"-\" || marker === \"\\\\\";\n}\n\nfunction consumeLenientPatchLine(state: LenientPatchState, line: string): void {\n  if (line.startsWith(\"diff --git\")) {\n    closeLenientFile(state);\n    state.current = emptyStructuredPatch();\n    return;\n  }\n  if (line.startsWith(\"--- \")) {\n    if (state.current) closeLenientHunk(state);\n    ensureCurrentFile(state).oldFileName = patchFileName(line.slice(4));\n    return;\n  }\n  if (line.startsWith(\"+++ \")) {\n    ensureCurrentFile(state).newFileName = patchFileName(line.slice(4));\n    return;\n  }\n\n  const header = lenientHunkHeader(line);\n  if (header) {\n    closeLenientHunk(state);\n    ensureCurrentFile(state);\n    state.hunk = { oldStart: header.oldStart, oldLines: 0, newStart: header.newStart, newLines: 0, lines: [] };\n    hunkRawHeader.set(state.hunk, header.scope);\n    return;\n  }\n\n  if (!state.hunk) return;\n  if (isHunkBodyLine(line)) {\n    state.hunk.lines.push(line);\n    return;\n  }\n  closeLenientHunk(state);\n}\n\n// trusts body over header, so miscounted @@ lines still render; unknown or blank line closes current hunk\nfunction parsePatchLenient(patchText: string): StructuredPatch[] {\n  const state: LenientPatchState = { files: [] };\n\n  for (const line of patchText.split(\"\\n\")) {\n    consumeLenientPatchLine(state, line);\n  }\n  closeLenientFile(state);\n  return state.files;\n}\n\n// matched positionally — jsdiff exposes no handle back to raw line\nfunction indexRawHunkHeaders(patchText: string, patches: StructuredPatch[]): void {\n  const scopes: string[] = [];\n  for (const line of patchText.split(\"\\n\")) {\n    const match = line.match(/^@@[^@]*@@(.*)$/);\n    const scope = match?.[1];\n    if (scope !== undefined) scopes.push(scope);\n  }\n  let hunkIndex = 0;\n  for (const patch of patches) {\n    for (const hunk of patch.hunks) {\n      const scope = scopes[hunkIndex];\n      if (scope !== undefined) hunkRawHeader.set(hunk, scope);\n      hunkIndex += 1;\n    }\n  }\n}\n"
    },
    {
      "path": "src/registry/sources/control-ui/recipes/code-diff.css",
      "target": "@components/control-ui/styles/recipes/code-diff.css",
      "type": "registry:file",
      "content": "@layer components {\n  :where([data-control-family=\"code-diff\"][data-slot=\"root\"]) {\n    --cui-code-diff-background: var(--background);\n    --cui-code-diff-border-color: var(--border);\n    --cui-code-diff-radius: var(--radius-panel);\n    --cui-code-diff-shadow: var(--shadow-sm);\n    --cui-code-diff-foreground: var(--code-foreground);\n    --cui-code-diff-add-background: var(--diff-add-line);\n    --cui-code-diff-del-background: var(--diff-del-line);\n    --cui-code-diff-expand-button-radius: var(--radius-control);\n    --cui-code-diff-expand-button-background: transparent;\n    --cui-code-diff-expand-button-foreground: var(--muted-foreground);\n    --cui-code-diff-expand-button-shadow: none;\n    --cui-code-diff-expand-button-hover-background: oklch(from var(--foreground) l c h / 0.08);\n    --cui-code-diff-expand-button-hover-foreground: var(--foreground);\n    border: 1px solid var(--cui-code-diff-border-color);\n    border-radius: var(--cui-code-diff-radius);\n    background: var(--cui-code-diff-background);\n    box-shadow: var(--cui-code-diff-shadow);\n  }\n\n  :where([data-control-family=\"code-diff\"]:is([data-slot=\"header\"], [data-slot=\"file-header\"])) {\n    border-bottom: 1px solid var(--border);\n    background: var(--diff-gutter-bg);\n  }\n\n  :where([data-control-family=\"code-diff\"][data-slot=\"title\"]),\n  :where([data-control-family=\"code-diff\"][data-slot=\"file-header\"] > span:first-child) {\n    color: var(--muted-foreground);\n    font-size: var(--text-label);\n  }\n\n  :where([data-control-family=\"code-diff\"][data-slot=\"body\"] > div) {\n    color: var(--cui-code-diff-foreground);\n    font-size: var(--text-label);\n  }\n\n  :where([data-control-family=\"code-diff\"][data-slot=\"line\"][data-line-type=\"add\"]) {\n    background: var(--cui-code-diff-add-background);\n  }\n\n  :where([data-control-family=\"code-diff\"][data-slot=\"line\"][data-line-type=\"del\"]) {\n    background: var(--cui-code-diff-del-background);\n  }\n\n  :where([data-control-family=\"code-diff\"][data-slot=\"emphasis\"]) {\n    border-radius: 2px;\n  }\n\n  :where([data-control-family=\"code-diff\"][data-slot=\"emphasis\"][data-line-type=\"add\"]) {\n    background: var(--diff-add-emphasis);\n  }\n\n  :where([data-control-family=\"code-diff\"][data-slot=\"emphasis\"][data-line-type=\"del\"]) {\n    background: var(--diff-del-emphasis);\n  }\n\n  :where([data-control-family=\"code-diff\"][data-slot=\"line\"][data-indicators=\"bars\"][data-line-type=\"add\"]) {\n    border-left: 2px solid var(--diff-add-fg);\n  }\n\n  :where([data-control-family=\"code-diff\"][data-slot=\"line\"][data-indicators=\"bars\"][data-line-type=\"del\"]) {\n    border-left: 2px solid var(--diff-del-fg);\n  }\n\n  :where([data-control-family=\"code-diff\"][data-slot=\"line\"][data-side=\"left\"]),\n  :where([data-control-family=\"code-diff\"][data-slot=\"empty-half\"][data-side=\"left\"]) {\n    border-right: 1px solid oklch(from var(--border) l c h / 0.6);\n  }\n\n  :where([data-control-family=\"code-diff\"][data-slot=\"empty-half\"]) {\n    background: var(--diff-gutter-bg);\n  }\n\n  :where([data-control-family=\"code-diff\"][data-slot=\"gutter\"]) {\n    color: var(--diff-context-fg);\n    opacity: 0.7;\n    text-align: right;\n    font-variant-numeric: tabular-nums;\n  }\n\n  :where([data-control-family=\"code-diff\"][data-slot=\"gutter\"][data-line-type=\"add\"]) {\n    background: var(--diff-add-gutter);\n  }\n\n  :where([data-control-family=\"code-diff\"][data-slot=\"gutter\"][data-line-type=\"del\"]) {\n    background: var(--diff-del-gutter);\n  }\n\n  :where([data-control-family=\"code-diff\"][data-slot=\"marker\"]) {\n    color: transparent;\n    font-variant-numeric: tabular-nums;\n  }\n\n  :where([data-control-family=\"code-diff\"][data-slot=\"marker\"][data-line-type=\"add\"]),\n  :where([data-control-family=\"code-diff\"][data-slot=\"stat-additions\"]) {\n    color: var(--diff-add-fg);\n  }\n\n  :where([data-control-family=\"code-diff\"][data-slot=\"marker\"][data-line-type=\"del\"]),\n  :where([data-control-family=\"code-diff\"][data-slot=\"stat-deletions\"]) {\n    color: var(--diff-del-fg);\n  }\n\n  :where([data-control-family=\"code-diff\"][data-slot=\"stat\"]) {\n    font-size: var(--text-micro);\n    font-variant-numeric: tabular-nums;\n  }\n\n  :where([data-control-family=\"code-diff\"][data-slot=\"expander\"]) {\n    background: var(--diff-gutter-bg);\n    color: var(--muted-foreground);\n    font-size: var(--text-micro);\n  }\n\n  :where([data-control-family=\"code-diff\"][data-slot=\"expand-button\"]) {\n    border-radius: var(--cui-code-diff-expand-button-radius);\n    background: var(--cui-code-diff-expand-button-background);\n    box-shadow: var(--cui-code-diff-expand-button-shadow);\n    color: var(--cui-code-diff-expand-button-foreground);\n    outline-style: none;\n  }\n\n  :where([data-control-family=\"code-diff\"][data-slot=\"expand-button\"]:hover) {\n    background: var(--cui-code-diff-expand-button-hover-background);\n    color: var(--cui-code-diff-expand-button-hover-foreground);\n  }\n\n  :where([data-control-family=\"code-diff\"][data-slot=\"title\"]) {\n    font-family: var(--font-mono);\n  }\n\n  :where([data-control-family=\"code-diff\"][data-slot=\"file-title\"]) {\n    font-family: var(--font-mono);\n    font-size: var(--text-label);\n    color: var(--muted-foreground);\n  }\n\n  :where([data-control-family=\"code-diff\"][data-slot=\"expander-label\"]) {\n    font-family: var(--font-mono);\n  }\n}\n\n@property --cui-code-diff-radius {\n  syntax: \"<length>\";\n  inherits: true;\n  initial-value: 0px;\n}\n\n@property --cui-code-diff-shadow {\n  syntax: \"*\";\n  inherits: true;\n}\n\n@property --cui-code-diff-background {\n  syntax: \"<color>\";\n  inherits: true;\n  initial-value: transparent;\n}\n\n@property --cui-code-diff-border-color {\n  syntax: \"<color>\";\n  inherits: true;\n  initial-value: transparent;\n}\n\n@property --cui-code-diff-foreground {\n  syntax: \"<color>\";\n  inherits: true;\n  initial-value: transparent;\n}\n\n@property --cui-code-diff-add-background {\n  syntax: \"<color>\";\n  inherits: true;\n  initial-value: transparent;\n}\n\n@property --cui-code-diff-del-background {\n  syntax: \"<color>\";\n  inherits: true;\n  initial-value: transparent;\n}\n\n@property --cui-code-diff-expand-button-radius {\n  syntax: \"<length-percentage>\";\n  inherits: true;\n  initial-value: 0px;\n}\n\n@property --cui-code-diff-expand-button-background {\n  syntax: \"<color>\";\n  inherits: true;\n  initial-value: transparent;\n}\n\n@property --cui-code-diff-expand-button-foreground {\n  syntax: \"<color>\";\n  inherits: true;\n  initial-value: transparent;\n}\n\n@property --cui-code-diff-expand-button-shadow {\n  syntax: \"*\";\n  inherits: true;\n}\n\n@property --cui-code-diff-expand-button-hover-background {\n  syntax: \"<color>\";\n  inherits: true;\n  initial-value: transparent;\n}\n\n@property --cui-code-diff-expand-button-hover-foreground {\n  syntax: \"<color>\";\n  inherits: true;\n  initial-value: transparent;\n}\n"
    },
    {
      "path": "src/registry/sources/control-ui/ui/code-diff.tsx",
      "target": "@components/control-ui/ui/code-diff.tsx",
      "type": "registry:ui",
      "content": "\"use client\";\n\nimport { useVirtualizer } from \"@tanstack/react-virtual\";\nimport type { ComponentProps, CSSProperties, ReactNode } from \"react\";\nimport { useEffect, useMemo, useRef, useState } from \"react\";\nimport type { CodeDiffKnobStyle } from \"@/components/control-ui/knob-contracts/code-diff-knobs\";\nimport { cn } from \"@/components/control-ui/lib/cn\";\nimport { type CodeTokenLines, highlightToTokens, mergeCodeTokenLineWithEmphasis } from \"@/components/control-ui/lib/code-tokens\";\nimport { buildDiffFromFiles, buildDiffFromPatch, type DiffFile, type DiffLine, diffRunEnd } from \"@/components/control-ui/lib/diff\";\nimport type { CodeOverflow } from \"@/components/control-ui/ui/code\";\nimport { CodeCopy, type CodeCopyProps, CodeFloatingCopy, CodeTokenLine } from \"@/components/control-ui/ui/code\";\nimport { ScrollArea } from \"@/components/control-ui/ui/scroll-area\";\n\nexport type DiffStyle = \"unified\" | \"split\";\n\nexport type DiffIndicators = \"classic\" | \"bars\" | \"none\";\n\nexport type DiffLineKind = \"word\" | \"char\" | \"none\";\n\n/*\n * Split mode virtualizes one aligned-row list rather than two synced panes, so there is no scroll-sync to drift.\n * Line numbers and +/- markers sit in select-none cells, so text selection copies clean source.\n */\n\nconst ESTIMATED_ROW_HEIGHT = 20;\nconst VIRTUALIZE_THRESHOLD = 150;\n\ntype SideTokens = { old: CodeTokenLines | null; new: CodeTokenLines | null };\n\ntype VisualRow =\n  | { kind: \"separator\"; id: string; gapIndex: number; label: string; canExpand: boolean }\n  | { kind: \"unified\"; id: string; line: DiffLine }\n  | { kind: \"split\"; id: string; left: DiffLine | null; right: DiffLine | null };\n\nexport type CodeDiffProps = Omit<ComponentProps<\"figure\">, \"children\" | \"style\"> & {\n  // one path or other: `patch` is partial and cannot expand context, text pair can\n  patch?: string;\n  oldText?: string;\n  newText?: string;\n  lang?: string;\n  name?: string;\n  diffStyle?: DiffStyle;\n  diffIndicators?: DiffIndicators;\n  lineDiffType?: DiffLineKind;\n  overflow?: CodeOverflow;\n  maxLineDiffLength?: number;\n  maxHeight?: string;\n  header?: boolean;\n  children?: ReactNode;\n  style?: CSSProperties & CodeDiffKnobStyle;\n};\n\n// tokens are indexed by line number, so partial file's holes are rebuilt as blank lines to keep indices honest\nfunction sideTexts(file: DiffFile): { old: string; new: string } {\n  if (!file.isPartial) return { old: file.oldLines.join(\"\\n\"), new: file.newLines.join(\"\\n\") };\n  const oldArr: string[] = [];\n  const newArr: string[] = [];\n  for (const hunk of file.hunks) {\n    for (const line of hunk.lines) {\n      if (line.oldNo !== undefined) oldArr[line.oldNo - 1] = line.text;\n      if (line.newNo !== undefined) newArr[line.newNo - 1] = line.text;\n    }\n  }\n  return { old: oldArr.join(\"\\n\"), new: newArr.join(\"\\n\") };\n}\n\nfunction useSideTokens(oldText: string, newText: string, lang: string | undefined, enabled: boolean): SideTokens {\n  const requestKey = `${lang ?? \"\"}\\n${oldText.length}:${oldText}${newText}`;\n  const [state, setState] = useState<{ key: string; tokens: SideTokens } | null>(null);\n\n  useEffect(() => {\n    if (!enabled || !lang) return;\n    let cancelled = false;\n    void Promise.all([highlightToTokens(oldText, lang), highlightToTokens(newText, lang)])\n      .then(([oldTokens, newTokens]) => {\n        if (!cancelled) setState({ key: requestKey, tokens: { old: oldTokens, new: newTokens } });\n      })\n      .catch(() => {\n        if (!cancelled) setState({ key: requestKey, tokens: { old: null, new: null } });\n      });\n    return () => {\n      cancelled = true;\n    };\n  }, [oldText, newText, lang, enabled, requestKey]);\n\n  if (!enabled) return { old: null, new: null };\n  return state?.key === requestKey ? state.tokens : { old: null, new: null };\n}\n\nfunction lineTokens(line: DiffLine, tokens: SideTokens): CodeTokenLines[number] | null {\n  if (line.type === \"del\") return tokens.old?.[(line.oldNo ?? 1) - 1] ?? null;\n  if (line.type === \"add\") return tokens.new?.[(line.newNo ?? 1) - 1] ?? null;\n  return tokens.new?.[(line.newNo ?? 1) - 1] ?? tokens.old?.[(line.oldNo ?? 1) - 1] ?? null;\n}\n\n// context lines sit on both sides of pair\nfunction splitPairs(lines: DiffLine[]): { left: DiffLine | null; right: DiffLine | null }[] {\n  const rows: { left: DiffLine | null; right: DiffLine | null }[] = [];\n  let index = 0;\n  while (index < lines.length) {\n    const line = lines[index];\n    if (!line) break;\n    if (line.type === \"context\") {\n      rows.push({ left: line, right: line });\n      index += 1;\n      continue;\n    }\n    const delEnd = diffRunEnd(lines, index, \"del\");\n    const addEnd = diffRunEnd(lines, delEnd, \"add\");\n    const dels = lines.slice(index, delEnd);\n    const adds = lines.slice(delEnd, addEnd);\n    const count = Math.max(dels.length, adds.length);\n    for (let pair = 0; pair < count; pair += 1) rows.push({ left: dels[pair] ?? null, right: adds[pair] ?? null });\n    index = addEnd;\n  }\n  return rows;\n}\n\n// non-partial only — numbering comes from file's full text\nfunction expandedContext(file: DiffFile, gapIndex: number): DiffLine[] {\n  const hunk = file.hunks[gapIndex];\n  if (!hunk) return [];\n  const offset = hunk.newStart - hunk.oldStart;\n  const lines: DiffLine[] = [];\n  for (let oldNo = hunk.oldStart - hunk.collapsedBefore; oldNo < hunk.oldStart; oldNo += 1) {\n    const text = file.oldLines[oldNo - 1] ?? \"\";\n    lines.push({ type: \"context\", oldNo, newNo: oldNo + offset, text });\n  }\n  return lines;\n}\n\ntype DiffHunk = DiffFile[\"hunks\"][number];\n\nfunction gapRows(file: DiffFile, hunk: DiffHunk, gapIndex: number, diffStyle: DiffStyle, expanded: ReadonlySet<number>): VisualRow[] {\n  const hasGap = gapIndex > 0 || hunk.collapsedBefore > 0;\n  if (!hasGap) return [];\n  if (expanded.has(gapIndex) && !file.isPartial) {\n    return expandedContext(file, gapIndex).map((line) => rowForLine(diffStyle, line, `exp-${gapIndex}-${line.oldNo ?? line.newNo ?? 0}`));\n  }\n\n  const label = hunk.header ?? `@@ -${hunk.oldStart},${hunk.oldCount} +${hunk.newStart},${hunk.newCount} @@`;\n  return [\n    {\n      kind: \"separator\",\n      id: `sep-${gapIndex}`,\n      gapIndex,\n      label,\n      canExpand: !file.isPartial && hunk.collapsedBefore > 0,\n    },\n  ];\n}\n\nfunction changedRows(hunk: DiffHunk, gapIndex: number, diffStyle: DiffStyle): VisualRow[] {\n  if (diffStyle === \"unified\") {\n    return hunk.lines.map((line, lineIndex) => rowForLine(\"unified\", line, `u-${gapIndex}-${lineIndex}`));\n  }\n  return splitPairs(hunk.lines).map((pair, pairIndex) => ({\n    kind: \"split\",\n    id: `s-${gapIndex}-${pairIndex}`,\n    left: pair.left,\n    right: pair.right,\n  }));\n}\n\nfunction buildRows(file: DiffFile, diffStyle: DiffStyle, expanded: ReadonlySet<number>): VisualRow[] {\n  const rows: VisualRow[] = [];\n  for (const [gapIndex, hunk] of file.hunks.entries()) {\n    rows.push(...gapRows(file, hunk, gapIndex, diffStyle, expanded));\n    rows.push(...changedRows(hunk, gapIndex, diffStyle));\n  }\n  return rows;\n}\n\nfunction rowForLine(diffStyle: DiffStyle, line: DiffLine, id: string): VisualRow {\n  if (diffStyle === \"unified\") return { kind: \"unified\", id, line };\n  if (line.type === \"context\") return { kind: \"split\", id, left: line, right: line };\n  if (line.type === \"del\") return { kind: \"split\", id, left: line, right: null };\n  return { kind: \"split\", id, left: null, right: line };\n}\n\nconst markerFor: Record<DiffLine[\"type\"], string> = { add: \"+\", del: \"-\", context: \"\" };\n\nfunction DiffCode({\n  line,\n  tokens,\n  overflow,\n}: {\n  line: DiffLine;\n  tokens: CodeTokenLines[number] | null;\n  overflow: CodeOverflow;\n}): ReactNode {\n  const wrapClass = overflow === \"wrap\" ? \"whitespace-pre-wrap break-words\" : \"whitespace-pre\";\n  if (line.segments && line.segments.length > 0) {\n    const runs = mergeCodeTokenLineWithEmphasis(line.text, tokens, line.segments);\n    return (\n      <code className={cn(\"min-w-0 flex-1 pr-4\", wrapClass)}>\n        {runs.map((run) => {\n          const style: CSSProperties | undefined = run.style || undefined;\n          if (run.emphasis) {\n            return (\n              <span\n                key={run.start}\n                data-control-ui=\"code-diff\"\n                data-control-family=\"code-diff\"\n                data-slot=\"emphasis\"\n                data-line-type={line.type}\n                style={style}\n              >\n                {run.content}\n              </span>\n            );\n          }\n          return (\n            <span key={run.start} style={style}>\n              {run.content}\n            </span>\n          );\n        })}\n      </code>\n    );\n  }\n  return (\n    <code className={cn(\"min-w-0 flex-1 pr-4\", wrapClass)}>\n      <CodeTokenLine tokens={tokens} plain={line.text} />\n    </code>\n  );\n}\n\nfunction Gutter({ children, type }: { children: ReactNode; type: DiffLine[\"type\"] }) {\n  return (\n    <span\n      data-control-ui=\"code-diff\"\n      data-control-family=\"code-diff\"\n      data-line-type={type}\n      data-slot=\"gutter\"\n      aria-hidden=\"true\"\n      className=\"shrink-0 select-none px-2\"\n      style={{ minWidth: \"2.75rem\" }}\n    >\n      {children}\n    </span>\n  );\n}\n\nfunction Marker({ type, indicators }: { type: DiffLine[\"type\"]; indicators: DiffIndicators }) {\n  if (indicators !== \"classic\") return null;\n  return (\n    <span\n      data-control-ui=\"code-diff\"\n      data-control-family=\"code-diff\"\n      data-slot=\"marker\"\n      data-line-type={type}\n      aria-hidden=\"true\"\n      className=\"shrink-0 select-none pl-1 pr-1\"\n    >\n      {markerFor[type] || \" \"}\n    </span>\n  );\n}\n\nfunction UnifiedRow({\n  line,\n  tokens,\n  indicators,\n  overflow,\n}: {\n  line: DiffLine;\n  tokens: CodeTokenLines[number] | null;\n  indicators: DiffIndicators;\n  overflow: CodeOverflow;\n}) {\n  return (\n    <div\n      data-control-ui=\"code-diff\"\n      data-control-family=\"code-diff\"\n      data-slot=\"line\"\n      data-line-type={line.type}\n      data-indicators={indicators}\n      aria-hidden=\"true\"\n      className=\"flex w-full\"\n    >\n      <Gutter type={line.type}>{line.oldNo ?? \"\"}</Gutter>\n      <Gutter type={line.type}>{line.newNo ?? \"\"}</Gutter>\n      <Marker type={line.type} indicators={indicators} />\n      <DiffCode line={line} tokens={tokens} overflow={overflow} />\n    </div>\n  );\n}\n\nfunction SplitHalf({\n  line,\n  tokens,\n  indicators,\n  overflow,\n  side,\n}: {\n  line: DiffLine | null;\n  tokens: CodeTokenLines[number] | null;\n  indicators: DiffIndicators;\n  overflow: CodeOverflow;\n  side: \"left\" | \"right\";\n}) {\n  if (!line)\n    return (\n      <div\n        data-control-ui=\"code-diff\"\n        data-control-family=\"code-diff\"\n        data-slot=\"empty-half\"\n        data-side={side}\n        aria-hidden=\"true\"\n        className=\"flex min-w-0 flex-1\"\n      />\n    );\n  return (\n    <div\n      data-control-ui=\"code-diff\"\n      data-control-family=\"code-diff\"\n      data-slot=\"line\"\n      data-line-type={line.type}\n      data-indicators={indicators}\n      data-side={side}\n      aria-hidden=\"true\"\n      className=\"flex min-w-0 flex-1\"\n    >\n      <Gutter type={line.type}>{side === \"left\" ? (line.oldNo ?? \"\") : (line.newNo ?? \"\")}</Gutter>\n      <Marker type={line.type} indicators={indicators} />\n      <DiffCode line={line} tokens={tokens} overflow={overflow} />\n    </div>\n  );\n}\n\nfunction fileTitle(file: DiffFile): string {\n  return file.oldName && file.type === \"rename\" ? `${file.oldName} → ${file.name}` : file.name;\n}\n\nfunction DiffStats({ additions, deletions }: { additions: number; deletions: number }) {\n  return (\n    <span data-control-ui=\"code-diff\" data-control-family=\"code-diff\" data-slot=\"stat\" className=\"flex items-center gap-1.5\">\n      <span data-control-ui=\"code-diff\" data-control-family=\"code-diff\" data-slot=\"stat-additions\">\n        +{additions}\n      </span>\n      <span data-control-ui=\"code-diff\" data-control-family=\"code-diff\" data-slot=\"stat-deletions\">\n        −{deletions}\n      </span>\n    </span>\n  );\n}\n\nfunction lineDescription(label: string, line: DiffLine, number: number | undefined): string {\n  return `${label} line ${number ?? \"?\"}: ${line.text}`;\n}\n\nfunction unifiedLineDescription(line: DiffLine): string {\n  if (line.type === \"add\") return lineDescription(\"Added\", line, line.newNo);\n  if (line.type === \"del\") return lineDescription(\"Deleted\", line, line.oldNo);\n  return lineDescription(\"Unchanged\", line, line.newNo ?? line.oldNo);\n}\n\nfunction splitRowDescriptions(row: Extract<VisualRow, { kind: \"split\" }>): string[] {\n  if (row.left?.type === \"context\") return [lineDescription(\"Unchanged\", row.left, row.left.newNo ?? row.left.oldNo)];\n  const descriptions: string[] = [];\n  if (row.left) descriptions.push(lineDescription(row.right ? \"Original\" : \"Deleted\", row.left, row.left.oldNo));\n  if (row.right) descriptions.push(lineDescription(row.left ? \"Modified\" : \"Added\", row.right, row.right.newNo));\n  return descriptions;\n}\n\nfunction rowDescriptions(row: VisualRow): string[] {\n  if (row.kind === \"separator\") return [];\n  if (row.kind === \"unified\") return [unifiedLineDescription(row.line)];\n  return splitRowDescriptions(row);\n}\n\nfunction accessibleDiffText(file: DiffFile, rows: VisualRow[]): string {\n  return [fileTitle(file), ...rows.flatMap(rowDescriptions)].join(\"\\n\");\n}\n\nfunction fileIdentity(file: DiffFile): string {\n  const hunks = file.hunks\n    .map((hunk) => `${hunk.oldStart}:${hunk.newStart}:${hunk.lines.map((line) => `${line.type}:${line.text}`).join(\"\\n\")}`)\n    .join(\"\\n\");\n  return `${file.oldName ?? \"\"}->${file.name}:${file.type}:${hunks}`;\n}\n\nfunction CodeDiffFileSection({\n  file,\n  lang,\n  diffStyle,\n  diffIndicators,\n  overflow,\n  maxHeight,\n  showFileHeader,\n}: {\n  file: DiffFile;\n  lang: string | undefined;\n  diffStyle: DiffStyle;\n  diffIndicators: DiffIndicators;\n  overflow: CodeOverflow;\n  maxHeight: string;\n  showFileHeader: boolean;\n}) {\n  const [expanded, setExpanded] = useState<ReadonlySet<number>>(() => new Set());\n  const texts = useMemo(() => sideTexts(file), [file]);\n  const tokens = useSideTokens(texts.old, texts.new, lang, Boolean(lang));\n  const rows = useMemo(() => buildRows(file, diffStyle, expanded), [file, diffStyle, expanded]);\n  const scrollRef = useRef<HTMLDivElement>(null);\n  const shouldVirtualize = rows.length > VIRTUALIZE_THRESHOLD;\n  // react-doctor-disable-next-line react-hooks-js/incompatible-library\n  const virtualizer = useVirtualizer({\n    count: rows.length,\n    getScrollElement: () => scrollRef.current,\n    estimateSize: () => ESTIMATED_ROW_HEIGHT,\n    overscan: 24,\n    enabled: shouldVirtualize,\n  });\n\n  function expandGap(gapIndex: number) {\n    setExpanded((current) => new Set(current).add(gapIndex));\n  }\n\n  const gridClassName = overflow === \"scroll\" ? \"w-max min-w-full\" : \"w-full\";\n\n  function renderRow(row: VisualRow): ReactNode {\n    if (row.kind === \"separator\") {\n      return (\n        <div data-control-ui=\"code-diff\" data-control-family=\"code-diff\" data-slot=\"expander\" className=\"flex items-center gap-2 px-3 py-1\">\n          {row.canExpand ? (\n            <button\n              type=\"button\"\n              data-control-ui=\"code-diff\"\n              data-control-family=\"code-diff\"\n              data-slot=\"expand-button\"\n              data-control=\"true\"\n              onClick={() => expandGap(row.gapIndex)}\n              className=\"cursor-pointer px-1.5 py-0.5\"\n              aria-label=\"Expand hidden lines\"\n            >\n              ⋯\n            </button>\n          ) : (\n            <span aria-hidden=\"true\" className=\"px-1.5\">\n              ⋯\n            </span>\n          )}\n          <span data-control-ui=\"code-diff\" data-control-family=\"code-diff\" data-slot=\"expander-label\" className=\"truncate\">\n            {row.label}\n          </span>\n        </div>\n      );\n    }\n    if (row.kind === \"unified\") {\n      return <UnifiedRow line={row.line} tokens={lineTokens(row.line, tokens)} indicators={diffIndicators} overflow={overflow} />;\n    }\n    return (\n      <div data-control-ui=\"code-diff\" data-control-family=\"code-diff\" data-slot=\"row\" className=\"flex w-full\">\n        <SplitHalf\n          line={row.left}\n          tokens={row.left ? lineTokens(row.left, tokens) : null}\n          indicators={diffIndicators}\n          overflow={overflow}\n          side=\"left\"\n        />\n        <SplitHalf\n          line={row.right}\n          tokens={row.right ? lineTokens(row.right, tokens) : null}\n          indicators={diffIndicators}\n          overflow={overflow}\n          side=\"right\"\n        />\n      </div>\n    );\n  }\n\n  return (\n    <section data-control-ui=\"code-diff\" data-control-family=\"code-diff\" data-slot=\"file\" data-file-name={file.name}>\n      {showFileHeader ? (\n        <div\n          data-control-ui=\"code-diff\"\n          data-control-family=\"code-diff\"\n          data-slot=\"file-header\"\n          className=\"flex min-h-9 items-center justify-between gap-3 px-3 py-1.5\"\n        >\n          <span data-control-ui=\"code-diff\" data-control-family=\"code-diff\" data-slot=\"file-title\" className=\"min-w-0 truncate\">\n            {fileTitle(file)}\n          </span>\n          <DiffStats additions={file.additions} deletions={file.deletions} />\n        </div>\n      ) : null}\n      <pre data-control-ui=\"code-diff\" data-control-family=\"code-diff\" data-slot=\"accessible-source\" className=\"sr-only\">\n        <code>{accessibleDiffText(file, rows)}</code>\n      </pre>\n      <ScrollArea\n        maxHeight={maxHeight}\n        viewportClassName={undefined}\n        viewportProps={{\n          \"data-control-ui\": \"code-diff\",\n          \"data-control-family\": \"code-diff\",\n          \"data-slot\": \"body\",\n        }}\n        viewportRef={scrollRef}\n      >\n        {shouldVirtualize ? (\n          <div className={gridClassName} style={{ position: \"relative\", height: `${virtualizer.getTotalSize()}px` }}>\n            {virtualizer.getVirtualItems().map((item) => {\n              const row = rows[item.index];\n              if (!row) return null;\n              return (\n                <div\n                  key={row.id}\n                  ref={virtualizer.measureElement}\n                  data-index={item.index}\n                  style={{ position: \"absolute\", top: 0, left: 0, width: \"100%\", transform: `translateY(${item.start}px)` }}\n                >\n                  {renderRow(row)}\n                </div>\n              );\n            })}\n          </div>\n        ) : (\n          <div className={gridClassName}>\n            {rows.map((row) => (\n              <div key={row.id}>{renderRow(row)}</div>\n            ))}\n          </div>\n        )}\n      </ScrollArea>\n    </section>\n  );\n}\n\nexport function CodeDiff({\n  patch,\n  oldText,\n  newText,\n  lang,\n  name,\n  diffStyle = \"unified\",\n  diffIndicators = \"bars\",\n  lineDiffType = \"word\",\n  overflow = \"scroll\",\n  maxLineDiffLength,\n  maxHeight = \"32rem\",\n  header = true,\n  className,\n  style,\n  ...props\n}: CodeDiffProps) {\n  const options = { name, lang, lineDiffType, maxLineDiffLength };\n  const parsedFiles =\n    patch === undefined ? [buildDiffFromFiles(oldText ?? \"\", newText ?? \"\", options)] : buildDiffFromPatch(patch, options);\n  const files: DiffFile[] = parsedFiles.length > 0 ? parsedFiles : [emptyFile(name)];\n  const additions = files.reduce((total, file) => total + file.additions, 0);\n  const deletions = files.reduce((total, file) => total + file.deletions, 0);\n  const copyValue = patch ?? newText ?? \"\";\n  const firstFile = files[0];\n\n  return (\n    <figure\n      data-control-ui=\"code-diff\"\n      data-control-family=\"code-diff\"\n      data-slot=\"root\"\n      data-surface=\"panel\"\n      data-diff-style={diffStyle}\n      data-file-count={files.length}\n      data-header={header ? \"true\" : undefined}\n      className={cn(\"my-4 overflow-hidden\", !header && \"relative pt-9\", className)}\n      style={style}\n      {...props}\n    >\n      {header ? (\n        <figcaption\n          data-control-ui=\"code-diff\"\n          data-control-family=\"code-diff\"\n          data-slot=\"header\"\n          className=\"flex min-h-10 items-center justify-between gap-3 px-3 py-1.5\"\n        >\n          <span data-control-ui=\"code-diff\" data-control-family=\"code-diff\" data-slot=\"title\" className=\"min-w-0 truncate\">\n            {files.length === 1 && firstFile ? fileTitle(firstFile) : `${files.length} files`}\n          </span>\n          <div data-control-ui=\"code-diff\" data-control-family=\"code-diff\" data-slot=\"actions\" className=\"flex shrink-0 items-center gap-2\">\n            <DiffStats additions={additions} deletions={deletions} />\n            <CodeDiffCopy value={copyValue} />\n          </div>\n        </figcaption>\n      ) : (\n        <CodeFloatingCopy value={copyValue} />\n      )}\n      {files.map((file) => (\n        <CodeDiffFileSection\n          key={fileIdentity(file)}\n          file={file}\n          lang={lang}\n          diffStyle={diffStyle}\n          diffIndicators={diffIndicators}\n          overflow={overflow}\n          maxHeight={maxHeight}\n          showFileHeader={files.length > 1}\n        />\n      ))}\n    </figure>\n  );\n}\n\nexport type CodeDiffCopyProps = CodeCopyProps;\n\n// IS CodeCopy, so diff header's copy and a code header's copy can never drift apart.\nexport function CodeDiffCopy(props: CodeDiffCopyProps) {\n  return <CodeCopy {...props} />;\n}\n\nfunction emptyFile(name: string | undefined): DiffFile {\n  return { name: name ?? \"file\", type: \"change\", hunks: [], oldLines: [], newLines: [], isPartial: true, additions: 0, deletions: 0 };\n}\n"
    }
  ],
  "css": {
    "@import \"../components/control-ui/styles/recipes/code-diff.css\"": {}
  },
  "meta": {}
}
