Skip to content

[SHELL-0] Add shell contracts and measurement baseline - #129

Merged
DJAscendance merged 4 commits into
mainfrom
feature/shell-0-contracts
Oct 8, 2026
Merged

DJAscendance merged 4 commits into
mainfrom
feature/shell-0-contracts

Conversation

@DJAscendance

@DJAscendance DJAscendance commented Oct 8, 2026 •

Copy link
Copy Markdown
Collaborator

Refs #128 (SHELL-0). Follows APP-ARCH-0 / #127.

This PR does not start #121 or #122.
This PR does not implement the persistent shell.

Full detail: docs/architecture/SHELL_0_CONTRACTS_AND_MEASUREMENTS.md.

Contract modules (src/shell/, dormant)

disposable, contribution, services, profiles, command-service, panel-service, tool-registry, contextual-panels, menu-boundary, document-session. Command/panel services wrap the existing UI-0 registries rather than replacing them. Contracts are layout-neutral (no assumed quad-view, toolbar side, Inspector or Source placement).

Product load boundary: no production file loads src/shell — main.js, preload.js, renderer/** and src/** are source-scanned by test/shell/boundaries.test.js. No command or panel migrated.

Lifecycle model

  • Disposables: idempotent, reverse-order, every child cleaned even when several throw; multiple failures surface as one AggregateError holding all of them.
  • Panels: one active mount at a time; hide-while-docked stays mounted; host change while mounted refused; unmount → remount (same or new host) is a fresh mount; unregister while mounted disposes; repeated disposal safe.
  • Contextual panels: mount only for a proven selection; re-activation is a fresh host + mount; failing appliesTo/mount/update/dispose leaves no stale active state and never stops other records reconciling. Host callbacks are isolated the same way: a throwing createHost leaves that record inactive, a throwing releaseHost never stops remaining cleanup, a throwing resolveContext fails closed (all mounted editors disposed, nothing mounted, contextFailed: true). A throwing appliesTo is logged and treated as not applicable (not entered in errors); reconcile() reports lifecycle and host-callback failures (resolveContext, createHost, mount, releaseHost, update, unmount) in errors; dispose + releaseHost both failing → one AggregateError (ECONTEXTUAL_CLEANUP_FAILED) holding both.
  • Commands: profiles adds a gate (profileAllowed && originalEnabled()) and never weakens registry validation — a non-function enabled is rejected with the registry's own ECOMMAND_INVALID, exactly as for a profile-neutral record.

DocumentSession boundary

A controller over existing authorities (CodeMirror text/undo, sceneSelection, main's EditorSession) — not a second document. SHELL-0 does not settle the final dirty-baseline owner; Shell-2 must resolve it without creating a second canonical source or undo model.

#121 / #122 boundaries

#121 receives the command/panel/tool contracts; #122 receives the renderer menu boundary (menu-boundary.js). Neither is started here.

Measurements (repeatable SHELL-0 QA baselines)

Startup, page-switch and memory baselines via VisualQaRunner + capture-server mode (qa/shell-0-baseline/). Linux/X11, Ryzen 9 5900X, Node v24.21.0, Electron 41.7.1, --no-sandbox, warm cache, small fixtures. Not end-user performance claims; RESULTS.json is evidence, not a golden file (PIDs anonymized, no paths). Memory: no retained renderer growth proven; main-process +11 MB RSS drift needs a longer soak before classification (not called a leak); old-page memory persists for several navigations before release.

ESM spike → ESBUILD_ENTRY_BUNDLES

Native ESM works over file:// in sandboxed Electron 41 but cannot import the shared CommonJS src/ by name. On a synthetic 42-module graph (X_ITE excluded), one bundle loaded in 11.6 ms vs 33.5 ms — supports the architecture choice, not a guaranteed product speedup. Bundle output is deterministic for a fixed build root / absWorkingDir (esbuild path comments depend on the working directory). Not activated; no CLAUDE.md/policy change.

Validation

Candidate 92347a685f76dc9fac53ffbd35af4b85d7626218 (independent-QA fix commit for RF1/RF2 on top of 2eec9e24):

  • node --test test/shell/*.test.js: 71/71 pass
  • npm run check: 2625 tests — 2621 pass, 0 fail, 4 skip; 327 files parsed
  • Visual QA — not reported as a pass (run at the pre-fix candidate; not rerun for the fix commit, which only touches dormant src/shell contract code, its tests and the architecture doc):
    • npm run test:visual: pre-existing runner failure under Node 24 (Cannot find module '…/test/visual')
    • WRL_FORGE_ALLOW_VISUAL=1 node --test --test-concurrency=1 test/visual/*.test.js: 20 tests, 18 pass, 2 fail
    • The 2 failures are the pre-existing Extrusion bounds case (bbox == null, reproduced on main at 957102ac) and its parent group
    • No tracker exists yet; suggested: [QA] Repair canonical visual test invocation on Node 24, [QA] Investigate pre-existing Extrusion visual bounds failure

Unchanged

No user-facing behavior change. No dependency change. No repository-policy change (CLAUDE.md, package.json, lockfile untouched). Only non-new file: scripts/run-tests.js (+test/shell).

SHELL-0 (#128), following APP-ARCH-0 (#127). Dormant foundation only:
no production page, main or preload loads src/shell.

- src/shell: disposable, contribution, services, profiles, command and
  panel services over the UI-0 registries, tool registry, contextual
  panels, menu boundary, DocumentSession controller boundary.
- Panel and contextual lifecycles are remountable; one record's failure
  never stops the others; multiple disposal failures are aggregated.
- test/shell: focused contract tests, registered in run-tests.js.
- qa/shell-0-baseline: repeatable QA baselines for startup, page switch
  and memory (evidence, not a golden file; pids anonymized).
- spikes/shell-0-module-loading: native ESM vs esbuild spike;
  recommendation ESBUILD_ENTRY_BUNDLES, not activated.
- docs/architecture/SHELL_0_CONTRACTS_AND_MEASUREMENTS.md.

No product behavior, dependency or repository-policy change.

Signed-off-by: Ryan Bundy <ascendance@skate.fm>
The IIFE/export structural check matched LF only, so a Windows CRLF
checkout failed it although every module is wrapped. Normalize line
endings before the structural checks and prove the check against LF,
CRLF and unwrapped/export-less sources.

Signed-off-by: Ryan Bundy <ascendance@skate.fm>
Independent QA of PR #129 (RF1, RF2, N2).

RF1 contextual-panels: createHost, releaseHost and resolveContext are now
isolated like the records. A throwing createHost leaves that record
inactive and reconciliation continues; a throwing releaseHost never stops
the remaining cleanup; a throwing resolveContext fails closed (every
mounted editor disposed, nothing mounted, no update, contextFailed).
reconcile() reports every failure in `errors`; dispose + releaseHost
failing together yield one AggregateError (ECONTEXTUAL_CLEANUP_FAILED)
holding both.

RF2 command-service: `profiles` no longer weakens registry validation.
A non-function `enabled` is rejected with the registry's own
ECOMMAND_INVALID instead of being treated as always enabled.

N2 docs: esbuild bundle determinism is stated for a fixed build root /
absWorkingDir; deferred QA notes recorded in §21c.

src/shell remains unused by production.

Signed-off-by: Ryan Bundy <ascendance@skate.fm>
appliesTo() exceptions are logged and treated as not applicable (fail
closed); they are not entered in reconcile().errors. errors records the
reconciliation lifecycle and host-callback failures (resolveContext,
createHost, mount, releaseHost, update, unmount). Narrow the module header
and SHELL-0 doc §9, which overstated that every failure is returned.

Add one focused assertion that a later record still reconciles in the same
pass as a throwing appliesTo. No runtime behaviour change.

Signed-off-by: Ryan Bundy <ascendance@skate.fm>
@DJAscendance
DJAscendance merged commit afb4158 into main Oct 8, 2026
3 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