Let a stacked composite find its siblings in either tree
Some checks are pending
build-gate / build (push) Waiting to run

stack-demo rendered two empty sections in dev. Its sibling bundles were addressed with
a tree-relative path, which the browser resolves against the document URL, so no host
and no attribute can redirect it — correct in the built tree, a 404 in dev. Measured:
the dev entry answers 200 while the path the relative form resolves to answers 404. The
sources now resolve at runtime, so the same file is right in both trees, and the
production branch compiles away to the string it replaces.

That was the third place this defect appeared. Both capability-5 shells carried the same
hardcoded paths and were cured the same way, and each instance was found separately
after the previous one was fixed — because a literal parent-directory path reads as
correct in review and is invisible to a gate that only ever checks the built tree.

Removing the 404 was not enough: the sections were still empty with zero failed
requests. The shared layout injected each bundle with a classic script, and a classic
script pointed at a dev ES module fires its load handler successfully and only then
throws, so the promise resolved while the tag never registered and the wait for
definition quietly timed out. A load handler cannot detect that.

The module rule now lives in the shared layout rather than in a fourth private copy of
the same loader, so the two other composites that inject through it are covered too.
Verified after: both elements define, sixty-six facet rows and twenty-four cards on
stack-demo and on dashboard-plus-grid, zero failed requests on each.
This commit is contained in:
doxa-maps-88655c70 2026-09-22 15:37:41 +00:00
parent e678b1ecab
commit 5725f5b9d9
2 changed files with 39 additions and 5 deletions

View file

@ -89,6 +89,18 @@ function injectScriptOnce(url) {
_stlLoaded[url] = new Promise((ok, fail) => {
const s = document.createElement('script')
s.src = url
// ★ A DEV ENTRY IS AN ES MODULE, AND A CLASSIC <script> LIES ABOUT LOADING IT.
// Pointing a classic script at /plugin-designs/<bundle>/index.js fires onload SUCCESSFULLY and
// only THEN throws "Cannot use import statement outside a module", asynchronously. So this
// promise resolved, waitForDefine() below timed out, the tag never registered, and the section
// rendered empty — with NO failed request and NO console error at the injection site. onload is
// structurally incapable of detecting this.
// MEASURED on stack-demo's dev leaf 2026-09-22: 0 failed requests, and yet
// customElements.get('research-dashboard-map') === false with 0 rows rendered.
// Same rule as plugin-studio.vue:255, which hit this first. It belongs HERE because StackLayout
// is the SHARED layout — dashboard-plus-grid and research-map-plus-grid inject through this same
// function, so one fix covers all three consumers instead of becoming a fourth private copy.
if (/^\/plugin-designs\//.test(url) || /\/index\.js(\?|$)/.test(url)) s.type = 'module'
s.onload = () => ok()
s.onerror = () => { delete _stlLoaded[url]; fail(new Error('failed to load ' + url)) }
document.head.appendChild(s)

View file

@ -17,16 +17,38 @@ import StackLayout from '@map/layouts/StackLayout.vue'
// The two elements to stack. `src` is TREE-RELATIVE at build; the leaf pages resolve it against the
// bundle tree root. Both elements ship their own bundles in the same catalog.
// ── WHERE A SIBLING BUNDLE LIVES DEPENDS ON WHICH TREE IS SERVING ────────────────────────────
// A tree-relative src is resolved by the BROWSER against the document URL, so no attribute and no
// host can redirect it — which makes it correct in exactly one tree and a 404 in the other.
// BUILT: this leaf is served two levels deep (doxa-maps/stack-demo/stack-demo/index.html), so a
// sibling bundle is '../../<bundle>/<bundle>.js'.
// DEV: the dev server serves SOURCE, and a bundle's entry is '/plugin-designs/<bundle>/index.js'.
// MEASURED 2026-09-22 against the running dev server:
// GET /plugin-designs/research-dashboard/index.js ........... 200
// GET /research-dashboard/research-dashboard.js ............. 404 <- what '../../' resolves to
// So stack-demo took 2 x 404 and rendered empty sections in dev, with no console error and no failed
// mount — the sections simply never got their element. Confirmed PRE-EXISTING and reproducible on this
// bundle's own standalone leaf, independent of any composite that nests it.
//
// ★ THIS IS THE THIRD PLACE THIS EXACT DEFECT APPEARED. plugin-studio and doxa-maps-spa both carried
// hardcoded tree-relative paths and both were cured the same way: resolve at RUNTIME from something
// that knows which tree is serving, rather than baking one tree's shape into a string. Each instance
// was found separately, after the previous one was "fixed", because a literal '../..' looks correct
// in review and is invisible to a gate that only ever checks the built tree (check:composite-srcs
// PASSES on this file today, for precisely that reason).
//
// import.meta.env.DEV is statically replaced at build time, so the production branch is the only one
// that survives into the IIFE — the built output is byte-identical to the hardcoded form it replaces.
const bundleSrc = (bundle, file) =>
import.meta.env.DEV ? `/plugin-designs/${bundle}/index.js` : `../../${bundle}/${file}.js`
const sections = [
// src is resolved by the browser relative to THIS leaf page, which is served two levels deep
// (doxa-maps/stack-demo/stack-demo/index.html) — so go UP TWO to reach a sibling bundle's tree
// (doxa-maps/<bundle>/<bundle>.js). (Off-by-one here = a 404 + an empty section, caught silently.)
{
key: 'dashboard',
title: 'Research Dashboard',
sub: 'facets · map · charts · table (live)',
element: 'research-dashboard-map',
src: '../../research-dashboard/research-dashboard.js',
src: bundleSrc('research-dashboard', 'research-dashboard'),
// ★ REQUIRED, and its absence was a live defect (found + fixed 2026-09-22). research-dashboard's
// entry mounts ProfileLoader, which reads profile-config.profile to choose WHICH .vue to load.
// With none, the element still mounted at its full 844px and still reported its tag — so every
@ -49,7 +71,7 @@ const sections = [
title: 'People Groups',
sub: 'the full searchable list (live)',
element: 'people-groups-grid',
src: '../../people-groups-grid/people-groups-grid.js'
src: bundleSrc('people-groups-grid', 'people-groups-grid')
}
]
</script>