Skip to content

refactor(node): split node backend by concern#284

Merged
RtlZeroMemory merged 1 commit intomainfrom
codex/split-node-backend
Mar 17, 2026
Merged

refactor(node): split node backend by concern#284
RtlZeroMemory merged 1 commit intomainfrom
codex/split-node-backend

Conversation

@RtlZeroMemory
Copy link
Copy Markdown
Owner

@RtlZeroMemory RtlZeroMemory commented Mar 17, 2026

Summary

  • Split packages/node/src/backend/nodeBackend.ts into internal helper modules.
  • Kept the public API unchanged.
  • Broke the nodeBackend.ts / nodeBackendInline.ts cycle.
  • No intended behavior change.

Why

Reduce the worker-backend monolith and remove a real architectural cycle before touching the worker entrypoint.

Validation

  • npm install (required in the fresh worktree so repo tooling and native deps were present)
  • npm run lint
  • npm run typecheck
  • npm run build
  • node scripts/run-tests.mjs --filter "packages/node/dist/__tests__/"
  • node scripts/run-tests.mjs --filter "packages/node/dist/__e2e__/" (matched 0 files in this repo's test runner)
  • node scripts/run-e2e.mjs
  • npm run build:native
  • node scripts/run-tests.mjs

PTY / Frame Audit Evidence

  • Deterministic PTY viewport used: 300x68, then resized to 120x40
  • Worker-mode audit run: REZI_STARSHIP_EXECUTION_MODE=worker REZI_FRAME_AUDIT=1 npx tsx packages/create-rezi/templates/starship/src/main.ts
  • Exercised start/stop lifecycle, frame submission, input/route changes, resize, and debug logging
  • node scripts/frame-audit-report.mjs /tmp/rezi-frame-audit.ndjson --latest-pid
  • Report summary:
    • records=17067
    • pid_filter=3791
    • backend_submitted=812
    • worker_payload=812
    • worker_accepted=812
    • worker_completed=812
    • hash_mismatch_backend_vs_worker=0
    • missing_worker_payload=0
    • missing_worker_accepted=0
    • missing_worker_completed=0
  • Route summary from audit:
    • engineering: submitted=544 completed=544 avgBytes=58970.0 avgCmds=645.0
    • bridge: submitted=255 completed=255 avgBytes=115692.4 avgCmds=826.7
    • crew: submitted=13 completed=13 avgBytes=27740.9 avgCmds=471.6
  • Runtime log evidence captured route/input/resize commands including go-engineering, go-crew, cycle-theme, quit, and resize to 120x40

Summary by CodeRabbit

  • New Features

    • Exported selectNodeBackendExecutionMode function for public use in execution mode selection.
    • Expanded public API with additional type exports for backend configuration and performance monitoring.
  • Refactor

    • Reorganized internal backend state management into modular, dedicated subsystems for improved maintainability and separation of concerns.

@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 17, 2026

📝 Walkthrough

Walkthrough

This pull request refactors the Node backend's internal state management by extracting monolithic inline logic into dedicated, modular state objects and helpers. Frame tracking, debug operations, execution mode selection, and frame transport are moved from embedded code into separate modules with structured APIs for state initialization, lifecycle management, and error handling.

Changes

Cohort / File(s) Summary
Core Backend Refactoring
packages/node/src/backend/nodeBackend.ts, packages/node/src/backend/nodeBackendInline.ts
Refactors main backend to route frame waiter lifecycle (acceptance, completion, rejection) through new frameTracking and debugChannel state objects; removes inline audit/debug state variables; imports and uses dedicated tracking/channel helpers; exports selectNodeBackendExecutionMode and public types from shared module.
Shared Infrastructure
packages/node/src/backend/nodeBackend/shared.ts
Introduces public configuration, type, and utility definitions: NodeBackendConfig, NodeBackendPerfSnapshot, NodeBackendPerf, Deferred<T> type/factory, deferred() and safeErr() utilities; establishes execution mode input/output types for mode selection and perf metrics structures.
Debug Channel State Management
packages/node/src/backend/nodeBackend/debugChannel.ts
Introduces centralized debug state tracking with NodeBackendDebugChannelState type holding debug operation deferreds and a serialization chain; provides factory, waiter rejection, and enqueueing helpers for coordinated debug operations.
Frame Tracking and Lifecycle
packages/node/src/backend/nodeBackend/frameTracking.ts
Introduces frame tracking with audit support: NodeBackendFrameTrackingState type, frame acceptance/completion waiter maps, audit logging; provides frame registration, acceptance/completion settlement, and waiter rejection helpers with optional audit event emission.
Frame Transport (SharedArrayBuffer)
packages/node/src/backend/nodeBackend/frameTransport.ts
Introduces SAB-based frame transport with slot management: SabFrameTransport type, slot acquisition mechanisms (acquireSabSlot, acquireSabSlotTracked), frame publishing, and transport factory; manages control headers, per-slot states, and data buffers.
Execution Mode Selection
packages/node/src/backend/nodeBackend/executionMode.ts
Introduces execution mode resolver with worker entry resolution, TTY detection, and mode selection logic; exports selectNodeBackendExecutionMode() to determine inline vs. worker execution; validates worker environment requirements.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • PR #213: Modifies Node backend frame submission and SAB transport surface, introducing shared frame transport mechanics that align with the transport extraction in this PR.
  • PR #254: Updates Node backend execution-mode fallback behavior, directly related to the new selectNodeBackendExecutionMode module extraction.
  • PR #257: Refactors Node backend to extract and centralize shared backend helpers and types, paralleling the shared infrastructure consolidation in this PR.

Poem

🐰 Hop, hop—the backend takes a leap,
State scattered now in modules deep,
Frame trackers hound each path with care,
Debug channels queue their debts to share!
A cleaner warren, refactored with pride.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'refactor(node): split node backend by concern' accurately and concisely describes the main objective of the changeset—splitting the monolithic nodeBackend.ts module into focused helper modules organized by concern.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/split-node-backend
📝 Coding Plan
  • Generate coding plan for human review comments

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

CodeRabbit can generate a title for your PR based on the changes with custom instructions.

Set the reviews.auto_title_instructions setting to generate a title for your PR based on the changes in the PR with custom instructions.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9425d625-1727-476a-bd72-72c50c139942

📥 Commits

Reviewing files that changed from the base of the PR and between 3a17dda and 9861712.

📒 Files selected for processing (7)
  • packages/node/src/backend/nodeBackend.ts
  • packages/node/src/backend/nodeBackend/debugChannel.ts
  • packages/node/src/backend/nodeBackend/executionMode.ts
  • packages/node/src/backend/nodeBackend/frameTracking.ts
  • packages/node/src/backend/nodeBackend/frameTransport.ts
  • packages/node/src/backend/nodeBackend/shared.ts
  • packages/node/src/backend/nodeBackendInline.ts

Comment on lines +40 to +65
export function selectNodeBackendExecutionMode(
input: NodeBackendExecutionModeSelectionInput,
): NodeBackendExecutionModeSelection {
const { requestedExecutionMode, fpsCap } = input;
const resolvedExecutionMode: "worker" | "inline" =
requestedExecutionMode === "inline"
? "inline"
: requestedExecutionMode === "worker"
? "worker"
: fpsCap <= 30
? "inline"
: "worker";
return {
resolvedExecutionMode,
selectedExecutionMode: resolvedExecutionMode,
fallbackReason: null,
};
}

export function assertWorkerEnvironmentSupported(nativeShimModule: string | undefined): void {
if (nativeShimModule !== undefined) return;
if (hasInteractiveTty()) return;
throw new ZrUiError(
"ZRUI_BACKEND_ERROR",
'Worker backend requires a TTY when using @rezi-ui/native. Use `executionMode: "inline"` for headless runs or pass `nativeShimModule` in test harnesses.',
);
Copy link
Copy Markdown

@coderabbitai coderabbitai bot Mar 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Handle headless auto fallback here.

selectNodeBackendExecutionMode() never uses hasAnyTty or nativeShimModule, so selectedExecutionMode can never differ from resolvedExecutionMode and fallbackReason is dead. In headless executionMode: "auto" runs with fpsCap > 30, createNodeBackendInternal() will still choose the worker backend and then fail at start() instead of selecting inline, and nativeShimModule: "" also bypasses the environment check.

Suggested fix
 export function selectNodeBackendExecutionMode(
   input: NodeBackendExecutionModeSelectionInput,
 ): NodeBackendExecutionModeSelection {
-  const { requestedExecutionMode, fpsCap } = input;
+  const { requestedExecutionMode, fpsCap, nativeShimModule, hasAnyTty } = input;
   const resolvedExecutionMode: "worker" | "inline" =
     requestedExecutionMode === "inline"
       ? "inline"
       : requestedExecutionMode === "worker"
         ? "worker"
         : fpsCap <= 30
           ? "inline"
           : "worker";
+  const hasNativeShim =
+    typeof nativeShimModule === "string" && nativeShimModule.length > 0;
+  const canUseWorker = hasNativeShim || hasAnyTty;
+  const selectedExecutionMode =
+    requestedExecutionMode === "auto" && resolvedExecutionMode === "worker" && !canUseWorker
+      ? "inline"
+      : resolvedExecutionMode;
   return {
     resolvedExecutionMode,
-    selectedExecutionMode: resolvedExecutionMode,
-    fallbackReason: null,
+    selectedExecutionMode,
+    fallbackReason:
+      selectedExecutionMode !== resolvedExecutionMode
+        ? "worker backend requires a TTY or native shim; falling back to inline"
+        : null,
   };
 }
 
 export function assertWorkerEnvironmentSupported(nativeShimModule: string | undefined): void {
-  if (nativeShimModule !== undefined) return;
+  if (typeof nativeShimModule === "string" && nativeShimModule.length > 0) return;
   if (hasInteractiveTty()) return;

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keeping this unchanged in this PR. The extracted selectNodeBackendExecutionMode() and assertWorkerEnvironmentSupported() logic are mechanically preserved from origin/main, and this refactor is intentionally scoped to extraction/wiring only. Changing auto to headless-fallback inline would be a behavioral change to worker/inline selection semantics, which is out of scope for this backend split. The current docs in docs/backend/node.md also describe auto as fpsCap <= 30 => inline, otherwise worker. If we want headless auto fallback, that should land as a separate behavior PR with updated tests/docs.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@RtlZeroMemory, understood — preserving the extracted logic verbatim from origin/main is the right call for a refactor-only PR. I'll drop the suggestion.

Would you like me to open a follow-up issue to track:

  1. Headless auto mode fallback to inline when no TTY/native-shim is present (hasAnyTty/nativeShimModule integration in selectNodeBackendExecutionMode()).
  2. Tightening the assertWorkerEnvironmentSupported guard to reject empty-string nativeShimModule (i.e., nativeShimModule.length > 0).

Both changes would need updated tests and a docs/backend/node.md update before landing.

( ˶ᵔ ᵕ ᵔ˶ )


✏️ Learnings added
Learnt from: RtlZeroMemory
Repo: RtlZeroMemory/Rezi PR: 284
File: packages/node/src/backend/nodeBackend/executionMode.ts:40-65
Timestamp: 2026-03-17T13:58:17.665Z
Learning: In `packages/node/src/backend/nodeBackend/executionMode.ts`, `selectNodeBackendExecutionMode()` and `assertWorkerEnvironmentSupported()` are mechanically extracted from `origin/main` with no behavioral changes. The `auto` mode resolves solely via `fpsCap <= 30 => inline`, otherwise `worker` — headless auto-fallback (using `hasAnyTty`/`nativeShimModule`) is intentionally deferred to a separate behavior PR with updated tests and docs. Do not flag missing headless-fallback logic as a bug in extraction/refactor PRs targeting this file.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: RtlZeroMemory
Repo: RtlZeroMemory/Rezi PR: 281
File: packages/core/src/app/createApp/eventLoop.ts:97-125
Timestamp: 2026-03-17T08:37:02.317Z
Learning: In `packages/core/src/app/createApp/eventLoop.ts`, the `commitUpdates()` catch block intentionally calls `options.fatalNowOrEnqueue` without rethrowing after a thrown state updater, allowing the current turn to continue. This control flow is carried over unchanged from the pre-split `createApp.ts` and is considered correct semantics for this runtime path. Do not flag it as a bug in extraction/refactor PRs.

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-03T10:00:48.227Z
Learning: For rendering/layout/theme regressions, include mandatory live PTY validation: run app in PTY with deterministic viewport, exercise routes, capture REZI_FRAME_AUDIT logs, analyze with frame-audit-report.mjs, include concrete evidence in report

Learnt from: RtlZeroMemory
Repo: RtlZeroMemory/Rezi PR: 281
File: packages/core/src/app/createApp/renderLoop.ts:327-341
Timestamp: 2026-03-17T08:37:29.774Z
Learning: In `packages/core/src/app/createApp/renderLoop.ts`, the post-submit callback ordering is intentional: `emitFocusChangeIfNeeded()`, `emitInternalRenderMetrics()`, and `emitInternalLayoutSnapshot()` run *before* `scheduleFrameSettlement()` is attached in both the raw and widget render branches. This ordering is preserved from the pre-split `createApp.ts` on `origin/main`. Reordering would change `framesInFlight`/ack timing and callback sequencing in the runtime orchestrator. Do not flag this as a bug in extraction/refactor PRs.

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-03T10:00:48.227Z
Learning: Applies to packages/core/src/runtime/{commit.ts,reconcile.ts,router/wheel.ts}|packages/core/src/app/createApp.ts|packages/core/src/layout/**|packages/core/src/renderer/**|packages/core/src/drawlist/**|packages/core/src/binary/** : Extra care required when modifying danger zones: runtime commit/reconcile, wheel router, app creation, layout engine, renderer, drawlist, and binary modules; must verify runtime invariants and reconciliation behavior

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-03T10:00:48.227Z
Learning: Risk triage: docs-only updates (low risk) need lint/grep validation; widget prop renames (medium risk) need compile + affected tests + docs parity; runtime/router/reconcile/layout/drawlist changes (high risk) need full test suite + integration coverage + PTY evidence

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-03T10:00:48.227Z
Learning: Agent coordination: assign clear ownership by path/purpose, keep one agent as integration owner, split by independent concerns (API/type changes, runtime behavior, JSX parity, docs)

Learnt from: RtlZeroMemory
Repo: RtlZeroMemory/Rezi PR: 216
File: scripts/drawlist-spec.ts:1-3
Timestamp: 2026-02-26T10:29:26.658Z
Learning: In the Rezi project, public API references and documentation must consistently use the current public protocol version (v1) after the reset, and must not reference the old v6 iteration. Review tests, scripts, and docs (including scripts/drawlist-spec.ts) to replace any v6 mentions with v1, and add a short note in release/docs where protocol versions are referenced to reflect the reset.

@RtlZeroMemory RtlZeroMemory merged commit 31db168 into main Mar 17, 2026
32 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant