Pipeline Phases
Phase 1: State → View
Input: Application state objectOutput: VNode tree
Location:
packages/core/src/app/widgetRenderer.ts
The application’s view(state) function is called to produce a declarative VNode tree. This function must be pure—no side effects, no mutations.
Phase 2: VNode Tree → Commit
Input: VNode treeOutput: RuntimeInstance tree
Location:
packages/core/src/runtime/commit.ts
The commit phase reconciles the new VNode tree against the previous committed tree:
- Allocate instance IDs for new nodes
- Validate uniqueness of interactive widget IDs
- Match children using keys (keyed) or position (unkeyed)
- Track lifecycle — mounted, reused, unmounted instances
- Clean up local state for unmounted instances
- Match by position if widget kinds are compatible
- Reuse existing RuntimeInstance when possible
- Create new instances for additions
- Unmount instances for removals
key prop):
- Build lookup map of previous children by key
- Match new children to previous children by key
- Reuse when key matches
- Fatal error if duplicate keys detected
packages/core/src/runtime/reconcile.ts
Phase 3: Layout Computation
Input: RuntimeInstance tree + viewport sizeOutput: LayoutTree with cell coordinates
Location:
packages/core/src/layout/layout.ts
The layout engine computes cell coordinates for every widget:
- Measure pass — compute intrinsic sizes via
measureMinContent/measureMaxContent - Constraint solving — resolve flex, grid, and absolute positioning
- Position assignment — assign final
(x, y, width, height)to each node - Overflow detection — track scroll regions and clipping bounds
- Stack flex layout (
packages/core/src/layout/engine/flex.ts) - Grid layout with auto-placement (
packages/core/src/layout/kinds/grid.ts) - Intrinsic sizing protocol (
packages/core/src/layout/engine/intrinsic.ts) - Absolute positioning (
packages/core/src/layout/positioning.ts)
Phase 4: Metadata Collection
Input: LayoutTreeOutput: Focus metadata, scroll targets, overlay geometry Collects widget-specific metadata:
- Focusable widgets — IDs and bounding boxes for focus traversal
- Scroll targets — regions with
overflow: "scroll" - Dropdown geometry — overlay positioning constraints
packages/core/src/runtime/focus.ts
Phase 5: Overlay Manager (LayerRegistry)
Input: Metadata from overlays (modals, dropdowns, tooltips)Output: Resolved z-order and clipping
Location:
packages/core/src/layout/dropdownGeometry.ts
Manages overlays with proper stacking:
- Computes dropdown positioning relative to anchors
- Resolves z-order for modals and command palettes
- Applies overlay sizing constraints
Phase 6: Render to Drawlist
Input: LayoutTreeOutput: ZRDL binary drawlist
Location:
packages/core/src/renderer/renderToDrawlist/renderTree.ts
Stack-based depth-first traversal that emits rendering commands:
OP_CLEAR— clear framebufferOP_FILL_RECT— fill rectangle with styleOP_DRAW_TEXT— draw string at positionOP_PUSH_CLIP/OP_POP_CLIP— manage clipping regionsOP_SET_CURSOR— position terminal cursorOP_DRAW_TEXT_RUN— multi-segment styled text
Phase 7: Builder Build
Input: Accumulated commands in builderOutput:
Uint8Array containing ZRDL binaryLocation:
packages/core/src/drawlist/builder_v1.ts
The builder finalizes the drawlist:
- Write 64-byte header with section offsets
- Write command stream
- Write string span table and byte pool
- Write blob span table and byte pool
- Validate alignment and caps
Phase 8: Backend Submit
Input: ZRDL binaryOutput: Transfer to worker thread (or inline submission)
Location:
packages/node/src/backend/nodeBackend.ts
Depends on execution mode:
- Worker mode: Transfer buffer to worker thread via
postMessage - Inline mode: Submit directly to engine on main thread
- SAB mode: Write to SharedArrayBuffer mailbox slot
Phase 9: Engine Present
Input: ZRDL binaryOutput: ANSI bytes written to terminal
Engine: Zireael C engine The engine:
- Validates ZRDL header and version
- Parses commands into internal command buffer
- Executes commands against next framebuffer
- Diffs previous vs. next framebuffer
- Emits minimal ANSI escape sequences
- Writes single ANSI chunk to terminal
Reconciliation Details
Keyed Reconciliation
When children have explicitkey props:
- Build map:
prevChildrenByKey = new Map([[key, instance], ...]) - For each new child:
- Look up
prevChildrenByKey.get(newChild.key) - Reuse instance if found
- Create new instance if not found
- Look up
- Unmount instances with keys not in new children
- Duplicate keys in new children →
ZRUI_DUPLICATE_KEY - Duplicate widget IDs across tree →
ZRUI_DUPLICATE_ID
Unkeyed Reconciliation
When children lack explicit keys, match by position:- Match
newChildren[i]withprevChildren[i] - Reuse if widget kinds are compatible
- Create new instance if kind changed
- Unmount trailing previous children if new array is shorter
- Mount new children if new array is longer
Optimization Strategies
String Interning
Within a single drawlist, identical strings are stored once in the string table. AllOP_DRAW_TEXT commands reference the same string index.
Benefit: Reduces drawlist size for repeated strings (labels, headers, etc.)
Encoded String Cache
Optional cache for UTF-8 encoded strings across frames:TextEncoder.encode() calls for stable strings
Trade-off: Memory overhead; disabled by default
Dirty Tracking
The layout engine tracks which nodes changed since the last frame:Incremental Layout (Experimental)
Layout engine supports partial layout updates for localized changes:- Only recompute layout for dirty subtrees
- Reuse layout results for clean subtrees
Performance Characteristics
| Phase | Typical Time (1000-node tree) | Notes |
|---|---|---|
| Commit | ~500 µs | Dominated by reconciliation |
| Layout | ~800 µs | Depends on constraint complexity |
| Render | ~400 µs | String interning helps |
| Build | ~100 µs | Binary writing is fast |
| Engine | ~200 µs | Native C diff rendering |
Related Documentation
- Layout Engine — Constraint solving algorithms
- Event System — Event routing back to widgets
- ZRDL Protocol — Binary drawlist format
- Determinism — Reproducibility guarantees