Skip to content

Node/Bun backend

The Node/Bun backend owns:

  • native engine execution mode (auto | worker | inline)
  • frame scheduling and buffer pooling
  • transfer of drawlists to the engine and event batches back to core

Most apps should construct the app via:

import { createNodeApp } from "@rezi-ui/node";

const app = createNodeApp({
  initialState: { count: 0 },
  config: {
    executionMode: "auto",
    fpsCap: 60,
    maxEventBytes: 1 << 20,
  },
});

createNodeApp is the recommended path because it keeps core/backend config knobs aligned:

  • maxEventBytes is applied to both app parsing and backend transport buffers.
  • fpsCap is the single scheduling knob.
  • executionMode: "auto" resolves to inline when fpsCap <= 30, otherwise it prefers worker mode and falls back to inline for headless runs without a TTY or nativeShimModule.

Development hot reload:

  • createNodeApp({ hotReload: ... }) watches source files and hot-swaps either:
  • widget views via app.replaceView(...), or
  • route tables via app.replaceRoutes(...) for route-managed apps.
  • lifecycle is automatic: watcher starts/stops with app.start()/stop()/run().
  • app.hotReload exposes manual reloadNow() when needed.
  • low-level createHotStateReload(...) remains available for advanced manual wiring.
  • Raw draw mode remains excluded.

Execution mode details:

  • auto (default): select inline for low-fps workloads (fpsCap <= 30); otherwise prefer worker mode and fall back to inline when no TTY or nativeShimModule is available.
  • worker: force worker-thread engine execution. With the real native addon this requires an interactive TTY; test harnesses can provide nativeShimModule instead.
  • inline: run the engine inline on the main JS thread.

For headless snapshots and unit tests, prefer createTestRenderer() / @rezi-ui/testkit over the real native backend.

NO_COLOR support:

  • If NO_COLOR is present in the process environment, createNodeApp(...) forces a monochrome theme (no semantic color accents).
  • createNodeApp(...) returns an app object with app.isNoColor so app code can detect this mode.
import { createNodeApp } from "@rezi-ui/node";

const app = createNodeApp({ initialState: {} });
if (app.isNoColor) {
  // Optional app-level behavior for colorless terminals/CI.
}

Screen mode (alt vs inline):

  • screen.mode selects the terminal presentation: "alt" (default, full-screen on the alternate buffer) or "inline" (a bounded region of screen.inlineRows rows on the primary screen with scrollback preserved above and the final frame left in scrollback on exit).
  • screen.inlineRows (1..1024) is required with "inline" and rejected otherwise; the effective viewport is clamped to the live terminal height.
  • Unrelated to executionMode: "inline" (engine-on-main-thread); the two options compose freely.
  • See the Screen Modes guide and examples/inline-status for a runnable app.
const app = createNodeApp({
  initialState: {},
  config: {
    screen: { mode: "inline", inlineRows: 9 },
  },
});

Inline screen-mode runtime APIs (engine ABI 1.4.0+):

  • app.printAbove(view, opts?) renders a standalone widget tree and commits it into terminal scrollback above the region (backend commitScrollback(drawlist, rows)).
  • app.setInlineRows(rows) changes the inline viewport height at runtime (backend setInlineRows(rows)), re-sending the created runtime config with the new height.
  • Both reject unless the backend was created with screen.mode: "inline".

Emoji width policy:

  • emojiWidthPolicy keeps core text measurement and native rendering aligned.
  • Allowed values:
  • "auto" (default): resolve from native overrides/env, optional probe, then fallback to "wide".
  • "wide": emoji clusters occupy at least 2 cells.
  • "narrow": emoji clusters occupy at least 1 cell.
  • Native overrides (nativeConfig.widthPolicy / width_policy) must match explicit policy values.
  • Optional terminal probe is disabled by default and only runs when ZRUI_EMOJI_WIDTH_PROBE=1 (opt-in to avoid startup input races).

Manual composition (advanced):

  • Prefer createNodeApp() for typical apps so related runtime knobs cannot drift.
  • If you compose manually with createNodeBackend() + createApp() (@rezi-ui/core), Rezi throws deterministic ZRUI_INVALID_PROPS errors when cursor/event/fps settings conflict.

Next: Worker model.