# ink-terminal

**High-performance React terminal renderer with 27 rendering optimizations.**

Extracted from [Claude Code](https://claude.ai/code)'s internal rendering engine. Provides a complete React-based terminal UI framework with double-buffering, hardware scrolling, damage-tracked diffing, and atomic output — all transparent to the application.

[**中文文档**](./README.zh-CN.md)

---

## Features

- **60fps Throttled Rendering** — `lodash.throttle` + `queueMicrotask` coalesces rapid state updates
- **Double Buffering** — Zero per-frame allocation via front/back frame swap
- **Blit Fast Path** — Clean subtrees bulk-copied via `TypedArray.set()` (memcpy-level)
- **Packed Int32Array Screen Buffer** — 2 words/cell with `CharPool`/`StylePool` pooling
- **Damage-Bounded Diff** — Only scans changed screen regions, not the entire buffer
- **DECSTBM Hardware Scrolling** — Terminal-side row shifting, only new rows are drawn
- **BSU/ESU Atomic Output** — DEC 2026 synchronized updates eliminate tearing
- **Incremental Markdown** — O(delta) parsing with token LRU cache
- **Virtual Scrolling** — 1000+ items render only the visible viewport
- **Pure TypeScript Yoga Layout** — Flexbox layout with zero native/WASM dependencies
- **Full Event System** — Keyboard, mouse click, focus, paste, resize events
- **React 19 + Custom Reconciler** — Full React component model in the terminal

## Architecture

```
ink-terminal/
├── core/          # Layer 1: Screen buffer, Diff, Terminal I/O (no React)
├── react/         # Layer 2: React reconciler, Components, Hooks
├── markdown/      # Streaming markdown renderer
├── events/        # Event system (input, click, focus, keyboard, paste, resize)
├── termio/        # Terminal I/O primitives (ANSI, CSI, DEC, ESC, OSC, SGR)
├── layout/        # Flexbox layout engine (pure TS Yoga implementation)
├── providers/     # Plugin/Callback interface for app-specific integration
└── internal/      # Vendored utilities (intl, stringWidth, sliceAnsi)
```

## Installation

```bash
bun add ink-terminal react react-reconciler
```

## Sub-path Exports

```typescript
import { ... } from 'ink-terminal'            // Full API (components + hooks + types)
import { ... } from 'ink-terminal/core'       // Screen buffer, diff (no React)
import { ... } from 'ink-terminal/react'      // React components, hooks, reconciler
import { ... } from 'ink-terminal/markdown'   // Streaming markdown renderer
import { ... } from 'ink-terminal/events'     // Event classes
import { ... } from 'ink-terminal/termio'     // Terminal escape sequences
import { ... } from 'ink-terminal/layout'     // Yoga layout engine
```

---

## Quick Start

### Minimal Example

```tsx
import { render, Box, Text } from 'ink-terminal'

const App = () => (
  <Box flexDirection="column" padding={1}>
    <Text bold color="ansi:green">Hello Terminal!</Text>
    <Text>Rendered with 60fps double-buffered pipeline.</Text>
  </Box>
)

await render(<App />)
```

### Using createRoot

```tsx
import { createRoot, Box, Text } from 'ink-terminal'

const root = await createRoot({ stdout: process.stdout })
root.render(
  <Box padding={1}>
    <Text bold>Managed root</Text>
  </Box>
)

// Later: update
root.render(<Box padding={1}><Text>Updated!</Text></Box>)

// Cleanup
root.unmount()
```

---

## Render API

### `render(node, options?)`

Mount a React component and start the rendering loop.

```tsx
import { render } from 'ink-terminal'

const instance = await render(<App />, {
  stdout: process.stdout,        // Output stream (default: process.stdout)
  stdin: process.stdin,           // Input stream (default: process.stdin)
  stderr: process.stderr,         // Error stream (default: process.stderr)
  exitOnCtrlC: true,              // Exit on Ctrl+C (default: true)
  patchConsole: true,             // Intercept console.log (default: true)
  onFrame: (event) => { ... },    // Called after each frame render
})

instance.rerender(<App updated />)  // Update root component
instance.unmount()                   // Manually unmount
await instance.waitUntilExit()       // Wait for app exit
```

### `renderSync(node, options?)`

Same as `render` but synchronous.

### `createRoot(options?)`

Create an Ink root for later rendering (like `react-dom` createRoot).

```tsx
const root = await createRoot({ stdout: process.stdout })
root.render(<App />)
root.unmount()
await root.waitUntilExit()
```

---

## Components

### Box

Flexbox layout container — the building block for all layouts.

```tsx
import { Box, Text } from 'ink-terminal'

// Row layout with gap
<Box flexDirection="row" gap={2}>
  <Text>Left</Text>
  <Text>Right</Text>
</Box>

// Column layout with padding and border
<Box flexDirection="column" padding={1} borderStyle="round" borderColor="ansi:cyan">
  <Text bold>Title</Text>
  <Text>Content here</Text>
</Box>

// flexGrow to fill space
<Box width={60}>
  <Box flexGrow={1} borderStyle="single"><Text>Grows</Text></Box>
  <Box width={20} borderStyle="single"><Text>Fixed</Text></Box>
</Box>

// Alignment
<Box justifyContent="space-between" alignItems="center" width={60} height={5}>
  <Text>Left</Text>
  <Text>Center</Text>
  <Text>Right</Text>
</Box>
```

**Layout props:** `flexDirection`, `flexWrap`, `flexGrow`, `flexShrink`, `flexBasis`, `gap`, `columnGap`, `rowGap`, `alignItems`, `alignSelf`, `justifyContent`, `display`, `position`, `width`, `height`, `minWidth`, `minHeight`, `maxWidth`, `maxHeight`, `top`, `bottom`, `left`, `right`.

**Spacing props:** `margin`, `marginX`, `marginY`, `marginTop`, `marginBottom`, `marginLeft`, `marginRight`, `padding`, `paddingX`, `paddingY`, `paddingTop`, `paddingBottom`, `paddingLeft`, `paddingRight`.

**Border props:** `borderStyle` (`'single'` | `'double'` | `'round'` | `'bold'` | `'dashed'` | `'classic'` | `'singleDouble'` | `'doubleSingle'`), `borderColor`, `borderTop`, `borderBottom`, `borderLeft`, `borderRight`, `borderDimColor`, `borderTextOptions`.

**Event props:** `onClick`, `onFocus`, `onBlur`, `onKeyDown`, `onMouseEnter`, `onMouseLeave` (+ capture variants).

**Focus props:** `tabIndex`, `autoFocus`, `ref`.

### Text

Styled terminal text.

```tsx
import { Text } from 'ink-terminal'

// Colors
<Text color="ansi:red">Red text</Text>
<Text color="#FF6B6B">Hex color</Text>
<Text color="rgb(50,205,50)">RGB color</Text>
<Text color="ansi256(214)">256-color</Text>
<Text backgroundColor="ansi:blue" color="ansi:white"> Highlighted </Text>

// Text decorations
<Text bold>Bold</Text>
<Text dim>Dim</Text>
<Text italic>Italic</Text>
<Text underline>Underline</Text>
<Text strikethrough>Strikethrough</Text>
<Text inverse>Inverse</Text>

// Combined styles
<Text bold color="ansi:green" underline>Bold green underlined</Text>

// Text wrapping
<Text wrap="wrap">Long text that wraps at container width...</Text>
<Text wrap="truncate-end">Long text truncated with ellipsis…</Text>
<Text wrap="truncate-start">…art of text visible at the end</Text>
<Text wrap="truncate-middle">Start…end</Text>
```

**Color type:** `'ansi:red'` | `'ansi:green'` | `'ansi:blue'` | ... (16 named) | `'#FF0000'` (hex) | `'rgb(255,0,0)'` (RGB) | `'ansi256(214)'` (256-color).

**Props:** `color`, `backgroundColor`, `bold`, `dim` (mutually exclusive with bold), `italic`, `underline`, `strikethrough`, `inverse`, `wrap`, `children`.

### Button

Interactive button with focus/hover/active states.

```tsx
import { Button, Box, Text, AlternateScreen, useInput, useApp } from 'ink-terminal'

function App() {
  const { exit } = useApp()
  useInput((_, key) => { if (key.escape) exit() })

  return (
    <AlternateScreen>
      <Box flexDirection="column" gap={1} padding={1}>
        <Button autoFocus onAction={() => console.log('clicked!')}>
          {({ focused, hovered, active }) => (
            <Box
              borderStyle={focused ? 'bold' : 'single'}
              borderColor={active ? 'ansi:green' : focused ? 'ansi:cyan' : undefined}
              paddingX={2}
            >
              <Text bold={focused}>
                {active ? '✓ Pressed!' : focused ? '→ Submit' : '  Submit'}
              </Text>
            </Box>
          )}
        </Button>

        <Button onAction={() => exit()}>
          <Text>Quit</Text>
        </Button>
      </Box>
    </AlternateScreen>
  )
}
```

**Props:** `onAction` (required), `tabIndex` (default 0), `autoFocus`, `children` (ReactNode or `(state: { focused, hovered, active }) => ReactNode`).

### ScrollBox

Scrollable container with sticky-scroll and hardware scrolling.

```tsx
import { Box, Text, ScrollBox } from 'ink-terminal'
import { useRef } from 'react'

function App() {
  const scrollRef = useRef(null)

  return (
    <Box height={20}>
      <ScrollBox ref={scrollRef} stickyScroll>
        {messages.map(msg => (
          <Box key={msg.id} paddingX={1}>
            <Text>{msg.content}</Text>
          </Box>
        ))}
      </ScrollBox>
    </Box>
  )
}

// Imperative scroll control via ref:
// scrollRef.current.scrollTo(0)
// scrollRef.current.scrollBy(10)
// scrollRef.current.scrollToBottom()
// scrollRef.current.getScrollTop()
// scrollRef.current.getScrollHeight()
// scrollRef.current.getViewportHeight()
// scrollRef.current.isSticky()
```

**Props:** `stickyScroll` (auto-scroll to bottom on new content), `ref` (ScrollBoxHandle for imperative control), plus all Box layout/style props.

### AlternateScreen

Full-screen mode with mouse tracking and cursor management.

```tsx
import { AlternateScreen, Box, Text, useInput, useApp } from 'ink-terminal'

function FullScreenApp() {
  const { exit } = useApp()
  useInput((_, key) => { if (key.escape) exit() })

  return (
    <AlternateScreen mouseTracking>
      <Box flexDirection="column" height="100%">
        <Box borderStyle="single" paddingX={1}>
          <Text bold>Header</Text>
        </Box>
        <Box flexGrow={1} padding={1}>
          <Text>Full screen content. Press Esc to exit.</Text>
        </Box>
        <Box borderStyle="single" paddingX={1}>
          <Text dim>Footer</Text>
        </Box>
      </Box>
    </AlternateScreen>
  )
}
```

**Props:** `mouseTracking` (default true — enables SGR mouse tracking for click/drag/wheel), `children`.

### Link

Terminal hyperlink with fallback.

```tsx
import { Link, Box, Text } from 'ink-terminal'

<Box flexDirection="column">
  <Link url="https://github.com">GitHub</Link>
  <Link url="https://example.com" fallback={<Text dim>(link not supported)</Text>}>
    Click here
  </Link>
</Box>
```

**Props:** `url` (required), `children` (link text, defaults to url), `fallback` (shown when terminal doesn't support hyperlinks).

### VirtualList

Virtualized list for rendering 1000+ items efficiently.

```tsx
import { Box, Text, ScrollBox, VirtualList, AlternateScreen } from 'ink-terminal'
import { useRef } from 'react'

const items = Array.from({ length: 1000 }, (_, i) => ({
  id: String(i),
  text: `Item #${i}`,
}))

