# Code Block

> Code surface with view, edit, and terminal modes — Shiki highlighting, dual-theme CSS variables, and chrome presets for docs, chat, and terminal UIs.

A shadcn/ui block from the [@ilinxa](https://ui.ilinxa.com) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/ilinxa/code-block).

- Page, with a live preview: https://shadcn.exchange/r/ilinxa/code-block
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/ilinxa/code-block-light-54ba993a4dc504c6.png
- Registry item: https://ui.ilinxa.com/r/code-block.json
- Categories: code
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://ui.ilinxa.com/r/code-block.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- npm packages: `@codemirror/autocomplete`, `@codemirror/commands`, `@codemirror/lang-css`, `@codemirror/lang-html`, `@codemirror/lang-javascript`, `@codemirror/lang-json`, `@codemirror/lang-markdown`, `@codemirror/lang-python`, `@codemirror/language`, `@codemirror/state`, `@codemirror/view`, `@lezer/highlight`, `lucide-react`, `shiki`
- Registry items: `button`, `dialog`, `tooltip`

## Files

### `src/registry/components/code/code-block/code-block.tsx`

```tsx
"use client";
import {
  useCallback,
  useImperativeHandle,
  useMemo,
  useRef,
  useState,
} from "react";
import { TooltipProvider } from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
import {
  CodeBlockProvider,
} from "./hooks/use-code-block-context";
import { useControllableState } from "./hooks/use-controllable-state";
import { useCopyToClipboard } from "./hooks/use-copy-to-clipboard";
import { resolveLang } from "./lib/lang-resolution";
import { joinTerminalLines } from "./lib/terminal-utils";
import { CodeBlockBodyEdit } from "./parts/code-block-body-edit";
import { CodeBlockBodyTerminal } from "./parts/code-block-body-terminal";
import { CodeBlockBodyView } from "./parts/code-block-body-view";
import { CodeBlockCopyButton } from "./parts/code-block-copy-button";
import { CodeBlockDownloadButton } from "./parts/code-block-download-button";
import { CodeBlockExpandButton } from "./parts/code-block-expand-button";
import { CodeBlockExpandModal } from "./parts/code-block-expand-modal";
import { CodeBlockFooter } from "./parts/code-block-footer";
import { CodeBlockHeader } from "./parts/code-block-header";
import { CodeBlockTrafficLights } from "./parts/code-block-traffic-lights";
import { CodeBlockWrapButton } from "./parts/code-block-wrap-button";
import {
  DEFAULT_LABELS,
  type CodeBlockHandle,
  type CodeBlockProps,
} from "./types";

const DEFAULT_THEMES = {
  light: "github-light",
  dark: "github-dark-default",
} as const;

export function CodeBlock(props: CodeBlockProps) {
  const {
    value: valueProp,
    defaultValue,
    lines,
    lang: langProp,
    filename,
    filenameToLang,
    mode = "view",
    readOnly = false,
    streaming = false,
    onChange,
    onSave,
    tabSize = 4,
    editorExtensions,
    header = true,
    showLanguage = true,
    showCopy = true,
    showExpand = false,
    showWrap = false,
    showDownload = false,
    showTrafficLights = false,
    actions,
    renderHeader,
    renderExpandModal,
    footer,
    showLineNumbers,
    wrap: wrapProp,
    highlightedLines,
    annotations,
    renderAnnotation,
    maxLines,
    expanded: expandedProp,
    defaultExpanded,
    onExpandedChange,
    onWrapChange,
    onLineClick,
    onCopy,
    onDownload,
    themes,
    regexEngine,
    maxHeight,
    emptyMessage,
    className,
    style,
    ariaLabel,
    labels: labelsProp,
    ref,
  } = props;

  // Resolved values
  const labels = useMemo(
    () => ({ ...DEFAULT_LABELS, ...(labelsProp ?? {}) }),
    [labelsProp],
  );
  const themesResolved = themes ?? DEFAULT_THEMES;
  const lang = useMemo(
    () => resolveLang(langProp, filename, filenameToLang),
    [langProp, filename, filenameToLang],
  );

  const lineDerivedValue = useMemo(() => {
    if (mode === "terminal" && lines) return joinTerminalLines(lines);
    return undefined;
  }, [mode, lines]);

  // Controlled/uncontrolled value for edit mode.
  const [editValue, setEditValueInternal] = useControllableState<string>({
    prop: mode === "edit" ? valueProp : undefined,
    defaultProp: defaultValue ?? valueProp ?? "",
    onChange: (next) => onChange?.({ value: next }),
  });

  const displayValue =
    mode === "edit"
      ? editValue
      : lineDerivedValue !== undefined
        ? lineDerivedValue
        : (valueProp ?? "");

  // Wrap state (controlled or local)
  const [wrap, setWrapInternal] = useControllableState<"wrap" | "scroll">({
    prop: wrapProp,
    defaultProp: wrapProp ?? "scroll",
    onChange: (next) => onWrapChange?.({ wrap: next }),
  });

  // Expanded (collapse) state
  const [expanded, setExpandedInternal] = useControllableState<boolean>({
    prop: expandedProp,
    defaultProp: defaultExpanded ?? false,
    onChange: (next) => onExpandedChange?.({ expanded: next }),
  });

  const [modalOpen, setModalOpen] = useState(false);
  /**
   * v0.2.0 — set when the user takes the "Reload as view-only" recovery after
   * a failed CodeMirror mount. Local, not a `mode` mutation: `mode` belongs to
   * the host.
   *
   * Reset whenever `mode` changes, so a host with an Edit/Preview toggle can
   * get its editor back after a transient failure. Without this the flag is a
   * one-way trap: the host flips `mode` back to "edit", nothing happens, and
   * the only escape is remounting via `key` — which nothing documents.
   * Adjusting state during render (React's documented pattern for deriving
   * from a changed prop) rather than in an effect, which would be a cascading
   * -render hazard the compiler lint rejects.
   */
  const [editorFellBack, setEditorFellBack] = useState(false);
  const [lastMode, setLastMode] = useState(mode);
  if (lastMode !== mode) {
    setLastMode(mode);
    if (editorFellBack) setEditorFellBack(false);
  }

  // Line numbers default per mode
  const resolvedShowLineNumbers =
    showLineNumbers !== undefined
      ? showLineNumbers
      : mode === "edit"
        ? true
        : false;

  // Copy
  const { copy: copyToClipboard, copied, failed: copyFailed } = useCopyToClipboard();
  const handleCopy = useCallback(async () => {
    const ok = await copyToClipboard(displayValue);
    if (ok) onCopy?.({ value: displayValue });
    return ok;
  }, [copyToClipboard, displayValue, onCopy]);

  // Download
  const handleDownload = useCallback(() => {
    const resolvedFilename = filename ?? `code.${lang === "plaintext" ? "txt" : lang}`;
    if (onDownload) {
      onDownload({ value: displayValue, filename: resolvedFilename });
      return;
    }
    if (typeof window === "undefined") return;
    try {
      const blob = new Blob([displayValue], { type: "text/plain;charset=utf-8" });
      const url = URL.createObjectURL(blob);
      const a = document.createElement("a");
      a.href = url;
      a.download = resolvedFilename;
      document.body.appendChild(a);
      a.click();
      document.body.removeChild(a);
      URL.revokeObjectURL(url);
    } catch {
      // Soft-fail; download is best-effort.
    }
  }, [displayValue, filename, lang, onDownload]);

  // Imperative handle wired to body refs
  const rootRef = useRef<HTMLElement | null>(null);
  const editorImperativeRef = useRef<{
    focus: () => void;
    getValue: () => string;
    scrollToLine: (line: number) => boolean;
  } | null>(null);

  /**
   * v0.2.1 — `scrollToLine` was an empty function body from v0.1.0 while
   * `meta.ts` advertised it as a working handle method. Calling it did nothing,
   * silently, for three minor versions.
   *
   * Three bodies, three scrollers, one entry point:
   *   edit     → CodeMirror owns its scroller; dispatch scrollIntoView.
   *   view     → Shiki emits `.line` rows (so does the v0.2.0 plaintext
   *              fallback), inside the `[data-cb-scroller]` div.
   *   terminal → rows carry `data-cb-line`.
   *
   * Collapsed blocks expand first. `maxLines` clips by height rather than by
   * dropping rows, so the target row exists in the DOM but sits under the fade
   * — scrolling to it without expanding would move the scroller and still show
   * the user nothing, which is the same silent no-op this method is being
   * fixed for.
   */
  const scrollToLine = useCallback(
    (line: number) => {
      if (mode === "edit" && !editorFellBack) {
        if (editorImperativeRef.current?.scrollToLine(line)) return;
      }
      const root = rootRef.current;
      if (!root) return;
      const scroller = root.querySelector<HTMLElement>("[data-cb-scroller]");
      if (!scroller) return;
      const rows = scroller.querySelectorAll<HTMLElement>("[data-cb-line], .line");
      if (rows.length === 0) return;
      const index = Math.min(Math.max(Math.trunc(line), 1), rows.length) - 1;
      const row = rows[index];
      if (!row) return;

      const reveal = () => {
        if (typeof row.scrollIntoView === "function") {
          row.scrollIntoView({ block: "center" });
        } else {
          scroller.scrollTop = Math.max(
            0,
            row.offsetTop - scroller.clientHeight / 2,
          );
        }
      };

      const collapsed =
        maxLines !== undefined && !expanded && index + 1 > maxLines;
      if (collapsed) {
        setExpandedInternal(true);
        if (typeof requestAnimationFrame === "function") {
          requestAnimationFrame(reveal);
          return;
        }
      }
      reveal();
    },
    [mode, editorFellBack, maxLines, expanded, setExpandedInternal],
  );

  const handle = useMemo<CodeBlockHandle>(
    () => ({
      copy: handleCopy,
      focus: () => editorImperativeRef.current?.focus(),
      getValue: () =>
        mode === "edit"
          ? (editorImperativeRef.current?.getValue() ?? editValue)
          : displayValue,
      scrollToLine,
    }),
    [handleCopy, displayValue, editValue, mode, scrollToLine],
  );

  useImperativeHandle(ref, () => handle, [handle]);

  // Context value
  const ctxValue = useMemo(
    () => ({
      value: displayValue,
      filename,
      lang,
      resolvedLang: lang,
      mode,
      streaming,
      wrap,
      showLineNumbers: resolvedShowLineNumbers,
      expanded,
      setExpanded: setExpandedInternal,
      setWrap: setWrapInternal,
      modalOpen,
      setModalOpen,
      labels,
      copy: handleCopy,
      copied,
      copyFailed,
      download: handleDownload,
      handle,
    }),
    [
      displayValue,
      filename,
      lang,
      mode,
      streaming,
      wrap,
      resolvedShowLineNumbers,
      expanded,
      setExpandedInternal,
      setWrapInternal,
      modalOpen,
      labels,
      handleCopy,
      copied,
      copyFailed,
      handleDownload,
      handle,
    ],
  );

  // Resolved aria-label
  const resolvedAriaLabel =
    ariaLabel ??
    (filename
      ? `Code block — ${filename}`
      : `Code block — ${lang === "plaintext" ? "text" : lang}`);

  const body = (() => {
    // v0.2.0 — editor mount can fail (grammar chunk fetch, EditorView throw).
    // The recovery renders the VIEW body instead, as a local override: `mode`
    // is the host's prop and a component must not mutate it, so this stays
    // component-local and resets naturally on remount.
    if (mode === "edit" && !editorFellBack) {
      return (
        <CodeBlockBodyEdit
          labels={labels}
          onFallbackToView={() => setEditorFellBack(true)}
          value={editValue}
          lang={lang}
          readOnly={readOnly}
          wrap={wrap}
          tabSize={tabSize}
          showLineNumbers={resolvedShowLineNumbers}
          onChange={(v) => setEditValueInternal(v)}
          onSave={onSave ? (v) => onSave({ value: v }) : undefined}
          editorExtensions={editorExtensions}
          maxHeight={maxHeight}
          registerImperative={(h) => {
            editorImperativeRef.current = h;
          }}
        />
      );
    }
    if (mode === "terminal") {
      return (
        <CodeBlockBodyTerminal
          value={valueProp ?? ""}
          lines={lines}
          wrap={wrap}
          streaming={streaming}
          emptyMessage={emptyMessage}
          maxHeight={maxHeight}
        />
      );
    }
    return (
      <CodeBlockBodyView
        // When we land here as the edit-mode fallback, the host's `value` prop
        // may be undefined (uncontrolled edit mode keeps it in `editValue`).
        value={mode === "edit" ? editValue : (valueProp ?? "")}
        lang={lang}
        themes={themesResolved}
        regexEngine={regexEngine}
        highlightedLines={highlightedLines}
        annotations={annotations}
        renderAnnotation={renderAnnotation}
        showLineNumbers={resolvedShowLineNumbers}
        wrap={wrap}
        streaming={streaming}
        expanded={expanded}
        maxLines={maxLines}
        emptyMessage={emptyMessage}
        maxHeight={maxHeight}
        onLineClick={onLineClick}
      />
    );
  })();

  // Header — either the default orchestrator or the renderHeader slot.
  const headerNode = (() => {
    if (header === false) return null;
    if (renderHeader) {
      const headerCtx = {
        filename,
        lang,
        copyButton: showCopy ? <CodeBlockCopyButton /> : null,
        expandButton: showExpand ? <CodeBlockExpandButton /> : null,
        wrapButton: showWrap ? <CodeBlockWrapButton /> : null,
        downloadButton: showDownload ? <CodeBlockDownloadButton /> : null,
        trafficLights: showTrafficLights ? <CodeBlockTrafficLights /> : null,
        actions: actions ?? null,
      };
      return <>{renderHeader(headerCtx)}</>;
    }
    return (
      <CodeBlockHeader
        showLanguage={showLanguage}
        showCopy={showCopy}
        showExpand={showExpand}
        showWrap={showWrap}
        showDownload={showDownload}
        showTrafficLights={showTrafficLights}
        actions={actions}
      />
    );
  })();

  // Modal body — same instance with expand off + maxLines undefined.
  // `ref={undefined}` (v0.1.2, review 5.10): `{...props}` would re-pass the
  // consumer's `ref` to this inner clone — the imperative handle would
  // re-target the modal instance and go stale when the modal closes. The
  // outer instance stays the sole handle owner.
  const expandedInner = (
    <CodeBlock
      {...props}
      ref={undefined}
      header={true}
      showExpand={false}
      maxLines={undefined}
      maxHeight={undefined}
      className="rounded-none border-0 shadow-none"
    />
  );

  const modalNode = (() => {
    if (!showExpand) return null;
    if (renderExpandModal) {
      return renderExpandModal({
        open: modalOpen,
        onOpenChange: setModalOpen,
        code: expandedInner,
      });
    }
    return (
      <CodeBlockExpandModal
        open={modalOpen}
        onOpenChange={setModalOpen}
        title={filename ?? lang}
      >
        {expandedInner}
      </CodeBlockExpandModal>
    );
  })();

  // No delayDuration — Radix-only prop; Base UI's TooltipProvider rejects it (F-cross-13).
  return (
    <CodeBlockProvider value={ctxValue}>
      <TooltipProvider>
        <section
          ref={rootRef}
          role="region"
          aria-label={resolvedAriaLabel}
          className={cn(
            "code-block group relative overflow-hidden rounded-lg border border-border/60 bg-card text-card-foreground shadow-sm",
            className,
          )}
          style={style}
        >
          {headerNode}
          {body}
          {footer ? <CodeBlockFooter>{footer}</CodeBlockFooter> : null}
        </section>
        {modalNode}
      </TooltipProvider>
    </CodeBlockProvider>
  );
}

CodeBlock.displayName = "CodeBlock";
```

### `src/registry/components/code/code-block/index.ts`

```ts
export { CodeBlock } from "./code-block";
export { CodeBlockHeader } from "./parts/code-block-header";
export { CodeBlockFilename } from "./parts/code-block-filename";
export { CodeBlockLangPill } from "./parts/code-block-lang-pill";
export { CodeBlockCopyButton } from "./parts/code-block-copy-button";
export { CodeBlockExpandButton } from "./parts/code-block-expand-button";
export { CodeBlockWrapButton } from "./parts/code-block-wrap-button";
export { CodeBlockDownloadButton } from "./parts/code-block-download-button";
export { CodeBlockTrafficLights } from "./parts/code-block-traffic-lights";
export { useCodeBlock } from "./hooks/use-code-block-context";
export { resolveLang, FILENAME_TO_LANG_MAP } from "./lib/lang-resolution";
export type {
  CodeBlockProps,
  CodeBlockServerProps,
  CodeBlockHandle,
  CodeBlockMode,
  CodeBlockRegexEngine,
  CodeBlockWrap,
  CodeBlockAnnotation,
  CodeBlockAnnotationType,
  CodeBlockLineRange,
  CodeBlockLabels,
  CodeBlockThemes,
  CodeBlockChangeArgs,
  CodeBlockCopyArgs,
  CodeBlockSaveArgs,
  CodeBlockDownloadArgs,
  CodeBlockLineClickArgs,
  CodeBlockExpandedChangeArgs,
  CodeBlockWrapChangeArgs,
  CodeBlockFilenameToLangArgs,
  CodeBlockHeaderContext,
  CodeBlockAnnotationRenderArgs,
  CodeBlockExpandModalContext,
  ShikiThemeObject,
  TerminalLine,
  TerminalLineKind,
} from "./types";
```

### `src/registry/components/code/code-block/types.ts`

```ts
import type { CSSProperties, ReactNode, Ref } from "react";
import type { Extension } from "@codemirror/state";

// ─── Core enums ──────────────────────────────────────────────────────────────

export type CodeBlockMode = "view" | "edit" | "terminal";

/**
 * Regex engine for the Shiki highlighter (v0.2.0).
 *
 * `"oniguruma"` is WebAssembly-backed and requires `'wasm-unsafe-eval'` in the
 * host's CSP `script-src`; `"javascript"` needs no wasm at all.
 */
export type CodeBlockRegexEngine = "oniguruma" | "javascript";
export type CodeBlockWrap = "wrap" | "scroll";
export type CodeBlockAnnotationType = "info" | "warn" | "error";
export type TerminalLineKind = "input" | "output" | "error";

// ─── Value-shaped types ──────────────────────────────────────────────────────

export interface CodeBlockLineRange {
  from: number;
  to: number;
}

export interface TerminalLine {
  kind: TerminalLineKind;
  text: string;
}

export interface CodeBlockAnnotation {
  line: number;
  type: CodeBlockAnnotationType;
  message: string;
}

export type ShikiThemeObject = {
  name: string;
  type: "light" | "dark";
  [key: string]: unknown;
};

export interface CodeBlockThemes {
  light: string | ShikiThemeObject;
  dark: string | ShikiThemeObject;
}

// ─── Imperative handle ───────────────────────────────────────────────────────

export interface CodeBlockHandle {
  copy: () => Promise<boolean>;
  focus: () => void;
  getValue: () => string;
  scrollToLine: (line: number) => void;
}

// ─── Callback arg shapes (object-shape per F-cross-12) ───────────────────────

export interface CodeBlockChangeArgs {
  value: string;
}
export interface CodeBlockCopyArgs {
  value: string;
}
export interface CodeBlockSaveArgs {
  value: string;
}
export interface CodeBlockDownloadArgs {
  value: string;
  filename: string;
}
export interface CodeBlockLineClickArgs {
  line: number;
}
export interface CodeBlockExpandedChangeArgs {
  expanded: boolean;
}
export interface CodeBlockWrapChangeArgs {
  wrap: CodeBlockWrap;
}
export interface CodeBlockFilenameToLangArgs {
  filename: string;
}

// ─── Slot contexts ───────────────────────────────────────────────────────────

export interface CodeBlockHeaderContext {
  filename: string | undefined;
  lang: string;
  /** `null` when `showCopy` is false (v0.1.2 — widened; was an unsound `null as never`). */
  copyButton: ReactNode | null;
  expandButton: ReactNode | null;
  wrapButton: ReactNode | null;
  downloadButton: ReactNode | null;
  trafficLights: ReactNode | null;
  actions: ReactNode | null;
}

export interface CodeBlockAnnotationRenderArgs {
  annotation: CodeBlockAnnotation;
  defaultMarker: ReactNode;
}

export interface CodeBlockExpandModalContext {
  open: boolean;
  onOpenChange: (next: boolean) => void;
  code: ReactNode;
}

// ─── i18n labels ─────────────────────────────────────────────────────────────

export type CodeBlockLabels = Partial<{
  copy: string;
  copied: string;
  copyFailed: string;
  expand: string;
  wrap: string;
  download: string;
  showMore: string;
  showLess: string;
  streamingCursor: string;
  closeModal: string;
  emptyDefault: string;
  /** Shown when CodeMirror fails to initialise in edit mode (v0.2.0). */
  editorFailed: string;
  /** Recovery control rendered beside `editorFailed` (v0.2.0). */
  reloadAsViewOnly: string;
}>;

export const DEFAULT_LABELS: Required<CodeBlockLabels> = {
  copy: "Copy code",
  copied: "Copied",
  copyFailed: "Copy failed — select and copy manually",
  expand: "Expand",
  wrap: "Toggle wrap",
  download: "Download",
  showMore: "Show all",
  showLess: "Show less",
  streamingCursor: "Streaming",
  closeModal: "Close",
  emptyDefault: "",
  editorFailed: "The editor could not be loaded.",
  reloadAsViewOnly: "Reload as view-only",
};

// ─── Top-level props (client variant) ────────────────────────────────────────

export interface CodeBlockProps {
  // Content
  value?: string;
  lines?: TerminalLine[];
  defaultValue?: string;

  // Language
  lang?: string;
  filename?: string;
  filenameToLang?: (args: CodeBlockFilenameToLangArgs) => string | undefined;

  // Mode
  mode?: CodeBlockMode;
  readOnly?: boolean;
  streaming?: boolean;

  // Edit
  onChange?: (args: CodeBlockChangeArgs) => void;
  onSave?: (args: CodeBlockSaveArgs) => void;
  /**
   * Editor tab size. INITIAL-ONLY (v0.1.2): applied when the CodeMirror
   * editor is created; later changes are not re-applied. Remount the block
   * (React `key`) to change it after mount.
   */
  tabSize?: number;
  /**
   * Extra CodeMirror extensions for edit mode. INITIAL-ONLY (v0.1.2): the
   * array captured at editor creation is baked into the EditorState; identity
   * or content changes after mount are ignored. Remount (React `key`) to swap.
   */
  editorExtensions?: Extension[];

  // Header
  header?: boolean;
  showLanguage?: boolean;
  showCopy?: boolean;
  showExpand?: boolean;
  showWrap?: boolean;
  showDownload?: boolean;
  showTrafficLights?: boolean;
  actions?: ReactNode;
  renderHeader?: (ctx: CodeBlockHeaderContext) => ReactNode;
  renderExpandModal?: (ctx: CodeBlockExpandModalContext) => ReactNode;

  // Footer
  footer?: ReactNode;

  // Body
  showLineNumbers?: boolean;
  wrap?: CodeBlockWrap;
  highlightedLines?: Array<number | CodeBlockLineRange>;
  annotations?: CodeBlockAnnotation[];
  renderAnnotation?: (args: CodeBlockAnnotationRenderArgs) => ReactNode;

  // Collapse
  maxLines?: number;
  expanded?: boolean;
  defaultExpanded?: boolean;
  onExpandedChange?: (args: CodeBlockExpandedChangeArgs) => void;

  // Wrap toggle
  onWrapChange?: (args: CodeBlockWrapChangeArgs) => void;

  // Line click
  onLineClick?: (args: CodeBlockLineClickArgs) => void;

  // Copy + download
  onCopy?: (args: CodeBlockCopyArgs) => void;
  onDownload?: (args: CodeBlockDownloadArgs) => void;

  // Theme
  themes?: CodeBlockThemes;

  /**
   * Regex engine backing the view-mode highlighter (v0.2.0).
   *
   * - `"oniguruma"` (default) — Shiki's WebAssembly engine. Widest grammar
   *   support, and what every 0.1.x consumer already gets.
   * - `"javascript"` — pure-JS engine, **no WebAssembly**. Use it when the host
   *   serves a strict `Content-Security-Policy` without `'wasm-unsafe-eval'`,
   *   where the wasm engine cannot compile at all. Slightly narrower grammar
   *   coverage is the trade.
   *
   * Highlighters are cached per engine, so mixing both on one page is safe.
   */
  regexEngine?: CodeBlockRegexEngine;

  // Sizing
  maxHeight?: number | string;

  // Empty
  emptyMessage?: string;

  // Polymorphic
  className?: string;
  style?: CSSProperties;

  // ARIA
  ariaLabel?: string;

  // i18n
  labels?: CodeBlockLabels;

  // Imperative handle
  ref?: Ref<CodeBlockHandle>;
}

// ─── RSC variant props (typed narrowing) ─────────────────────────────────────

/**
 * RSC-variant props — **the `/server` entry point does not exist.**
 *
 * @deprecated Since v0.2.1. This type shipped in v0.1.0 as a scaffold so
 * consumers could "prepare for the v0.2.0 RSC export without breaking
 * changes". v0.2.0 shipped without that export, and the type spent three
 * minor versions describing a component nobody could import: this package
 * ships no server entry module and the registry item lists no server file, so
 * `@ilinxa/code-block/server` resolves to nothing.
 *
 * It is kept (rather than deleted) because it is importable today and removing
 * it would break any consumer using it as an annotation — but it narrows props
 * for a component that is not shipped. Use {@link CodeBlockProps}; the client
 * component server-renders its first paint correctly under Next.js.
 *
 * If the zero-client-Shiki variant is ever built, it is a feature behind its
 * own planning gate, and this type becomes real again at that point.
 */
export type CodeBlockServerProps = Omit<
  CodeBlockProps,
  | "mode"
  | "readOnly"
  | "streaming"
  | "editorExtensions"
  | "onChange"
  | "onSave"
  | "tabSize"
  | "onWrapChange"
  | "showWrap"
  | "expanded"
  | "defaultExpanded"
  | "onExpandedChange"
  | "ref"
> & {
  mode?: "view" | "terminal";
};
```

### `src/registry/components/code/code-block/parts/code-block-header.tsx`

```tsx
"use client";
import type { ReactNode } from "react";
import { cn } from "@/lib/utils";
import { useCodeBlock } from "../hooks/use-code-block-context";
import { CodeBlockCopyButton } from "./code-block-copy-button";
import { CodeBlockDownloadButton } from "./code-block-download-button";
import { CodeBlockExpandButton } from "./code-block-expand-button";
import { CodeBlockFilename } from "./code-block-filename";
import { CodeBlockLangPill } from "./code-block-lang-pill";
import { CodeBlockTrafficLights } from "./code-block-traffic-lights";
import { CodeBlockWrapButton } from "./code-block-wrap-button";

export interface CodeBlockHeaderProps {
  showLanguage?: boolean;
  showCopy?: boolean;
  showExpand?: boolean;
  showWrap?: boolean;
  showDownload?: boolean;
  showTrafficLights?: boolean;
  actions?: ReactNode;
  className?: string;
}

export function CodeBlockHeader({
  showLanguage = true,
  showCopy = true,
  showExpand = false,
  showWrap = false,
  showDownload = false,
  showTrafficLights = false,
  actions,
  className,
}: CodeBlockHeaderProps) {
  const { filename, resolvedLang } = useCodeBlock();

  return (
    <div
      className={cn(
        "flex items-center gap-2 border-b border-border/60 bg-card/50 px-3 py-1.5",
        "min-h-9",
        className,
      )}
    >
      {showTrafficLights ? <CodeBlockTrafficLights className="mr-1" /> : null}
      <CodeBlockFilename filename={filename} />
      {filename && showLanguage && resolvedLang && resolvedLang !== "plaintext" ? (
        <span aria-hidden="true" className="text-muted-foreground/40">·</span>
      ) : null}
      {showLanguage ? <CodeBlockLangPill lang={resolvedLang} /> : null}
      <div className="flex-1" />
      {actions ? <div className="flex items-center gap-1">{actions}</div> : null}
      {showWrap ? <CodeBlockWrapButton /> : null}
      {showDownload ? <CodeBlockDownloadButton /> : null}
      {showExpand ? <CodeBlockExpandButton /> : null}
      {showCopy ? <CodeBlockCopyButton /> : null}
    </div>
  );
}
```

### `src/registry/components/code/code-block/parts/code-block-filename.tsx`

```tsx
"use client";
import { cn } from "@/lib/utils";

export interface CodeBlockFilenameProps {
  filename?: string;
  className?: string;
}

export function CodeBlockFilename({ filename, className }: CodeBlockFilenameProps) {
  if (!filename) return null;
  return (
    <span
      className={cn(
        "font-mono text-[0.78rem] text-foreground/85 tracking-tight",
        className,
      )}
    >
      {filename}
    </span>
  );
}
```

### `src/registry/components/code/code-block/parts/code-block-lang-pill.tsx`

```tsx
"use client";
import { cn } from "@/lib/utils";

export interface CodeBlockLangPillProps {
  lang: string;
  className?: string;
}

export function CodeBlockLangPill({ lang, className }: CodeBlockLangPillProps) {
  if (!lang || lang === "plaintext") return null;
  return (
    <span
      className={cn(
        "font-mono text-[0.7rem] uppercase tracking-wider text-muted-foreground/80",
        className,
      )}
    >
      {lang}
    </span>
  );
}
```

### `src/registry/components/code/code-block/parts/code-block-copy-button.tsx`

```tsx
"use client";
import { Check, Copy, X } from "lucide-react";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { useCodeBlock } from "../hooks/use-code-block-context";

export interface CodeBlockCopyButtonProps {
  className?: string;
}

export function CodeBlockCopyButton({ className }: CodeBlockCopyButtonProps) {
  const { copy, copied, copyFailed, labels } = useCodeBlock();

  const Icon = copyFailed ? X : copied ? Check : Copy;
  const label = copyFailed ? labels.copyFailed : copied ? labels.copied : labels.copy;

  return (
    <>
      <Button
        type="button"
        size="icon"
        variant="ghost"
        className={cn("size-7 text-muted-foreground hover:text-foreground", className)}
        onClick={() => void copy()}
        aria-label={label}
        title={label}
      >
        <Icon className="size-3.5" aria-hidden="true" />
      </Button>
      {/* Screen-reader-only status. role="status" with aria-live="polite" so
          the SR announces "Copied" after the icon swap. */}
      <span role="status" aria-live="polite" className="sr-only">
        {copied ? labels.copied : copyFailed ? labels.copyFailed : ""}
      </span>
    </>
  );
}
```

### `src/registry/components/code/code-block/parts/code-block-expand-button.tsx`

```tsx
"use client";
import { Maximize2 } from "lucide-react";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { useCodeBlock } from "../hooks/use-code-block-context";

export interface CodeBlockExpandButtonProps {
  className?: string;
}

export function CodeBlockExpandButton({ className }: CodeBlockExpandButtonProps) {
  const { setModalOpen, labels } = useCodeBlock();

  return (
    <Button
      type="button"
      size="icon"
      variant="ghost"
      className={cn("size-7 text-muted-foreground hover:text-foreground", className)}
      onClick={() => setModalOpen(true)}
      aria-label={labels.expand}
      title={labels.expand}
    >
      <Maximize2 className="size-3.5" aria-hidden="true" />
    </Button>
  );
}
```

### `src/registry/components/code/code-block/parts/code-block-wrap-button.tsx`

```tsx
"use client";
import { WrapText } from "lucide-react";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { useCodeBlock } from "../hooks/use-code-block-context";

export interface CodeBlockWrapButtonProps {
  className?: string;
}

export function CodeBlockWrapButton({ className }: CodeBlockWrapButtonProps) {
  const { wrap, setWrap, labels } = useCodeBlock();
  const isWrapped = wrap === "wrap";

  return (
    <Button
      type="button"
      size="icon"
      variant="ghost"
      className={cn(
        "size-7 text-muted-foreground hover:text-foreground",
        isWrapped && "text-foreground",
        className,
      )}
      onClick={() => setWrap(isWrapped ? "scroll" : "wrap")}
      aria-label={labels.wrap}
      aria-pressed={isWrapped}
      title={labels.wrap}
    >
      <WrapText className="size-3.5" aria-hidden="true" />
    </Button>
  );
}
```

### `src/registry/components/code/code-block/parts/code-block-download-button.tsx`

```tsx
"use client";
import { Download } from "lucide-react";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { useCodeBlock } from "../hooks/use-code-block-context";

export interface CodeBlockDownloadButtonProps {
  className?: string;
}

export function CodeBlockDownloadButton({ className }: CodeBlockDownloadButtonProps) {
  const { download, labels } = useCodeBlock();

  return (
    <Button
      type="button"
      size="icon"
      variant="ghost"
      className={cn("size-7 text-muted-foreground hover:text-foreground", className)}
      onClick={download}
      aria-label={labels.download}
      title={labels.download}
    >
      <Download className="size-3.5" aria-hidden="true" />
    </Button>
  );
}
```

### `src/registry/components/code/code-block/parts/code-block-traffic-lights.tsx`

```tsx
"use client";
import { cn } from "@/lib/utils";

export interface CodeBlockTrafficLightsProps {
  className?: string;
}

/**
 * macOS-style traffic-light decoration (three muted circles).
 * Purely presentational — no behaviour. Opt-in via the `showTrafficLights`
 * prop on `<CodeBlock>`, or compose manually inside `renderHeader`.
 */
export function CodeBlockTrafficLights({ className }: CodeBlockTrafficLightsProps) {
  return (
    <div
      aria-hidden="true"
      className={cn("flex items-center gap-1.5", className)}
    >
      <span className="block size-3 rounded-full bg-[oklch(0.78_0.16_25)] opacity-70" />
      <span className="block size-3 rounded-full bg-[oklch(0.84_0.15_85)] opacity-70" />
      <span className="block size-3 rounded-full bg-[oklch(0.78_0.16_145)] opacity-70" />
    </div>
  );
}
```

### `src/registry/components/code/code-block/parts/code-block-footer.tsx`

```tsx
"use client";
import type { ReactNode } from "react";
import { cn } from "@/lib/utils";

interface CodeBlockFooterProps {
  children: ReactNode;
  className?: string;
}

export function CodeBlockFooter({ children, className }: CodeBlockFooterProps) {
  return (
    <div
      className={cn(
        "flex items-center gap-2 border-t border-border/60 bg-card/40 px-3 py-1.5 text-xs text-muted-foreground",
        className,
      )}
    >
      {children}
    </div>
  );
}
```

### `src/registry/components/code/code-block/parts/code-block-empty.tsx`

```tsx
"use client";
import { cn } from "@/lib/utils";

interface CodeBlockEmptyProps {
  message?: string;
  className?: string;
}

export function CodeBlockEmpty({ message, className }: CodeBlockEmptyProps) {
  return (
    <div
      className={cn(
        "flex min-h-12 items-center justify-center px-4 py-6 text-center font-mono text-xs text-muted-foreground/60",
        className,
      )}
    >
      {message ?? <span aria-hidden="true">&nbsp;</span>}
    </div>
  );
}
```

### `src/registry/components/code/code-block/parts/code-block-streaming-cursor.tsx`

```tsx
"use client";
import { cn } from "@/lib/utils";

interface CodeBlockStreamingCursorProps {
  className?: string;
}

export function CodeBlockStreamingCursor({ className }: CodeBlockStreamingCursorProps) {
  return (
    <span
      aria-hidden="true"
      className={cn(
        "inline-block h-[1em] w-[0.5ch] -mb-[2px] translate-y-[0.15em] bg-foreground/80",
        "animate-[cb-blink_1s_steps(2)_infinite]",
        className,
      )}
    />
  );
}
```

### `src/registry/components/code/code-block/parts/code-block-collapse-fade.tsx`

```tsx
"use client";
import { ChevronDown, ChevronUp } from "lucide-react";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { useCodeBlock } from "../hooks/use-code-block-context";

interface CodeBlockCollapseFadeProps {
  hiddenLineCount: number;
  className?: string;
}

export function CodeBlockCollapseFade({
  hiddenLineCount,
  className,
}: CodeBlockCollapseFadeProps) {
  const { expanded, setExpanded, labels } = useCodeBlock();

  if (expanded) {
    return (
      <div className={cn("flex justify-center border-t border-border/60 py-2", className)}>
        <Button
          type="button"
          variant="ghost"
          size="sm"
          className="h-7 gap-1 text-xs text-muted-foreground hover:text-foreground"
          onClick={() => setExpanded(false)}
        >
          <ChevronUp className="size-3.5" aria-hidden="true" />
          {labels.showLess}
        </Button>
      </div>
    );
  }

  return (
    <div
      className={cn(
        "pointer-events-none absolute inset-x-0 bottom-0 flex h-20 items-end justify-center",
        "bg-gradient-to-t from-card via-card/85 to-transparent",
        className,
      )}
    >
      <Button
        type="button"
        variant="ghost"
        size="sm"
        className="pointer-events-auto mb-2 h-7 gap-1 text-xs text-muted-foreground hover:text-foreground"
        onClick={() => setExpanded(true)}
      >
        <ChevronDown className="size-3.5" aria-hidden="true" />
        {`${labels.showMore} (${hiddenLineCount} more lines)`}
      </Button>
    </div>
  );
}
```

### `src/registry/components/code/code-block/parts/code-block-annotation-marker.tsx`

```tsx
"use client";
import { CircleAlert, CircleX, Info } from "lucide-react";
import {
  Tooltip,
  TooltipContent,
  TooltipTrigger,
} from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
import type {
  CodeBlockAnnotation,
  CodeBlockAnnotationRenderArgs,
} from "../types";

interface CodeBlockAnnotationMarkerProps {
  annotation: CodeBlockAnnotation;
  renderAnnotation?: (args: CodeBlockAnnotationRenderArgs) => React.ReactNode;
}

const ICON_BY_TYPE = {
  info: Info,
  warn: CircleAlert,
  error: CircleX,
} as const;

const COLOR_BY_TYPE = {
  info: "text-blue-500 dark:text-blue-400",
  warn: "text-amber-500 dark:text-amber-400",
  error: "text-destructive",
} as const;

export function CodeBlockAnnotationMarker({
  annotation,
  renderAnnotation,
}: CodeBlockAnnotationMarkerProps) {
  const Icon = ICON_BY_TYPE[annotation.type];
  const colorClass = COLOR_BY_TYPE[annotation.type];

  // F-cross-13: no `asChild` — the trigger IS the marker button.
  const defaultMarker = (
    <Tooltip>
      <TooltipTrigger
        type="button"
        className={cn(
          "inline-flex size-3.5 items-center justify-center align-middle outline-none",
          "focus-visible:ring-2 focus-visible:ring-ring rounded",
          colorClass,
        )}
        aria-label={`${annotation.type}: ${annotation.message}`}
      >
        <Icon className="size-3.5" aria-hidden="true" />
      </TooltipTrigger>
      <TooltipContent side="right" className="max-w-xs text-xs">
        {annotation.message}
      </TooltipContent>
    </Tooltip>
  );

  if (renderAnnotation) {
    return <>{renderAnnotation({ annotation, defaultMarker })}</>;
  }
  return defaultMarker;
}
```

### `src/registry/components/code/code-block/parts/code-block-line-numbers.tsx`

```tsx
"use client";
import { cn } from "@/lib/utils";
import { gutterWidth } from "../lib/line-utils";

interface CodeBlockLineNumbersProps {
  totalLines: number;
  highlighted?: Set<number>;
  onLineClick?: (line: number) => void;
  className?: string;
}

export function CodeBlockLineNumbers({
  totalLines,
  highlighted,
  onLineClick,
  className,
}: CodeBlockLineNumbersProps) {
  const width = gutterWidth(totalLines);
  const lines = Array.from({ length: totalLines }, (_, i) => i + 1);

  return (
    <div
      aria-hidden="true"
      className={cn(
        "select-none pr-3 pl-3 text-right font-mono text-[0.75rem] text-muted-foreground/60",
        className,
      )}
      style={{ minWidth: `${width + 2}ch` }}
    >
      {lines.map((n) => (
        <div
          key={n}
          className={cn(
            "leading-relaxed",
            highlighted?.has(n) && "text-foreground/80",
            onLineClick && "cursor-pointer hover:text-foreground",
          )}
          onClick={onLineClick ? () => onLineClick(n) : undefined}
        >
          {n}
        </div>
      ))}
    </div>
  );
}
```

### `src/registry/components/code/code-block/parts/code-block-body-view.tsx`

```tsx
"use client";
import { useMemo } from "react";
import { cn } from "@/lib/utils";
import { useShikiHighlighter } from "../hooks/use-shiki-highlighter";
import { lineCount, rangeToLines, splitToLines } from "../lib/line-utils";
import type {
  CodeBlockAnnotation,
  CodeBlockAnnotationRenderArgs,
  CodeBlockLineRange,
  CodeBlockLineClickArgs,
  CodeBlockRegexEngine,
  CodeBlockThemes,
} from "../types";
import { CodeBlockAnnotationMarker } from "./code-block-annotation-marker";
import { CodeBlockCollapseFade } from "./code-block-collapse-fade";
import { CodeBlockEmpty } from "./code-block-empty";
import { CodeBlockLineNumbers } from "./code-block-line-numbers";
import { CodeBlockStreamingCursor } from "./code-block-streaming-cursor";

interface CodeBlockBodyViewProps {
  value: string;
  lang: string;
  themes: CodeBlockThemes | undefined;
  highlightedLines?: Array<number | CodeBlockLineRange>;
  annotations?: CodeBlockAnnotation[];
  renderAnnotation?: (args: CodeBlockAnnotationRenderArgs) => React.ReactNode;
  showLineNumbers: boolean;
  wrap: "wrap" | "scroll";
  streaming: boolean;
  expanded: boolean;
  maxLines: number | undefined;
  emptyMessage?: string;
  maxHeight?: number | string;
  onLineClick?: (args: CodeBlockLineClickArgs) => void;
  /** v0.2.0 — see `CodeBlockProps.regexEngine`. */
  regexEngine?: CodeBlockRegexEngine;
}

export function CodeBlockBodyView({
  value,
  lang,
  themes,
  highlightedLines,
  annotations,
  renderAnnotation,
  showLineNumbers,
  wrap,
  streaming,
  expanded,
  maxLines,
  emptyMessage,
  maxHeight,
  onLineClick,
  regexEngine,
}: CodeBlockBodyViewProps) {
  const { html, failed } = useShikiHighlighter({
    value,
    lang,
    themes,
    highlightedLines,
    streaming,
    regexEngine,
  });

  const total = lineCount(value);
  const showCollapse = maxLines !== undefined && total > maxLines && !expanded;
  const hiddenCount = showCollapse ? total - maxLines : 0;

  const visibleHtml = useMemo(() => {
    if (!showCollapse) return html;
    // Naive but correct: cap the rendered <pre> visual via max-height.
    // Slicing tokenized HTML by line is unreliable across grammars; we keep
    // the full HTML and clip visually.
    return html;
  }, [html, showCollapse]);

  const highlightedSet = useMemo(() => rangeToLines(highlightedLines), [highlightedLines]);
  const annotationByLine = useMemo(() => {
    const m = new Map<number, CodeBlockAnnotation[]>();
    for (const a of annotations ?? []) {
      const list = m.get(a.line) ?? [];
      list.push(a);
      m.set(a.line, list);
    }
    return m;
  }, [annotations]);

  if (value === "" && !streaming) {
    return <CodeBlockEmpty message={emptyMessage} />;
  }

  const clipHeight = showCollapse
    ? `calc(${maxLines} * 1.6em + 2rem)`
    : maxHeight !== undefined
      ? typeof maxHeight === "number"
        ? `${maxHeight}px`
        : maxHeight
      : undefined;

  const lines = splitToLines(value);

  return (
    <div
      className={cn(
        "relative flex w-full overflow-hidden",
        wrap === "wrap" ? "whitespace-pre-wrap" : "",
      )}
      style={{ maxHeight: clipHeight }}
    >
      {showLineNumbers || (annotations && annotations.length > 0) ? (
        <div className="relative flex items-stretch">
          {showLineNumbers ? (
            <CodeBlockLineNumbers
              totalLines={Math.max(total, 1)}
              highlighted={highlightedSet}
              onLineClick={onLineClick ? (line) => onLineClick({ line }) : undefined}
            />
          ) : null}
          {annotations && annotations.length > 0 ? (
            <div className="relative w-5 select-none">
              {Array.from(annotationByLine.entries()).map(([line, items]) => (
                <div
                  key={line}
                  className="absolute left-0 flex items-center"
                  style={{ top: `calc(${line - 1} * 1.6em)`, height: "1.6em" }}
                >
                  {items.map((a, i) => (
                    <CodeBlockAnnotationMarker
                      key={i}
                      annotation={a}
                      renderAnnotation={renderAnnotation}
                    />
                  ))}
                </div>
              ))}
            </div>
          ) : null}
        </div>
      ) : null}

      <div
        // Observable highlight state (v0.2.0). "failed" means the engine gave
        // up and the body below is raw text, not tokens — a host that saw an
        // empty panel and an empty console now has something to assert on, and
        // hosts can style/telemeter the degraded state without new props.
        data-highlight={failed ? "failed" : html ? "ready" : "pending"}
        // Scroll seam for `CodeBlockHandle.scrollToLine` (v0.2.1). This div is
        // the element that actually scrolls, and Shiki's own output marks each
        // row `.line` — as does the v0.2.0 plaintext fallback — so one query
        // resolves rows on BOTH the highlighted and the degraded path.
        data-cb-scroller=""
        className={cn(
          "code-block-shiki-body relative min-w-0 flex-1 overflow-auto px-4 py-3 font-mono text-[0.8rem] leading-relaxed",
          wrap === "wrap" ? "whitespace-pre-wrap break-words" : "whitespace-pre",
        )}
      >
        {visibleHtml ? (
          <div
            // dangerouslySetInnerHTML: Shiki output is server-controlled and
            // sanitized by the library (no user-supplied HTML can leak through
            // the code path).
            dangerouslySetInnerHTML={{ __html: visibleHtml }}
          />
        ) : (
          /*
           * v0.2.0 — soft-failure for the highlighter itself.
           *
           * `html` is "" both before the first tokenize AND forever after the
           * engine gives up (strict CSP with no `wasm-unsafe-eval`, failed
           * grammar chunk). Rendering nothing in that state produced an empty
           * panel with no message and nothing in the console — the one
           * documented failure mode that had no fallback.
           *
           * Showing the raw code covers both cases: it is the pre-highlight
           * paint AND the permanent degradation. Deliberately NOT gated on
           * `failed`, because "still loading" should show the code too.
           *
           * Structure mirrors Shiki's own output (block-level lines at the
           * same 1.6em rhythm) so the line-number gutter stays aligned.
           */
          <pre className="m-0 bg-transparent p-0">
            <code className="block">
              {lines.map((line, i) => (
                <span
                  key={i}
                  // `line` + `data-highlighted` are what globals.css keys the
                  // row tint and gutter accent-bar off. Without them a degraded
                  // block still bolds the gutter NUMBER (that comes from
                  // CodeBlockLineNumbers) while the row itself stays plain —
                  // a half-applied highlight.
                  className="line block min-h-[1.6em] -ml-1 pl-1"
                  data-highlighted={highlightedSet.has(i + 1) ? "true" : undefined}
                >
                  {line === "" ? " " : line}
                </span>
              ))}
            </code>
          </pre>
        )}
        {streaming ? <CodeBlockStreamingCursor /> : null}
      </div>

      {showCollapse ? <CodeBlockCollapseFade hiddenLineCount={hiddenCount} /> : null}
    </div>
  );
}
```

### `src/registry/components/code/code-block/parts/code-block-body-edit.tsx`

```tsx
"use client";
import { useEffect } from "react";
import type { Extension } from "@codemirror/state";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { useCodeMirror } from "../hooks/use-code-mirror";
import { CODEMIRROR_THEME_CSS } from "../lib/codemirror-theme";
import type { CodeBlockLabels } from "../types";

interface CodeBlockBodyEditProps {
  value: string;
  lang: string;
  readOnly: boolean;
  wrap: "wrap" | "scroll";
  tabSize: number;
  showLineNumbers: boolean;
  onChange?: (value: string) => void;
  onSave?: (value: string) => void;
  editorExtensions?: Extension[];
  maxHeight?: number | string;
  registerImperative?: (handle: {
    focus: () => void;
    getValue: () => string;
    scrollToLine: (line: number) => boolean;
  }) => void;
  /** Resolved labels (defaults merged) — needed for the init-failure branch. */
  labels: Required<CodeBlockLabels>;
  /**
   * Invoked when the user takes the "Reload as view-only" recovery after a
   * failed editor mount. Omit it and the error is still shown, just without
   * the recovery control.
   */
  onFallbackToView?: () => void;
}

export function CodeBlockBodyEdit({
  value,
  lang,
  readOnly,
  wrap,
  tabSize,
  showLineNumbers,
  onChange,
  onSave,
  editorExtensions,
  maxHeight,
  registerImperative,
  labels,
  onFallbackToView,
}: CodeBlockBodyEditProps) {
  const { containerRef, focus, getValue, scrollToLine, error } = useCodeMirror({
    value,
    lang,
    readOnly,
    wrap,
    tabSize,
    showLineNumbers,
    onChange,
    onSave,
    editorExtensions,
  });

  useEffect(() => {
    registerImperative?.({ focus, getValue, scrollToLine });
  }, [registerImperative, focus, getValue, scrollToLine]);

  const heightStyle =
    maxHeight !== undefined
      ? typeof maxHeight === "number"
        ? `${maxHeight}px`
        : maxHeight
      : undefined;

  /*
   * v0.2.0 — the soft-failure policy always documented an inline error plus a
   * "Reload as view-only" recovery here; nothing implemented it, and a failed
   * editor mount was an unhandled rejection over a blank box. `onFallbackToView`
   * is what actually swaps the body, because `mode` is a plain prop the host
   * owns — the component must not try to mutate it.
   */
  if (error) {
    return (
      <div
        role="alert"
        data-editor="failed"
        className="flex flex-wrap items-center gap-x-3 gap-y-2 px-4 py-3 text-sm text-muted-foreground"
      >
        <span>{labels.editorFailed}</span>
        {onFallbackToView ? (
          <Button type="button" size="sm" variant="outline" onClick={onFallbackToView}>
            {labels.reloadAsViewOnly}
          </Button>
        ) : null}
      </div>
    );
  }

  return (
    <>
      <style dangerouslySetInnerHTML={{ __html: CODEMIRROR_THEME_CSS }} />
      <div
        ref={containerRef}
        className={cn("code-block-editor relative min-h-32 overflow-hidden")}
        style={{ maxHeight: heightStyle }}
      />
    </>
  );
}
```

### `src/registry/components/code/code-block/parts/code-block-body-terminal.tsx`

```tsx
"use client";
import { useMemo } from "react";
import { cn } from "@/lib/utils";
import { parseTerminalLines, promptPrefix } from "../lib/terminal-utils";
import type { TerminalLine } from "../types";
import { CodeBlockEmpty } from "./code-block-empty";
import { CodeBlockStreamingCursor } from "./code-block-streaming-cursor";

interface CodeBlockBodyTerminalProps {
  value: string;
  lines?: TerminalLine[];
  wrap: "wrap" | "scroll";
  streaming: boolean;
  emptyMessage?: string;
  maxHeight?: number | string;
}

export function CodeBlockBodyTerminal({
  value,
  lines,
  wrap,
  streaming,
  emptyMessage,
  maxHeight,
}: CodeBlockBodyTerminalProps) {
  const resolvedLines = useMemo<TerminalLine[]>(() => {
    if (lines) return lines;
    return parseTerminalLines(value);
  }, [lines, value]);

  if (resolvedLines.length === 0 && !streaming) {
    return <CodeBlockEmpty message={emptyMessage} />;
  }

  const lastInputIdx = (() => {
    for (let i = resolvedLines.length - 1; i >= 0; i--) {
      if (resolvedLines[i].kind === "input") return i;
    }
    return resolvedLines.length - 1;
  })();

  const heightStyle =
    maxHeight !== undefined
      ? typeof maxHeight === "number"
        ? `${maxHeight}px`
        : maxHeight
      : undefined;

  return (
    <div
      role="log"
      aria-live="off"
      // Scroll seam for `CodeBlockHandle.scrollToLine` (v0.2.1). Terminal rows
      // carry `data-cb-line` rather than Shiki's `.line`, because `.line` has
      // highlight CSS bound to it in globals.css that a transcript row must not
      // inherit.
      data-cb-scroller=""
      className={cn(
        "min-w-0 overflow-auto px-4 py-3 font-mono text-[0.8rem] leading-relaxed",
        wrap === "wrap" ? "whitespace-pre-wrap break-words" : "whitespace-pre",
      )}
      style={{ maxHeight: heightStyle }}
    >
      {resolvedLines.map((line, idx) => {
        if (line.kind === "input") {
          const { prefix, rest } = promptPrefix(line.text);
          return (
            <div key={idx} data-cb-line={idx + 1} className="text-foreground">
              <span className="text-muted-foreground/70">{prefix}</span>
              <span>{rest}</span>
              {streaming && idx === lastInputIdx ? (
                <CodeBlockStreamingCursor className="ml-0.5" />
              ) : null}
            </div>
          );
        }
        if (line.kind === "error") {
          return (
            <div key={idx} data-cb-line={idx + 1} className="text-destructive">
              {line.text || " "}
            </div>
          );
        }
        return (
          <div key={idx} data-cb-line={idx + 1} className="text-muted-foreground">
            {line.text || " "}
          </div>
        );
      })}
    </div>
  );
}
```

### `src/registry/components/code/code-block/parts/code-block-expand-modal.tsx`

```tsx
"use client";
import type { ReactNode } from "react";
import {
  Dialog,
  DialogContent,
  DialogHeader,
  DialogTitle,
} from "@/components/ui/dialog";

interface CodeBlockExpandModalProps {
  open: boolean;
  onOpenChange: (next: boolean) => void;
  title?: string;
  children: ReactNode;
}

export function CodeBlockExpandModal({
  open,
  onOpenChange,
  title,
  children,
}: CodeBlockExpandModalProps) {
  return (
    <Dialog open={open} onOpenChange={onOpenChange}>
      <DialogContent
        className="max-w-[min(95vw,1200px)] gap-0 p-0"
        aria-describedby={undefined}
      >
        <DialogHeader className="border-b border-border/60 px-4 py-3">
          <DialogTitle className="text-sm font-medium">
            {title ?? "Code"}
          </DialogTitle>
        </DialogHeader>
        <div className="max-h-[80vh] overflow-auto">{children}</div>
      </DialogContent>
    </Dialog>
  );
}
```

### `src/registry/components/code/code-block/hooks/use-code-block-context.tsx`

```tsx
"use client";
import { createContext, useContext, type ReactNode } from "react";
import type {
  CodeBlockHandle,
  CodeBlockLabels,
  CodeBlockWrap,
} from "../types";

interface CodeBlockContextValue {
  value: string;
  filename: string | undefined;
  lang: string;
  resolvedLang: string;
  mode: "view" | "edit" | "terminal";
  streaming: boolean;
  wrap: CodeBlockWrap;
  showLineNumbers: boolean;
  expanded: boolean;
  setExpanded: (next: boolean) => void;
  setWrap: (next: CodeBlockWrap) => void;
  modalOpen: boolean;
  setModalOpen: (next: boolean) => void;
  labels: Required<CodeBlockLabels>;
  copy: () => Promise<boolean>;
  copied: boolean;
  copyFailed: boolean;
  download: () => void;
  handle: CodeBlockHandle;
}

const CodeBlockContext = createContext<CodeBlockContextValue | null>(null);

export function CodeBlockProvider({
  value,
  children,
}: {
  value: CodeBlockContextValue;
  children: ReactNode;
}) {
  return (
    <CodeBlockContext.Provider value={value}>{children}</CodeBlockContext.Provider>
  );
}

export function useCodeBlock(): CodeBlockContextValue {
  const ctx = useContext(CodeBlockContext);
  if (!ctx) {
    throw new Error(
      "[CodeBlock] useCodeBlock must be called inside <CodeBlock>. " +
        "Header parts (<CodeBlockHeader>, <CodeBlockCopyButton>, etc.) must " +
        "be composed inside a <CodeBlock> or its renderHeader slot.",
    );
  }
  return ctx;
}
```

### `src/registry/components/code/code-block/hooks/use-controllable-state.ts`

```ts
"use client";
import { useCallback, useEffect, useRef, useState } from "react";

interface UseControllableStateArgs<T> {
  prop: T | undefined;
  defaultProp: T;
  onChange?: (value: T) => void;
}

/**
 * Standard controlled/uncontrolled state helper. When `prop` is defined it
 * is treated as the source of truth (controlled); otherwise component state
 * is used (uncontrolled). `onChange` always fires.
 */
export function useControllableState<T>({
  prop,
  defaultProp,
  onChange,
}: UseControllableStateArgs<T>): [T, (next: T) => void] {
  const [uncontrolledValue, setUncontrolledValue] = useState<T>(defaultProp);
  const isControlled = prop !== undefined;
  const value = isControlled ? prop : uncontrolledValue;

  const onChangeRef = useRef(onChange);
  useEffect(() => {
    onChangeRef.current = onChange;
  }, [onChange]);

  const setValue = useCallback(
    (next: T) => {
      if (!isControlled) {
        setUncontrolledValue(next);
      }
      onChangeRef.current?.(next);
    },
    [isControlled],
  );

  return [value, setValue];
}
```

### `src/registry/components/code/code-block/hooks/use-copy-to-clipboard.ts`

```ts
"use client";
import { useCallback, useRef, useState } from "react";

interface UseCopyToClipboardResult {
  copy: (text: string) => Promise<boolean>;
  copied: boolean;
  failed: boolean;
}

/**
 * Clipboard write with legacy `document.execCommand('copy')` fallback for
 * browsers without the async Clipboard API. Returns transient `copied` /
 * `failed` flags that auto-clear after `revertMs` for icon-swap UX.
 */
export function useCopyToClipboard(revertMs = 1500): UseCopyToClipboardResult {
  const [copied, setCopied] = useState(false);
  const [failed, setFailed] = useState(false);
  const timeoutRef = useRef<number | null>(null);

  const reset = useCallback(() => {
    if (timeoutRef.current !== null) {
      window.clearTimeout(timeoutRef.current);
      timeoutRef.current = null;
    }
    timeoutRef.current = window.setTimeout(() => {
      setCopied(false);
      setFailed(false);
      timeoutRef.current = null;
    }, revertMs);
  }, [revertMs]);

  const copy = useCallback(
    async (text: string): Promise<boolean> => {
      let ok = false;
      try {
        if (
          typeof navigator !== "undefined" &&
          navigator.clipboard &&
          typeof navigator.clipboard.writeText === "function"
        ) {
          await navigator.clipboard.writeText(text);
          ok = true;
        }
      } catch {
        ok = false;
      }
      if (!ok && typeof document !== "undefined") {
        try {
          const ta = document.createElement("textarea");
          ta.value = text;
          ta.style.position = "fixed";
          ta.style.left = "-9999px";
          ta.setAttribute("readonly", "");
          document.body.appendChild(ta);
          ta.select();
          ok = document.execCommand("copy");
          document.body.removeChild(ta);
        } catch {
          ok = false;
        }
      }
      if (ok) {
        setCopied(true);
        setFailed(false);
      } else {
        setCopied(false);
        setFailed(true);
      }
      reset();
      return ok;
    },
    [reset],
  );

  return { copy, copied, failed };
}
```

### `src/registry/components/code/code-block/hooks/use-shiki-highlighter.ts`

```ts
"use client";
import { useEffect, useRef, useState } from "react";
import type { HighlighterCore, ThemeRegistrationAny } from "shiki/core";
import {
  DEFAULT_THEME_NAMES,
  ensureLangLoaded,
  ensureThemeLoaded,
  getHighlighter,
  normalizeLang,
} from "../lib/shiki-bundle";
import { rangeToLines } from "../lib/line-utils";
import {
  diffForRetokenize,
  emptyCache,
  type StreamingCache,
} from "../lib/streaming-cache";
import type {
  CodeBlockLineRange,
  CodeBlockRegexEngine,
  CodeBlockThemes,
} from "../types";

interface UseShikiHighlighterArgs {
  value: string;
  lang: string;
  themes: CodeBlockThemes | undefined;
  highlightedLines?: Array<number | CodeBlockLineRange>;
  streaming?: boolean;
  /** v0.2.0 — see `CodeBlockProps.regexEngine`. Undefined = oniguruma. */
  regexEngine?: CodeBlockRegexEngine;
}

interface UseShikiHighlighterResult {
  html: string;
  ready: boolean;
  /**
   * The highlighter gave up for this input — engine unavailable (e.g. a strict
   * CSP with no `wasm-unsafe-eval`), grammar chunk failed to load, or the
   * default-theme retry itself threw.
   *
   * v0.2.0: exists so the view body can degrade to plain text instead of
   * rendering an empty panel. Distinct from `!ready`, which is also true while
   * highlighting is merely still in flight.
   */
  failed: boolean;
  resolvedLang: string;
}

// v0.1.2 (review 5.9) — resolve AND register a theme entry. String entries
// keep the ensureThemeLoaded dynamic-import path; OBJECT entries (the declared
// `ShikiThemeObject` support) are registered directly via
// `highlighter.loadTheme(entry)` — previously the object was dropped, a
// `shiki/themes/<name>.mjs` import was attempted, and the subsequent
// `codeToHtml` threw as an unhandled rejection, leaving the block blank.
async function ensureThemeEntry(
  highlighter: HighlighterCore,
  themes: CodeBlockThemes | undefined,
  variant: "light" | "dark",
): Promise<string> {
  const entry = themes?.[variant];
  if (!entry) return DEFAULT_THEME_NAMES[variant];
  if (typeof entry === "string") {
    await ensureThemeLoaded(highlighter, entry);
    return entry;
  }
  if (!highlighter.getLoadedThemes().includes(entry.name)) {
    // ShikiThemeObject is a structural subset of shiki's own registration
    // shape; the cast hands the full object through.
    await highlighter.loadTheme(entry as unknown as ThemeRegistrationAny);
  }
  return entry.name;
}

function highlight(
  highlighter: HighlighterCore,
  value: string,
  lang: string,
  lightTheme: string,
  darkTheme: string,
  highlightedSet: Set<number>,
): string {
  const html = highlighter.codeToHtml(value, {
    lang,
    themes: { light: lightTheme, dark: darkTheme },
    defaultColor: false,
    cssVariablePrefix: "--shiki-",
    transformers: [
      {
        name: "code-block-highlighted-lines",
        line(node, lineNumber) {
          if (highlightedSet.has(lineNumber)) {
            node.properties = node.properties ?? {};
            node.properties["data-highlighted"] = "true";
          }
        },
      },
    ],
  });
  return html;
}

export function useShikiHighlighter({
  value,
  lang,
  themes,
  highlightedLines,
  streaming,
  regexEngine,
}: UseShikiHighlighterArgs): UseShikiHighlighterResult {
  const [html, setHtml] = useState<string>("");
  const [ready, setReady] = useState(false);
  const [failed, setFailed] = useState(false);
  const [resolvedLang, setResolvedLang] = useState<string>(normalizeLang(lang));
  const cacheRef = useRef<StreamingCache>(emptyCache());
  const rafRef = useRef<number | null>(null);

  useEffect(() => {
    let cancelled = false;
    const highlightedSet = rangeToLines(highlightedLines);

    const run = async () => {
      const highlighter = await getHighlighter(regexEngine);
      if (cancelled) return;
      const langForRender = await ensureLangLoaded(highlighter, lang);
      if (cancelled) return;

      // v0.1.2 (review 5.9) — theme resolution + tokenization are fallible
      // (unknown theme name, malformed theme object). Fall back to the
      // always-loaded defaults instead of throwing an unhandled rejection
      // and leaving the block permanently blank.
      let lightTheme: string;
      let darkTheme: string;
      try {
        [lightTheme, darkTheme] = await Promise.all([
          ensureThemeEntry(highlighter, themes, "light"),
          ensureThemeEntry(highlighter, themes, "dark"),
        ]);
      } catch (err) {
        if (process.env.NODE_ENV !== "production") {
          console.warn(
            "[CodeBlock] Failed to load custom theme(s) — falling back to defaults.",
            err,
          );
        }
        lightTheme = DEFAULT_THEME_NAMES.light;
        darkTheme = DEFAULT_THEME_NAMES.dark;
      }
      if (cancelled) return;
      setResolvedLang(langForRender);

      // v0.1.0 streaming strategy: full re-tokenize per update, but batched
      // to a single rAF (suppresses thrash from rapid char-level updates).
      // Shiki caches grammars + themes, so warm-state tokenization is cheap
      // (<5 ms for typical view-mode blocks). Pure append-only diff path
      // is locked at lib/streaming-cache.ts and wired in for v0.2.
      let full: string;
      try {
        full = highlight(
          highlighter,
          value,
          langForRender,
          lightTheme,
          darkTheme,
          highlightedSet,
        );
      } catch (err) {
        // Theme registered but unusable at tokenize time — retry once on the
        // default pair (loaded with the highlighter core, cannot miss).
        if (process.env.NODE_ENV !== "production") {
          console.warn(
            "[CodeBlock] Highlighting failed with the configured themes — retrying with defaults.",
            err,
          );
        }
        full = highlight(
          highlighter,
          value,
          langForRender,
          DEFAULT_THEME_NAMES.light,
          DEFAULT_THEME_NAMES.dark,
          highlightedSet,
        );
      }
      if (streaming) {
        // Surface the cache helper so it isn't tree-shaken; v0.2 will diff
        // against it for the pure append optimisation.
        diffForRetokenize(cacheRef.current, value);
      }
      cacheRef.current = { prevValue: value, prevHtmlLines: [full] };
      if (!cancelled) setHtml(full);
      if (!cancelled) setReady(true);
    };

    if (rafRef.current !== null) {
      cancelAnimationFrame(rafRef.current);
    }
    rafRef.current = requestAnimationFrame(() => {
      // Fresh inputs get a fresh verdict — a block that failed once should
      // retry rather than stay degraded forever, and a stale `failed` would
      // mislabel a subsequently successful render. Reset here rather than in
      // the effect body: a synchronous setState there is a cascading-render
      // hazard the React Compiler lint (correctly) rejects.
      if (!cancelled) setFailed(false);

      // Final safety net (5.9): any residual rejection (network-failed wasm /
      // grammar import, malformed default retry) must never surface as an
      // unhandled rejection.
      run().catch((err) => {
        if (process.env.NODE_ENV !== "production") {
          console.warn("[CodeBlock] Highlighter initialization failed.", err);
        }
        // v0.2.0 — the warning above is dev-only, so in production this was
        // previously a completely silent death: `html` stayed "" and the body
        // rendered an empty panel. Flag it so the view can show the raw code.
        if (!cancelled) setFailed(true);
      });
    });

    return () => {
      cancelled = true;
      if (rafRef.current !== null) {
        cancelAnimationFrame(rafRef.current);
        rafRef.current = null;
      }
    };
  }, [value, lang, themes, highlightedLines, streaming, regexEngine]);

  return { html, ready, failed, resolvedLang };
}
```

### `src/registry/components/code/code-block/hooks/use-code-mirror.ts`

```ts
"use client";
import { useCallback, useEffect, useRef, useState } from "react";
import { Compartment, EditorState, type Extension } from "@codemirror/state";
import { EditorView, keymap, lineNumbers as cmLineNumbers } from "@codemirror/view";
import {
  defaultKeymap,
  history,
  historyKeymap,
  indentLess,
  indentMore,
} from "@codemirror/commands";
import { bracketMatching, indentOnInput } from "@codemirror/language";
import { closeBrackets, closeBracketsKeymap } from "@codemirror/autocomplete";
import { buildCodeMirrorTheme } from "../lib/codemirror-theme";
import { loadCodeMirrorLang } from "../lib/codemirror-langs";
import { normalizeLang } from "../lib/shiki-bundle";

interface UseCodeMirrorArgs {
  value: string;
  lang: string;
  readOnly: boolean;
  wrap: "wrap" | "scroll";
  /**
   * INITIAL-ONLY (v0.1.2): applied when the editor is created; later changes
   * are not re-applied. Remount the editor (e.g. via a React `key`) to change
   * it after mount. Making it reactive needs a dedicated compartment — noted
   * for a future minor.
   */
  tabSize: number;
  showLineNumbers: boolean;
  onChange?: (value: string) => void;
  onSave?: (value: string) => void;
  /**
   * INITIAL-ONLY (v0.1.2): the array captured at editor creation is baked into
   * the EditorState; identity or content changes after mount are ignored.
   * Remount (React `key`) to swap extensions.
   */
  editorExtensions?: Extension[];
}

interface UseCodeMirrorResult {
  containerRef: React.RefObject<HTMLDivElement | null>;
  view: EditorView | null;
  focus: () => void;
  getValue: () => string;
  /**
   * Scroll a 1-indexed line into view. Returns `false` when no editor is
   * mounted yet, so the caller can fall back to the DOM row path.
   */
  scrollToLine: (line: number) => boolean;
  /**
   * Editor construction failed (grammar chunk fetch, EditorView throw).
   * v0.2.0: previously this could only manifest as an unhandled rejection and
   * a blank editor; the edit body now renders a recovery affordance from it.
   */
  error: Error | null;
}

export function useCodeMirror({
  value,
  lang,
  readOnly,
  wrap,
  tabSize,
  showLineNumbers,
  onChange,
  onSave,
  editorExtensions,
}: UseCodeMirrorArgs): UseCodeMirrorResult {
  const containerRef = useRef<HTMLDivElement | null>(null);
  const viewRef = useRef<EditorView | null>(null);
  const wrapCompartmentRef = useRef(new Compartment());
  const langCompartmentRef = useRef(new Compartment());
  const readOnlyCompartmentRef = useRef(new Compartment());

  // Refs for callbacks so the editor doesn't remount on identity changes.
  // Synced via useEffect to avoid setting refs during render (React 19 rule).
  const onChangeRef = useRef(onChange);
  const onSaveRef = useRef(onSave);
  useEffect(() => {
    onChangeRef.current = onChange;
  }, [onChange]);
  useEffect(() => {
    onSaveRef.current = onSave;
  }, [onSave]);

  // v0.1.2 (review 5.8) — refs mirroring the reactive props. The mount effect
  // awaits a dynamic lang import; props can change during that window. The
  // editor is created from THESE refs (current at creation time), not the
  // mount closure's stale first-render values; the per-prop sync effects
  // below additionally re-run once `view` flips non-null, so nothing that
  // changed mid-window is lost.
  const valueRef = useRef(value);
  const wrapRef = useRef(wrap);
  const readOnlyRef = useRef(readOnly);
  useEffect(() => {
    valueRef.current = value;
  }, [value]);
  useEffect(() => {
    wrapRef.current = wrap;
  }, [wrap]);
  useEffect(() => {
    readOnlyRef.current = readOnly;
  }, [readOnly]);

  const [view, setView] = useState<EditorView | null>(null);
  const [error, setError] = useState<Error | null>(null);

  // Mount (once per container)
  useEffect(() => {
    const container = containerRef.current;
    if (!container) return;
    let cancelled = false;
    setError(null);

    const mount = async () => {
      const normalizedLang = normalizeLang(lang);
      const langExt = (await loadCodeMirrorLang(normalizedLang)) ?? [];
      if (cancelled) return;

      const extensions: Extension[] = [
        history(),
        bracketMatching(),
        indentOnInput(),
        closeBrackets(),
        keymap.of([
          ...defaultKeymap,
          ...historyKeymap,
          ...closeBracketsKeymap,
          { key: "Tab", run: indentMore, shift: indentLess },
          {
            key: "Mod-s",
            preventDefault: true,
            run: (v) => {
              const text = v.state.doc.toString();
              if (onSaveRef.current) {
                onSaveRef.current(text);
              } else if (process.env.NODE_ENV !== "production") {
                console.warn(
                  "[CodeBlock] Cmd+S pressed in edit mode but `onSave` is not wired — no-op.",
                );
              }
              return true;
            },
          },
        ]),
        // tabSize + editorExtensions are INITIAL-ONLY by contract (see the
        // args JSDoc) — closure capture here is deliberate.
        EditorState.tabSize.of(tabSize),
        EditorState.allowMultipleSelections.of(true),
        // Reactive props read from refs — current at creation time even when
        // they changed while the lang import above was in flight (5.8).
        wrapCompartmentRef.current.of(
          wrapRef.current === "wrap" ? EditorView.lineWrapping : [],
        ),
        langCompartmentRef.current.of(langExt),
        readOnlyCompartmentRef.current.of([
          EditorView.editable.of(!readOnlyRef.current),
          EditorState.readOnly.of(readOnlyRef.current),
        ]),
        ...(showLineNumbers ? [cmLineNumbers()] : []),
        EditorView.updateListener.of((u) => {
          if (u.docChanged) {
            onChangeRef.current?.(u.state.doc.toString());
          }
        }),
        buildCodeMirrorTheme(),
        ...(editorExtensions ?? []),
      ];

      const v = new EditorView({
        state: EditorState.create({ doc: valueRef.current, extensions }),
        parent: container,
      });
      viewRef.current = v;
      if (!cancelled) setView(v);
    };

    // v0.2.0 — this used to be a bare `void mount();`. `mount()` awaits a
    // dynamic grammar import and then constructs an EditorView; either can
    // reject (a failed chunk fetch is the same class of failure that takes the
    // Shiki engine down under a strict CSP). The result was an unhandled
    // rejection plus a permanently empty editor — the soft-failure policy
    // documented an inline error + "Reload as view-only" here, and nothing
    // implemented it.
    void mount().catch((err: unknown) => {
      if (process.env.NODE_ENV !== "production") {
        console.warn("[CodeBlock] Editor initialization failed.", err);
      }
      if (!cancelled) {
        setError(err instanceof Error ? err : new Error(String(err)));
      }
    });
    return () => {
      cancelled = true;
      viewRef.current?.destroy();
      viewRef.current = null;
      setView(null);
    };
    // Intentionally only depend on `showLineNumbers` for mount — value sync
    // happens in its own effect below; mid-mount prop changes are covered by
    // the prop refs above (5.8).
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [showLineNumbers]);

  // Controlled value sync. Keyed on `view` too (5.8): when the async mount
  // resolves, this re-runs against the fresh view and applies any value that
  // changed while the editor was still being created.
  useEffect(() => {
    const v = viewRef.current;
    if (!v) return;
    const current = v.state.doc.toString();
    if (current === value) return;
    v.dispatch({
      changes: { from: 0, to: current.length, insert: value },
    });
  }, [value, view]);

  // Wrap reconfigure (view-keyed for the async-mount window, 5.8)
  useEffect(() => {
    const v = viewRef.current;
    if (!v) return;
    v.dispatch({
      effects: wrapCompartmentRef.current.reconfigure(
        wrap === "wrap" ? EditorView.lineWrapping : [],
      ),
    });
  }, [wrap, view]);

  // Lang reconfigure (async — load lang package on change; view-keyed, 5.8)
  useEffect(() => {
    const v = viewRef.current;
    if (!v) return;
    let cancelled = false;
    const apply = async () => {
      const normalizedLang = normalizeLang(lang);
      const langExt = (await loadCodeMirrorLang(normalizedLang)) ?? [];
      if (cancelled || !viewRef.current) return;
      viewRef.current.dispatch({
        effects: langCompartmentRef.current.reconfigure(langExt),
      });
    };
    void apply();
    return () => {
      cancelled = true;
    };
  }, [lang, view]);

  // Read-only reconfigure (view-keyed for the async-mount window, 5.8)
  useEffect(() => {
    const v = viewRef.current;
    if (!v) return;
    v.dispatch({
      effects: readOnlyCompartmentRef.current.reconfigure([
        EditorView.editable.of(!readOnly),
        EditorState.readOnly.of(readOnly),
      ]),
    });
  }, [readOnly, view]);

  const focus = useCallback(() => viewRef.current?.focus(), []);
  const getValue = useCallback(
    () => viewRef.current?.state.doc.toString() ?? "",
    [],
  );

  /**
   * Scroll a 1-indexed line into view (v0.2.1).
   *
   * `CodeBlockHandle.scrollToLine` shipped in v0.1.0 as an empty function body
   * that `meta.ts` advertised as a working handle method for three minor
   * versions. This is the integration that was owed.
   *
   * Out-of-range input is clamped rather than thrown: the handle is called from
   * host code reacting to data (a stack trace line, a search hit) that can
   * legitimately point past the end of a document that has since shrunk.
   */
  const scrollToLine = useCallback((line: number) => {
    const v = viewRef.current;
    if (!v) return false;
    const total = v.state.doc.lines;
    const target = Math.min(Math.max(Math.trunc(line), 1), total);
    const pos = v.state.doc.line(target).from;
    v.dispatch({
      effects: EditorView.scrollIntoView(pos, { y: "center" }),
    });
    return true;
  }, []);

  return { containerRef, view, focus, getValue, scrollToLine, error };
}
```

### `src/registry/components/code/code-block/hooks/use-resolved-theme.ts`

```ts
"use client";
import { useEffect, useState } from "react";

function readInitialIsDark(): boolean {
  if (typeof document === "undefined") return false;
  return document.documentElement.classList.contains("dark");
}

/**
 * Observes the document's `.dark` class. Used informationally only —
 * the actual palette swap happens via CSS variables on the
 * `.code-block-editor` host element. Useful for assistive-tech
 * announcements or `aria-` attributes that need the resolved theme.
 */
export function useResolvedTheme(): "light" | "dark" {
  const [isDark, setIsDark] = useState<boolean>(readInitialIsDark);

  useEffect(() => {
    if (typeof document === "undefined") return;
    const root = document.documentElement;
    const observer = new MutationObserver(() => {
      setIsDark(root.classList.contains("dark"));
    });
    observer.observe(root, { attributes: true, attributeFilter: ["class"] });
    return () => observer.disconnect();
  }, []);

  return isDark ? "dark" : "light";
}
```

### `src/registry/components/code/code-block/lib/lang-resolution.ts`

```ts
import type { CodeBlockFilenameToLangArgs } from "../types";

export const FILENAME_TO_LANG_MAP: Record<string, string> = {
  ts: "ts",
  tsx: "tsx",
  js: "js",
  jsx: "jsx",
  mjs: "js",
  cjs: "js",
  json: "json",
  jsonc: "json",
  json5: "json",
  py: "python",
  pyw: "python",
  rb: "ruby",
  rake: "ruby",
  go: "go",
  rs: "rust",
  java: "java",
  c: "c",
  h: "c",
  cpp: "cpp",
  cxx: "cpp",
  cc: "cpp",
  hpp: "cpp",
  cs: "csharp",
  php: "php",
  swift: "swift",
  kt: "kotlin",
  kts: "kotlin",
  sh: "bash",
  bash: "bash",
  zsh: "bash",
  yml: "yaml",
  yaml: "yaml",
  toml: "toml",
  ini: "ini",
  md: "markdown",
  mdx: "markdown",
  html: "html",
  htm: "html",
  css: "css",
  scss: "scss",
  graphql: "graphql",
  gql: "graphql",
  sql: "sql",
  diff: "diff",
  patch: "diff",
  txt: "plaintext",
  log: "plaintext",
};

const FILENAME_OVERRIDES: Record<string, string> = {
  dockerfile: "dockerfile",
  makefile: "makefile",
};

export function resolveLang(
  lang: string | undefined,
  filename: string | undefined,
  override: ((args: CodeBlockFilenameToLangArgs) => string | undefined) | undefined,
): string {
  if (lang) return lang;
  if (!filename) return "plaintext";

  const lowered = filename.toLowerCase();
  const exact = FILENAME_OVERRIDES[lowered];
  if (exact) return exact;

  if (override) {
    const overridden = override({ filename });
    if (overridden) return overridden;
  }

  const ext = filename.split(".").pop()?.toLowerCase() ?? "";
  return FILENAME_TO_LANG_MAP[ext] ?? "plaintext";
}
```

### `src/registry/components/code/code-block/lib/line-utils.ts`

```ts
import type { CodeBlockLineRange } from "../types";

export function splitToLines(value: string): string[] {
  if (value === "") return [];
  return value.split("\n");
}

export function lineCount(value: string): number {
  if (value === "") return 0;
  return value.split("\n").length;
}

export function rangeToLines(
  highlighted: Array<number | CodeBlockLineRange> | undefined,
): Set<number> {
  const out = new Set<number>();
  if (!highlighted) return out;
  for (const entry of highlighted) {
    if (typeof entry === "number") {
      if (entry > 0) out.add(entry);
      continue;
    }
    const { from, to } = entry;
    const start = Math.max(1, Math.min(from, to));
    const end = Math.max(from, to);
    for (let i = start; i <= end; i++) out.add(i);
  }
  return out;
}

export function gutterWidth(totalLines: number): number {
  if (totalLines < 10) return 1;
  if (totalLines < 100) return 2;
  if (totalLines < 1000) return 3;
  return 4;
}
```

### `src/registry/components/code/code-block/lib/terminal-utils.ts`

```ts
import type { TerminalLine, TerminalLineKind } from "../types";

const PROMPT_PATTERNS = ["$ ", "> ", "# "] as const;

export function promptDetect(line: string): TerminalLineKind {
  for (const p of PROMPT_PATTERNS) {
    if (line.startsWith(p)) return "input";
  }
  return "output";
}

export function parseTerminalLines(value: string): TerminalLine[] {
  if (value === "") return [];
  return value.split("\n").map((text) => ({ kind: promptDetect(text), text }));
}

export function promptPrefix(text: string): { prefix: string; rest: string } {
  for (const p of PROMPT_PATTERNS) {
    if (text.startsWith(p)) {
      return { prefix: p, rest: text.slice(p.length) };
    }
  }
  return { prefix: "", rest: text };
}

export function joinTerminalLines(lines: TerminalLine[]): string {
  return lines.map((l) => l.text).join("\n");
}
```

### `src/registry/components/code/code-block/lib/shiki-bundle.ts`

```ts
/**
 * Shiki bundle setup with fine-grained imports + on-demand grammar loading.
 *
 * Default ships ~10 common grammars synchronously (ts/tsx/js/jsx/json/python/
 * bash/markdown/html/css). Other grammars dynamic-import on first use.
 *
 * Themes default to GitHub Light + GitHub Dark Default (small, sync-loaded).
 */
import {
  createHighlighterCore,
  type HighlighterCore,
  type LanguageRegistration,
  type ThemeRegistrationAny,
} from "shiki/core";
import { createOnigurumaEngine } from "shiki/engine/oniguruma";
import type { CodeBlockRegexEngine } from "../types";

/**
 * Highlighter cache, **keyed by engine** (v0.2.0).
 *
 * This used to be a single module-level promise with no key. Once the engine
 * became selectable that would have been a silent bug: the first block to
 * mount would win, and every later block asking for the other engine would get
 * the cached one instead. Keying is what makes the option real.
 */
const cachedHighlighters = new Map<CodeBlockRegexEngine, Promise<HighlighterCore>>();

const LAZY_LANG_LOADERS: Record<string, () => Promise<unknown>> = {
  rust: () => import("shiki/langs/rust.mjs"),
  go: () => import("shiki/langs/go.mjs"),
  sql: () => import("shiki/langs/sql.mjs"),
  yaml: () => import("shiki/langs/yaml.mjs"),
  diff: () => import("shiki/langs/diff.mjs"),
  java: () => import("shiki/langs/java.mjs"),
  c: () => import("shiki/langs/c.mjs"),
  cpp: () => import("shiki/langs/cpp.mjs"),
  csharp: () => import("shiki/langs/csharp.mjs"),
  ruby: () => import("shiki/langs/ruby.mjs"),
  php: () => import("shiki/langs/php.mjs"),
  swift: () => import("shiki/langs/swift.mjs"),
  kotlin: () => import("shiki/langs/kotlin.mjs"),
  graphql: () => import("shiki/langs/graphql.mjs"),
  toml: () => import("shiki/langs/toml.mjs"),
  ini: () => import("shiki/langs/ini.mjs"),
  scss: () => import("shiki/langs/scss.mjs"),
  dockerfile: () => import("shiki/langs/dockerfile.mjs"),
  makefile: () => import("shiki/langs/makefile.mjs"),
  patch: () => import("shiki/langs/diff.mjs"),
};

const LANG_ALIASES: Record<string, string> = {
  javascript: "js",
  typescript: "ts",
  py: "python",
  rb: "ruby",
  rs: "rust",
  yml: "yaml",
  sh: "bash",
  zsh: "bash",
  md: "markdown",
  mdx: "markdown",
};

export function normalizeLang(lang: string | undefined): string {
  if (!lang) return "plaintext";
  const lower = lang.toLowerCase();
  return LANG_ALIASES[lower] ?? lower;
}

async function loadCoreGrammars(): Promise<LanguageRegistration[]> {
  const [ts, tsx, js, jsx, json, bash, python, markdown, html, css] = await Promise.all([
    import("shiki/langs/ts.mjs"),
    import("shiki/langs/tsx.mjs"),
    import("shiki/langs/javascript.mjs"),
    import("shiki/langs/jsx.mjs"),
    import("shiki/langs/json.mjs"),
    import("shiki/langs/bash.mjs"),
    import("shiki/langs/python.mjs"),
    import("shiki/langs/markdown.mjs"),
    import("shiki/langs/html.mjs"),
    import("shiki/langs/css.mjs"),
  ]);
  const out = [ts, tsx, js, jsx, json, bash, python, markdown, html, css]
    .map((m) => (m as { default: unknown }).default)
    .flat() as LanguageRegistration[];
  // No bookkeeping needed: these are passed to `createHighlighterCore`, so
  // each instance reports them from `getLoadedLanguages()` itself.
  return out;
}

async function loadCoreThemes(): Promise<ThemeRegistrationAny[]> {
  const [light, dark] = await Promise.all([
    import("shiki/themes/github-light.mjs"),
    import("shiki/themes/github-dark-default.mjs"),
  ]);
  return [
    (light as { default: ThemeRegistrationAny }).default,
    (dark as { default: ThemeRegistrationAny }).default,
  ];
}

/**
 * Build the regex engine.
 *
 * `oniguruma` (default) is WebAssembly and needs `'wasm-unsafe-eval'` in the
 * host's `script-src` under a strict CSP — without it Chrome refuses the
 * compile outright and highlighting can never work. `javascript` uses no wasm
 * at all, so it is the escape hatch for CSP-restricted hosts; it supports
 * slightly fewer grammar constructs, which is the trade being offered.
 */
async function buildEngine(engine: CodeBlockRegexEngine) {
  if (engine === "javascript") {
    const { createJavaScriptRegexEngine } = await import("shiki/engine/javascript");
    return createJavaScriptRegexEngine();
  }
  // shiki/wasm exports the wasm-loader function as `default`.
  const wasmMod = await import("shiki/wasm");
  return createOnigurumaEngine(wasmMod.default);
}

export function getHighlighter(
  engine: CodeBlockRegexEngine = "oniguruma",
): Promise<HighlighterCore> {
  const cached = cachedHighlighters.get(engine);
  if (cached) return cached;
  const created = (async () => {
    const [langs, themes, regexEngine] = await Promise.all([
      loadCoreGrammars(),
      loadCoreThemes(),
      buildEngine(engine),
    ]);
    return createHighlighterCore({ engine: regexEngine, langs, themes });
  })();
  // Evict on failure so the cache cannot pin a permanent rejection. Without
  // this, one failed wasm compile poisons every block for the page's lifetime
  // and the hook's retry-on-new-input can never actually retry.
  created.catch(() => {
    if (cachedHighlighters.get(engine) === created) cachedHighlighters.delete(engine);
  });
  cachedHighlighters.set(engine, created);
  return created;
}

export async function ensureLangLoaded(
  highlighter: HighlighterCore,
  lang: string,
): Promise<string> {
  const normalized = normalizeLang(lang);
  if (normalized === "plaintext") return normalized;
  /*
   * Ask the HIGHLIGHTER what it has loaded — never a module-global set.
   *
   * Until v0.2.0 there was exactly one highlighter, so a shared
   * `loadedLangs` Set was a harmless shortcut. Making the engine selectable
   * created a second instance and turned that Set into a cross-instance lie:
   * once the oniguruma highlighter loaded `rust`, the javascript one would
   * short-circuit here, never call `loadLanguage`, and then throw
   * "Language `rust` not found" on the very next `codeToHtml` — surfacing as
   * a permanent plaintext fallback on a perfectly healthy engine.
   * `getLoadedLanguages()` is per-instance and cannot drift.
   */
  if (highlighter.getLoadedLanguages().includes(normalized)) return normalized;
  const loader = LAZY_LANG_LOADERS[normalized];
  if (!loader) {
    if (process.env.NODE_ENV !== "production") {
      console.warn(`[CodeBlock] Unknown lang "${lang}" — falling back to plaintext.`);
    }
    return "plaintext";
  }
  const mod = (await loader()) as { default: LanguageRegistration | LanguageRegistration[] };
  const reg = Array.isArray(mod.default) ? mod.default : [mod.default];
  await highlighter.loadLanguage(...reg);
  return normalized;
}

export async function ensureThemeLoaded(
  highlighter: HighlighterCore,
  themeName: string,
): Promise<void> {
  if (highlighter.getLoadedThemes().includes(themeName)) return;
  // Attempt dynamic import from shiki/themes/<name>.mjs
  try {
    const mod = (await import(/* @vite-ignore */ `shiki/themes/${themeName}.mjs`)) as {
      default: ThemeRegistrationAny;
    };
    await highlighter.loadTheme(mod.default);
  } catch {
    if (process.env.NODE_ENV !== "production") {
      console.warn(`[CodeBlock] Theme "${themeName}" not found in shiki/themes/.`);
    }
  }
}

export const DEFAULT_THEME_NAMES = {
  light: "github-light",
  dark: "github-dark-default",
} as const;
```

### `src/registry/components/code/code-block/lib/codemirror-langs.ts`

```ts
import type { Extension } from "@codemirror/state";
import { normalizeLang } from "./shiki-bundle";

type LangLoader = () => Promise<Extension | null>;

const CM_LANG_LOADERS: Record<string, LangLoader> = {
  ts: async () =>
    (await import("@codemirror/lang-javascript")).javascript({
      typescript: true,
      jsx: false,
    }),
  tsx: async () =>
    (await import("@codemirror/lang-javascript")).javascript({
      typescript: true,
      jsx: true,
    }),
  js: async () =>
    (await import("@codemirror/lang-javascript")).javascript({ jsx: false }),
  jsx: async () =>
    (await import("@codemirror/lang-javascript")).javascript({ jsx: true }),
  json: async () => (await import("@codemirror/lang-json")).json(),
  python: async () => (await import("@codemirror/lang-python")).python(),
  html: async () => (await import("@codemirror/lang-html")).html(),
  css: async () => (await import("@codemirror/lang-css")).css(),
  markdown: async () => (await import("@codemirror/lang-markdown")).markdown(),
};

export async function loadCodeMirrorLang(
  lang: string,
): Promise<Extension | null> {
  const normalized = normalizeLang(lang);
  const loader = CM_LANG_LOADERS[normalized];
  if (!loader) return null;
  try {
    return await loader();
  } catch (err) {
    if (process.env.NODE_ENV !== "production") {
      console.warn(
        `[CodeBlock] CodeMirror lang package for "${lang}" failed to load:`,
        err,
      );
    }
    return null;
  }
}
```

### `src/registry/components/code/code-block/lib/codemirror-theme.ts`

```ts
/**
 * Custom CodeMirror theme + HighlightStyle approximating Shiki's
 * GitHub Light + GitHub Dark Default token palettes.
 *
 * Colors are emitted as CSS variables (`--cb-fg-<token>`) so the active
 * `.dark` class on an ancestor flips the palette without re-mounting
 * or reconfiguring CodeMirror.
 *
 * v0.2.0 upgrade path: replace this file with a hand-rolled Shiki →
 * CodeMirror bridge (tokenize doc on change, apply tokens as a
 * StateField<DecorationSet>) for pixel-perfect parity with view mode.
 */
import { HighlightStyle, syntaxHighlighting } from "@codemirror/language";
import { EditorView } from "@codemirror/view";
import type { Extension } from "@codemirror/state";
import { tags as t } from "@lezer/highlight";

const highlightStyle = HighlightStyle.define([
  { tag: t.keyword, color: "var(--cb-fg-keyword)" },
  { tag: t.controlKeyword, color: "var(--cb-fg-keyword)" },
  { tag: t.operatorKeyword, color: "var(--cb-fg-keyword)" },
  { tag: t.modifier, color: "var(--cb-fg-keyword)" },

  { tag: [t.string, t.special(t.string)], color: "var(--cb-fg-string)" },
  { tag: t.regexp, color: "var(--cb-fg-string)" },

  { tag: t.number, color: "var(--cb-fg-number)" },
  { tag: t.bool, color: "var(--cb-fg-number)" },
  { tag: t.null, color: "var(--cb-fg-number)" },

  {
    tag: [t.comment, t.lineComment, t.blockComment, t.docComment],
    color: "var(--cb-fg-comment)",
    fontStyle: "italic",
  },

  { tag: [t.variableName, t.propertyName], color: "var(--cb-fg-variable)" },
  { tag: t.definition(t.variableName), color: "var(--cb-fg-variable)" },

  { tag: [t.typeName, t.className], color: "var(--cb-fg-type)" },
  { tag: [t.namespace, t.tagName], color: "var(--cb-fg-type)" },

  { tag: t.function(t.variableName), color: "var(--cb-fg-function)" },
  { tag: t.function(t.propertyName), color: "var(--cb-fg-function)" },
  { tag: t.macroName, color: "var(--cb-fg-function)" },

  { tag: [t.operator, t.derefOperator, t.arithmeticOperator], color: "var(--cb-fg-operator)" },
  { tag: [t.compareOperator, t.logicOperator], color: "var(--cb-fg-operator)" },

  { tag: [t.punctuation, t.separator, t.bracket], color: "var(--cb-fg-punctuation)" },

  { tag: t.attributeName, color: "var(--cb-fg-attribute)" },
  { tag: t.attributeValue, color: "var(--cb-fg-string)" },

  { tag: [t.meta, t.processingInstruction], color: "var(--cb-fg-meta)" },

  { tag: t.invalid, color: "var(--cb-fg-invalid)" },

  { tag: t.heading, color: "var(--cb-fg-keyword)", fontWeight: "bold" },
  { tag: t.link, color: "var(--cb-fg-string)", textDecoration: "underline" },
  { tag: t.emphasis, fontStyle: "italic" },
  { tag: t.strong, fontWeight: "bold" },
]);

const editorTheme = EditorView.theme(
  {
    "&": {
      backgroundColor: "transparent",
      color: "var(--cb-fg-variable)",
      fontFamily: "var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace)",
      fontSize: "0.875rem",
      lineHeight: "1.6",
      height: "100%",
    },
    "&.cm-focused": {
      outline: "none",
    },
    ".cm-scroller": {
      fontFamily: "inherit",
      lineHeight: "inherit",
    },
    ".cm-content": {
      padding: "1rem 1rem 1rem 0",
      caretColor: "var(--foreground)",
    },
    ".cm-gutters": {
      backgroundColor: "transparent",
      border: "none",
      color: "var(--muted-foreground)",
      fontFamily: "inherit",
      paddingRight: "0.75rem",
    },
    ".cm-lineNumbers .cm-gutterElement": {
      padding: "0 0.5rem",
      minWidth: "2.5em",
      textAlign: "right",
    },
    ".cm-activeLine": {
      backgroundColor: "color-mix(in oklch, var(--accent) 4%, transparent)",
    },
    ".cm-activeLineGutter": {
      backgroundColor: "color-mix(in oklch, var(--accent) 4%, transparent)",
      color: "var(--foreground)",
    },
    ".cm-selectionBackground, &.cm-focused .cm-selectionBackground, ::selection": {
      backgroundColor: "color-mix(in oklch, var(--accent) 25%, transparent) !important",
    },
    ".cm-cursor": {
      borderLeftColor: "var(--foreground)",
      borderLeftWidth: "2px",
    },
    ".cm-matchingBracket, .cm-nonmatchingBracket": {
      backgroundColor: "color-mix(in oklch, var(--accent) 18%, transparent)",
      outline: "none",
    },
  },
  { dark: false },
);

/**
 * Build the editor theme extension. The themes arg is reserved for the
 * v0.2.0 Shiki bridge — in v0.1.0 it is informational only (the active
 * palette is CSS-variable driven via .dark class).
 */
export function buildCodeMirrorTheme(): Extension {
  return [editorTheme, syntaxHighlighting(highlightStyle)];
}

/**
 * CSS variable definitions for both light + dark palettes.
 * Inject this stylesheet once per CodeBlock instance (or globally).
 *
 * Palette source: GitHub Light + GitHub Dark Default (Shiki's bundled themes).
 */
export const CODEMIRROR_THEME_CSS = `
.code-block-editor {
  --cb-fg-keyword: #cf222e;
  --cb-fg-string: #0a3069;
  --cb-fg-number: #0550ae;
  --cb-fg-comment: #6e7781;
  --cb-fg-variable: #1f2328;
  --cb-fg-type: #953800;
  --cb-fg-function: #8250df;
  --cb-fg-operator: #cf222e;
  --cb-fg-punctuation: #24292f;
  --cb-fg-attribute: #0550ae;
  --cb-fg-meta: #6639ba;
  --cb-fg-invalid: #82071e;
}
:where(.dark) .code-block-editor {
  --cb-fg-keyword: #ff7b72;
  --cb-fg-string: #a5d6ff;
  --cb-fg-number: #79c0ff;
  --cb-fg-comment: #8b949e;
  --cb-fg-variable: #e6edf3;
  --cb-fg-type: #ffa657;
  --cb-fg-function: #d2a8ff;
  --cb-fg-operator: #ff7b72;
  --cb-fg-punctuation: #c9d1d9;
  --cb-fg-attribute: #79c0ff;
  --cb-fg-meta: #d2a8ff;
  --cb-fg-invalid: #ffa198;
}
`;
```

### `src/registry/components/code/code-block/lib/streaming-cache.ts`

```ts
/**
 * Append-only streaming-tokenize cache.
 *
 * When the consumer streams new content (each render's `value` is the
 * previous `value` + a tail), retokenizing the entire document is wasteful.
 *
 * Algorithm:
 *  - Stable boundary = the last newline in the previous value. Lines before
 *    the boundary cannot change (tokenization is line-local in Shiki/TextMate).
 *  - On each new value:
 *    - If !startsWith(prev): not an append → full retokenize.
 *    - Else: retokenize only `prevValue.slice(lastNewline + 1) ++ newTail`,
 *      reuse the cached HTML for lines before the boundary.
 *
 * The cache holds per-line HTML strings; concatenation rebuilds the full
 * output without re-running the tokenizer on stable lines.
 */
export interface StreamingCache {
  prevValue: string;
  prevHtmlLines: string[];
}

export function emptyCache(): StreamingCache {
  return { prevValue: "", prevHtmlLines: [] };
}

export interface CacheDiff {
  /** Lines before the stable boundary — reuse cached HTML. */
  stablePrefixLines: string[];
  /** The substring that needs re-tokenizing (suffix from last stable newline). */
  retokenizeSlice: string;
  /** Whether this is an append (true) or a replace (false). */
  isAppend: boolean;
}

export function diffForRetokenize(
  cache: StreamingCache,
  nextValue: string,
): CacheDiff {
  if (nextValue === cache.prevValue) {
    return {
      stablePrefixLines: cache.prevHtmlLines,
      retokenizeSlice: "",
      isAppend: true,
    };
  }
  if (!nextValue.startsWith(cache.prevValue)) {
    return {
      stablePrefixLines: [],
      retokenizeSlice: nextValue,
      isAppend: false,
    };
  }
  const lastNewline = cache.prevValue.lastIndexOf("\n");
  if (lastNewline < 0) {
    return {
      stablePrefixLines: [],
      retokenizeSlice: nextValue,
      isAppend: true,
    };
  }
  const stableLineCount = cache.prevValue.slice(0, lastNewline).split("\n").length;
  return {
    stablePrefixLines: cache.prevHtmlLines.slice(0, stableLineCount),
    retokenizeSlice: nextValue.slice(lastNewline + 1),
    isAppend: true,
  };
}
```
