Authored by Claude Opus 4.7 (Claude Code) as @neo-opus-ada. Session provenance: lane-claim A2A MESSAGE:894591f6 (#12132).
FAIR-band: operator-directed pickup — the operator explicitly assigned #12132 (and #12133) this session, superseding the standing "no new items until open team PRs merge" hold. Author-lane pickup discipline is N/A for directed work.
Resolves #12132
A Store with autoLoad: true + a websocket api lost a boot race: Store.onConstructed() fires load() one microtask after construction, but the remotes-api stubs register behind an async dynamic import + fetch, so load() found no stub and bailed at console.error('Api is not defined') — no RPC, no load.
The fix reuses the existing core.Base initAsync()/ready() lifecycle rather than a hand-rolled promise:
Neo.remotes.Api awaits load() inside initAsync(), so the singleton only reaches isReady once the api JSON is fetched and the service stubs are registered.
Store.load()'s api branch awaits Neo.remotes.Api.ready() when the stub is missing (gated on Neo.config.remotesApiUrl) before giving up, then re-resolves the stub.
src/worker/App.mjs now just imports the module (creation triggers initAsync → load); the explicit .then(module.default.load()) is removed to avoid a double-load.
url/static autoLoad stores and apps without remotesApiUrl are unaffected; no boot latency added for non-remote apps.
Evidence: L1 (53/53 test/playwright/unit/data/ regression green; rides the core/Base initAsync/ready() primitive) → L3 required (api-store autoLoads on boot — observable runtime effect across the App↔Data worker boundary the single-thread unit sim cannot model). Residual: runtime AC verified via integration with a real autoLoad:true + websocket-api store. [#12132]
Deltas from ticket
- The ticket proposed a "registration-ready signal on
Neo.remotes.Api"; implemented via the existing initAsync/ready() primitive instead of a bespoke promise — cleaner, less new surface.
- Incidental: removed pre-existing trailing whitespace flagged by the
check-shorthand pre-commit hook in the two touched files (App.mjs duplicated JSDoc blocks, Api.mjs). Mechanical, no logic change.
Test Evidence
npm run test-unit -- test/playwright/unit/data/ → 53 passed (no regression from the Store.load change).
node --check clean on all three changed files.
- A dedicated boot-race unit test is impractical: the
api load path bottoms out in a cross-worker RPC (promiseMessage('data', {action:'rpc'})) the single-thread unit sim does not model, and the remotes Api singleton is created at import-time (mocks cannot be injected before initAsync). The fix rides the already-covered initAsync/ready() primitive.
Post-Merge Validation
Cross-family review
Per §6.1 this needs a cross-family Approved review before merge; will route to a primary reviewer once CI is green.
Authored by Claude Opus 4.7 (Claude Code) as @neo-opus-ada. Session provenance: lane-claim A2A
MESSAGE:894591f6(#12132).FAIR-band: operator-directed pickup — the operator explicitly assigned #12132 (and #12133) this session, superseding the standing "no new items until open team PRs merge" hold. Author-lane pickup discipline is N/A for directed work.
Resolves #12132
A
StorewithautoLoad: true+ a websocketapilost a boot race:Store.onConstructed()firesload()one microtask after construction, but theremotes-apistubs register behind an async dynamic import +fetch, soload()found no stub and bailed atconsole.error('Api is not defined')— no RPC, no load.The fix reuses the existing
core.BaseinitAsync()/ready()lifecycle rather than a hand-rolled promise:Neo.remotes.Apiawaitsload()insideinitAsync(), so the singleton only reachesisReadyonce the api JSON is fetched and the service stubs are registered.Store.load()'sapibranch awaitsNeo.remotes.Api.ready()when the stub is missing (gated onNeo.config.remotesApiUrl) before giving up, then re-resolves the stub.src/worker/App.mjsnow just imports the module (creation triggersinitAsync→load); the explicit.then(module.default.load())is removed to avoid a double-load.url/staticautoLoadstores and apps withoutremotesApiUrlare unaffected; no boot latency added for non-remote apps.Evidence: L1 (53/53
test/playwright/unit/data/regression green; rides thecore/BaseinitAsync/ready()primitive) → L3 required (api-storeautoLoads on boot — observable runtime effect across the App↔Data worker boundary the single-thread unit sim cannot model). Residual: runtime AC verified via integration with a realautoLoad:true+ websocket-apistore. [#12132]Deltas from ticket
Neo.remotes.Api"; implemented via the existinginitAsync/ready()primitive instead of a bespoke promise — cleaner, less new surface.check-shorthandpre-commit hook in the two touched files (App.mjsduplicated JSDoc blocks,Api.mjs). Mechanical, no logic change.Test Evidence
npm run test-unit -- test/playwright/unit/data/→ 53 passed (no regression from theStore.loadchange).node --checkclean on all three changed files.apiload path bottoms out in a cross-worker RPC (promiseMessage('data', {action:'rpc'})) the single-thread unit sim does not model, and the remotesApisingleton is created at import-time (mocks cannot be injected beforeinitAsync). The fix rides the already-coveredinitAsync/ready()primitive.Post-Merge Validation
StorewithautoLoad: true+ websocketapi: {read}populates on boot with no controller-side workaround."Api is not defined"console error for a correctly-wired remoteapistore on boot.Cross-family review
Per §6.1 this needs a cross-family Approved review before merge; will route to a primary reviewer once CI is green.