function App() {
  const scrollRef = useRef(null)

  return (
    <AlternateScreen>
      <ScrollBox ref={scrollRef} height={30}>
        <VirtualList
          items={items}
          scrollRef={scrollRef}
          columns={process.stdout.columns ?? 80}
          itemKey={(item) => item.id}
          renderItem={(item) => (
            <Box paddingX={1}>
              <Text>{item.text}</Text>
            </Box>
          )}
        />
      </ScrollBox>
    </AlternateScreen>
  )
}
```

**Props:** `items` (array), `scrollRef` (ref to parent ScrollBox), `columns` (terminal width), `itemKey` (extract unique key), `renderItem` (render function), `resultRef` (optional, for imperative access to VirtualScrollResult).

### Markdown

Render markdown as styled ANSI text with streaming support.

```tsx
import { Markdown } from 'ink-terminal/markdown'
import { Box } from 'ink-terminal'

// Static rendering
<Box padding={1}>
  <Markdown>{`
# Hello World

This is **bold** and *italic* with \`inline code\`.

- List item 1
- List item 2

> Blockquote text

\`\`\`
code block
\`\`\`
  `}</Markdown>
</Box>

// Streaming mode — only re-parses the unstable tail (O(delta))
function StreamingChat() {
  const [text, setText] = useState('')
  // ... append text from streaming API ...
  return <Markdown streaming>{text}</Markdown>
}
```

**Props:** `children` (markdown string), `streaming` (enable incremental parsing), `dimColor` (dim all output).

### FpsCounter

Real-time frame rate display.

```tsx
import { FpsCounter } from 'ink-terminal'

