Skip to content

refactor(core): split createApp orchestration controllers#256

Merged
RtlZeroMemory merged 2 commits intomainfrom
codex/refactor-core-orchestration
Mar 6, 2026
Merged

refactor(core): split createApp orchestration controllers#256
RtlZeroMemory merged 2 commits intomainfrom
codex/refactor-core-orchestration

Conversation

@RtlZeroMemory
Copy link
Owner

@RtlZeroMemory RtlZeroMemory commented Mar 5, 2026

Summary

  • extract dirty-plan computation out of createApp.ts into its own controller module
  • extract focus-change dispatch, run-signal lifecycle handling, and top-level view error helpers
  • add focused controller tests so these orchestration seams can be exercised independently

Validation

  • npm run build
  • npm run lint
  • node scripts/run-tests.mjs

Summary by CodeRabbit

  • Tests

    • Added comprehensive tests covering dirty-state tracking, focus dispatcher behavior, run-signal lifecycle, and top-level error handling.
  • Refactor

    • Modularized focus notifications, dirty-flag tracking, run/signal management, and top-level error UI for clearer lifecycle and render planning.
  • Bug Fixes / Behavior

    • More consistent focus emission, centralized dirty-flag lifecycle, and safer shutdown/signal handling; enhanced runtime error presentation.

@coderabbitai
Copy link

coderabbitai bot commented Mar 5, 2026

📝 Walkthrough

Walkthrough

Refactors createApp internals by extracting error handling, focus dispatch, dirty-flag management, and run-signal wiring into new modules (dirtyPlan.ts, focusDispatcher.ts, runSignals.ts, topLevelViewError.ts) and adds tests validating the new abstractions.

Changes

Cohort / File(s) Summary
New Test Modules
packages/core/src/app/__tests__/dirtyPlan.controller.test.ts, packages/core/src/app/__tests__/focusDispatcher.controller.test.ts, packages/core/src/app/__tests__/runSignals.controller.test.ts, packages/core/src/app/__tests__/resilience.test.ts, packages/core/src/app/__tests__/topLevelViewError.test.ts
Adds unit tests for dirty tracker, focus dispatcher, run-signal controller, resilience around start/error detach, and top-level Ctrl+C detection.
Dirty Tracking Module
packages/core/src/app/createApp/dirtyPlan.ts
New DirtyTracker implementation, DIRTY_RENDER/LAYOUT/VIEW constants, snapshot/versioning semantics, clearConsumedFlags logic, and buildWidgetRenderPlan to derive commit/layout/checkLayoutStability + timestamp.
Focus Dispatcher Module
packages/core/src/app/createApp/focusDispatcher.ts
New FocusDispatcher generic: emitIfChanged, getLastEmittedId, register/disposer, per-handler activation, centralized onHandlerError handling.
Run Signal Controller Module
packages/core/src/app/createApp/runSignals.ts
New RunSignalController and ProcessLike types, readProcessLike/removeSignalHandler utilities, createRunSignalController with signal registration, detach/settle semantics, promise resolution, and safe handler cleanup.
Top-Level Error Module
packages/core/src/app/createApp/topLevelViewError.ts
New TopLevelViewError type, captureTopLevelViewError, buildTopLevelViewErrorScreen, and keyboard/text helpers for retry/quit/Ctrl+C detection and rendering integration.
Refactored Main Module
packages/core/src/app/createApp.ts
Replaces in-file error/focus/dirty/signal logic with imports from new modules (createDirtyTracker, createFocusDispatcher, createRunSignalController, top-level error helpers); removes many inline helpers and moves lifecycle wiring to the new abstractions.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I nibbled bugs and pulled a thread,

New modules hopped into my head.
Dirty flags and signals spring,
Focus bells and error sing.
A tidy code burrow — joy, I said!

🚥 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 accurately describes the main refactoring objective: extracting orchestration controllers (dirty-plan, focus-dispatcher, run-signals, top-level error handling) from createApp.ts into separate modules.

✏️ 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
  • Post copyable unit tests in a comment
  • Commit unit tests in branch codex/refactor-core-orchestration

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

Copy link

@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: 4

🧹 Nitpick comments (2)
packages/core/src/app/createApp/topLevelViewError.ts (1)

62-62: Callout text omits the Ctrl+C quit path.

