* feat: 第一版大重构 * fix: 修复类型问题 * chore: 更新版本到 1.3.2 * Add brave as alternative WebSearchTool * fix: 修正顺序 * fix: 修复对穷鬼模式的 auto dream 和 session memory 越过 * feat: 穷鬼模式去除 session-summary * feat: 创建 builtin-tools 包,搬运所有工具实现 将 src/tools/ 下的全部 60 个工具目录迁移至 packages/builtin-tools/src/tools/, 内部导入路径已更新为 src/ alias 模式。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: 更新 src/ 中所有工具引用至 builtin-tools 包,删除 src/tools/ - src/tools.ts 及 178 个 src/ 文件的 import 路径从 ./tools/ 改为 builtin-tools/tools/ - 删除 src/tools/ 整个目录(已迁移至 packages/builtin-tools/) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: 添加 builtin-tools 路径别名至 tsconfig,更新 bun.lock - tsconfig.json 新增 builtin-tools/* 和 builtin-tools 路径映射 - 新增 packages/builtin-tools/src 至 include Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: 为 builtin-tools、mcp-client、agent-tools 添加 @claude-code-best 作用域前缀 所有包名及 import 路径统一添加 @claude-code-best/ 前缀: - builtin-tools → @claude-code-best/builtin-tools - mcp-client → @claude-code-best/mcp-client - agent-tools → @claude-code-best/agent-tools Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: 修复 node 环境没有 bun 的问题 --------- Co-authored-by: Eric-Guo <eric.guocz@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
8.4 KiB
Chapter 11: Core Architecture
This chapter covers the internal rendering pipeline, DOM model, and screen buffer system. This is advanced material -- most users only need the component and hooks APIs.
Rendering Pipeline
React Component Tree
↓ (React reconciler)
Ink DOM Tree (virtual terminal DOM)
↓ (Yoga layout)
Positioned DOM Tree (computed x, y, width, height)
↓ (renderNodeToOutput)
Output Buffer (styled characters)
↓ (renderer → Screen)
Screen Buffer (Int32Array of cells)
↓ (diffEach)
ANSI Diff Patches (minimal escape sequences)
↓ (writeDiffToTerminal)
Terminal stdout
Frame Lifecycle
Each render cycle (onRender) follows these phases:
- React Commit -- React reconciles the virtual tree; host config updates Ink DOM
- Yoga Layout -- All dirty nodes have their styles applied and layout computed
- Renderer -- Creates Output buffer, calls
renderNodeToOutputfor the full tree - Screen Diff -- New frame is compared against previous frame cell-by-cell
- Optimize -- Patches are merged and ordered for minimal cursor movement
- Write -- ANSI escape sequences are written to stdout
Frame Timing
const FRAME_INTERVAL_MS = 16 // ~60fps cap
Renders are throttled. Multiple state updates in one frame are batched.
Double Buffering
Two frames are maintained:
frontFrame-- The currently displayed framebackFrame-- The frame being rendered
After rendering, they are swapped. This prevents partial updates from being visible.
Ink DOM
Node Types
type ElementNames =
| 'ink-root' // Root container
| 'ink-box' // Box component
| 'ink-text' // Text component
| 'ink-virtual-text' // Intermediate text wrapper
| 'ink-link' // Link component
| 'ink-raw-ansi' // Raw ANSI content
DOMElement
type DOMElement = {
nodeName: ElementNames
attributes: Record<string, unknown>
childNodes: DOMNode[] // DOMElement | TextNode
yogaNode?: LayoutNode // Yoga layout node
textStyles?: TextStyles // Inherited text styles
// Scroll state
scrollTop?: number
scrollHeight?: number
scrollViewportHeight?: number
scrollViewportTop?: number
stickyScroll?: boolean
pendingScrollDelta?: number
scrollAnchor?: { el: DOMElement; offset: number }
// Dirty tracking
dirty: boolean
// Event handlers (stored separately)
onClick?: (event: ClickEvent) => void
onFocus?: (event: FocusEvent) => void
onBlur?: (event: FocusEvent) => void
onKeyDown?: (event: KeyboardEvent) => void
onMouseEnter?: () => void
onMouseLeave?: () => void
}
TextNode
type TextNode = {
nodeName: '#text'
nodeValue: string
yogaNode?: LayoutNode
}
DOM Operations
// Node creation
createNode(nodeName: string): DOMElement
createTextNode(text: string): TextNode
// Tree manipulation
appendChildNode(parent: DOMElement, child: DOMNode): void
insertBeforeNode(parent: DOMElement, child: DOMNode, before: DOMNode): void
removeChildNode(parent: DOMElement, child: DOMNode): void
// Attribute manipulation
setAttribute(node: DOMElement, key: string, value: unknown): void
setStyle(node: DOMElement, style: Styles): void
setTextStyles(node: DOMElement, styles: TextStyles): void
// Dirty tracking
markDirty(node: DOMElement): void
scheduleRenderFrom(node: DOMElement): void
Screen Buffer
Cell Storage
The screen buffer uses packed Int32Array storage for memory efficiency:
type Screen = {
width: number
height: number
cells: Int32Array // 2 Int32s per cell: [charId, packed_style_hyperlink_width]
cells64: BigInt64Array // For bulk fill operations
charPool: CharPool // String interning
stylePool: StylePool // ANSI code interning
hyperlinkPool: HyperlinkPool
emptyStyleId: number
damage: Rectangle | undefined // Bounding box of changed cells
noSelect: Uint8Array // Per-cell no-select bitmap
softWrap: Int32Array // Per-row soft-wrap markers
}
Cell Width
enum CellWidth {
Narrow = 0, // Regular character (1 column)
Wide = 1, // CJK/emoji (2 columns)
SpacerTail = 2, // Right half of wide character
SpacerHead = 3, // Soft-wrapped wide character
}
Style Pool
ANSI style codes are interned for efficiency:
class StylePool {
intern(codes: AnsiCode[]): number // Returns compact ID
get(id: number): AnsiCode[]
transition(from: number, to: number): string // Cached ANSI transition
withInverse(id: number): number // Selection overlay
setSelectionBg(bg: AnsiCode): void // Theme-aware selection bg
}
Diff Algorithm
diffEach(prev: Screen, next: Screen, callback: (x, y, oldCell, newCell) => void): void
Only iterates cells within the damage bounding box. Unchanged regions are skipped entirely.
Screen Operations
createScreen(width, height, stylePool, charPool, hyperlinkPool): Screen
setCellAt(screen, x, y, cell): void
cellAt(screen, x, y): Cell
clearRegion(screen, x, y, width, height): void
blitRegion(dst, src, x, y, maxX, maxY): void
shiftRows(screen, top, bottom, n): void
Layout Engine
Yoga Integration
Ink wraps Facebook's Yoga layout engine for Flexbox computation:
// Layout node types
enum LayoutDisplay { Flex, None }
enum LayoutPositionType { Absolute, Relative }
enum LayoutOverflow { Visible, Hidden, Scroll }
enum LayoutFlexDirection { Row, Column, RowReverse, ColumnReverse }
enum LayoutWrap { NoWrap, Wrap, WrapReverse }
enum LayoutAlign { FlexStart, Center, FlexEnd, Stretch }
enum LayoutJustify { FlexStart, Center, FlexEnd, SpaceBetween, SpaceAround, SpaceEvenly }
enum LayoutEdge { Top, Bottom, Left, Right, Start, End, Horizontal, Vertical, All }
enum LayoutGutter { Column, Row, All }
Style Application
Styles from React props are applied to Yoga nodes during the commit phase:
function styles(node: LayoutNode, style: Styles, resolvedStyle?: Styles): void
This function maps each CSS-like prop to the corresponding Yoga setter.
Output Buffer
Intermediate rendering target before screen diff:
class Output {
write(text: string, x: number, y: number, styles: TextStyles): void
wrap(width: number, textWrap: TextWrap): void
}
renderNodeToOutput walks the DOM tree and writes styled characters into this buffer.
Reconciler
Custom React reconciler that bridges React and the Ink DOM:
- Host config -- Defines how React operations map to Ink DOM mutations
- Concurrent mode -- Supports
ConcurrentRootfor React 19 features - Yoga integration -- Applies styles during commit phase
- DevTools -- Connected in development mode
Host Config Methods
| Method | Purpose |
|---|---|
createInstance |
Create ink-box, ink-text, etc. |
createTextInstance |
Create #text node |
appendChildNode |
Add child to parent |
removeChildNode |
Remove child from parent |
insertBefore |
Insert child before sibling |
commitUpdate |
Update element attributes/styles |
commitTextUpdate |
Update text content |
getPublicInstance |
Return DOMElement for refs |
Performance Optimizations
- String Interning -- CharPool deduplicates character strings across frames
- Style Caching -- StylePool caches ANSI transition strings
- Damage Tracking -- Only diff cells within the changed bounding box
- Bulk Operations --
Int32Array.set()for fast region blit - Throttled Rendering -- Frame rate capped at ~60fps
- Viewport Culling -- ScrollBox only renders visible children
- Microtask Coalescing -- Multiple scroll deltas merged into one render
Frame Events
Debug instrumentation for render performance:
type FrameEvent = {
durationMs: number
phases: {
renderer: number // Yoga + renderNodeToOutput
diff: number // Screen diff
optimize: number // Patch optimization
write: number // Terminal write
patches: number // Number of ANSI patches
yoga: number // Yoga layout time
commit: number // React commit time
yogaVisited: number // Yoga nodes visited
yogaMeasured: number // Yoga nodes measured
yogaCacheHits: number // Cached measurements
yogaLive: number // Active Yoga nodes
}
flickers: FlickerReason[]
}
Enable with onFrame in RenderOptions:
render(<App />, {
onFrame: (event) => {
console.log(`Frame: ${event.durationMs}ms`)
}
})