// Compact: "60 fps  2.1ms  #142"
<FpsCounter />

// Detailed: adds per-phase breakdown
<FpsCounter detailed />

// Custom sample window
<FpsCounter sampleWindowMs={2000} />
```

**Props:** `detailed` (show render/diff/write/yoga phase timing), `sampleWindowMs` (rolling window, default 1000ms).

### Other Components

```tsx
import { Spacer, Newline, NoSelect, RawAnsi, Ansi } from 'ink-terminal'

// Spacer — fills available space
<Box>
  <Text>Left</Text>
  <Spacer />
  <Text>Right</Text>
</Box>

// Newline — insert line breaks inside Text
<Text>Line 1<Newline />Line 2<Newline count={2} />Line 4</Text>

// NoSelect — exclude from text selection in fullscreen mode
<NoSelect><Text dim>│</Text></NoSelect>

// RawAnsi — bypass ANSI parsing for pre-rendered content
<RawAnsi lines={['\x1b[31mRed\x1b[0m']} width={80} />

// Ansi — parse and render ANSI escape codes
<Ansi>{'\x1b[1;32mBold green\x1b[0m normal'}</Ansi>
<Ansi dimColor>{ansiString}</Ansi>
```

---

## Hooks

### useApp()

Access the app instance to manually exit.

```tsx
import { useApp } from 'ink-terminal'

