{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "code-block",
  "title": "Code Block",
  "author": "ilinxa",
  "description": "Code surface with view, edit, and terminal modes — Shiki highlighting, dual-theme CSS variables, and chrome presets for docs, chat, and terminal UIs.",
  "dependencies": [
    "shiki@^4.0.2",
    "@codemirror/state@^6.6.0",
    "@codemirror/view@^6.41.1",
    "@codemirror/commands@^6.10.3",
    "@codemirror/language@^6.12.3",
    "@codemirror/autocomplete@^6.20.1",
    "@lezer/highlight@^1.2.3",
    "@codemirror/lang-javascript@^6.2.5",
    "@codemirror/lang-json@^6.0.2",
    "@codemirror/lang-python@^6.2.1",
    "@codemirror/lang-html@^6.4.11",
    "@codemirror/lang-css@^6.3.1",
    "@codemirror/lang-markdown@^6.5.0",
    "lucide-react@^1.11.0"
  ],
  "registryDependencies": [
    "button",
    "dialog",
    "tooltip"
  ],
  "files": [
    {
      "path": "src/registry/components/code/code-block/code-block.tsx",
      "content": "\"use client\";\nimport {\n  useCallback,\n  useImperativeHandle,\n  useMemo,\n  useRef,\n  useState,\n} from \"react\";\nimport { TooltipProvider } from \"@/components/ui/tooltip\";\nimport { cn } from \"@/lib/utils\";\nimport {\n  CodeBlockProvider,\n} from \"./hooks/use-code-block-context\";\nimport { useControllableState } from \"./hooks/use-controllable-state\";\nimport { useCopyToClipboard } from \"./hooks/use-copy-to-clipboard\";\nimport { resolveLang } from \"./lib/lang-resolution\";\nimport { joinTerminalLines } from \"./lib/terminal-utils\";\nimport { CodeBlockBodyEdit } from \"./parts/code-block-body-edit\";\nimport { CodeBlockBodyTerminal } from \"./parts/code-block-body-terminal\";\nimport { CodeBlockBodyView } from \"./parts/code-block-body-view\";\nimport { CodeBlockCopyButton } from \"./parts/code-block-copy-button\";\nimport { CodeBlockDownloadButton } from \"./parts/code-block-download-button\";\nimport { CodeBlockExpandButton } from \"./parts/code-block-expand-button\";\nimport { CodeBlockExpandModal } from \"./parts/code-block-expand-modal\";\nimport { CodeBlockFooter } from \"./parts/code-block-footer\";\nimport { CodeBlockHeader } from \"./parts/code-block-header\";\nimport { CodeBlockTrafficLights } from \"./parts/code-block-traffic-lights\";\nimport { CodeBlockWrapButton } from \"./parts/code-block-wrap-button\";\nimport {\n  DEFAULT_LABELS,\n  type CodeBlockHandle,\n  type CodeBlockProps,\n} from \"./types\";\n\nconst DEFAULT_THEMES = {\n  light: \"github-light\",\n  dark: \"github-dark-default\",\n} as const;\n\nexport function CodeBlock(props: CodeBlockProps) {\n  const {\n    value: valueProp,\n    defaultValue,\n    lines,\n    lang: langProp,\n    filename,\n    filenameToLang,\n    mode = \"view\",\n    readOnly = false,\n    streaming = false,\n    onChange,\n    onSave,\n    tabSize = 4,\n    editorExtensions,\n    header = true,\n    showLanguage = true,\n    showCopy = true,\n    showExpand = false,\n    showWrap = false,\n    showDownload = false,\n    showTrafficLights = false,\n    actions,\n    renderHeader,\n    renderExpandModal,\n    footer,\n    showLineNumbers,\n    wrap: wrapProp,\n    highlightedLines,\n    annotations,\n    renderAnnotation,\n    maxLines,\n    expanded: expandedProp,\n    defaultExpanded,\n    onExpandedChange,\n    onWrapChange,\n    onLineClick,\n    onCopy,\n    onDownload,\n    themes,\n    maxHeight,\n    emptyMessage,\n    className,\n    style,\n    ariaLabel,\n    labels: labelsProp,\n    ref,\n  } = props;\n\n  // Resolved values\n  const labels = useMemo(\n    () => ({ ...DEFAULT_LABELS, ...(labelsProp ?? {}) }),\n    [labelsProp],\n  );\n  const themesResolved = themes ?? DEFAULT_THEMES;\n  const lang = useMemo(\n    () => resolveLang(langProp, filename, filenameToLang),\n    [langProp, filename, filenameToLang],\n  );\n\n  const lineDerivedValue = useMemo(() => {\n    if (mode === \"terminal\" && lines) return joinTerminalLines(lines);\n    return undefined;\n  }, [mode, lines]);\n\n  // Controlled/uncontrolled value for edit mode.\n  const [editValue, setEditValueInternal] = useControllableState<string>({\n    prop: mode === \"edit\" ? valueProp : undefined,\n    defaultProp: defaultValue ?? valueProp ?? \"\",\n    onChange: (next) => onChange?.({ value: next }),\n  });\n\n  const displayValue =\n    mode === \"edit\"\n      ? editValue\n      : lineDerivedValue !== undefined\n        ? lineDerivedValue\n        : (valueProp ?? \"\");\n\n  // Wrap state (controlled or local)\n  const [wrap, setWrapInternal] = useControllableState<\"wrap\" | \"scroll\">({\n    prop: wrapProp,\n    defaultProp: wrapProp ?? \"scroll\",\n    onChange: (next) => onWrapChange?.({ wrap: next }),\n  });\n\n  // Expanded (collapse) state\n  const [expanded, setExpandedInternal] = useControllableState<boolean>({\n    prop: expandedProp,\n    defaultProp: defaultExpanded ?? false,\n    onChange: (next) => onExpandedChange?.({ expanded: next }),\n  });\n\n  const [modalOpen, setModalOpen] = useState(false);\n\n  // Line numbers default per mode\n  const resolvedShowLineNumbers =\n    showLineNumbers !== undefined\n      ? showLineNumbers\n      : mode === \"edit\"\n        ? true\n        : false;\n\n  // Copy\n  const { copy: copyToClipboard, copied, failed: copyFailed } = useCopyToClipboard();\n  const handleCopy = useCallback(async () => {\n    const ok = await copyToClipboard(displayValue);\n    if (ok) onCopy?.({ value: displayValue });\n    return ok;\n  }, [copyToClipboard, displayValue, onCopy]);\n\n  // Download\n  const handleDownload = useCallback(() => {\n    const resolvedFilename = filename ?? `code.${lang === \"plaintext\" ? \"txt\" : lang}`;\n    if (onDownload) {\n      onDownload({ value: displayValue, filename: resolvedFilename });\n      return;\n    }\n    if (typeof window === \"undefined\") return;\n    try {\n      const blob = new Blob([displayValue], { type: \"text/plain;charset=utf-8\" });\n      const url = URL.createObjectURL(blob);\n      const a = document.createElement(\"a\");\n      a.href = url;\n      a.download = resolvedFilename;\n      document.body.appendChild(a);\n      a.click();\n      document.body.removeChild(a);\n      URL.revokeObjectURL(url);\n    } catch {\n      // Soft-fail; download is best-effort.\n    }\n  }, [displayValue, filename, lang, onDownload]);\n\n  // Imperative handle wired to body refs\n  const editorImperativeRef = useRef<{\n    focus: () => void;\n    getValue: () => string;\n  } | null>(null);\n\n  const handle = useMemo<CodeBlockHandle>(\n    () => ({\n      copy: handleCopy,\n      focus: () => editorImperativeRef.current?.focus(),\n      getValue: () =>\n        mode === \"edit\"\n          ? (editorImperativeRef.current?.getValue() ?? editValue)\n          : displayValue,\n      scrollToLine: () => {\n        // v0.1.0: best-effort no-op; reserved for v0.2 CodeMirror integration.\n      },\n    }),\n    [handleCopy, displayValue, editValue, mode],\n  );\n\n  useImperativeHandle(ref, () => handle, [handle]);\n\n  // Context value\n  const ctxValue = useMemo(\n    () => ({\n      value: displayValue,\n      filename,\n      lang,\n      resolvedLang: lang,\n      mode,\n      streaming,\n      wrap,\n      showLineNumbers: resolvedShowLineNumbers,\n      expanded,\n      setExpanded: setExpandedInternal,\n      setWrap: setWrapInternal,\n      modalOpen,\n      setModalOpen,\n      labels,\n      copy: handleCopy,\n      copied,\n      copyFailed,\n      download: handleDownload,\n      handle,\n    }),\n    [\n      displayValue,\n      filename,\n      lang,\n      mode,\n      streaming,\n      wrap,\n      resolvedShowLineNumbers,\n      expanded,\n      setExpandedInternal,\n      setWrapInternal,\n      modalOpen,\n      labels,\n      handleCopy,\n      copied,\n      copyFailed,\n      handleDownload,\n      handle,\n    ],\n  );\n\n  // Resolved aria-label\n  const resolvedAriaLabel =\n    ariaLabel ??\n    (filename\n      ? `Code block — ${filename}`\n      : `Code block — ${lang === \"plaintext\" ? \"text\" : lang}`);\n\n  const body = (() => {\n    if (mode === \"edit\") {\n      return (\n        <CodeBlockBodyEdit\n          value={editValue}\n          lang={lang}\n          readOnly={readOnly}\n          wrap={wrap}\n          tabSize={tabSize}\n          showLineNumbers={resolvedShowLineNumbers}\n          onChange={(v) => setEditValueInternal(v)}\n          onSave={onSave ? (v) => onSave({ value: v }) : undefined}\n          editorExtensions={editorExtensions}\n          maxHeight={maxHeight}\n          registerImperative={(h) => {\n            editorImperativeRef.current = h;\n          }}\n        />\n      );\n    }\n    if (mode === \"terminal\") {\n      return (\n        <CodeBlockBodyTerminal\n          value={valueProp ?? \"\"}\n          lines={lines}\n          wrap={wrap}\n          streaming={streaming}\n          emptyMessage={emptyMessage}\n          maxHeight={maxHeight}\n        />\n      );\n    }\n    return (\n      <CodeBlockBodyView\n        value={valueProp ?? \"\"}\n        lang={lang}\n        themes={themesResolved}\n        highlightedLines={highlightedLines}\n        annotations={annotations}\n        renderAnnotation={renderAnnotation}\n        showLineNumbers={resolvedShowLineNumbers}\n        wrap={wrap}\n        streaming={streaming}\n        expanded={expanded}\n        maxLines={maxLines}\n        emptyMessage={emptyMessage}\n        maxHeight={maxHeight}\n        onLineClick={onLineClick}\n      />\n    );\n  })();\n\n  // Header — either the default orchestrator or the renderHeader slot.\n  const headerNode = (() => {\n    if (header === false) return null;\n    if (renderHeader) {\n      const headerCtx = {\n        filename,\n        lang,\n        copyButton: showCopy ? <CodeBlockCopyButton /> : null,\n        expandButton: showExpand ? <CodeBlockExpandButton /> : null,\n        wrapButton: showWrap ? <CodeBlockWrapButton /> : null,\n        downloadButton: showDownload ? <CodeBlockDownloadButton /> : null,\n        trafficLights: showTrafficLights ? <CodeBlockTrafficLights /> : null,\n        actions: actions ?? null,\n      };\n      return <>{renderHeader(headerCtx)}</>;\n    }\n    return (\n      <CodeBlockHeader\n        showLanguage={showLanguage}\n        showCopy={showCopy}\n        showExpand={showExpand}\n        showWrap={showWrap}\n        showDownload={showDownload}\n        showTrafficLights={showTrafficLights}\n        actions={actions}\n      />\n    );\n  })();\n\n  // Modal body — same instance with expand off + maxLines undefined.\n  // `ref={undefined}` (v0.1.2, review 5.10): `{...props}` would re-pass the\n  // consumer's `ref` to this inner clone — the imperative handle would\n  // re-target the modal instance and go stale when the modal closes. The\n  // outer instance stays the sole handle owner.\n  const expandedInner = (\n    <CodeBlock\n      {...props}\n      ref={undefined}\n      header={true}\n      showExpand={false}\n      maxLines={undefined}\n      maxHeight={undefined}\n      className=\"rounded-none border-0 shadow-none\"\n    />\n  );\n\n  const modalNode = (() => {\n    if (!showExpand) return null;\n    if (renderExpandModal) {\n      return renderExpandModal({\n        open: modalOpen,\n        onOpenChange: setModalOpen,\n        code: expandedInner,\n      });\n    }\n    return (\n      <CodeBlockExpandModal\n        open={modalOpen}\n        onOpenChange={setModalOpen}\n        title={filename ?? lang}\n      >\n        {expandedInner}\n      </CodeBlockExpandModal>\n    );\n  })();\n\n  // No delayDuration — Radix-only prop; Base UI's TooltipProvider rejects it (F-cross-13).\n  return (\n    <CodeBlockProvider value={ctxValue}>\n      <TooltipProvider>\n        <section\n          role=\"region\"\n          aria-label={resolvedAriaLabel}\n          className={cn(\n            \"code-block group relative overflow-hidden rounded-lg border border-border/60 bg-card text-card-foreground shadow-sm\",\n            className,\n          )}\n          style={style}\n        >\n          {headerNode}\n          {body}\n          {footer ? <CodeBlockFooter>{footer}</CodeBlockFooter> : null}\n        </section>\n        {modalNode}\n      </TooltipProvider>\n    </CodeBlockProvider>\n  );\n}\n\nCodeBlock.displayName = \"CodeBlock\";\n",
      "type": "registry:component",
      "target": "components/code-block/code-block.tsx"
    },
    {
      "path": "src/registry/components/code/code-block/index.ts",
      "content": "export { CodeBlock } from \"./code-block\";\nexport { CodeBlockHeader } from \"./parts/code-block-header\";\nexport { CodeBlockFilename } from \"./parts/code-block-filename\";\nexport { CodeBlockLangPill } from \"./parts/code-block-lang-pill\";\nexport { CodeBlockCopyButton } from \"./parts/code-block-copy-button\";\nexport { CodeBlockExpandButton } from \"./parts/code-block-expand-button\";\nexport { CodeBlockWrapButton } from \"./parts/code-block-wrap-button\";\nexport { CodeBlockDownloadButton } from \"./parts/code-block-download-button\";\nexport { CodeBlockTrafficLights } from \"./parts/code-block-traffic-lights\";\nexport { useCodeBlock } from \"./hooks/use-code-block-context\";\nexport { resolveLang, FILENAME_TO_LANG_MAP } from \"./lib/lang-resolution\";\nexport type {\n  CodeBlockProps,\n  CodeBlockServerProps,\n  CodeBlockHandle,\n  CodeBlockMode,\n  CodeBlockWrap,\n  CodeBlockAnnotation,\n  CodeBlockAnnotationType,\n  CodeBlockLineRange,\n  CodeBlockLabels,\n  CodeBlockThemes,\n  CodeBlockChangeArgs,\n  CodeBlockCopyArgs,\n  CodeBlockSaveArgs,\n  CodeBlockDownloadArgs,\n  CodeBlockLineClickArgs,\n  CodeBlockExpandedChangeArgs,\n  CodeBlockWrapChangeArgs,\n  CodeBlockFilenameToLangArgs,\n  CodeBlockHeaderContext,\n  CodeBlockAnnotationRenderArgs,\n  CodeBlockExpandModalContext,\n  ShikiThemeObject,\n  TerminalLine,\n  TerminalLineKind,\n} from \"./types\";\n",
      "type": "registry:component",
      "target": "components/code-block/index.ts"
    },
    {
      "path": "src/registry/components/code/code-block/types.ts",
      "content": "import type { CSSProperties, ReactNode, Ref } from \"react\";\nimport type { Extension } from \"@codemirror/state\";\n\n// ─── Core enums ──────────────────────────────────────────────────────────────\n\nexport type CodeBlockMode = \"view\" | \"edit\" | \"terminal\";\nexport type CodeBlockWrap = \"wrap\" | \"scroll\";\nexport type CodeBlockAnnotationType = \"info\" | \"warn\" | \"error\";\nexport type TerminalLineKind = \"input\" | \"output\" | \"error\";\n\n// ─── Value-shaped types ──────────────────────────────────────────────────────\n\nexport interface CodeBlockLineRange {\n  from: number;\n  to: number;\n}\n\nexport interface TerminalLine {\n  kind: TerminalLineKind;\n  text: string;\n}\n\nexport interface CodeBlockAnnotation {\n  line: number;\n  type: CodeBlockAnnotationType;\n  message: string;\n}\n\nexport type ShikiThemeObject = {\n  name: string;\n  type: \"light\" | \"dark\";\n  [key: string]: unknown;\n};\n\nexport interface CodeBlockThemes {\n  light: string | ShikiThemeObject;\n  dark: string | ShikiThemeObject;\n}\n\n// ─── Imperative handle ───────────────────────────────────────────────────────\n\nexport interface CodeBlockHandle {\n  copy: () => Promise<boolean>;\n  focus: () => void;\n  getValue: () => string;\n  scrollToLine: (line: number) => void;\n}\n\n// ─── Callback arg shapes (object-shape per F-cross-12) ───────────────────────\n\nexport interface CodeBlockChangeArgs {\n  value: string;\n}\nexport interface CodeBlockCopyArgs {\n  value: string;\n}\nexport interface CodeBlockSaveArgs {\n  value: string;\n}\nexport interface CodeBlockDownloadArgs {\n  value: string;\n  filename: string;\n}\nexport interface CodeBlockLineClickArgs {\n  line: number;\n}\nexport interface CodeBlockExpandedChangeArgs {\n  expanded: boolean;\n}\nexport interface CodeBlockWrapChangeArgs {\n  wrap: CodeBlockWrap;\n}\nexport interface CodeBlockFilenameToLangArgs {\n  filename: string;\n}\n\n// ─── Slot contexts ───────────────────────────────────────────────────────────\n\nexport interface CodeBlockHeaderContext {\n  filename: string | undefined;\n  lang: string;\n  /** `null` when `showCopy` is false (v0.1.2 — widened; was an unsound `null as never`). */\n  copyButton: ReactNode | null;\n  expandButton: ReactNode | null;\n  wrapButton: ReactNode | null;\n  downloadButton: ReactNode | null;\n  trafficLights: ReactNode | null;\n  actions: ReactNode | null;\n}\n\nexport interface CodeBlockAnnotationRenderArgs {\n  annotation: CodeBlockAnnotation;\n  defaultMarker: ReactNode;\n}\n\nexport interface CodeBlockExpandModalContext {\n  open: boolean;\n  onOpenChange: (next: boolean) => void;\n  code: ReactNode;\n}\n\n// ─── i18n labels ─────────────────────────────────────────────────────────────\n\nexport type CodeBlockLabels = Partial<{\n  copy: string;\n  copied: string;\n  copyFailed: string;\n  expand: string;\n  wrap: string;\n  download: string;\n  showMore: string;\n  showLess: string;\n  streamingCursor: string;\n  closeModal: string;\n  emptyDefault: string;\n}>;\n\nexport const DEFAULT_LABELS: Required<CodeBlockLabels> = {\n  copy: \"Copy code\",\n  copied: \"Copied\",\n  copyFailed: \"Copy failed — select and copy manually\",\n  expand: \"Expand\",\n  wrap: \"Toggle wrap\",\n  download: \"Download\",\n  showMore: \"Show all\",\n  showLess: \"Show less\",\n  streamingCursor: \"Streaming\",\n  closeModal: \"Close\",\n  emptyDefault: \"\",\n};\n\n// ─── Top-level props (client variant) ────────────────────────────────────────\n\nexport interface CodeBlockProps {\n  // Content\n  value?: string;\n  lines?: TerminalLine[];\n  defaultValue?: string;\n\n  // Language\n  lang?: string;\n  filename?: string;\n  filenameToLang?: (args: CodeBlockFilenameToLangArgs) => string | undefined;\n\n  // Mode\n  mode?: CodeBlockMode;\n  readOnly?: boolean;\n  streaming?: boolean;\n\n  // Edit\n  onChange?: (args: CodeBlockChangeArgs) => void;\n  onSave?: (args: CodeBlockSaveArgs) => void;\n  /**\n   * Editor tab size. INITIAL-ONLY (v0.1.2): applied when the CodeMirror\n   * editor is created; later changes are not re-applied. Remount the block\n   * (React `key`) to change it after mount.\n   */\n  tabSize?: number;\n  /**\n   * Extra CodeMirror extensions for edit mode. INITIAL-ONLY (v0.1.2): the\n   * array captured at editor creation is baked into the EditorState; identity\n   * or content changes after mount are ignored. Remount (React `key`) to swap.\n   */\n  editorExtensions?: Extension[];\n\n  // Header\n  header?: boolean;\n  showLanguage?: boolean;\n  showCopy?: boolean;\n  showExpand?: boolean;\n  showWrap?: boolean;\n  showDownload?: boolean;\n  showTrafficLights?: boolean;\n  actions?: ReactNode;\n  renderHeader?: (ctx: CodeBlockHeaderContext) => ReactNode;\n  renderExpandModal?: (ctx: CodeBlockExpandModalContext) => ReactNode;\n\n  // Footer\n  footer?: ReactNode;\n\n  // Body\n  showLineNumbers?: boolean;\n  wrap?: CodeBlockWrap;\n  highlightedLines?: Array<number | CodeBlockLineRange>;\n  annotations?: CodeBlockAnnotation[];\n  renderAnnotation?: (args: CodeBlockAnnotationRenderArgs) => ReactNode;\n\n  // Collapse\n  maxLines?: number;\n  expanded?: boolean;\n  defaultExpanded?: boolean;\n  onExpandedChange?: (args: CodeBlockExpandedChangeArgs) => void;\n\n  // Wrap toggle\n  onWrapChange?: (args: CodeBlockWrapChangeArgs) => void;\n\n  // Line click\n  onLineClick?: (args: CodeBlockLineClickArgs) => void;\n\n  // Copy + download\n  onCopy?: (args: CodeBlockCopyArgs) => void;\n  onDownload?: (args: CodeBlockDownloadArgs) => void;\n\n  // Theme\n  themes?: CodeBlockThemes;\n\n  // Sizing\n  maxHeight?: number | string;\n\n  // Empty\n  emptyMessage?: string;\n\n  // Polymorphic\n  className?: string;\n  style?: CSSProperties;\n\n  // ARIA\n  ariaLabel?: string;\n\n  // i18n\n  labels?: CodeBlockLabels;\n\n  // Imperative handle\n  ref?: Ref<CodeBlockHandle>;\n}\n\n// ─── RSC variant props (typed narrowing) ─────────────────────────────────────\n\n/**\n * RSC-variant props. Compile-time narrowing of CodeBlockProps that removes\n * fields requiring client interactivity. TypeScript rejects mode='edit' and\n * streaming at compile time; the runtime guard in code-block.server.tsx is\n * a backstop for JS consumers.\n */\nexport type CodeBlockServerProps = Omit<\n  CodeBlockProps,\n  | \"mode\"\n  | \"readOnly\"\n  | \"streaming\"\n  | \"editorExtensions\"\n  | \"onChange\"\n  | \"onSave\"\n  | \"tabSize\"\n  | \"onWrapChange\"\n  | \"showWrap\"\n  | \"expanded\"\n  | \"defaultExpanded\"\n  | \"onExpandedChange\"\n  | \"ref\"\n> & {\n  mode?: \"view\" | \"terminal\";\n};\n",
      "type": "registry:component",
      "target": "components/code-block/types.ts"
    },
    {
      "path": "src/registry/components/code/code-block/parts/code-block-header.tsx",
      "content": "\"use client\";\nimport type { ReactNode } from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport { useCodeBlock } from \"../hooks/use-code-block-context\";\nimport { CodeBlockCopyButton } from \"./code-block-copy-button\";\nimport { CodeBlockDownloadButton } from \"./code-block-download-button\";\nimport { CodeBlockExpandButton } from \"./code-block-expand-button\";\nimport { CodeBlockFilename } from \"./code-block-filename\";\nimport { CodeBlockLangPill } from \"./code-block-lang-pill\";\nimport { CodeBlockTrafficLights } from \"./code-block-traffic-lights\";\nimport { CodeBlockWrapButton } from \"./code-block-wrap-button\";\n\nexport interface CodeBlockHeaderProps {\n  showLanguage?: boolean;\n  showCopy?: boolean;\n  showExpand?: boolean;\n  showWrap?: boolean;\n  showDownload?: boolean;\n  showTrafficLights?: boolean;\n  actions?: ReactNode;\n  className?: string;\n}\n\nexport function CodeBlockHeader({\n  showLanguage = true,\n  showCopy = true,\n  showExpand = false,\n  showWrap = false,\n  showDownload = false,\n  showTrafficLights = false,\n  actions,\n  className,\n}: CodeBlockHeaderProps) {\n  const { filename, resolvedLang } = useCodeBlock();\n\n  return (\n    <div\n      className={cn(\n        \"flex items-center gap-2 border-b border-border/60 bg-card/50 px-3 py-1.5\",\n        \"min-h-9\",\n        className,\n      )}\n    >\n      {showTrafficLights ? <CodeBlockTrafficLights className=\"mr-1\" /> : null}\n      <CodeBlockFilename filename={filename} />\n      {filename && showLanguage && resolvedLang && resolvedLang !== \"plaintext\" ? (\n        <span aria-hidden=\"true\" className=\"text-muted-foreground/40\">·</span>\n      ) : null}\n      {showLanguage ? <CodeBlockLangPill lang={resolvedLang} /> : null}\n      <div className=\"flex-1\" />\n      {actions ? <div className=\"flex items-center gap-1\">{actions}</div> : null}\n      {showWrap ? <CodeBlockWrapButton /> : null}\n      {showDownload ? <CodeBlockDownloadButton /> : null}\n      {showExpand ? <CodeBlockExpandButton /> : null}\n      {showCopy ? <CodeBlockCopyButton /> : null}\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/code-block/parts/code-block-header.tsx"
    },
    {
      "path": "src/registry/components/code/code-block/parts/code-block-filename.tsx",
      "content": "\"use client\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface CodeBlockFilenameProps {\n  filename?: string;\n  className?: string;\n}\n\nexport function CodeBlockFilename({ filename, className }: CodeBlockFilenameProps) {\n  if (!filename) return null;\n  return (\n    <span\n      className={cn(\n        \"font-mono text-[0.78rem] text-foreground/85 tracking-tight\",\n        className,\n      )}\n    >\n      {filename}\n    </span>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/code-block/parts/code-block-filename.tsx"
    },
    {
      "path": "src/registry/components/code/code-block/parts/code-block-lang-pill.tsx",
      "content": "\"use client\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface CodeBlockLangPillProps {\n  lang: string;\n  className?: string;\n}\n\nexport function CodeBlockLangPill({ lang, className }: CodeBlockLangPillProps) {\n  if (!lang || lang === \"plaintext\") return null;\n  return (\n    <span\n      className={cn(\n        \"font-mono text-[0.7rem] uppercase tracking-wider text-muted-foreground/80\",\n        className,\n      )}\n    >\n      {lang}\n    </span>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/code-block/parts/code-block-lang-pill.tsx"
    },
    {
      "path": "src/registry/components/code/code-block/parts/code-block-copy-button.tsx",
      "content": "\"use client\";\nimport { Check, Copy, X } from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\nimport { useCodeBlock } from \"../hooks/use-code-block-context\";\n\nexport interface CodeBlockCopyButtonProps {\n  className?: string;\n}\n\nexport function CodeBlockCopyButton({ className }: CodeBlockCopyButtonProps) {\n  const { copy, copied, copyFailed, labels } = useCodeBlock();\n\n  const Icon = copyFailed ? X : copied ? Check : Copy;\n  const label = copyFailed ? labels.copyFailed : copied ? labels.copied : labels.copy;\n\n  return (\n    <>\n      <Button\n        type=\"button\"\n        size=\"icon\"\n        variant=\"ghost\"\n        className={cn(\"size-7 text-muted-foreground hover:text-foreground\", className)}\n        onClick={() => void copy()}\n        aria-label={label}\n        title={label}\n      >\n        <Icon className=\"size-3.5\" aria-hidden=\"true\" />\n      </Button>\n      {/* Screen-reader-only status. role=\"status\" with aria-live=\"polite\" so\n          the SR announces \"Copied\" after the icon swap. */}\n      <span role=\"status\" aria-live=\"polite\" className=\"sr-only\">\n        {copied ? labels.copied : copyFailed ? labels.copyFailed : \"\"}\n      </span>\n    </>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/code-block/parts/code-block-copy-button.tsx"
    },
    {
      "path": "src/registry/components/code/code-block/parts/code-block-expand-button.tsx",
      "content": "\"use client\";\nimport { Maximize2 } from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\nimport { useCodeBlock } from \"../hooks/use-code-block-context\";\n\nexport interface CodeBlockExpandButtonProps {\n  className?: string;\n}\n\nexport function CodeBlockExpandButton({ className }: CodeBlockExpandButtonProps) {\n  const { setModalOpen, labels } = useCodeBlock();\n\n  return (\n    <Button\n      type=\"button\"\n      size=\"icon\"\n      variant=\"ghost\"\n      className={cn(\"size-7 text-muted-foreground hover:text-foreground\", className)}\n      onClick={() => setModalOpen(true)}\n      aria-label={labels.expand}\n      title={labels.expand}\n    >\n      <Maximize2 className=\"size-3.5\" aria-hidden=\"true\" />\n    </Button>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/code-block/parts/code-block-expand-button.tsx"
    },
    {
      "path": "src/registry/components/code/code-block/parts/code-block-wrap-button.tsx",
      "content": "\"use client\";\nimport { WrapText } from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\nimport { useCodeBlock } from \"../hooks/use-code-block-context\";\n\nexport interface CodeBlockWrapButtonProps {\n  className?: string;\n}\n\nexport function CodeBlockWrapButton({ className }: CodeBlockWrapButtonProps) {\n  const { wrap, setWrap, labels } = useCodeBlock();\n  const isWrapped = wrap === \"wrap\";\n\n  return (\n    <Button\n      type=\"button\"\n      size=\"icon\"\n      variant=\"ghost\"\n      className={cn(\n        \"size-7 text-muted-foreground hover:text-foreground\",\n        isWrapped && \"text-foreground\",\n        className,\n      )}\n      onClick={() => setWrap(isWrapped ? \"scroll\" : \"wrap\")}\n      aria-label={labels.wrap}\n      aria-pressed={isWrapped}\n      title={labels.wrap}\n    >\n      <WrapText className=\"size-3.5\" aria-hidden=\"true\" />\n    </Button>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/code-block/parts/code-block-wrap-button.tsx"
    },
    {
      "path": "src/registry/components/code/code-block/parts/code-block-download-button.tsx",
      "content": "\"use client\";\nimport { Download } from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\nimport { useCodeBlock } from \"../hooks/use-code-block-context\";\n\nexport interface CodeBlockDownloadButtonProps {\n  className?: string;\n}\n\nexport function CodeBlockDownloadButton({ className }: CodeBlockDownloadButtonProps) {\n  const { download, labels } = useCodeBlock();\n\n  return (\n    <Button\n      type=\"button\"\n      size=\"icon\"\n      variant=\"ghost\"\n      className={cn(\"size-7 text-muted-foreground hover:text-foreground\", className)}\n      onClick={download}\n      aria-label={labels.download}\n      title={labels.download}\n    >\n      <Download className=\"size-3.5\" aria-hidden=\"true\" />\n    </Button>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/code-block/parts/code-block-download-button.tsx"
    },
    {
      "path": "src/registry/components/code/code-block/parts/code-block-traffic-lights.tsx",
      "content": "\"use client\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface CodeBlockTrafficLightsProps {\n  className?: string;\n}\n\n/**\n * macOS-style traffic-light decoration (three muted circles).\n * Purely presentational — no behaviour. Opt-in via the `showTrafficLights`\n * prop on `<CodeBlock>`, or compose manually inside `renderHeader`.\n */\nexport function CodeBlockTrafficLights({ className }: CodeBlockTrafficLightsProps) {\n  return (\n    <div\n      aria-hidden=\"true\"\n      className={cn(\"flex items-center gap-1.5\", className)}\n    >\n      <span className=\"block size-3 rounded-full bg-[oklch(0.78_0.16_25)] opacity-70\" />\n      <span className=\"block size-3 rounded-full bg-[oklch(0.84_0.15_85)] opacity-70\" />\n      <span className=\"block size-3 rounded-full bg-[oklch(0.78_0.16_145)] opacity-70\" />\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/code-block/parts/code-block-traffic-lights.tsx"
    },
    {
      "path": "src/registry/components/code/code-block/parts/code-block-footer.tsx",
      "content": "\"use client\";\nimport type { ReactNode } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\ninterface CodeBlockFooterProps {\n  children: ReactNode;\n  className?: string;\n}\n\nexport function CodeBlockFooter({ children, className }: CodeBlockFooterProps) {\n  return (\n    <div\n      className={cn(\n        \"flex items-center gap-2 border-t border-border/60 bg-card/40 px-3 py-1.5 text-xs text-muted-foreground\",\n        className,\n      )}\n    >\n      {children}\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/code-block/parts/code-block-footer.tsx"
    },
    {
      "path": "src/registry/components/code/code-block/parts/code-block-empty.tsx",
      "content": "\"use client\";\nimport { cn } from \"@/lib/utils\";\n\ninterface CodeBlockEmptyProps {\n  message?: string;\n  className?: string;\n}\n\nexport function CodeBlockEmpty({ message, className }: CodeBlockEmptyProps) {\n  return (\n    <div\n      className={cn(\n        \"flex min-h-12 items-center justify-center px-4 py-6 text-center font-mono text-xs text-muted-foreground/60\",\n        className,\n      )}\n    >\n      {message ?? <span aria-hidden=\"true\">&nbsp;</span>}\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/code-block/parts/code-block-empty.tsx"
    },
    {
      "path": "src/registry/components/code/code-block/parts/code-block-streaming-cursor.tsx",
      "content": "\"use client\";\nimport { cn } from \"@/lib/utils\";\n\ninterface CodeBlockStreamingCursorProps {\n  className?: string;\n}\n\nexport function CodeBlockStreamingCursor({ className }: CodeBlockStreamingCursorProps) {\n  return (\n    <span\n      aria-hidden=\"true\"\n      className={cn(\n        \"inline-block h-[1em] w-[0.5ch] -mb-[2px] translate-y-[0.15em] bg-foreground/80\",\n        \"animate-[cb-blink_1s_steps(2)_infinite]\",\n        className,\n      )}\n    />\n  );\n}\n",
      "type": "registry:component",
      "target": "components/code-block/parts/code-block-streaming-cursor.tsx"
    },
    {
      "path": "src/registry/components/code/code-block/parts/code-block-collapse-fade.tsx",
      "content": "\"use client\";\nimport { ChevronDown, ChevronUp } from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\nimport { useCodeBlock } from \"../hooks/use-code-block-context\";\n\ninterface CodeBlockCollapseFadeProps {\n  hiddenLineCount: number;\n  className?: string;\n}\n\nexport function CodeBlockCollapseFade({\n  hiddenLineCount,\n  className,\n}: CodeBlockCollapseFadeProps) {\n  const { expanded, setExpanded, labels } = useCodeBlock();\n\n  if (expanded) {\n    return (\n      <div className={cn(\"flex justify-center border-t border-border/60 py-2\", className)}>\n        <Button\n          type=\"button\"\n          variant=\"ghost\"\n          size=\"sm\"\n          className=\"h-7 gap-1 text-xs text-muted-foreground hover:text-foreground\"\n          onClick={() => setExpanded(false)}\n        >\n          <ChevronUp className=\"size-3.5\" aria-hidden=\"true\" />\n          {labels.showLess}\n        </Button>\n      </div>\n    );\n  }\n\n  return (\n    <div\n      className={cn(\n        \"pointer-events-none absolute inset-x-0 bottom-0 flex h-20 items-end justify-center\",\n        \"bg-gradient-to-t from-card via-card/85 to-transparent\",\n        className,\n      )}\n    >\n      <Button\n        type=\"button\"\n        variant=\"ghost\"\n        size=\"sm\"\n        className=\"pointer-events-auto mb-2 h-7 gap-1 text-xs text-muted-foreground hover:text-foreground\"\n        onClick={() => setExpanded(true)}\n      >\n        <ChevronDown className=\"size-3.5\" aria-hidden=\"true\" />\n        {`${labels.showMore} (${hiddenLineCount} more lines)`}\n      </Button>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/code-block/parts/code-block-collapse-fade.tsx"
    },
    {
      "path": "src/registry/components/code/code-block/parts/code-block-annotation-marker.tsx",
      "content": "\"use client\";\nimport { CircleAlert, CircleX, Info } from \"lucide-react\";\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipTrigger,\n} from \"@/components/ui/tooltip\";\nimport { cn } from \"@/lib/utils\";\nimport type {\n  CodeBlockAnnotation,\n  CodeBlockAnnotationRenderArgs,\n} from \"../types\";\n\ninterface CodeBlockAnnotationMarkerProps {\n  annotation: CodeBlockAnnotation;\n  renderAnnotation?: (args: CodeBlockAnnotationRenderArgs) => React.ReactNode;\n}\n\nconst ICON_BY_TYPE = {\n  info: Info,\n  warn: CircleAlert,\n  error: CircleX,\n} as const;\n\nconst COLOR_BY_TYPE = {\n  info: \"text-blue-500 dark:text-blue-400\",\n  warn: \"text-amber-500 dark:text-amber-400\",\n  error: \"text-destructive\",\n} as const;\n\nexport function CodeBlockAnnotationMarker({\n  annotation,\n  renderAnnotation,\n}: CodeBlockAnnotationMarkerProps) {\n  const Icon = ICON_BY_TYPE[annotation.type];\n  const colorClass = COLOR_BY_TYPE[annotation.type];\n\n  // F-cross-13: no `asChild` — the trigger IS the marker button.\n  const defaultMarker = (\n    <Tooltip>\n      <TooltipTrigger\n        type=\"button\"\n        className={cn(\n          \"inline-flex size-3.5 items-center justify-center align-middle outline-none\",\n          \"focus-visible:ring-2 focus-visible:ring-ring rounded\",\n          colorClass,\n        )}\n        aria-label={`${annotation.type}: ${annotation.message}`}\n      >\n        <Icon className=\"size-3.5\" aria-hidden=\"true\" />\n      </TooltipTrigger>\n      <TooltipContent side=\"right\" className=\"max-w-xs text-xs\">\n        {annotation.message}\n      </TooltipContent>\n    </Tooltip>\n  );\n\n  if (renderAnnotation) {\n    return <>{renderAnnotation({ annotation, defaultMarker })}</>;\n  }\n  return defaultMarker;\n}\n",
      "type": "registry:component",
      "target": "components/code-block/parts/code-block-annotation-marker.tsx"
    },
    {
      "path": "src/registry/components/code/code-block/parts/code-block-line-numbers.tsx",
      "content": "\"use client\";\nimport { cn } from \"@/lib/utils\";\nimport { gutterWidth } from \"../lib/line-utils\";\n\ninterface CodeBlockLineNumbersProps {\n  totalLines: number;\n  highlighted?: Set<number>;\n  onLineClick?: (line: number) => void;\n  className?: string;\n}\n\nexport function CodeBlockLineNumbers({\n  totalLines,\n  highlighted,\n  onLineClick,\n  className,\n}: CodeBlockLineNumbersProps) {\n  const width = gutterWidth(totalLines);\n  const lines = Array.from({ length: totalLines }, (_, i) => i + 1);\n\n  return (\n    <div\n      aria-hidden=\"true\"\n      className={cn(\n        \"select-none pr-3 pl-3 text-right font-mono text-[0.75rem] text-muted-foreground/60\",\n        className,\n      )}\n      style={{ minWidth: `${width + 2}ch` }}\n    >\n      {lines.map((n) => (\n        <div\n          key={n}\n          className={cn(\n            \"leading-relaxed\",\n            highlighted?.has(n) && \"text-foreground/80\",\n            onLineClick && \"cursor-pointer hover:text-foreground\",\n          )}\n          onClick={onLineClick ? () => onLineClick(n) : undefined}\n        >\n          {n}\n        </div>\n      ))}\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/code-block/parts/code-block-line-numbers.tsx"
    },
    {
      "path": "src/registry/components/code/code-block/parts/code-block-body-view.tsx",
      "content": "\"use client\";\nimport { useMemo } from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport { useShikiHighlighter } from \"../hooks/use-shiki-highlighter\";\nimport { lineCount, rangeToLines, splitToLines } from \"../lib/line-utils\";\nimport type {\n  CodeBlockAnnotation,\n  CodeBlockAnnotationRenderArgs,\n  CodeBlockLineRange,\n  CodeBlockLineClickArgs,\n  CodeBlockThemes,\n} from \"../types\";\nimport { CodeBlockAnnotationMarker } from \"./code-block-annotation-marker\";\nimport { CodeBlockCollapseFade } from \"./code-block-collapse-fade\";\nimport { CodeBlockEmpty } from \"./code-block-empty\";\nimport { CodeBlockLineNumbers } from \"./code-block-line-numbers\";\nimport { CodeBlockStreamingCursor } from \"./code-block-streaming-cursor\";\n\ninterface CodeBlockBodyViewProps {\n  value: string;\n  lang: string;\n  themes: CodeBlockThemes | undefined;\n  highlightedLines?: Array<number | CodeBlockLineRange>;\n  annotations?: CodeBlockAnnotation[];\n  renderAnnotation?: (args: CodeBlockAnnotationRenderArgs) => React.ReactNode;\n  showLineNumbers: boolean;\n  wrap: \"wrap\" | \"scroll\";\n  streaming: boolean;\n  expanded: boolean;\n  maxLines: number | undefined;\n  emptyMessage?: string;\n  maxHeight?: number | string;\n  onLineClick?: (args: CodeBlockLineClickArgs) => void;\n}\n\nexport function CodeBlockBodyView({\n  value,\n  lang,\n  themes,\n  highlightedLines,\n  annotations,\n  renderAnnotation,\n  showLineNumbers,\n  wrap,\n  streaming,\n  expanded,\n  maxLines,\n  emptyMessage,\n  maxHeight,\n  onLineClick,\n}: CodeBlockBodyViewProps) {\n  const { html } = useShikiHighlighter({\n    value,\n    lang,\n    themes,\n    highlightedLines,\n    streaming,\n  });\n\n  const total = lineCount(value);\n  const showCollapse = maxLines !== undefined && total > maxLines && !expanded;\n  const hiddenCount = showCollapse ? total - maxLines : 0;\n\n  const visibleHtml = useMemo(() => {\n    if (!showCollapse) return html;\n    // Naive but correct: cap the rendered <pre> visual via max-height.\n    // Slicing tokenized HTML by line is unreliable across grammars; we keep\n    // the full HTML and clip visually.\n    return html;\n  }, [html, showCollapse]);\n\n  const highlightedSet = useMemo(() => rangeToLines(highlightedLines), [highlightedLines]);\n  const annotationByLine = useMemo(() => {\n    const m = new Map<number, CodeBlockAnnotation[]>();\n    for (const a of annotations ?? []) {\n      const list = m.get(a.line) ?? [];\n      list.push(a);\n      m.set(a.line, list);\n    }\n    return m;\n  }, [annotations]);\n\n  if (value === \"\" && !streaming) {\n    return <CodeBlockEmpty message={emptyMessage} />;\n  }\n\n  const clipHeight = showCollapse\n    ? `calc(${maxLines} * 1.6em + 2rem)`\n    : maxHeight !== undefined\n      ? typeof maxHeight === \"number\"\n        ? `${maxHeight}px`\n        : maxHeight\n      : undefined;\n\n  // splitToLines is only used to ensure totalLines & line-number alignment.\n  const lines = splitToLines(value);\n  void lines; // unused at render time; ref'd above via lineCount\n\n  return (\n    <div\n      className={cn(\n        \"relative flex w-full overflow-hidden\",\n        wrap === \"wrap\" ? \"whitespace-pre-wrap\" : \"\",\n      )}\n      style={{ maxHeight: clipHeight }}\n    >\n      {showLineNumbers || (annotations && annotations.length > 0) ? (\n        <div className=\"relative flex items-stretch\">\n          {showLineNumbers ? (\n            <CodeBlockLineNumbers\n              totalLines={Math.max(total, 1)}\n              highlighted={highlightedSet}\n              onLineClick={onLineClick ? (line) => onLineClick({ line }) : undefined}\n            />\n          ) : null}\n          {annotations && annotations.length > 0 ? (\n            <div className=\"relative w-5 select-none\">\n              {Array.from(annotationByLine.entries()).map(([line, items]) => (\n                <div\n                  key={line}\n                  className=\"absolute left-0 flex items-center\"\n                  style={{ top: `calc(${line - 1} * 1.6em)`, height: \"1.6em\" }}\n                >\n                  {items.map((a, i) => (\n                    <CodeBlockAnnotationMarker\n                      key={i}\n                      annotation={a}\n                      renderAnnotation={renderAnnotation}\n                    />\n                  ))}\n                </div>\n              ))}\n            </div>\n          ) : null}\n        </div>\n      ) : null}\n\n      <div\n        className={cn(\n          \"code-block-shiki-body relative min-w-0 flex-1 overflow-auto px-4 py-3 font-mono text-[0.8rem] leading-relaxed\",\n          wrap === \"wrap\" ? \"whitespace-pre-wrap break-words\" : \"whitespace-pre\",\n        )}\n      >\n        <div\n          // dangerouslySetInnerHTML: Shiki output is server-controlled and\n          // sanitized by the library (no user-supplied HTML can leak through\n          // the code path).\n          dangerouslySetInnerHTML={{ __html: visibleHtml }}\n        />\n        {streaming ? <CodeBlockStreamingCursor /> : null}\n      </div>\n\n      {showCollapse ? <CodeBlockCollapseFade hiddenLineCount={hiddenCount} /> : null}\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/code-block/parts/code-block-body-view.tsx"
    },
    {
      "path": "src/registry/components/code/code-block/parts/code-block-body-edit.tsx",
      "content": "\"use client\";\nimport { useEffect } from \"react\";\nimport type { Extension } from \"@codemirror/state\";\nimport { cn } from \"@/lib/utils\";\nimport { useCodeMirror } from \"../hooks/use-code-mirror\";\nimport { CODEMIRROR_THEME_CSS } from \"../lib/codemirror-theme\";\n\ninterface CodeBlockBodyEditProps {\n  value: string;\n  lang: string;\n  readOnly: boolean;\n  wrap: \"wrap\" | \"scroll\";\n  tabSize: number;\n  showLineNumbers: boolean;\n  onChange?: (value: string) => void;\n  onSave?: (value: string) => void;\n  editorExtensions?: Extension[];\n  maxHeight?: number | string;\n  registerImperative?: (handle: {\n    focus: () => void;\n    getValue: () => string;\n  }) => void;\n}\n\nexport function CodeBlockBodyEdit({\n  value,\n  lang,\n  readOnly,\n  wrap,\n  tabSize,\n  showLineNumbers,\n  onChange,\n  onSave,\n  editorExtensions,\n  maxHeight,\n  registerImperative,\n}: CodeBlockBodyEditProps) {\n  const { containerRef, focus, getValue } = useCodeMirror({\n    value,\n    lang,\n    readOnly,\n    wrap,\n    tabSize,\n    showLineNumbers,\n    onChange,\n    onSave,\n    editorExtensions,\n  });\n\n  useEffect(() => {\n    registerImperative?.({ focus, getValue });\n  }, [registerImperative, focus, getValue]);\n\n  const heightStyle =\n    maxHeight !== undefined\n      ? typeof maxHeight === \"number\"\n        ? `${maxHeight}px`\n        : maxHeight\n      : undefined;\n\n  return (\n    <>\n      <style dangerouslySetInnerHTML={{ __html: CODEMIRROR_THEME_CSS }} />\n      <div\n        ref={containerRef}\n        className={cn(\"code-block-editor relative min-h-32 overflow-hidden\")}\n        style={{ maxHeight: heightStyle }}\n      />\n    </>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/code-block/parts/code-block-body-edit.tsx"
    },
    {
      "path": "src/registry/components/code/code-block/parts/code-block-body-terminal.tsx",
      "content": "\"use client\";\nimport { useMemo } from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport { parseTerminalLines, promptPrefix } from \"../lib/terminal-utils\";\nimport type { TerminalLine } from \"../types\";\nimport { CodeBlockEmpty } from \"./code-block-empty\";\nimport { CodeBlockStreamingCursor } from \"./code-block-streaming-cursor\";\n\ninterface CodeBlockBodyTerminalProps {\n  value: string;\n  lines?: TerminalLine[];\n  wrap: \"wrap\" | \"scroll\";\n  streaming: boolean;\n  emptyMessage?: string;\n  maxHeight?: number | string;\n}\n\nexport function CodeBlockBodyTerminal({\n  value,\n  lines,\n  wrap,\n  streaming,\n  emptyMessage,\n  maxHeight,\n}: CodeBlockBodyTerminalProps) {\n  const resolvedLines = useMemo<TerminalLine[]>(() => {\n    if (lines) return lines;\n    return parseTerminalLines(value);\n  }, [lines, value]);\n\n  if (resolvedLines.length === 0 && !streaming) {\n    return <CodeBlockEmpty message={emptyMessage} />;\n  }\n\n  const lastInputIdx = (() => {\n    for (let i = resolvedLines.length - 1; i >= 0; i--) {\n      if (resolvedLines[i].kind === \"input\") return i;\n    }\n    return resolvedLines.length - 1;\n  })();\n\n  const heightStyle =\n    maxHeight !== undefined\n      ? typeof maxHeight === \"number\"\n        ? `${maxHeight}px`\n        : maxHeight\n      : undefined;\n\n  return (\n    <div\n      role=\"log\"\n      aria-live=\"off\"\n      className={cn(\n        \"min-w-0 overflow-auto px-4 py-3 font-mono text-[0.8rem] leading-relaxed\",\n        wrap === \"wrap\" ? \"whitespace-pre-wrap break-words\" : \"whitespace-pre\",\n      )}\n      style={{ maxHeight: heightStyle }}\n    >\n      {resolvedLines.map((line, idx) => {\n        if (line.kind === \"input\") {\n          const { prefix, rest } = promptPrefix(line.text);\n          return (\n            <div key={idx} className=\"text-foreground\">\n              <span className=\"text-muted-foreground/70\">{prefix}</span>\n              <span>{rest}</span>\n              {streaming && idx === lastInputIdx ? (\n                <CodeBlockStreamingCursor className=\"ml-0.5\" />\n              ) : null}\n            </div>\n          );\n        }\n        if (line.kind === \"error\") {\n          return (\n            <div key={idx} className=\"text-destructive\">\n              {line.text || \" \"}\n            </div>\n          );\n        }\n        return (\n          <div key={idx} className=\"text-muted-foreground\">\n            {line.text || \" \"}\n          </div>\n        );\n      })}\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/code-block/parts/code-block-body-terminal.tsx"
    },
    {
      "path": "src/registry/components/code/code-block/parts/code-block-expand-modal.tsx",
      "content": "\"use client\";\nimport type { ReactNode } from \"react\";\nimport {\n  Dialog,\n  DialogContent,\n  DialogHeader,\n  DialogTitle,\n} from \"@/components/ui/dialog\";\n\ninterface CodeBlockExpandModalProps {\n  open: boolean;\n  onOpenChange: (next: boolean) => void;\n  title?: string;\n  children: ReactNode;\n}\n\nexport function CodeBlockExpandModal({\n  open,\n  onOpenChange,\n  title,\n  children,\n}: CodeBlockExpandModalProps) {\n  return (\n    <Dialog open={open} onOpenChange={onOpenChange}>\n      <DialogContent\n        className=\"max-w-[min(95vw,1200px)] gap-0 p-0\"\n        aria-describedby={undefined}\n      >\n        <DialogHeader className=\"border-b border-border/60 px-4 py-3\">\n          <DialogTitle className=\"text-sm font-medium\">\n            {title ?? \"Code\"}\n          </DialogTitle>\n        </DialogHeader>\n        <div className=\"max-h-[80vh] overflow-auto\">{children}</div>\n      </DialogContent>\n    </Dialog>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/code-block/parts/code-block-expand-modal.tsx"
    },
    {
      "path": "src/registry/components/code/code-block/hooks/use-code-block-context.tsx",
      "content": "\"use client\";\nimport { createContext, useContext, type ReactNode } from \"react\";\nimport type {\n  CodeBlockHandle,\n  CodeBlockLabels,\n  CodeBlockWrap,\n} from \"../types\";\n\ninterface CodeBlockContextValue {\n  value: string;\n  filename: string | undefined;\n  lang: string;\n  resolvedLang: string;\n  mode: \"view\" | \"edit\" | \"terminal\";\n  streaming: boolean;\n  wrap: CodeBlockWrap;\n  showLineNumbers: boolean;\n  expanded: boolean;\n  setExpanded: (next: boolean) => void;\n  setWrap: (next: CodeBlockWrap) => void;\n  modalOpen: boolean;\n  setModalOpen: (next: boolean) => void;\n  labels: Required<CodeBlockLabels>;\n  copy: () => Promise<boolean>;\n  copied: boolean;\n  copyFailed: boolean;\n  download: () => void;\n  handle: CodeBlockHandle;\n}\n\nconst CodeBlockContext = createContext<CodeBlockContextValue | null>(null);\n\nexport function CodeBlockProvider({\n  value,\n  children,\n}: {\n  value: CodeBlockContextValue;\n  children: ReactNode;\n}) {\n  return (\n    <CodeBlockContext.Provider value={value}>{children}</CodeBlockContext.Provider>\n  );\n}\n\nexport function useCodeBlock(): CodeBlockContextValue {\n  const ctx = useContext(CodeBlockContext);\n  if (!ctx) {\n    throw new Error(\n      \"[CodeBlock] useCodeBlock must be called inside <CodeBlock>. \" +\n        \"Header parts (<CodeBlockHeader>, <CodeBlockCopyButton>, etc.) must \" +\n        \"be composed inside a <CodeBlock> or its renderHeader slot.\",\n    );\n  }\n  return ctx;\n}\n",
      "type": "registry:component",
      "target": "components/code-block/hooks/use-code-block-context.tsx"
    },
    {
      "path": "src/registry/components/code/code-block/hooks/use-controllable-state.ts",
      "content": "\"use client\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\n\ninterface UseControllableStateArgs<T> {\n  prop: T | undefined;\n  defaultProp: T;\n  onChange?: (value: T) => void;\n}\n\n/**\n * Standard controlled/uncontrolled state helper. When `prop` is defined it\n * is treated as the source of truth (controlled); otherwise component state\n * is used (uncontrolled). `onChange` always fires.\n */\nexport function useControllableState<T>({\n  prop,\n  defaultProp,\n  onChange,\n}: UseControllableStateArgs<T>): [T, (next: T) => void] {\n  const [uncontrolledValue, setUncontrolledValue] = useState<T>(defaultProp);\n  const isControlled = prop !== undefined;\n  const value = isControlled ? prop : uncontrolledValue;\n\n  const onChangeRef = useRef(onChange);\n  useEffect(() => {\n    onChangeRef.current = onChange;\n  }, [onChange]);\n\n  const setValue = useCallback(\n    (next: T) => {\n      if (!isControlled) {\n        setUncontrolledValue(next);\n      }\n      onChangeRef.current?.(next);\n    },\n    [isControlled],\n  );\n\n  return [value, setValue];\n}\n",
      "type": "registry:component",
      "target": "components/code-block/hooks/use-controllable-state.ts"
    },
    {
      "path": "src/registry/components/code/code-block/hooks/use-copy-to-clipboard.ts",
      "content": "\"use client\";\nimport { useCallback, useRef, useState } from \"react\";\n\ninterface UseCopyToClipboardResult {\n  copy: (text: string) => Promise<boolean>;\n  copied: boolean;\n  failed: boolean;\n}\n\n/**\n * Clipboard write with legacy `document.execCommand('copy')` fallback for\n * browsers without the async Clipboard API. Returns transient `copied` /\n * `failed` flags that auto-clear after `revertMs` for icon-swap UX.\n */\nexport function useCopyToClipboard(revertMs = 1500): UseCopyToClipboardResult {\n  const [copied, setCopied] = useState(false);\n  const [failed, setFailed] = useState(false);\n  const timeoutRef = useRef<number | null>(null);\n\n  const reset = useCallback(() => {\n    if (timeoutRef.current !== null) {\n      window.clearTimeout(timeoutRef.current);\n      timeoutRef.current = null;\n    }\n    timeoutRef.current = window.setTimeout(() => {\n      setCopied(false);\n      setFailed(false);\n      timeoutRef.current = null;\n    }, revertMs);\n  }, [revertMs]);\n\n  const copy = useCallback(\n    async (text: string): Promise<boolean> => {\n      let ok = false;\n      try {\n        if (\n          typeof navigator !== \"undefined\" &&\n          navigator.clipboard &&\n          typeof navigator.clipboard.writeText === \"function\"\n        ) {\n          await navigator.clipboard.writeText(text);\n          ok = true;\n        }\n      } catch {\n        ok = false;\n      }\n      if (!ok && typeof document !== \"undefined\") {\n        try {\n          const ta = document.createElement(\"textarea\");\n          ta.value = text;\n          ta.style.position = \"fixed\";\n          ta.style.left = \"-9999px\";\n          ta.setAttribute(\"readonly\", \"\");\n          document.body.appendChild(ta);\n          ta.select();\n          ok = document.execCommand(\"copy\");\n          document.body.removeChild(ta);\n        } catch {\n          ok = false;\n        }\n      }\n      if (ok) {\n        setCopied(true);\n        setFailed(false);\n      } else {\n        setCopied(false);\n        setFailed(true);\n      }\n      reset();\n      return ok;\n    },\n    [reset],\n  );\n\n  return { copy, copied, failed };\n}\n",
      "type": "registry:component",
      "target": "components/code-block/hooks/use-copy-to-clipboard.ts"
    },
    {
      "path": "src/registry/components/code/code-block/hooks/use-shiki-highlighter.ts",
      "content": "\"use client\";\nimport { useEffect, useRef, useState } from \"react\";\nimport type { HighlighterCore, ThemeRegistrationAny } from \"shiki/core\";\nimport {\n  DEFAULT_THEME_NAMES,\n  ensureLangLoaded,\n  ensureThemeLoaded,\n  getHighlighter,\n  normalizeLang,\n} from \"../lib/shiki-bundle\";\nimport { rangeToLines } from \"../lib/line-utils\";\nimport {\n  diffForRetokenize,\n  emptyCache,\n  type StreamingCache,\n} from \"../lib/streaming-cache\";\nimport type { CodeBlockLineRange, CodeBlockThemes } from \"../types\";\n\ninterface UseShikiHighlighterArgs {\n  value: string;\n  lang: string;\n  themes: CodeBlockThemes | undefined;\n  highlightedLines?: Array<number | CodeBlockLineRange>;\n  streaming?: boolean;\n}\n\ninterface UseShikiHighlighterResult {\n  html: string;\n  ready: boolean;\n  resolvedLang: string;\n}\n\n// v0.1.2 (review 5.9) — resolve AND register a theme entry. String entries\n// keep the ensureThemeLoaded dynamic-import path; OBJECT entries (the declared\n// `ShikiThemeObject` support) are registered directly via\n// `highlighter.loadTheme(entry)` — previously the object was dropped, a\n// `shiki/themes/<name>.mjs` import was attempted, and the subsequent\n// `codeToHtml` threw as an unhandled rejection, leaving the block blank.\nasync function ensureThemeEntry(\n  highlighter: HighlighterCore,\n  themes: CodeBlockThemes | undefined,\n  variant: \"light\" | \"dark\",\n): Promise<string> {\n  const entry = themes?.[variant];\n  if (!entry) return DEFAULT_THEME_NAMES[variant];\n  if (typeof entry === \"string\") {\n    await ensureThemeLoaded(highlighter, entry);\n    return entry;\n  }\n  if (!highlighter.getLoadedThemes().includes(entry.name)) {\n    // ShikiThemeObject is a structural subset of shiki's own registration\n    // shape; the cast hands the full object through.\n    await highlighter.loadTheme(entry as unknown as ThemeRegistrationAny);\n  }\n  return entry.name;\n}\n\nfunction highlight(\n  highlighter: HighlighterCore,\n  value: string,\n  lang: string,\n  lightTheme: string,\n  darkTheme: string,\n  highlightedSet: Set<number>,\n): string {\n  const html = highlighter.codeToHtml(value, {\n    lang,\n    themes: { light: lightTheme, dark: darkTheme },\n    defaultColor: false,\n    cssVariablePrefix: \"--shiki-\",\n    transformers: [\n      {\n        name: \"code-block-highlighted-lines\",\n        line(node, lineNumber) {\n          if (highlightedSet.has(lineNumber)) {\n            node.properties = node.properties ?? {};\n            node.properties[\"data-highlighted\"] = \"true\";\n          }\n        },\n      },\n    ],\n  });\n  return html;\n}\n\nexport function useShikiHighlighter({\n  value,\n  lang,\n  themes,\n  highlightedLines,\n  streaming,\n}: UseShikiHighlighterArgs): UseShikiHighlighterResult {\n  const [html, setHtml] = useState<string>(\"\");\n  const [ready, setReady] = useState(false);\n  const [resolvedLang, setResolvedLang] = useState<string>(normalizeLang(lang));\n  const cacheRef = useRef<StreamingCache>(emptyCache());\n  const rafRef = useRef<number | null>(null);\n\n  useEffect(() => {\n    let cancelled = false;\n    const highlightedSet = rangeToLines(highlightedLines);\n\n    const run = async () => {\n      const highlighter = await getHighlighter();\n      if (cancelled) return;\n      const langForRender = await ensureLangLoaded(highlighter, lang);\n      if (cancelled) return;\n\n      // v0.1.2 (review 5.9) — theme resolution + tokenization are fallible\n      // (unknown theme name, malformed theme object). Fall back to the\n      // always-loaded defaults instead of throwing an unhandled rejection\n      // and leaving the block permanently blank.\n      let lightTheme: string;\n      let darkTheme: string;\n      try {\n        [lightTheme, darkTheme] = await Promise.all([\n          ensureThemeEntry(highlighter, themes, \"light\"),\n          ensureThemeEntry(highlighter, themes, \"dark\"),\n        ]);\n      } catch (err) {\n        if (process.env.NODE_ENV !== \"production\") {\n          console.warn(\n            \"[CodeBlock] Failed to load custom theme(s) — falling back to defaults.\",\n            err,\n          );\n        }\n        lightTheme = DEFAULT_THEME_NAMES.light;\n        darkTheme = DEFAULT_THEME_NAMES.dark;\n      }\n      if (cancelled) return;\n      setResolvedLang(langForRender);\n\n      // v0.1.0 streaming strategy: full re-tokenize per update, but batched\n      // to a single rAF (suppresses thrash from rapid char-level updates).\n      // Shiki caches grammars + themes, so warm-state tokenization is cheap\n      // (<5 ms for typical view-mode blocks). Pure append-only diff path\n      // is locked at lib/streaming-cache.ts and wired in for v0.2.\n      let full: string;\n      try {\n        full = highlight(\n          highlighter,\n          value,\n          langForRender,\n          lightTheme,\n          darkTheme,\n          highlightedSet,\n        );\n      } catch (err) {\n        // Theme registered but unusable at tokenize time — retry once on the\n        // default pair (loaded with the highlighter core, cannot miss).\n        if (process.env.NODE_ENV !== \"production\") {\n          console.warn(\n            \"[CodeBlock] Highlighting failed with the configured themes — retrying with defaults.\",\n            err,\n          );\n        }\n        full = highlight(\n          highlighter,\n          value,\n          langForRender,\n          DEFAULT_THEME_NAMES.light,\n          DEFAULT_THEME_NAMES.dark,\n          highlightedSet,\n        );\n      }\n      if (streaming) {\n        // Surface the cache helper so it isn't tree-shaken; v0.2 will diff\n        // against it for the pure append optimisation.\n        diffForRetokenize(cacheRef.current, value);\n      }\n      cacheRef.current = { prevValue: value, prevHtmlLines: [full] };\n      if (!cancelled) setHtml(full);\n      if (!cancelled) setReady(true);\n    };\n\n    if (rafRef.current !== null) {\n      cancelAnimationFrame(rafRef.current);\n    }\n    rafRef.current = requestAnimationFrame(() => {\n      // Final safety net (5.9): any residual rejection (network-failed wasm /\n      // grammar import, malformed default retry) must never surface as an\n      // unhandled rejection.\n      run().catch((err) => {\n        if (process.env.NODE_ENV !== \"production\") {\n          console.warn(\"[CodeBlock] Highlighter initialization failed.\", err);\n        }\n      });\n    });\n\n    return () => {\n      cancelled = true;\n      if (rafRef.current !== null) {\n        cancelAnimationFrame(rafRef.current);\n        rafRef.current = null;\n      }\n    };\n  }, [value, lang, themes, highlightedLines, streaming]);\n\n  return { html, ready, resolvedLang };\n}\n",
      "type": "registry:component",
      "target": "components/code-block/hooks/use-shiki-highlighter.ts"
    },
    {
      "path": "src/registry/components/code/code-block/hooks/use-code-mirror.ts",
      "content": "\"use client\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { Compartment, EditorState, type Extension } from \"@codemirror/state\";\nimport { EditorView, keymap, lineNumbers as cmLineNumbers } from \"@codemirror/view\";\nimport {\n  defaultKeymap,\n  history,\n  historyKeymap,\n  indentLess,\n  indentMore,\n} from \"@codemirror/commands\";\nimport { bracketMatching, indentOnInput } from \"@codemirror/language\";\nimport { closeBrackets, closeBracketsKeymap } from \"@codemirror/autocomplete\";\nimport { buildCodeMirrorTheme } from \"../lib/codemirror-theme\";\nimport { loadCodeMirrorLang } from \"../lib/codemirror-langs\";\nimport { normalizeLang } from \"../lib/shiki-bundle\";\n\ninterface UseCodeMirrorArgs {\n  value: string;\n  lang: string;\n  readOnly: boolean;\n  wrap: \"wrap\" | \"scroll\";\n  /**\n   * INITIAL-ONLY (v0.1.2): applied when the editor is created; later changes\n   * are not re-applied. Remount the editor (e.g. via a React `key`) to change\n   * it after mount. Making it reactive needs a dedicated compartment — noted\n   * for a future minor.\n   */\n  tabSize: number;\n  showLineNumbers: boolean;\n  onChange?: (value: string) => void;\n  onSave?: (value: string) => void;\n  /**\n   * INITIAL-ONLY (v0.1.2): the array captured at editor creation is baked into\n   * the EditorState; identity or content changes after mount are ignored.\n   * Remount (React `key`) to swap extensions.\n   */\n  editorExtensions?: Extension[];\n}\n\ninterface UseCodeMirrorResult {\n  containerRef: React.RefObject<HTMLDivElement | null>;\n  view: EditorView | null;\n  focus: () => void;\n  getValue: () => string;\n}\n\nexport function useCodeMirror({\n  value,\n  lang,\n  readOnly,\n  wrap,\n  tabSize,\n  showLineNumbers,\n  onChange,\n  onSave,\n  editorExtensions,\n}: UseCodeMirrorArgs): UseCodeMirrorResult {\n  const containerRef = useRef<HTMLDivElement | null>(null);\n  const viewRef = useRef<EditorView | null>(null);\n  const wrapCompartmentRef = useRef(new Compartment());\n  const langCompartmentRef = useRef(new Compartment());\n  const readOnlyCompartmentRef = useRef(new Compartment());\n\n  // Refs for callbacks so the editor doesn't remount on identity changes.\n  // Synced via useEffect to avoid setting refs during render (React 19 rule).\n  const onChangeRef = useRef(onChange);\n  const onSaveRef = useRef(onSave);\n  useEffect(() => {\n    onChangeRef.current = onChange;\n  }, [onChange]);\n  useEffect(() => {\n    onSaveRef.current = onSave;\n  }, [onSave]);\n\n  // v0.1.2 (review 5.8) — refs mirroring the reactive props. The mount effect\n  // awaits a dynamic lang import; props can change during that window. The\n  // editor is created from THESE refs (current at creation time), not the\n  // mount closure's stale first-render values; the per-prop sync effects\n  // below additionally re-run once `view` flips non-null, so nothing that\n  // changed mid-window is lost.\n  const valueRef = useRef(value);\n  const wrapRef = useRef(wrap);\n  const readOnlyRef = useRef(readOnly);\n  useEffect(() => {\n    valueRef.current = value;\n  }, [value]);\n  useEffect(() => {\n    wrapRef.current = wrap;\n  }, [wrap]);\n  useEffect(() => {\n    readOnlyRef.current = readOnly;\n  }, [readOnly]);\n\n  const [view, setView] = useState<EditorView | null>(null);\n\n  // Mount (once per container)\n  useEffect(() => {\n    const container = containerRef.current;\n    if (!container) return;\n    let cancelled = false;\n\n    const mount = async () => {\n      const normalizedLang = normalizeLang(lang);\n      const langExt = (await loadCodeMirrorLang(normalizedLang)) ?? [];\n      if (cancelled) return;\n\n      const extensions: Extension[] = [\n        history(),\n        bracketMatching(),\n        indentOnInput(),\n        closeBrackets(),\n        keymap.of([\n          ...defaultKeymap,\n          ...historyKeymap,\n          ...closeBracketsKeymap,\n          { key: \"Tab\", run: indentMore, shift: indentLess },\n          {\n            key: \"Mod-s\",\n            preventDefault: true,\n            run: (v) => {\n              const text = v.state.doc.toString();\n              if (onSaveRef.current) {\n                onSaveRef.current(text);\n              } else if (process.env.NODE_ENV !== \"production\") {\n                console.warn(\n                  \"[CodeBlock] Cmd+S pressed in edit mode but `onSave` is not wired — no-op.\",\n                );\n              }\n              return true;\n            },\n          },\n        ]),\n        // tabSize + editorExtensions are INITIAL-ONLY by contract (see the\n        // args JSDoc) — closure capture here is deliberate.\n        EditorState.tabSize.of(tabSize),\n        EditorState.allowMultipleSelections.of(true),\n        // Reactive props read from refs — current at creation time even when\n        // they changed while the lang import above was in flight (5.8).\n        wrapCompartmentRef.current.of(\n          wrapRef.current === \"wrap\" ? EditorView.lineWrapping : [],\n        ),\n        langCompartmentRef.current.of(langExt),\n        readOnlyCompartmentRef.current.of([\n          EditorView.editable.of(!readOnlyRef.current),\n          EditorState.readOnly.of(readOnlyRef.current),\n        ]),\n        ...(showLineNumbers ? [cmLineNumbers()] : []),\n        EditorView.updateListener.of((u) => {\n          if (u.docChanged) {\n            onChangeRef.current?.(u.state.doc.toString());\n          }\n        }),\n        buildCodeMirrorTheme(),\n        ...(editorExtensions ?? []),\n      ];\n\n      const v = new EditorView({\n        state: EditorState.create({ doc: valueRef.current, extensions }),\n        parent: container,\n      });\n      viewRef.current = v;\n      if (!cancelled) setView(v);\n    };\n\n    void mount();\n    return () => {\n      cancelled = true;\n      viewRef.current?.destroy();\n      viewRef.current = null;\n      setView(null);\n    };\n    // Intentionally only depend on `showLineNumbers` for mount — value sync\n    // happens in its own effect below; mid-mount prop changes are covered by\n    // the prop refs above (5.8).\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [showLineNumbers]);\n\n  // Controlled value sync. Keyed on `view` too (5.8): when the async mount\n  // resolves, this re-runs against the fresh view and applies any value that\n  // changed while the editor was still being created.\n  useEffect(() => {\n    const v = viewRef.current;\n    if (!v) return;\n    const current = v.state.doc.toString();\n    if (current === value) return;\n    v.dispatch({\n      changes: { from: 0, to: current.length, insert: value },\n    });\n  }, [value, view]);\n\n  // Wrap reconfigure (view-keyed for the async-mount window, 5.8)\n  useEffect(() => {\n    const v = viewRef.current;\n    if (!v) return;\n    v.dispatch({\n      effects: wrapCompartmentRef.current.reconfigure(\n        wrap === \"wrap\" ? EditorView.lineWrapping : [],\n      ),\n    });\n  }, [wrap, view]);\n\n  // Lang reconfigure (async — load lang package on change; view-keyed, 5.8)\n  useEffect(() => {\n    const v = viewRef.current;\n    if (!v) return;\n    let cancelled = false;\n    const apply = async () => {\n      const normalizedLang = normalizeLang(lang);\n      const langExt = (await loadCodeMirrorLang(normalizedLang)) ?? [];\n      if (cancelled || !viewRef.current) return;\n      viewRef.current.dispatch({\n        effects: langCompartmentRef.current.reconfigure(langExt),\n      });\n    };\n    void apply();\n    return () => {\n      cancelled = true;\n    };\n  }, [lang, view]);\n\n  // Read-only reconfigure (view-keyed for the async-mount window, 5.8)\n  useEffect(() => {\n    const v = viewRef.current;\n    if (!v) return;\n    v.dispatch({\n      effects: readOnlyCompartmentRef.current.reconfigure([\n        EditorView.editable.of(!readOnly),\n        EditorState.readOnly.of(readOnly),\n      ]),\n    });\n  }, [readOnly, view]);\n\n  const focus = useCallback(() => viewRef.current?.focus(), []);\n  const getValue = useCallback(\n    () => viewRef.current?.state.doc.toString() ?? \"\",\n    [],\n  );\n\n  return { containerRef, view, focus, getValue };\n}\n",
      "type": "registry:component",
      "target": "components/code-block/hooks/use-code-mirror.ts"
    },
    {
      "path": "src/registry/components/code/code-block/hooks/use-resolved-theme.ts",
      "content": "\"use client\";\nimport { useEffect, useState } from \"react\";\n\nfunction readInitialIsDark(): boolean {\n  if (typeof document === \"undefined\") return false;\n  return document.documentElement.classList.contains(\"dark\");\n}\n\n/**\n * Observes the document's `.dark` class. Used informationally only —\n * the actual palette swap happens via CSS variables on the\n * `.code-block-editor` host element. Useful for assistive-tech\n * announcements or `aria-` attributes that need the resolved theme.\n */\nexport function useResolvedTheme(): \"light\" | \"dark\" {\n  const [isDark, setIsDark] = useState<boolean>(readInitialIsDark);\n\n  useEffect(() => {\n    if (typeof document === \"undefined\") return;\n    const root = document.documentElement;\n    const observer = new MutationObserver(() => {\n      setIsDark(root.classList.contains(\"dark\"));\n    });\n    observer.observe(root, { attributes: true, attributeFilter: [\"class\"] });\n    return () => observer.disconnect();\n  }, []);\n\n  return isDark ? \"dark\" : \"light\";\n}\n",
      "type": "registry:component",
      "target": "components/code-block/hooks/use-resolved-theme.ts"
    },
    {
      "path": "src/registry/components/code/code-block/lib/lang-resolution.ts",
      "content": "import type { CodeBlockFilenameToLangArgs } from \"../types\";\n\nexport const FILENAME_TO_LANG_MAP: Record<string, string> = {\n  ts: \"ts\",\n  tsx: \"tsx\",\n  js: \"js\",\n  jsx: \"jsx\",\n  mjs: \"js\",\n  cjs: \"js\",\n  json: \"json\",\n  jsonc: \"json\",\n  json5: \"json\",\n  py: \"python\",\n  pyw: \"python\",\n  rb: \"ruby\",\n  rake: \"ruby\",\n  go: \"go\",\n  rs: \"rust\",\n  java: \"java\",\n  c: \"c\",\n  h: \"c\",\n  cpp: \"cpp\",\n  cxx: \"cpp\",\n  cc: \"cpp\",\n  hpp: \"cpp\",\n  cs: \"csharp\",\n  php: \"php\",\n  swift: \"swift\",\n  kt: \"kotlin\",\n  kts: \"kotlin\",\n  sh: \"bash\",\n  bash: \"bash\",\n  zsh: \"bash\",\n  yml: \"yaml\",\n  yaml: \"yaml\",\n  toml: \"toml\",\n  ini: \"ini\",\n  md: \"markdown\",\n  mdx: \"markdown\",\n  html: \"html\",\n  htm: \"html\",\n  css: \"css\",\n  scss: \"scss\",\n  graphql: \"graphql\",\n  gql: \"graphql\",\n  sql: \"sql\",\n  diff: \"diff\",\n  patch: \"diff\",\n  txt: \"plaintext\",\n  log: \"plaintext\",\n};\n\nconst FILENAME_OVERRIDES: Record<string, string> = {\n  dockerfile: \"dockerfile\",\n  makefile: \"makefile\",\n};\n\nexport function resolveLang(\n  lang: string | undefined,\n  filename: string | undefined,\n  override: ((args: CodeBlockFilenameToLangArgs) => string | undefined) | undefined,\n): string {\n  if (lang) return lang;\n  if (!filename) return \"plaintext\";\n\n  const lowered = filename.toLowerCase();\n  const exact = FILENAME_OVERRIDES[lowered];\n  if (exact) return exact;\n\n  if (override) {\n    const overridden = override({ filename });\n    if (overridden) return overridden;\n  }\n\n  const ext = filename.split(\".\").pop()?.toLowerCase() ?? \"\";\n  return FILENAME_TO_LANG_MAP[ext] ?? \"plaintext\";\n}\n",
      "type": "registry:component",
      "target": "components/code-block/lib/lang-resolution.ts"
    },
    {
      "path": "src/registry/components/code/code-block/lib/line-utils.ts",
      "content": "import type { CodeBlockLineRange } from \"../types\";\n\nexport function splitToLines(value: string): string[] {\n  if (value === \"\") return [];\n  return value.split(\"\\n\");\n}\n\nexport function lineCount(value: string): number {\n  if (value === \"\") return 0;\n  return value.split(\"\\n\").length;\n}\n\nexport function rangeToLines(\n  highlighted: Array<number | CodeBlockLineRange> | undefined,\n): Set<number> {\n  const out = new Set<number>();\n  if (!highlighted) return out;\n  for (const entry of highlighted) {\n    if (typeof entry === \"number\") {\n      if (entry > 0) out.add(entry);\n      continue;\n    }\n    const { from, to } = entry;\n    const start = Math.max(1, Math.min(from, to));\n    const end = Math.max(from, to);\n    for (let i = start; i <= end; i++) out.add(i);\n  }\n  return out;\n}\n\nexport function gutterWidth(totalLines: number): number {\n  if (totalLines < 10) return 1;\n  if (totalLines < 100) return 2;\n  if (totalLines < 1000) return 3;\n  return 4;\n}\n",
      "type": "registry:component",
      "target": "components/code-block/lib/line-utils.ts"
    },
    {
      "path": "src/registry/components/code/code-block/lib/terminal-utils.ts",
      "content": "import type { TerminalLine, TerminalLineKind } from \"../types\";\n\nconst PROMPT_PATTERNS = [\"$ \", \"> \", \"# \"] as const;\n\nexport function promptDetect(line: string): TerminalLineKind {\n  for (const p of PROMPT_PATTERNS) {\n    if (line.startsWith(p)) return \"input\";\n  }\n  return \"output\";\n}\n\nexport function parseTerminalLines(value: string): TerminalLine[] {\n  if (value === \"\") return [];\n  return value.split(\"\\n\").map((text) => ({ kind: promptDetect(text), text }));\n}\n\nexport function promptPrefix(text: string): { prefix: string; rest: string } {\n  for (const p of PROMPT_PATTERNS) {\n    if (text.startsWith(p)) {\n      return { prefix: p, rest: text.slice(p.length) };\n    }\n  }\n  return { prefix: \"\", rest: text };\n}\n\nexport function joinTerminalLines(lines: TerminalLine[]): string {\n  return lines.map((l) => l.text).join(\"\\n\");\n}\n",
      "type": "registry:component",
      "target": "components/code-block/lib/terminal-utils.ts"
    },
    {
      "path": "src/registry/components/code/code-block/lib/shiki-bundle.ts",
      "content": "/**\n * Shiki bundle setup with fine-grained imports + on-demand grammar loading.\n *\n * Default ships ~10 common grammars synchronously (ts/tsx/js/jsx/json/python/\n * bash/markdown/html/css). Other grammars dynamic-import on first use.\n *\n * Themes default to GitHub Light + GitHub Dark Default (small, sync-loaded).\n */\nimport {\n  createHighlighterCore,\n  type HighlighterCore,\n  type LanguageRegistration,\n  type ThemeRegistrationAny,\n} from \"shiki/core\";\nimport { createOnigurumaEngine } from \"shiki/engine/oniguruma\";\n\nlet cachedHighlighter: Promise<HighlighterCore> | null = null;\nconst loadedLangs = new Set<string>();\n\nconst LAZY_LANG_LOADERS: Record<string, () => Promise<unknown>> = {\n  rust: () => import(\"shiki/langs/rust.mjs\"),\n  go: () => import(\"shiki/langs/go.mjs\"),\n  sql: () => import(\"shiki/langs/sql.mjs\"),\n  yaml: () => import(\"shiki/langs/yaml.mjs\"),\n  diff: () => import(\"shiki/langs/diff.mjs\"),\n  java: () => import(\"shiki/langs/java.mjs\"),\n  c: () => import(\"shiki/langs/c.mjs\"),\n  cpp: () => import(\"shiki/langs/cpp.mjs\"),\n  csharp: () => import(\"shiki/langs/csharp.mjs\"),\n  ruby: () => import(\"shiki/langs/ruby.mjs\"),\n  php: () => import(\"shiki/langs/php.mjs\"),\n  swift: () => import(\"shiki/langs/swift.mjs\"),\n  kotlin: () => import(\"shiki/langs/kotlin.mjs\"),\n  graphql: () => import(\"shiki/langs/graphql.mjs\"),\n  toml: () => import(\"shiki/langs/toml.mjs\"),\n  ini: () => import(\"shiki/langs/ini.mjs\"),\n  scss: () => import(\"shiki/langs/scss.mjs\"),\n  dockerfile: () => import(\"shiki/langs/dockerfile.mjs\"),\n  makefile: () => import(\"shiki/langs/makefile.mjs\"),\n  patch: () => import(\"shiki/langs/diff.mjs\"),\n};\n\nconst LANG_ALIASES: Record<string, string> = {\n  javascript: \"js\",\n  typescript: \"ts\",\n  py: \"python\",\n  rb: \"ruby\",\n  rs: \"rust\",\n  yml: \"yaml\",\n  sh: \"bash\",\n  zsh: \"bash\",\n  md: \"markdown\",\n  mdx: \"markdown\",\n};\n\nexport function normalizeLang(lang: string | undefined): string {\n  if (!lang) return \"plaintext\";\n  const lower = lang.toLowerCase();\n  return LANG_ALIASES[lower] ?? lower;\n}\n\nasync function loadCoreGrammars(): Promise<LanguageRegistration[]> {\n  const [ts, tsx, js, jsx, json, bash, python, markdown, html, css] = await Promise.all([\n    import(\"shiki/langs/ts.mjs\"),\n    import(\"shiki/langs/tsx.mjs\"),\n    import(\"shiki/langs/javascript.mjs\"),\n    import(\"shiki/langs/jsx.mjs\"),\n    import(\"shiki/langs/json.mjs\"),\n    import(\"shiki/langs/bash.mjs\"),\n    import(\"shiki/langs/python.mjs\"),\n    import(\"shiki/langs/markdown.mjs\"),\n    import(\"shiki/langs/html.mjs\"),\n    import(\"shiki/langs/css.mjs\"),\n  ]);\n  const out = [ts, tsx, js, jsx, json, bash, python, markdown, html, css]\n    .map((m) => (m as { default: unknown }).default)\n    .flat() as LanguageRegistration[];\n  for (const reg of out) {\n    if (reg && typeof reg === \"object\" && \"name\" in reg && typeof reg.name === \"string\") {\n      loadedLangs.add(reg.name);\n    }\n  }\n  return out;\n}\n\nasync function loadCoreThemes(): Promise<ThemeRegistrationAny[]> {\n  const [light, dark] = await Promise.all([\n    import(\"shiki/themes/github-light.mjs\"),\n    import(\"shiki/themes/github-dark-default.mjs\"),\n  ]);\n  return [\n    (light as { default: ThemeRegistrationAny }).default,\n    (dark as { default: ThemeRegistrationAny }).default,\n  ];\n}\n\nexport function getHighlighter(): Promise<HighlighterCore> {\n  if (cachedHighlighter) return cachedHighlighter;\n  cachedHighlighter = (async () => {\n    const [langs, themes, wasmMod] = await Promise.all([\n      loadCoreGrammars(),\n      loadCoreThemes(),\n      import(\"shiki/wasm\"),\n    ]);\n    // shiki/wasm exports the wasm-loader function as `default`.\n    const wasm = wasmMod.default;\n    return createHighlighterCore({\n      engine: createOnigurumaEngine(wasm),\n      langs,\n      themes,\n    });\n  })();\n  return cachedHighlighter;\n}\n\nexport async function ensureLangLoaded(\n  highlighter: HighlighterCore,\n  lang: string,\n): Promise<string> {\n  const normalized = normalizeLang(lang);\n  if (normalized === \"plaintext\") return normalized;\n  if (loadedLangs.has(normalized)) return normalized;\n  if (highlighter.getLoadedLanguages().includes(normalized)) {\n    loadedLangs.add(normalized);\n    return normalized;\n  }\n  const loader = LAZY_LANG_LOADERS[normalized];\n  if (!loader) {\n    if (process.env.NODE_ENV !== \"production\") {\n      console.warn(`[CodeBlock] Unknown lang \"${lang}\" — falling back to plaintext.`);\n    }\n    return \"plaintext\";\n  }\n  const mod = (await loader()) as { default: LanguageRegistration | LanguageRegistration[] };\n  const reg = Array.isArray(mod.default) ? mod.default : [mod.default];\n  await highlighter.loadLanguage(...reg);\n  loadedLangs.add(normalized);\n  return normalized;\n}\n\nexport async function ensureThemeLoaded(\n  highlighter: HighlighterCore,\n  themeName: string,\n): Promise<void> {\n  if (highlighter.getLoadedThemes().includes(themeName)) return;\n  // Attempt dynamic import from shiki/themes/<name>.mjs\n  try {\n    const mod = (await import(/* @vite-ignore */ `shiki/themes/${themeName}.mjs`)) as {\n      default: ThemeRegistrationAny;\n    };\n    await highlighter.loadTheme(mod.default);\n  } catch {\n    if (process.env.NODE_ENV !== \"production\") {\n      console.warn(`[CodeBlock] Theme \"${themeName}\" not found in shiki/themes/.`);\n    }\n  }\n}\n\nexport const DEFAULT_THEME_NAMES = {\n  light: \"github-light\",\n  dark: \"github-dark-default\",\n} as const;\n",
      "type": "registry:component",
      "target": "components/code-block/lib/shiki-bundle.ts"
    },
    {
      "path": "src/registry/components/code/code-block/lib/codemirror-langs.ts",
      "content": "import type { Extension } from \"@codemirror/state\";\nimport { normalizeLang } from \"./shiki-bundle\";\n\ntype LangLoader = () => Promise<Extension | null>;\n\nconst CM_LANG_LOADERS: Record<string, LangLoader> = {\n  ts: async () =>\n    (await import(\"@codemirror/lang-javascript\")).javascript({\n      typescript: true,\n      jsx: false,\n    }),\n  tsx: async () =>\n    (await import(\"@codemirror/lang-javascript\")).javascript({\n      typescript: true,\n      jsx: true,\n    }),\n  js: async () =>\n    (await import(\"@codemirror/lang-javascript\")).javascript({ jsx: false }),\n  jsx: async () =>\n    (await import(\"@codemirror/lang-javascript\")).javascript({ jsx: true }),\n  json: async () => (await import(\"@codemirror/lang-json\")).json(),\n  python: async () => (await import(\"@codemirror/lang-python\")).python(),\n  html: async () => (await import(\"@codemirror/lang-html\")).html(),\n  css: async () => (await import(\"@codemirror/lang-css\")).css(),\n  markdown: async () => (await import(\"@codemirror/lang-markdown\")).markdown(),\n};\n\nexport async function loadCodeMirrorLang(\n  lang: string,\n): Promise<Extension | null> {\n  const normalized = normalizeLang(lang);\n  const loader = CM_LANG_LOADERS[normalized];\n  if (!loader) return null;\n  try {\n    return await loader();\n  } catch (err) {\n    if (process.env.NODE_ENV !== \"production\") {\n      console.warn(\n        `[CodeBlock] CodeMirror lang package for \"${lang}\" failed to load:`,\n        err,\n      );\n    }\n    return null;\n  }\n}\n",
      "type": "registry:component",
      "target": "components/code-block/lib/codemirror-langs.ts"
    },
    {
      "path": "src/registry/components/code/code-block/lib/codemirror-theme.ts",
      "content": "/**\n * Custom CodeMirror theme + HighlightStyle approximating Shiki's\n * GitHub Light + GitHub Dark Default token palettes.\n *\n * Colors are emitted as CSS variables (`--cb-fg-<token>`) so the active\n * `.dark` class on an ancestor flips the palette without re-mounting\n * or reconfiguring CodeMirror.\n *\n * v0.2.0 upgrade path: replace this file with a hand-rolled Shiki →\n * CodeMirror bridge (tokenize doc on change, apply tokens as a\n * StateField<DecorationSet>) for pixel-perfect parity with view mode.\n */\nimport { HighlightStyle, syntaxHighlighting } from \"@codemirror/language\";\nimport { EditorView } from \"@codemirror/view\";\nimport type { Extension } from \"@codemirror/state\";\nimport { tags as t } from \"@lezer/highlight\";\n\nconst highlightStyle = HighlightStyle.define([\n  { tag: t.keyword, color: \"var(--cb-fg-keyword)\" },\n  { tag: t.controlKeyword, color: \"var(--cb-fg-keyword)\" },\n  { tag: t.operatorKeyword, color: \"var(--cb-fg-keyword)\" },\n  { tag: t.modifier, color: \"var(--cb-fg-keyword)\" },\n\n  { tag: [t.string, t.special(t.string)], color: \"var(--cb-fg-string)\" },\n  { tag: t.regexp, color: \"var(--cb-fg-string)\" },\n\n  { tag: t.number, color: \"var(--cb-fg-number)\" },\n  { tag: t.bool, color: \"var(--cb-fg-number)\" },\n  { tag: t.null, color: \"var(--cb-fg-number)\" },\n\n  {\n    tag: [t.comment, t.lineComment, t.blockComment, t.docComment],\n    color: \"var(--cb-fg-comment)\",\n    fontStyle: \"italic\",\n  },\n\n  { tag: [t.variableName, t.propertyName], color: \"var(--cb-fg-variable)\" },\n  { tag: t.definition(t.variableName), color: \"var(--cb-fg-variable)\" },\n\n  { tag: [t.typeName, t.className], color: \"var(--cb-fg-type)\" },\n  { tag: [t.namespace, t.tagName], color: \"var(--cb-fg-type)\" },\n\n  { tag: t.function(t.variableName), color: \"var(--cb-fg-function)\" },\n  { tag: t.function(t.propertyName), color: \"var(--cb-fg-function)\" },\n  { tag: t.macroName, color: \"var(--cb-fg-function)\" },\n\n  { tag: [t.operator, t.derefOperator, t.arithmeticOperator], color: \"var(--cb-fg-operator)\" },\n  { tag: [t.compareOperator, t.logicOperator], color: \"var(--cb-fg-operator)\" },\n\n  { tag: [t.punctuation, t.separator, t.bracket], color: \"var(--cb-fg-punctuation)\" },\n\n  { tag: t.attributeName, color: \"var(--cb-fg-attribute)\" },\n  { tag: t.attributeValue, color: \"var(--cb-fg-string)\" },\n\n  { tag: [t.meta, t.processingInstruction], color: \"var(--cb-fg-meta)\" },\n\n  { tag: t.invalid, color: \"var(--cb-fg-invalid)\" },\n\n  { tag: t.heading, color: \"var(--cb-fg-keyword)\", fontWeight: \"bold\" },\n  { tag: t.link, color: \"var(--cb-fg-string)\", textDecoration: \"underline\" },\n  { tag: t.emphasis, fontStyle: \"italic\" },\n  { tag: t.strong, fontWeight: \"bold\" },\n]);\n\nconst editorTheme = EditorView.theme(\n  {\n    \"&\": {\n      backgroundColor: \"transparent\",\n      color: \"var(--cb-fg-variable)\",\n      fontFamily: \"var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace)\",\n      fontSize: \"0.875rem\",\n      lineHeight: \"1.6\",\n      height: \"100%\",\n    },\n    \"&.cm-focused\": {\n      outline: \"none\",\n    },\n    \".cm-scroller\": {\n      fontFamily: \"inherit\",\n      lineHeight: \"inherit\",\n    },\n    \".cm-content\": {\n      padding: \"1rem 1rem 1rem 0\",\n      caretColor: \"var(--foreground)\",\n    },\n    \".cm-gutters\": {\n      backgroundColor: \"transparent\",\n      border: \"none\",\n      color: \"var(--muted-foreground)\",\n      fontFamily: \"inherit\",\n      paddingRight: \"0.75rem\",\n    },\n    \".cm-lineNumbers .cm-gutterElement\": {\n      padding: \"0 0.5rem\",\n      minWidth: \"2.5em\",\n      textAlign: \"right\",\n    },\n    \".cm-activeLine\": {\n      backgroundColor: \"color-mix(in oklch, var(--accent) 4%, transparent)\",\n    },\n    \".cm-activeLineGutter\": {\n      backgroundColor: \"color-mix(in oklch, var(--accent) 4%, transparent)\",\n      color: \"var(--foreground)\",\n    },\n    \".cm-selectionBackground, &.cm-focused .cm-selectionBackground, ::selection\": {\n      backgroundColor: \"color-mix(in oklch, var(--accent) 25%, transparent) !important\",\n    },\n    \".cm-cursor\": {\n      borderLeftColor: \"var(--foreground)\",\n      borderLeftWidth: \"2px\",\n    },\n    \".cm-matchingBracket, .cm-nonmatchingBracket\": {\n      backgroundColor: \"color-mix(in oklch, var(--accent) 18%, transparent)\",\n      outline: \"none\",\n    },\n  },\n  { dark: false },\n);\n\n/**\n * Build the editor theme extension. The themes arg is reserved for the\n * v0.2.0 Shiki bridge — in v0.1.0 it is informational only (the active\n * palette is CSS-variable driven via .dark class).\n */\nexport function buildCodeMirrorTheme(): Extension {\n  return [editorTheme, syntaxHighlighting(highlightStyle)];\n}\n\n/**\n * CSS variable definitions for both light + dark palettes.\n * Inject this stylesheet once per CodeBlock instance (or globally).\n *\n * Palette source: GitHub Light + GitHub Dark Default (Shiki's bundled themes).\n */\nexport const CODEMIRROR_THEME_CSS = `\n.code-block-editor {\n  --cb-fg-keyword: #cf222e;\n  --cb-fg-string: #0a3069;\n  --cb-fg-number: #0550ae;\n  --cb-fg-comment: #6e7781;\n  --cb-fg-variable: #1f2328;\n  --cb-fg-type: #953800;\n  --cb-fg-function: #8250df;\n  --cb-fg-operator: #cf222e;\n  --cb-fg-punctuation: #24292f;\n  --cb-fg-attribute: #0550ae;\n  --cb-fg-meta: #6639ba;\n  --cb-fg-invalid: #82071e;\n}\n:where(.dark) .code-block-editor {\n  --cb-fg-keyword: #ff7b72;\n  --cb-fg-string: #a5d6ff;\n  --cb-fg-number: #79c0ff;\n  --cb-fg-comment: #8b949e;\n  --cb-fg-variable: #e6edf3;\n  --cb-fg-type: #ffa657;\n  --cb-fg-function: #d2a8ff;\n  --cb-fg-operator: #ff7b72;\n  --cb-fg-punctuation: #c9d1d9;\n  --cb-fg-attribute: #79c0ff;\n  --cb-fg-meta: #d2a8ff;\n  --cb-fg-invalid: #ffa198;\n}\n`;\n",
      "type": "registry:component",
      "target": "components/code-block/lib/codemirror-theme.ts"
    },
    {
      "path": "src/registry/components/code/code-block/lib/streaming-cache.ts",
      "content": "/**\n * Append-only streaming-tokenize cache.\n *\n * When the consumer streams new content (each render's `value` is the\n * previous `value` + a tail), retokenizing the entire document is wasteful.\n *\n * Algorithm:\n *  - Stable boundary = the last newline in the previous value. Lines before\n *    the boundary cannot change (tokenization is line-local in Shiki/TextMate).\n *  - On each new value:\n *    - If !startsWith(prev): not an append → full retokenize.\n *    - Else: retokenize only `prevValue.slice(lastNewline + 1) ++ newTail`,\n *      reuse the cached HTML for lines before the boundary.\n *\n * The cache holds per-line HTML strings; concatenation rebuilds the full\n * output without re-running the tokenizer on stable lines.\n */\nexport interface StreamingCache {\n  prevValue: string;\n  prevHtmlLines: string[];\n}\n\nexport function emptyCache(): StreamingCache {\n  return { prevValue: \"\", prevHtmlLines: [] };\n}\n\nexport interface CacheDiff {\n  /** Lines before the stable boundary — reuse cached HTML. */\n  stablePrefixLines: string[];\n  /** The substring that needs re-tokenizing (suffix from last stable newline). */\n  retokenizeSlice: string;\n  /** Whether this is an append (true) or a replace (false). */\n  isAppend: boolean;\n}\n\nexport function diffForRetokenize(\n  cache: StreamingCache,\n  nextValue: string,\n): CacheDiff {\n  if (nextValue === cache.prevValue) {\n    return {\n      stablePrefixLines: cache.prevHtmlLines,\n      retokenizeSlice: \"\",\n      isAppend: true,\n    };\n  }\n  if (!nextValue.startsWith(cache.prevValue)) {\n    return {\n      stablePrefixLines: [],\n      retokenizeSlice: nextValue,\n      isAppend: false,\n    };\n  }\n  const lastNewline = cache.prevValue.lastIndexOf(\"\\n\");\n  if (lastNewline < 0) {\n    return {\n      stablePrefixLines: [],\n      retokenizeSlice: nextValue,\n      isAppend: true,\n    };\n  }\n  const stableLineCount = cache.prevValue.slice(0, lastNewline).split(\"\\n\").length;\n  return {\n    stablePrefixLines: cache.prevHtmlLines.slice(0, stableLineCount),\n    retokenizeSlice: nextValue.slice(lastNewline + 1),\n    isAppend: true,\n  };\n}\n",
      "type": "registry:component",
      "target": "components/code-block/lib/streaming-cache.ts"
    }
  ],
  "categories": [
    "code"
  ],
  "type": "registry:block"
}