@rezi-ui/node is the Node.js/Bun backend for Rezi. This package owns terminal I/O, worker thread lifecycle, frame scheduling, and buffer transport between the TypeScript core and the native engine.
Overview
Responsibilities:- Configurable native engine execution mode (
auto|worker|inline) - Frame scheduling and buffer pooling
- Transfer of drawlists/events between core and native addon
- Terminal profile detection and capability negotiation
- Widget logic, layout math (lives in
@rezi-ui/core) - Terminal rendering (lives in native Zireael engine)
Creating a Backend
Recommended: createNodeApp()
- Wires
@rezi-ui/coreand@rezi-ui/nodewith matched cursor protocol, event caps, and FPS settings - Automatic execution mode selection
- Hot reload support
Direct Backend Creation
Execution Modes
The backend supports three execution modes:| Mode | Description | Best for |
|---|---|---|
auto | Selects inline when fpsCap <= 30, otherwise worker | Default choice |
worker | Native engine runs on dedicated worker thread | High FPS, responsive apps |
inline | Engine runs on main thread | Low FPS, minimal overhead |
Auto Mode
Automatic mode selection logic:Worker Mode
Architecture: Benefits:- Main thread never blocks on terminal I/O
- Better for high FPS (60+)
- Parallel rendering and event processing
- Worker thread spawn overhead (~5-10ms)
- Buffer transfer overhead
- Slightly higher memory usage
- Transfer mode: Copy drawlist buffer via
postMessagewith transfer - SAB mode: Write to SharedArrayBuffer mailbox (when available)
Inline Mode
Architecture: Benefits:- Lower latency (no worker hop)
- Lower memory overhead
- Simpler debugging
- Main thread blocks during terminal I/O
- Not suitable for high FPS
Frame Transport
When using worker mode, drawlists are transferred to the worker thread via one of two mechanisms:Transfer Mode (default)
Mechanism: Copy drawlistArrayBuffer via postMessage with [buffer] transfer list.
Pros:
- Works everywhere (no SharedArrayBuffer requirement)
- Zero-copy transfer of ownership
- Main thread cannot reuse buffer immediately
- Slight latency from
postMessage
SAB Mode
Mechanism: Write drawlist to pre-allocated SharedArrayBuffer mailbox slot. Pros:- No transfer overhead
- Main thread can immediately reuse builder
- Better for high-frequency updates
- Requires
SharedArrayBuffersupport (Node.js 16+, or--experimental-shared-array-buffer) - Slightly higher memory usage (pre-allocated slots)
N slots. Each slot can hold one drawlist up to M bytes. If all slots are full, the backend falls back to transfer mode for that frame.
Backend Configuration
Key Options
executionMode — Worker vs. inline execution (default: "auto")fpsCap — Maximum frames per second (default: 30)drawlistVersion — ZRDL format version (default: 5)frameTransport — Transfer mechanism for worker mode (default: "auto")emojiWidthPolicy — Emoji cell width policy (default: "auto")
See: Backend API reference for full details
Terminal Profile Detection
The backend detects terminal capabilities from environment variables: Detected from:TERM— Terminal type (xterm-256color,screen, etc.)COLORTERM— Truecolor support (truecolor,24bit)TERM_PROGRAM— Terminal emulator name (iTerm.app,vscode, etc.)CI/GITHUB_ACTIONS/GITLAB_CI— CI environment detection
truecolor— 24-bit RGB color supportunicode— Unicode rendering supportmouse— Mouse event supportfocus— Focus tracking support
packages/node/src/backend/terminalProfile.ts
Emoji Width Policy
The backend aligns core text measurement with native rendering for emoji clusters:| Policy | Behavior |
|---|---|
"auto" | Use native/env overrides; optional probe when ZRUI_EMOJI_WIDTH_PROBE=1; fallback to “wide” |
"wide" | Emoji clusters consume 2 cells |
"narrow" | Emoji clusters consume 1 cell |
ZRUI_EMOJI_WIDTH=wide/narrow— Explicit overrideZRUI_EMOJI_WIDTH_PROBE=1— Enable terminal probing
packages/node/src/backend/emojiWidthPolicy.ts
Lifecycle
Initialization
- Detect terminal profile
- Apply emoji width policy
- Spawn worker thread (if worker mode)
- Initialize native engine
- Negotiate capabilities
Frame Submission
- Transfer drawlist to worker (or submit inline)
- Worker submits to native engine
- Engine validates and executes drawlist
- Engine diffs framebuffers and emits ANSI
- Engine writes ANSI to terminal
Event Polling
- Engine polls terminal for input
- Engine parses raw bytes into ZREV events
- Engine builds ZREV batch
- Worker transfers batch to main thread
- Core parses and routes events
Cleanup
- Signal worker to terminate
- Wait for worker exit
- Destroy native engine
- Restore terminal state
Debug API
The backend exposes a debug API:- Frame count
- Event count
- Bytes transferred
- Worker thread status
packages/node/src/backend/nodeBackend.ts
Performance Monitoring
WhenREZI_PERF=1 is set:
poll— Event polling timepresent— Frame present timetransfer— Buffer transfer time
count,avg,p50,p95,p99,max,worst10
Related Documentation
- Worker Model — Thread ownership and backpressure
- Native Addon — N-API binding details
- Terminal Detection — Capability detection
- Protocol Overview — Binary formats