function App() {
  const { exit } = useApp()
  // exit()        — normal exit
  // exit(error)   — exit with error
}
```

### useInput(handler, options?)

Handle keyboard and mouse input.

```tsx
import { useInput } from 'ink-terminal'

function App() {
  useInput((input, key, event) => {
    // input: raw character ('a', 'A', ' ', etc.)
    // key: parsed key object
    if (key.escape) { /* Escape pressed */ }
    if (key.return) { /* Enter pressed */ }
    if (key.ctrl && input === 'c') { /* Ctrl+C */ }
    if (key.upArrow) { /* Up arrow */ }
    if (key.downArrow) { /* Down arrow */ }
    if (key.leftArrow) { /* Left arrow */ }
    if (key.rightArrow) { /* Right arrow */ }
    if (key.tab) { /* Tab */ }
    if (key.backspace) { /* Backspace */ }
    if (key.delete) { /* Delete */ }
    if (key.pageUp) { /* Page Up */ }
    if (key.pageDown) { /* Page Down */ }
    if (key.home) { /* Home */ }
    if (key.end) { /* End */ }

    // Disable: useInput(handler, { isActive: false })
  })
}
```

### useFps(sampleWindowMs?)

Measure real terminal frame rate.

```tsx
import { useFps } from 'ink-terminal'

function DebugOverlay() {
  const { fps, lastFrameMs, totalFrames, phases } = useFps()

  return (
    <Box gap={1}>
      <Text color={fps >= 50 ? 'ansi:green' : 'ansi:red'}>{fps} fps</Text>
      <Text dim>{lastFrameMs}ms/frame</Text>
      <Text dim>#{totalFrames}</Text>
      {phases && <Text dim>yoga:{phases.yoga.toFixed(1)}ms patches:{phases.patches}</Text>}
    </Box>
  )
}
```

**Returns:** `{ fps, lastFrameMs, totalFrames, phases }`. Phases include: `renderer`, `diff`, `optimize`, `write`, `yoga`, `commit`, `patches`, `yogaVisited`, `yogaMeasured`, `yogaCacheHits`, `yogaLive`.

### useInterval(callback, intervalMs)

Interval timer backed by shared clock. Pass `null` to pause.

```tsx
import { useInterval } from 'ink-terminal'

function Clock() {
  const [time, setTime] = useState(new Date())
  useInterval(() => setTime(new Date()), 1000)
  return <Text>{time.toLocaleTimeString()}</Text>
}

