Context
Reported by @tobiu, 2026-08-31: opening http://localhost:63342/neo/dist/esm/apps/portal/index.html#/home renders the portal home fully — including offscreen canvases — while the app worker emits the same rejection thousands of times.
Base.mjs:411 Uncaught (in promise) undefined
(anonymous) @ Base.mjs:411
promiseMessage @ Base.mjs:405
(anonymous) @ RemoteMethodAccess.mjs:164
(anonymous) @ Body.mjs:1271
Promise.then
onStoreLoad @ Body.mjs:1270
fire @ Observable.mjs:284
onCollectionMutate @ Store.mjs:1082
fire @ Observable.mjs:284
splice @ Base.mjs:1608
appendFeedBatch @ Workspace.mjs:620
(anonymous) @ Workspace.mjs:567
The rendering is genuinely correct. The defect this ticket files is what that trace proves about which tree is executing.
The Problem — measured
1. The built artifacts exist and are minified.
| file |
newlines |
dist/esm/src/worker/Base.mjs |
0 (4850 bytes, single line) |
dist/esm/apps/portal/app.mjs |
0 |
apps/portal/app.mjs (source) |
7 |
2. There are no source maps. find dist/esm -name '*.map' → 0 files; grep sourceMappingURL dist/esm/src/worker/Base.mjs → 0.
3. Therefore the reported line numbers cannot come from the built tree. A stack against a single-line file can only ever read Base.mjs:1. Base.mjs:411, Body.mjs:1271, Workspace.mjs:620 are source line numbers — and src/worker/Base.mjs:405 is return new Promise(function(resolve, reject) {, exactly the promiseMessage frame the trace names.
4. The emitted config points away from the emitted tree. dist/esm/apps/portal/neo-config.json:
{"appPath": "apps/portal/app.mjs", "basePath": "../../../../", "workerBasePath": "../../src/worker/"}Resolved from dist/esm/apps/portal/ on disk:
| value |
resolves to |
basePath: "../../../../" |
<repo> — the repository root |
workerBasePath: "../../src/worker/" |
<repo>/dist/esm/src/worker/ |
The source config carries basePath: "../../", which from apps/portal/ is also the repository root. esmDistTransforms#rewriteNeoConfig prefixes ../../ because the config moves two directories deeper — arithmetic that preserves the original destination. Its JSDoc states that intent: "basePath gains ../../ because the config moves two directories deeper in the output tree … it compensates for where the config now sits." Preserving the destination is the defect: a built app must resolve against the tree holding its own copied engine, so the value should remain ../../, which from the new location means dist/esm.
So dist/esm/apps/portal/index.html does not exercise the dist/esm build. It is a correct, complete, emitted tree that the runtime is configured to walk past. That is why it "fully works": the source tree works. Every defect the emitted tree could carry is invisible through this entry point.
What is NOT established
Stated explicitly so the next reader does not inherit a guess:
- The rejection's root cause.
worker/Base.mjs:311 rejects with promise.reject(data.data), so an undefined reason means a reply arrived with reject set and no payload. Which remote call refuses, and why, is not diagnosed here. Neo.main.DomAccess.scrollTo itself cannot throw — it is this.getElement(id)?.scrollTo(...), a no-op on a missing node.
- The exact resolution path by which
appPath becomes a URL. The basePath arithmetic above is measured on disk; the runtime's consumption of it was not traced to a single line.
- Whether the same re-anchoring affects
dist/production / dist/development.
The Fix
rewriteNeoConfig must re-anchor basePath to the emitted tree rather than preserve its original destination — the built value should resolve to dist/esm, not to the repository root. isFixedBasePath (absolute / protocol-relative) keeps its existing pass-through.
- A regression arm that asserts the resolved target, not the string: from
dist/esm/apps/<app>/, basePath must resolve inside dist/esm. A string comparison would have passed every version of this bug.
- The amplifier is separable and worth its own line:
grid/Body.mjs:1270 dispatches Neo.main.DomAccess.scrollTo as .then() with no .catch(), so any refusal becomes an unhandled rejection; apps/workstation/view/Workspace.mjs:566 runs a setInterval feed that mutates the store every tick, re-entering onStoreLoad. One refusal per tick is what turns a single fault into thousands of console lines. Fixing the resolution may silence this instance without fixing the pattern.
Acceptance Criteria
Out of Scope
- The
.then()-without-.catch() pattern at grid/Body.mjs:1270 and the workstation feed interval — the amplifier, not the fault.
#17972's WS/ addon dynamic import: same class (runtime reaching unbuilt source) via a different mechanism — a dynamic import inside Main.mjs rather than a static config value, and in a consumer workspace rather than the in-repo portal.
- Typing the reply-path rejection at
worker/Base.mjs:311. #17894 typed the dead-port path today; this one still rejects with whatever the reply carries.
Related
#17931 — the dist/esm boot arm. This is the defect that arm exists to catch, and could not be seen by a resolution-only fixture: every path it checks genuinely exists.
#17972 — sibling in the same class, different mechanism.
#17921 — the three original dist/esm load defects; this is a fourth on the same surface.
Live latest-open sweep: checked latest 20 open issues at 2026-08-31T20:03:41Z; #17972 and #17971 reviewed and distinguished above.
Origin Session ID: 56bc214a-5b55-41a2-848a-cdfa372abbcd
Context
Reported by @tobiu, 2026-08-31: opening
http://localhost:63342/neo/dist/esm/apps/portal/index.html#/homerenders the portal home fully — including offscreen canvases — while the app worker emits the same rejection thousands of times.The rendering is genuinely correct. The defect this ticket files is what that trace proves about which tree is executing.
The Problem — measured
1. The built artifacts exist and are minified.
dist/esm/src/worker/Base.mjsdist/esm/apps/portal/app.mjsapps/portal/app.mjs(source)2. There are no source maps.
find dist/esm -name '*.map'→ 0 files;grep sourceMappingURL dist/esm/src/worker/Base.mjs→ 0.3. Therefore the reported line numbers cannot come from the built tree. A stack against a single-line file can only ever read
Base.mjs:1.Base.mjs:411,Body.mjs:1271,Workspace.mjs:620are source line numbers — andsrc/worker/Base.mjs:405isreturn new Promise(function(resolve, reject) {, exactly thepromiseMessageframe the trace names.4. The emitted config points away from the emitted tree.
dist/esm/apps/portal/neo-config.json:{"appPath": "apps/portal/app.mjs", "basePath": "../../../../", "workerBasePath": "../../src/worker/"}Resolved from
dist/esm/apps/portal/on disk:basePath: "../../../../"<repo>— the repository rootworkerBasePath: "../../src/worker/"<repo>/dist/esm/src/worker/The source config carries
basePath: "../../", which fromapps/portal/is also the repository root.esmDistTransforms#rewriteNeoConfigprefixes../../because the config moves two directories deeper — arithmetic that preserves the original destination. Its JSDoc states that intent: "basePath gains../../because the config moves two directories deeper in the output tree … it compensates for where the config now sits." Preserving the destination is the defect: a built app must resolve against the tree holding its own copied engine, so the value should remain../../, which from the new location meansdist/esm.So
dist/esm/apps/portal/index.htmldoes not exercise thedist/esmbuild. It is a correct, complete, emitted tree that the runtime is configured to walk past. That is why it "fully works": the source tree works. Every defect the emitted tree could carry is invisible through this entry point.What is NOT established
Stated explicitly so the next reader does not inherit a guess:
worker/Base.mjs:311rejects withpromise.reject(data.data), so anundefinedreason means a reply arrived withrejectset and no payload. Which remote call refuses, and why, is not diagnosed here.Neo.main.DomAccess.scrollToitself cannot throw — it isthis.getElement(id)?.scrollTo(...), a no-op on a missing node.appPathbecomes a URL. ThebasePatharithmetic above is measured on disk; the runtime's consumption of it was not traced to a single line.dist/production/dist/development.The Fix
rewriteNeoConfigmust re-anchorbasePathto the emitted tree rather than preserve its original destination — the built value should resolve todist/esm, not to the repository root.isFixedBasePath(absolute / protocol-relative) keeps its existing pass-through.dist/esm/apps/<app>/,basePathmust resolve insidedist/esm. A string comparison would have passed every version of this bug.grid/Body.mjs:1270dispatchesNeo.main.DomAccess.scrollToas.then()with no.catch(), so any refusal becomes an unhandled rejection;apps/workstation/view/Workspace.mjs:566runs asetIntervalfeed that mutates the store every tick, re-enteringonStoreLoad. One refusal per tick is what turns a single fault into thousands of console lines. Fixing the resolution may silence this instance without fixing the pattern.Acceptance Criteria
dist/esm/apps/<app>/neo-config.json'sbasePathresolves insidedist/esm, asserted by resolution rather than string equality.dist/esm/apps/portal/index.htmlexecutes the built modules — provable because a deliberate edit to adist/esm/src/**file changes observed behaviour, and the equivalent source edit does not.isFixedBasePathvalues (absolute, protocol-relative, fully-qualified) still pass through unchanged, with an arm each.Out of Scope
.then()-without-.catch()pattern atgrid/Body.mjs:1270and the workstation feed interval — the amplifier, not the fault.#17972'sWS/addon dynamic import: same class (runtime reaching unbuilt source) via a different mechanism — a dynamic import insideMain.mjsrather than a static config value, and in a consumer workspace rather than the in-repo portal.worker/Base.mjs:311.#17894typed the dead-port path today; this one still rejects with whatever the reply carries.Related
#17931— thedist/esmboot arm. This is the defect that arm exists to catch, and could not be seen by a resolution-only fixture: every path it checks genuinely exists.#17972— sibling in the same class, different mechanism.#17921— the three originaldist/esmload defects; this is a fourth on the same surface.Live latest-open sweep: checked latest 20 open issues at 2026-08-31T20:03:41Z;
#17972and#17971reviewed and distinguished above.Origin Session ID: 56bc214a-5b55-41a2-848a-cdfa372abbcd