AI Assistant

Claude Code uses a heavily customized fork of Ink, a React renderer for terminal applications. The implementation in src/ink/ provides a full rendering pipeline from React reconciliation through layout computation to terminal output, along with a rich event system for keyboard, mouse, and focus handling.

Architecture Overview

The rendering pipeline flows through four stages:

React Reconciler  -->  Yoga Layout  -->  Output Buffer  -->  Terminal Screen
  (reconciler.ts)      (flexbox)      (render-node-to-output)  (render-to-screen)

Each frame follows this sequence:

  1. Reconcile: React reconciler processes component updates, creating/updating DOM nodes
  2. Layout: Yoga (flexbox engine) computes positions and dimensions for all nodes
  3. Render to Output: DOM nodes are rendered to an Output buffer with styled text
  4. Render to Screen: The output buffer is diffed against the previous frame and terminal escape sequences are emitted

Ink Class

The Ink class in src/ink/ink.tsx is the root coordinator. It owns the React fiber root, the terminal connection, the rendering scheduler, and the event dispatch system.

class Ink {
  private readonly log: LogUpdate
  private readonly terminal: Terminal
  private scheduleRender: (() => void) & { cancel?: () => void }
  // ...
}

Initialization

The Ink constructor sets up:

  • React fiber root via the custom reconciler with ConcurrentRoot mode
  • Terminal I/O through a Terminal abstraction wrapping stdout/stdin
  • Render scheduling using throttled callbacks at FRAME_INTERVAL_MS
  • Signal handling via onExit for clean shutdown (cursor restoration, alt-screen exit)
  • Alt-screen mode for full-screen rendering with cursor parking

Frame Rendering

Rendering is double-buffered with frontFrame and backFrame. Each render:

  1. Creates an Output instance (reused for charCache persistence)
  2. Calls renderNodeToOutput to walk the DOM tree
  3. Creates a Screen from the output
  4. Diffs the new screen against the previous frame
  5. Writes only changed regions to the terminal via writeDiffToTerminal

React Reconciler

src/ink/reconciler.ts implements a React reconciler using react-reconciler. It bridges React's component model to a custom DOM:

import createReconciler from 'react-reconciler'

DOM Nodes

The reconciler creates two types of nodes:

  • DOMElement: element nodes with styles, children, and event handlers. Created by createNode() with element names like 'ink-box', 'ink-text', 'ink-button', etc.
  • TextNode: text content nodes, created by createTextNode()

Operations

The reconciler implements the standard React reconciler host config:

  • createInstance / createTextInstance: create DOM nodes
  • appendChildNode / removeChildNode / insertBeforeNode: tree mutations
  • setAttribute / setStyle / setTextStyles: property updates
  • markDirty: flags nodes for re-layout

Event Dispatch

A Dispatcher handles event propagation. Event handler props (onClick, onKeyPress, onFocus) are recognized during reconciliation and wired to the dispatch system.

Profiling

The reconciler tracks performance metrics:

  • getLastCommitMs(): time spent in the last React commit phase
  • getLastYogaMs(): time spent in the last Yoga layout computation
  • resetProfileCounters(): resets profiling counters between frames

Renderer

src/ink/renderer.ts implements the createRenderer function that produces frame snapshots:

export default function createRenderer(
  node: DOMElement,
  stylePool: StylePool,
): Renderer {
  let output: Output | undefined
  return (options: RenderOptions) => {
    // Compute layout via Yoga
    // Render nodes to output buffer
    // Create screen from output
    // Return Frame with screen, viewport, and cursor
  }
}

Safety Checks

The renderer guards against invalid Yoga dimensions (NaN, Infinity, negative values) that would cause RangeError when creating arrays. When detected, it returns an empty frame and logs the issue for debugging.

Double Buffering

The RenderOptions include both frontFrame (current display) and backFrame (in-progress render), plus a prevFrameContaminated flag that indicates when the previous frame's screen buffer was mutated (e.g., by selection overlay) and full redraw is needed instead of incremental diff.

Layout Engine

Layout is computed by Yoga, a cross-platform flexbox implementation. The src/ink/layout/ directory contains layout node types and style mapping.

Key layout concepts:

  • Flexbox model: components use flex direction, alignment, padding, margin, and gap
  • Computed dimensions: yogaNode.getComputedWidth() / getComputedHeight()
  • Display modes: LayoutDisplay enum controls visibility
  • Style application: applyStyles() maps component props to Yoga node properties

Built-in Components

Boxlayout
Flexbox container: the fundamental layout primitive. Supports padding, margin, flex direction, borders, and alignment.
Textcontent
Styled text with color, bold, italic, underline, strikethrough, dimming, and wrapping.
ScrollBoxlayout
Scrollable container with keyboard-driven scrolling and scroll indicators.
Buttoninteractive
Clickable element with hover and focus states.
Linkinteractive
Hyperlink rendered using OSC 8 terminal escape sequences.

Event System

The event system handles multiple input types:

Keyboard events are parsed from raw terminal input via parse-keypress.ts. The system supports:

  • Standard keys and modifiers (Ctrl, Alt, Shift, Meta)
  • Kitty keyboard protocol (ENABLE_KITTY_KEYBOARD / DISABLE_KITTY_KEYBOARD)
  • Modified key sequences (ENABLE_MODIFY_OTHER_KEYS)
  • ParsedKey type with name, ctrl, meta, shift, and sequence fields

Optimization

Several optimization techniques keep rendering performant:

  • Node cache (node-cache.ts): caches computed layout results for unchanged subtrees
  • Line width cache: avoids recalculating text widths for unchanged lines
  • Output char cache: tokenization and grapheme clustering results persist across frames
  • Screen pooling: StylePool, CharPool, and HyperlinkPool reduce allocation pressure with generational reuse
  • Optimizer (optimizer.ts): post-processing pass that merges adjacent cells with identical styles
  • Throttled rendering: frames are throttled to FRAME_INTERVAL_MS to avoid overwhelming the terminal

Terminal I/O

The terminal layer handles low-level output:

ANSI/CSI Sequences

src/ink/termio/csi.ts provides cursor control primitives:

  • CURSOR_HOME: move cursor to top-left
  • ERASE_SCREEN: clear the terminal screen
  • cursorMove / cursorPosition: cursor positioning

DEC Private Modes

src/ink/termio/dec.ts manages terminal modes:

  • ENTER_ALT_SCREEN / EXIT_ALT_SCREEN: alternate screen buffer
  • ENABLE_MOUSE_TRACKING / DISABLE_MOUSE_TRACKING: mouse event reporting
  • SHOW_CURSOR: cursor visibility

OSC Sequences

src/ink/termio/osc.ts handles operating system commands:

  • setClipboard: copy text to system clipboard via OSC 52
  • supportsTabStatus: tab status line support
  • wrapForMultiplexer: wraps sequences for tmux/screen compatibility

Selection System

The selection system (selection.ts) supports text selection with mouse:

  • startSelection / extendSelection / updateSelection: selection tracking
  • getSelectedText: extract selected text
  • applySelectionOverlay: render selection highlight over the screen
  • selectWordAt / selectLineAt: double-click and triple-click selection
  • shiftSelection / shiftAnchor: scroll-aware selection adjustment
  • findPlainTextUrlAt: URL detection at cursor position