// Pause with null
useInterval(callback, isPaused ? null : 1000)
```

### useAnimationFrame(intervalMs?)

Frame-synced animation with viewport detection (pauses when offscreen).

```tsx
import { useAnimationFrame, Box, Text } from 'ink-terminal'

function Spinner() {
  const FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
  const [ref, time] = useAnimationFrame(80)
  const frame = Math.floor(time / 80) % FRAMES.length

  return (
    <Box ref={ref}>
      <Text color="ansi:cyan">{FRAMES[frame]} Loading...</Text>
    </Box>
  )
}

// Pause: useAnimationFrame(null)
```

**Returns:** `[ref, time]` — attach ref to the animated element, time is elapsed ms.

### useAnimationTimer(intervalMs)

Like `useAnimationFrame` but without the viewport ref — returns only the clock time.

```tsx
import { useAnimationTimer } from 'ink-terminal'

const time = useAnimationTimer(16) // ~60fps updates
const shimmerOffset = Math.floor(time / 100) % 20
```

### useStdin()

Access raw stdin stream and control raw mode.

```tsx
import { useStdin } from 'ink-terminal'

function App() {
  const { setRawMode } = useStdin()
  // setRawMode(true)  — enable raw mode (for key-by-key input)
  // setRawMode(false) — disable raw mode
}
```

### useTerminalViewport()

Detect if a component is within the terminal viewport.

```tsx
import { useTerminalViewport, Box, Text } from 'ink-terminal'

function LazyContent() {
  const [ref, { isVisible }] = useTerminalViewport()
  return (
    <Box ref={ref}>
      {isVisible ? <Text>Visible content</Text> : <Text dim>Offscreen</Text>}
    </Box>
  )
}
```

### useTerminalTitle(title)

Set the terminal tab/window title. Pass `null` to opt out.

```tsx
import { useTerminalTitle } from 'ink-terminal'

function App() {
  useTerminalTitle('My App — Running')
  // Changes the terminal tab title via OSC 0
}
```

### useTabStatus(kind)

Set terminal tab status indicator (iTerm2, Kitty, etc.).

```tsx
import { useTabStatus } from 'ink-terminal'

function App({ isLoading }) {
  useTabStatus(isLoading ? 'busy' : 'idle')
  // Values: 'idle' | 'busy' | 'waiting' | null
}
```

### useTerminalFocus()

Check if the terminal window has focus (DECSET 1004 focus reporting).

```tsx
import { useTerminalFocus, Text } from 'ink-terminal'

function FocusIndicator() {
  const focused = useTerminalFocus()
  return <Text>{focused ? '● Focused' : '○ Blurred'}</Text>
}
```

### useSelection()

Access text selection operations (fullscreen/alt-screen only).

```tsx
import { useSelection } from 'ink-terminal'

function App() {
  const selection = useSelection()
  // selection.hasSelection()      — check if text is selected
  // selection.copySelection()     — copy and clear
  // selection.clearSelection()    — clear selection
  // selection.subscribe(cb)       — subscribe to changes
}
```

### useVirtualScroll(scrollRef, itemKeys, columns)

Low-level virtual scroll hook (used internally by VirtualList).

```tsx
import { useVirtualScroll, ScrollBox, Box } from 'ink-terminal'
import { useRef, useMemo } from 'react'

function CustomVirtualList({ items }) {
  const scrollRef = useRef(null)
  const keys = useMemo(() => items.map(i => i.id), [items])
  const { range, topSpacer, bottomSpacer, measureRef, spacerRef } =
    useVirtualScroll(scrollRef, keys, process.stdout.columns ?? 80)

  const [start, end] = range

  return (
    <ScrollBox ref={scrollRef}>
      <Box ref={spacerRef} height={topSpacer} />
      {items.slice(start, end).map((item, i) => (
        <Box key={item.id} ref={measureRef(item.id)}>
          <Text>{item.text}</Text>
        </Box>
      ))}
      <Box height={bottomSpacer} />
    </ScrollBox>
  )
}
```

**Returns:** `{ range, topSpacer, bottomSpacer, measureRef, spacerRef, offsets, getItemTop, getItemElement, getItemHeight, scrollToIndex }`.

---

## Configuration

Inject app-specific callbacks. All callbacks are on cold paths — hot rendering has zero external calls.

```tsx
import { configure } from 'ink-terminal'