The behavior supports quitting via Ctrl+C, but the prompt only advertises R/Q. Consider making the instruction match actual behavior.

🧩 Tiny text parity fix
-        ui.callout("Press R to retry, Q to quit", { variant: "warning" }),
+        ui.callout("Press R to retry, Q or Ctrl+C to quit", { variant: "warning" }),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/app/createApp/topLevelViewError.ts` at line 62, Update the
callout text in topLevelViewError.ts where ui.callout is invoked (the line with
ui.callout("Press R to retry, Q to quit", { variant: "warning" }) ) to reflect
the actual quit options by including the Ctrl+C path (e.g., "Press R to retry, Q
to quit or Ctrl+C to quit"); keep the same ui.callout invocation and variant but
change only the displayed string so the prompt matches runtime behavior.
packages/core/src/app/__tests__/runSignals.controller.test.ts (1)

4-61: Add a regression test for sync-throwing onSignal.

Current coverage misses the case where onSignal throws synchronously. Add one test to assert controller.promise still resolves and handlers are detached in that path.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/app/__tests__/runSignals.controller.test.ts` around lines 4
- 61, Add a test that simulates a sync-throwing onSignal to ensure
createRunSignalController still resolves and detaches handlers: create a fake
processLike with on/off and listeners Map (as in the first test), pass an
onSignal that throws synchronously and an onDetached that records calls, verify
controller.canRegisterSignals is true, trigger the registered signal handlers
(e.g., call each handler for "SIGINT"), await controller.promise, then assert
events include "detached" and that listeners for "SIGINT"/"SIGTERM" were
removed; reference createRunSignalController, controller.promise, onSignal,
onDetached, and the listeners Map to locate and adapt the existing tests.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/core/src/app/createApp.ts`:
- Around line 1753-1764: app.start() may throw synchronously which currently
bypasses the promise rejection handler and leaves signal handlers registered;
wrap the call to app.start() in a try/catch so that synchronous throws also call
runController.detach() before rethrowing, preserving the existing behavior in
the async rejection branch (i.e., still rethrow the error after detaching), and
keep the existing logic that calls runController.settle() when
runController.canRegisterSignals is false in the successful path.

In `@packages/core/src/app/createApp/runSignals.ts`:
- Around line 89-97: The signal handler can let a synchronous throw from
options.onSignal escape before .catch/.finally attach; inside the handler (the
function named handler) replace Promise.resolve(options.onSignal()) with an
asynchronous initiation such as Promise.resolve().then(() => options.onSignal())
or explicitly capture synchronous throws via try/catch and convert to a rejected
promise, then chain .catch(() => undefined).finally(() => { resolvePromise();
}); ensure you keep the settled flag, detach(), and resolvePromise() behavior
intact so controller.promise is always resolved even if onSignal throws
synchronously.

In `@packages/core/src/app/createApp/topLevelViewError.ts`:
- Around line 6-11: Replace the exported alias TopLevelViewError (currently
declared as type Readonly<{ ... }>) with an exported interface TopLevelViewError
that defines the same properties as readonly (readonly code:
"ZRUI_USER_CODE_THROW"; readonly detail: string; readonly message: string;
readonly stack?: string), keeping the same export name and types so consumers
are unchanged; update any imports/usages if necessary to use the interface but
otherwise preserve semantics.
- Around line 95-102: The isUnhandledCtrlCKeyEvent predicate currently treats
Ctrl+Shift+C as the same as Ctrl+C; update the modifier check in
isUnhandledCtrlCKeyEvent to also exclude ZR_MOD_SHIFT so only plain Ctrl+C (no
Alt/Meta/Shift) returns true. Concretely, in the final return that currently
tests (ev.mods & (ZR_MOD_ALT | ZR_MOD_META)) === 0, include ZR_MOD_SHIFT in that
bitmask (e.g., (ZR_MOD_ALT | ZR_MOD_META | ZR_MOD_SHIFT)) and keep the earlier
CTRL presence check intact.

---

Nitpick comments:
In `@packages/core/src/app/__tests__/runSignals.controller.test.ts`:
- Around line 4-61: Add a test that simulates a sync-throwing onSignal to ensure
createRunSignalController still resolves and detaches handlers: create a fake
processLike with on/off and listeners Map (as in the first test), pass an
onSignal that throws synchronously and an onDetached that records calls, verify
controller.canRegisterSignals is true, trigger the registered signal handlers
(e.g., call each handler for "SIGINT"), await controller.promise, then assert
events include "detached" and that listeners for "SIGINT"/"SIGTERM" were
removed; reference createRunSignalController, controller.promise, onSignal,
onDetached, and the listeners Map to locate and adapt the existing tests.

In `@packages/core/src/app/createApp/topLevelViewError.ts`:
- Line 62: Update the callout text in topLevelViewError.ts where ui.callout is
invoked (the line with ui.callout("Press R to retry, Q to quit", { variant:
"warning" }) ) to reflect the actual quit options by including the Ctrl+C path
(e.g., "Press R to retry, Q to quit or Ctrl+C to quit"); keep the same
ui.callout invocation and variant but change only the displayed string so the
prompt matches runtime behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 19ea91a6-7b94-42ff-8e3a-eee154dd3331

📥 Commits

Reviewing files that changed from the base of the PR and between 2b19421 and ecab5cd.

📒 Files selected for processing (8)
  • packages/core/src/app/__tests__/dirtyPlan.controller.test.ts
  • packages/core/src/app/__tests__/focusDispatcher.controller.test.ts
  • packages/core/src/app/__tests__/runSignals.controller.test.ts
  • packages/core/src/app/createApp.ts
  • packages/core/src/app/createApp/dirtyPlan.ts
  • packages/core/src/app/createApp/focusDispatcher.ts
  • packages/core/src/app/createApp/runSignals.ts
  • packages/core/src/app/createApp/topLevelViewError.ts

Copy link

@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.

🧹 Nitpick comments (1)
packages/core/src/app/__tests__/topLevelViewError.test.ts (1)

2-3: Prefer shared keycode constant over raw 67 in this test.

Using KEY_C keeps this aligned with keycode definitions if mappings are ever centralized/adjusted.

♻️ Proposed tweak
-import { ZR_MOD_CTRL, ZR_MOD_SHIFT } from "../../keybindings/keyCodes.js";
+import { KEY_C, ZR_MOD_CTRL, ZR_MOD_SHIFT } from "../../keybindings/keyCodes.js";
@@
-      key: 67,
+      key: KEY_C,
@@
-      key: 67,
+      key: KEY_C,

Also applies to: 10-11, 21-22

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/app/__tests__/topLevelViewError.test.ts` around lines 2 -
3, Replace the hardcoded keycode 67 in the top-level view error test with the
shared KEY_C constant: add an import for KEY_C from the keyCodes module
alongside ZR_MOD_CTRL and ZR_MOD_SHIFT, then replace the raw 67 values in the
isUnhandledCtrlCKeyEvent test event objects (the occurrences around the uses of
ZR_MOD_CTRL/ZR_MOD_SHIFT and the Ctrl+C checks) with KEY_C so the test uses the
centralized keycode constant.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/core/src/app/__tests__/topLevelViewError.test.ts`:
- Around line 2-3: Replace the hardcoded keycode 67 in the top-level view error
test with the shared KEY_C constant: add an import for KEY_C from the keyCodes
module alongside ZR_MOD_CTRL and ZR_MOD_SHIFT, then replace the raw 67 values in
the isUnhandledCtrlCKeyEvent test event objects (the occurrences around the uses
of ZR_MOD_CTRL/ZR_MOD_SHIFT and the Ctrl+C checks) with KEY_C so the test uses
the centralized keycode constant.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b2f1758f-dca2-4497-85ab-03ea01009174

📥 Commits

Reviewing files that changed from the base of the PR and between ecab5cd and f548b98.

📒 Files selected for processing (6)
  • packages/core/src/app/__tests__/resilience.test.ts
  • packages/core/src/app/__tests__/runSignals.controller.test.ts
  • packages/core/src/app/__tests__/topLevelViewError.test.ts
  • packages/core/src/app/createApp.ts
  • packages/core/src/app/createApp/runSignals.ts
  • packages/core/src/app/createApp/topLevelViewError.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/core/src/app/createApp/topLevelViewError.ts
  • packages/core/src/app/createApp/runSignals.ts

@RtlZeroMemory RtlZeroMemory merged commit c7d3f6c into main Mar 6, 2026
29 checks passed
@RtlZeroMemory RtlZeroMemory deleted the codex/refactor-core-orchestration branch March 6, 2026 06:46
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