The gap
#17921 fixed three defects that made a workspace dist/esm build emit a tree the app worker could not load, and added a build-time guard that fails when an emitted import does not resolve. Its AC-4 asked for more than that guard delivers:
A greenfield workspace fixture (apps + one extra source root) builds dist/esm and the produced app boots headlessly — the app worker reaches Neo.apps with the main view mounted.
The PR for #17921 delivered the first half. test/playwright/unit/buildScripts/esmWorkspaceBuild.spec.mjs creates a greenfield workspace, runs the real buildScripts/build/esmodules.mjs over it, and asserts every emitted import resolves. It does not boot the output.
This ticket owns the boot arm, carved out so the three shipped defects were not held behind a materially heavier fixture.
Why the boot arm is worth its own cost
Resolution is a necessary condition for booting, not a sufficient one. The existing fixture proves every specifier points at a file that exists; it cannot see a neo-config.json whose basePath resolves but addresses the wrong tree, a worker that starts from the wrong root, or two disjoint copies of the engine registering classes into namespaces the running runtime never reads. That last one is the failure mode described in #17921's defect 1, and it is invisible to a path-existence check because every path involved exists.
The boot arm is also the only thing that would have caught the whole class before a consumer did — #17921 notes that apparently no workspace exercises dist/esm today, which is precisely why three independent defects accumulated in it unnoticed.
Acceptance Criteria
Scope transfer in (2026-08-31). #17943's AC-2 and AC-3 moved here rather than sitting un-closable on a ticket whose PR delivered everything else. They are struck through there with a pointer to this ticket. Nothing else about #17931's premise changed.
Notes for whoever takes this
- The existing
esmWorkspaceBuild.spec.mjs is the natural starting point; it already builds a workspace end to end and cleans up after itself.
- Using the real engine rather than a stub is the expensive part — the current fixture deliberately stubs it because path arithmetic needs no real engine, and Terser over the full
src/ tree adds minutes.
- Related surface: #17918 (runtime-side dist-root derivation for absolute mounts) exercises the same mount shape at a different layer.
Origin
Carved from #17921 AC-4 during its implementation, rather than reported as met. The three transform defects and the build-time resolution guard shipped; this arm did not.
The gap
#17921 fixed three defects that made a workspace
dist/esmbuild emit a tree the app worker could not load, and added a build-time guard that fails when an emitted import does not resolve. Its AC-4 asked for more than that guard delivers:The PR for #17921 delivered the first half.
test/playwright/unit/buildScripts/esmWorkspaceBuild.spec.mjscreates a greenfield workspace, runs the realbuildScripts/build/esmodules.mjsover it, and asserts every emitted import resolves. It does not boot the output.This ticket owns the boot arm, carved out so the three shipped defects were not held behind a materially heavier fixture.
Why the boot arm is worth its own cost
Resolution is a necessary condition for booting, not a sufficient one. The existing fixture proves every specifier points at a file that exists; it cannot see a
neo-config.jsonwhosebasePathresolves but addresses the wrong tree, a worker that starts from the wrong root, or two disjoint copies of the engine registering classes into namespaces the running runtime never reads. That last one is the failure mode described in #17921's defect 1, and it is invisible to a path-existence check because every path involved exists.The boot arm is also the only thing that would have caught the whole class before a consumer did — #17921 notes that apparently no workspace exercises
dist/esmtoday, which is precisely why three independent defects accumulated in it unnoticed.Acceptance Criteria
apps/plus at least one extra source root declared vianeo.esmSourceRoots) is built through the realdist/esmbuild, using the actual engine rather than the stub the current fixture copies.Neo.appswith the main view mounted.test/playwright/unitis the only Engine suite CI currently executes alongsidecomponents; an e2e-shaped placement would not run.HtmlTemplateProcessorloads and parses a template, so the arm proves the module works rather than that a path exists. #17943 shipped the artifact this needs (dist/parse5.mjsis now published); its own ACs could never reach here, because this fixture is the only thing that builds a real installed engine. Both consumers of the bundle are then covered: the build-time one by AC-1 (an installed engine that cannot start the build fails it) and the runtime one by this arm.Notes for whoever takes this
esmWorkspaceBuild.spec.mjsis the natural starting point; it already builds a workspace end to end and cleans up after itself.src/tree adds minutes.Origin
Carved from #17921 AC-4 during its implementation, rather than reported as met. The three transform defects and the build-time resolution guard shipped; this arm did not.