configure({
  logForDebugging: (msg) => myLogger.debug(msg),
  logError: (msg, ...args) => myLogger.error(msg, ...args),
  terminalName: 'my-app',
  onBeforeRender: () => { /* pre-frame hook */ },
  onScrollActivity: () => { /* scroll analytics */ },
  onInteraction: (immediate) => { /* interaction timestamp */ },
  onFirstInput: () => { /* first keystroke detected */ },
  isMouseClicksDisabled: () => false,
  isEnvTruthy: (v) => v === '1' || v === 'true',
})
```

---

## Frame Events

Subscribe to per-frame timing data for performance monitoring.

```tsx
// Via render options
const instance = await render(<App />, {
  onFrame: (event) => {
    console.log(`Frame: ${event.durationMs.toFixed(1)}ms`)
    if (event.phases) {
      console.log(`  Yoga: ${event.phases.yoga.toFixed(1)}ms`)
      console.log(`  Diff: ${event.phases.diff.toFixed(1)}ms`)
      console.log(`  Write: ${event.phases.write.toFixed(1)}ms`)
      console.log(`  Patches: ${event.phases.patches}`)
    }
    if (event.flickers.length > 0) {
      console.warn('Flickers detected:', event.flickers)
    }
  },
})

// Or via useFps hook inside components
const { fps, lastFrameMs, phases } = useFps()
```

**FrameEvent phases:** `renderer` (DOM→Yoga→screen), `diff` (screen diff→patches), `optimize` (patch merge), `write` (ANSI→stdout), `yoga` (layout), `commit` (React reconcile), `patches` (count), `yogaVisited`, `yogaMeasured`, `yogaCacheHits`, `yogaLive`.

---

## Styles Reference

### Color

```
'ansi:red'                    // 16 named ANSI colors
'ansi:greenBright'            // Bright variants
'#FF6B6B'                     // Hex
'rgb(255,99,71)'              // RGB
'ansi256(214)'                // 256-color palette
```

Full named colors: `black`, `red`, `green`, `yellow`, `blue`, `magenta`, `cyan`, `white`, plus `Bright` variants of each.

### Border Styles

```
┌──────┐  ╔══════╗  ╭──────╮  ┏━━━━━━┓
│single│  ║double║  │round │  ┃ bold ┃
└──────┘  ╚══════╝  ╰──────╯  ┗━━━━━━┛

+------+  ╓──────╖  ╒══════╕   ╌╌╌╌╌╌
|classc|  ║sngDbl║  │dblSng│  ╎dashed╎
+------+  ╙──────╜  ╘══════╛   ╌╌╌╌╌╌
```

### Text Wrap Modes

| Mode | Behavior |
|------|----------|
| `'wrap'` | Wrap at container width (default) |
| `'truncate-end'` | `Long text that gets cu…` |
| `'truncate-start'` | `…e end of the string` |
| `'truncate-middle'` | `Long te…he string` |

---

## Core Layer (No React)

Use the screen buffer and diff engine directly without React.

```typescript
import {
  createScreen, CharPool, StylePool, HyperlinkPool,
  setCellAt, cellAt, diffEach, blitRegion, CellWidth,
} from 'ink-terminal/core'

// Create pools and screen buffers
const charPool = new CharPool()
const stylePool = new StylePool()
const hyperlinkPool = new HyperlinkPool()
const screen = createScreen(80, 24, stylePool, charPool, hyperlinkPool)

// Write to cells
const charId = charPool.intern('A')
const styleId = stylePool.intern({ bold: true })
setCellAt(screen, 0, 0, charId, styleId, CellWidth.Narrow)

// Diff two screens
const oldScreen = createScreen(80, 24, stylePool, charPool, hyperlinkPool)
diffEach(oldScreen, screen, (x, y, removed, added) => {
  // Handle cell changes at (x, y)
})

// Copy regions between screens
blitRegion(screen, oldScreen, 0, 0, 40, 12)
```

### Core utilities

```typescript
import {
  colorize, applyTextStyles, applyColor,   // ANSI color generation
  measureText, wrapText, widestLine,        // Text measurement
  parseMultipleKeypresses, INITIAL_STATE,   // Keyboard input parsing
  clearTerminal, getClearTerminalSequence,  // Terminal control
  LogUpdate,                                 // Diff engine
  optimize,                                  // Patch optimizer
  reorderBidi,                               // Bidirectional text
  expandTabs,                                // Tab expansion
} from 'ink-terminal/core'
```

---

## Terminal I/O (termio)

Low-level terminal escape sequence generation and parsing.

```typescript
import {
  // CSI sequences
  cursorTo, cursorUp, cursorDown, cursorForward, cursorBack,
  eraseLines, eraseScreen, scrollUp, scrollDown, setScrollRegion,

  // DEC sequences
  SHOW_CURSOR, HIDE_CURSOR,
  ENTER_ALT_SCREEN, EXIT_ALT_SCREEN,
  BSU, ESU,  // Begin/End Synchronized Update
  ENABLE_MOUSE_TRACKING, DISABLE_MOUSE_TRACKING,
  decset, decreset,

  // OSC sequences
  link, linkEnd,          // Hyperlinks
  tabStatus, clearTabStatus, supportsTabStatus,
  setClipboard,           // Clipboard access

  // Parser
  Parser,                 // Streaming ANSI parser

  // Tokenizer
  createTokenizer,        // stdin → Token stream
} from 'ink-terminal/termio'

// Example: atomic screen update
process.stdout.write(BSU)                          // Begin synchronized update
process.stdout.write(cursorTo(0, 0))               // Move cursor
process.stdout.write(eraseScreen)                   // Clear
process.stdout.write('Hello world')                 // Content
process.stdout.write(ESU)                           // End synchronized update

// Example: parse ANSI stream
const parser = new Parser()
const actions = parser.feed('\x1b[31mRed text\x1b[0m normal')
// actions: [{ type: 'style', ... }, { type: 'text', graphemes: [...] }, ...]
```

---

## Events

```typescript
import {
  InputEvent,           // Keyboard/mouse input
  KeyboardEvent,        // DOM-style keyboard event (bubbles)
  ClickEvent,           // Mouse click (bubbles)
  FocusEvent,           // Focus/blur
  TerminalFocusEvent,   // Terminal window focus
  PasteEvent,           // Bracketed paste
  ResizeEvent,          // Terminal resize
} from 'ink-terminal/events'
```

---

## Rendering Pipeline

```
React setState
    │
    ▼
scheduleRender (lodash.throttle 16ms, leading+trailing)
    │
    ▼
queueMicrotask (defers to after useLayoutEffect)
    │
    ▼
onRender()
    ├── Yoga layout calculation
    ├── DFS tree traversal → Screen buffer
    │   ├── Blit fast path (clean nodes → TypedArray memcpy)
    │   └── Dirty nodes → re-render to cells
    ├── Diff (damage-bounded, 2 int comparisons/cell)
    ├── Optimize patches (merge cursor moves, dedup hyperlinks)
    └── Write to terminal (BSU → ANSI escapes → ESU)
```

---

## Examples

16 runnable examples in `examples/`:

```bash
bun run examples/hello.tsx            # Minimal app
bun run examples/text-styles.tsx      # Color palette + text decorations
bun run examples/layout.tsx           # Flexbox showcase with all border styles
bun run examples/table.tsx            # Table layout with data
bun run examples/scrollbox.tsx        # Scrollable container
bun run examples/animation.tsx        # Timer animations
bun run examples/streaming.tsx        # Character-by-character streaming
bun run examples/markdown.tsx         # Static + streaming markdown
bun run examples/link.tsx             # Terminal hyperlinks
bun run examples/progress.tsx         # Progress bars + spinners + pipeline
bun run examples/hooks-demo.tsx       # Clock, viewport, tab status, title
bun run examples/fps-demo.tsx         # FpsCounter + useFps hook
bun run examples/button.tsx           # Interactive buttons (needs TTY)
bun run examples/interactive.tsx      # Keyboard input (needs TTY)
bun run examples/alternate-screen.tsx # Full-screen todo app (needs TTY)
bun run examples/virtual-list.tsx     # 1000-item virtual scroll (needs TTY)
```

---

## Project Stats

- **120+ TypeScript files**, **24,000+ lines of code**
- **27 rendering optimizations**
- **231 tests**, strict mode (`tsc --noEmit`)
- Pure TypeScript Yoga layout engine (no WASM/native addon)
- 19 npm dependencies, 2 peer dependencies (`react`, `react-reconciler`)
- Bun + Node.js ≥20 runtime support

## License

MIT — see [LICENSE](./LICENSE).
