Correct the shell's account of how it mounts its tabs
Some checks are pending
build-gate / build (push) Waiting to run

The file header said tab 1 was bp-big-picture, mounted directly via its real tag.
Both halves are false: tab 1 is dashboard-plus-grid, and every tab is an iframe —
there is no direct-tag mount left in this shell. A reader trusting that text would
look for a mount path that does not exist and maintain the wrong bundle believing
it was the one users see. The big-picture bundle is still a valid element; it is
simply one of the parts already composed into the tab-1 composite.

The locale switcher also poked slot zero by hand, writing a profile-config named
big-picture onto what is now an iframe, which ignores it. Harmless, and that is
why it survived: the effectiveLocale watcher was doing the real work all along,
branching on the tab's mode and re-pointing the iframe src that carries the
locale. Removed rather than repaired, since a second hardcoded locale path is one
more thing to keep in sync for nothing. Verified by driving the switcher: the
active frame moves from locale=en to locale=es.

The direct-tag branch in the template is kept. It is generic, keyed on the tab's
mode rather than a bundle name, and remains the right way to nest a future
token-free element. Only the comment claiming tab 1 uses it was wrong.
This commit is contained in:
doxa-maps 2026-09-22 08:33:24 +00:00
parent 498bf9409c
commit 94dc3c64e8
9 changed files with 58 additions and 40 deletions

View file

@ -10,17 +10,26 @@
as its tabs. This IS the DX-maps app: the unified place a user opens to reach every research
map without getting confused by a dozen separate embeds.
TAB 1 = Big Picture (Cat-4 composite <bp-big-picture>) — the header (shared SearchBar + 4
FilterDropdowns) driving the real people-groups grid. Token-free + self-contained, so it is
mounted DIRECTLY via its real tag + its built .js (direct-JS nesting): the SPA injects
big-picture/big-picture.js ONCE (guarded), then creates <bp-big-picture>. This honors the
card's "load each nested element by its real tag + its built .js".
TAB 1 = Big Picture — the cap-4 composite `dashboard-plus-grid`: the research dashboard with the
real people-groups grid stacked beneath it, scrolled as one element.
TABS 2..N = every production research map — each nested as its OWN full SPA via that map's
standalone leaf page inside an <iframe>. WHY an iframe for the map tabs and a direct tag for
tab-1? The map bundles each register their OWN customElements.define (one-shot) AND need the
Mapbox peer-engine + token ladder; loading many such IIFEs into ONE document is the one-shot-
define hazard the research flagged. Giving each map its own document (its leaf page, which is
★ CORRECTED 2026-09-22. This header used to say tab-1 was <bp-big-picture>, "mounted DIRECTLY
via its real tag + its built .js (direct-JS nesting)". BOTH halves are now false and were
measured so: tab-1 is `dashboard-plus-grid` (see TAB_DEFS), and EVERY tab is mode:'iframe' —
there is no direct-tag mount left anywhere in this shell. The `big-picture` bundle still exists
and is still a valid cap-4 element, but it is NOT what this SPA opens on; it sits in
NOT_EXPERIMENTAL as one of the PARTS already composed into the tab-1 composite. A reader who
trusted the old text would go looking for a direct-mount path that no longer exists, and would
maintain the wrong bundle believing it was the one users see.
TABS 1..N = every tab is nested as its OWN full SPA via that bundle's standalone leaf page inside
an <iframe>. WHY an iframe rather than direct-tag nesting? The map bundles each register their
OWN customElements.define (one-shot) AND need the Mapbox peer-engine + token ladder; loading
many such IIFEs into ONE document is the one-shot-define hazard the research flagged. There is a
second reason that applies to EVERY tab, not just maps: window.MAP_TOKEN does not cross an
iframe boundary, so each leaf carries its own baked token (gated by check:nested-token) and a
nested map cannot be starved by whatever the parent document happens to hold. Giving each tab
its own document (its leaf page, which is
already a fully self-contained SPA with its own peer-engine + token ladder + profile-config)
SIDESTEPS that hazard entirely, keeps each map individually correct, and is position-
independent (leaf pages resolve everything relative to the tree root). Each iframe is lazy —
@ -34,8 +43,9 @@
back to a same-tree relative guess so a plain drop still resolves.
NO re-import of any nested plugin's components and NO re-registration of a tag another bundle
owns — the only tag this bundle defines is <doxa-maps-spa> (see index.js). bp-big-picture is
registered by big-picture.js when that IIFE loads; this SPA only USES the tag.
owns — the only tag this bundle defines is <doxa-maps-spa> (see index.js). Every nested bundle
registers its own tag inside its own iframe document, so a one-shot customElements.define in one
tab can never collide with another's.
-->
<template>
<!-- The SHELL (top bar + vertical tab rail + content frame) is the ONE shared beautiful layout,
@ -99,11 +109,20 @@
v-for="(t, i) in tabs" :key="t.id" class="dms-pane"
v-show="i === activeIndex" :hidden="i !== activeIndex">
<!-- TAB 1: direct-tag nesting of the token-free composite. The real <bp-big-picture>
is created imperatively into an EMPTY, Vue-unmanaged slot div (:ref, no children)
after big-picture.js is injected once — so Vue never fights the custom element's
own shadow DOM. The loading/error hint is a SEPARATE overlay sibling, so clearing
the slot with innerHTML never removes a Vue-managed node. -->
<!-- DIRECT-TAG NESTING (mode:'element') — the path for a TOKEN-FREE element that can safely
share this document. The tag is created imperatively into an EMPTY, Vue-unmanaged slot
div (:ref, no children) after that bundle's .js is injected once, so Vue never fights the
custom element's own shadow DOM. The loading/error hint is a SEPARATE overlay sibling, so
clearing the slot with innerHTML never removes a Vue-managed node.
★ NO TAB CURRENTLY USES THIS (measured 2026-09-22): every entry in TAB_DEFS is
mode:'iframe'. This comment used to say "TAB 1: ... The real <bp-big-picture>", which was
doubly wrong — tab-1 is `dashboard-plus-grid`, and it is iframed like the rest. The
branch is KEPT, not deleted: it is generic (keyed on t.mode, not on a bundle name) and it
is the correct way to nest a future token-free element without paying for a document.
Anything that owns a map must stay in the iframe path — one-shot customElements.define
collisions, the Mapbox peer engine, and the fact that window.MAP_TOKEN does not cross an
iframe boundary. -->
<template v-if="t.mode === 'element'">
<div class="dms-slot" :ref="el => setSlot(i, el)"></div>
<div v-if="t.state === 'loading'" class="dms-hint">Loading {{ t.label }}…</div>
@ -112,7 +131,7 @@
</div>
</template>
<!-- TABS 2..N: each map as its own full SPA in a lazy iframe (src set on first activation). -->
<!-- EVERY TAB TODAY: its own full SPA in a lazy iframe (src set on first activation). -->
<iframe
v-else
class="dms-frame"
@ -678,23 +697,22 @@ export default defineComponent({
.catch(() => { /* non-fatal: Experimental simply stays empty */ })
} catch { /* fetch unavailable (non-browser): same fail-soft path */ }
},
// Switch UI language. languages.js's contract: switching = re-setting profile-config.locale on
// the mounted element (ProfileLoader reads it, drives the bundle's i18n). We persist the choice
// and best-effort re-set the locale on tab-1's mounted <bp-big-picture> (the only direct element;
// the iframe maps carry their own locale ladder). No router, no dependency.
// Switch UI language. Persist the choice and let the `effectiveLocale` WATCHER apply it: that
// watcher already branches on the active tab's mode — re-point an iframe src (which carries
// ?locale=), or re-set profile-config on a direct element — generically, by t.id.
//
// ★ THIS ALSO USED TO POKE SLOT 0 BY HAND, and that block was stale in two ways at once
// (corrected 2026-09-22). It wrote a profile-config naming 'big-picture' onto slot 0's first
// child, described in its own comment as "tab-1's mounted <bp-big-picture> (the only direct
// element)". MEASURED: every tab is now mode:'iframe' — there is no direct element left — and
// tab-1 is `dashboard-plus-grid`, not `big-picture`. So it set an attribute an <iframe> ignores,
// naming a bundle that is not tab-1. Harmless, which is exactly why it survived: the watcher was
// doing the real work the whole time. Removed rather than repaired — a second hardcoded locale
// path is one more thing to keep in sync for no gain.
setLocale(code) {
this.locale = code
this.localeOpen = false
saveLocale(code)
try {
const slot = this._slots && this._slots[0]
const el = slot && slot.firstElementChild
if (el && el.setAttribute) {
const pc = { profile: 'big-picture', instanceId: 'dms-big-picture', locale: code }
if (this.tk || this.cfg.tk) pc.tk = this.tk || this.cfg.tk
el.setAttribute('profile-config', JSON.stringify(pc))
}
} catch { /* non-fatal: the switcher still persists + updates the UI */ }
},
// The shared rail emits the selected tab's `key` (its id); map it back to the index the
// pane-mount machinery below is keyed on.

View file

@ -1 +1 @@
pid=2317786 started=2026-09-22T08:07:49Z by=lk10x
pid=2427619 started=2026-09-22T08:27:28Z by=lk10x

File diff suppressed because one or more lines are too long

View file

@ -26,7 +26,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <doxa-maps-spa> custom element. -->
<script src="../../doxa-maps-spa.js" integrity="sha384-pmn3IM3nPXHdhYvfyIW11PrG4UhEkINrGQdzwi2vYl023Lomm9bRTeIpC4lh0lCl" crossorigin="anonymous"></script>
<script src="../../doxa-maps-spa.js" integrity="sha384-eUOhrqzBqC4p9TmS3XkDuKPTjgRooQV1WrhUFaa8p3NqmKUXmEfbwh8GxfB5AK90" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -26,7 +26,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <doxa-maps-spa> custom element. -->
<script src="../doxa-maps-spa.js" integrity="sha384-pmn3IM3nPXHdhYvfyIW11PrG4UhEkINrGQdzwi2vYl023Lomm9bRTeIpC4lh0lCl" crossorigin="anonymous"></script>
<script src="../doxa-maps-spa.js" integrity="sha384-eUOhrqzBqC4p9TmS3XkDuKPTjgRooQV1WrhUFaa8p3NqmKUXmEfbwh8GxfB5AK90" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -130,7 +130,7 @@ header.top .sub { margin-top: 3px; font: 12px/1.4 var(--sans); color: var(--mut)
<script>window.FORGE = {"mode":"built","scope":"bundle","manifestUrl":"../manifest.json","bundle":"doxa-maps-spa","hrefBase":"../"};
/* file:// contract: manifest inlined at build — no fetch needed to render. */
window.FORGE.manifest = {"schema":"doxa-maps-manifest@1","generatedFrom":"plugin-designs/*/*.vue","embedBase":".","bundleCount":19,"profileCount":28,"bundles":[{"bundle":"doxa-maps-spa","scriptUrl":"./doxa-maps-spa/doxa-maps-spa.js","output":"doxa-maps/doxa-maps-spa/doxa-maps-spa.js","integrity":"sha384-pmn3IM3nPXHdhYvfyIW11PrG4UhEkINrGQdzwi2vYl023Lomm9bRTeIpC4lh0lCl","elements":["doxa-maps-spa"],"profiles":[{"profile":"doxa-maps-spa","file":"doxa-maps-spa.vue","description":"The DX-maps app itself: one element whose vertical-tab rail nests every other","element":"doxa-maps-spa","type":"nested-mfe-spa","isMap":false,"iframeUrl":"./doxa-maps-spa/doxa-maps-spa/index.html","scriptUrl":"./doxa-maps-spa/doxa-maps-spa.js","embed":{"iframe":"\u003ciframe src=\"./doxa-maps-spa/doxa-maps-spa/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript>window.MAP_APP_API_URL=\"https://pray.doxa.life\";\u003c/script>\n\u003cscript src=\"./doxa-maps-spa/doxa-maps-spa.js\" integrity=\"sha384-pmn3IM3nPXHdhYvfyIW11PrG4UhEkINrGQdzwi2vYl023Lomm9bRTeIpC4lh0lCl\" crossorigin=\"anonymous\">\u003c/script>\n\u003cdoxa-maps-spa profile-config='{\"profile\":\"doxa-maps-spa\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'>\u003c/doxa-maps-spa>"},"instances":[{"id":"app","title":"DX-Maps SPA — Big Picture + every research map (app-shell)","apiBaseUrl":"","props":{"profile":"doxa-maps-spa","provider":"maplibre","instanceId":"doxa-maps-spa-app","title":"DX-Maps"},"element":"doxa-maps-spa","iframeUrl":"./doxa-maps-spa/doxa-maps-spa/app/index.html","embed":{"iframe":"\u003ciframe src=\"./doxa-maps-spa/doxa-maps-spa/app/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>"}}]}]}]};</script>
window.FORGE.manifest = {"schema":"doxa-maps-manifest@1","generatedFrom":"plugin-designs/*/*.vue","embedBase":".","bundleCount":19,"profileCount":28,"bundles":[{"bundle":"doxa-maps-spa","scriptUrl":"./doxa-maps-spa/doxa-maps-spa.js","output":"doxa-maps/doxa-maps-spa/doxa-maps-spa.js","integrity":"sha384-eUOhrqzBqC4p9TmS3XkDuKPTjgRooQV1WrhUFaa8p3NqmKUXmEfbwh8GxfB5AK90","elements":["doxa-maps-spa"],"profiles":[{"profile":"doxa-maps-spa","file":"doxa-maps-spa.vue","description":"The DX-maps app itself: one element whose vertical-tab rail nests every other","element":"doxa-maps-spa","type":"nested-mfe-spa","isMap":false,"iframeUrl":"./doxa-maps-spa/doxa-maps-spa/index.html","scriptUrl":"./doxa-maps-spa/doxa-maps-spa.js","embed":{"iframe":"\u003ciframe src=\"./doxa-maps-spa/doxa-maps-spa/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript>window.MAP_APP_API_URL=\"https://pray.doxa.life\";\u003c/script>\n\u003cscript src=\"./doxa-maps-spa/doxa-maps-spa.js\" integrity=\"sha384-eUOhrqzBqC4p9TmS3XkDuKPTjgRooQV1WrhUFaa8p3NqmKUXmEfbwh8GxfB5AK90\" crossorigin=\"anonymous\">\u003c/script>\n\u003cdoxa-maps-spa profile-config='{\"profile\":\"doxa-maps-spa\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'>\u003c/doxa-maps-spa>"},"instances":[{"id":"app","title":"DX-Maps SPA — Big Picture + every research map (app-shell)","apiBaseUrl":"","props":{"profile":"doxa-maps-spa","provider":"maplibre","instanceId":"doxa-maps-spa-app","title":"DX-Maps"},"element":"doxa-maps-spa","iframeUrl":"./doxa-maps-spa/doxa-maps-spa/app/index.html","embed":{"iframe":"\u003ciframe src=\"./doxa-maps-spa/doxa-maps-spa/app/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>"}}]}]}]};</script>
<script>/**
* staging-forge.js — the ONE-PAGE semantic staging environment (root + bundle pages).
*

File diff suppressed because one or more lines are too long

View file

@ -272,7 +272,7 @@
"bundle": "doxa-maps-spa",
"scriptUrl": "./doxa-maps-spa/doxa-maps-spa.js",
"output": "doxa-maps/doxa-maps-spa/doxa-maps-spa.js",
"integrity": "sha384-pmn3IM3nPXHdhYvfyIW11PrG4UhEkINrGQdzwi2vYl023Lomm9bRTeIpC4lh0lCl",
"integrity": "sha384-eUOhrqzBqC4p9TmS3XkDuKPTjgRooQV1WrhUFaa8p3NqmKUXmEfbwh8GxfB5AK90",
"elements": [
"doxa-maps-spa"
],
@ -288,7 +288,7 @@
"scriptUrl": "./doxa-maps-spa/doxa-maps-spa.js",
"embed": {
"iframe": "<iframe src=\"./doxa-maps-spa/doxa-maps-spa/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./doxa-maps-spa/doxa-maps-spa.js\" integrity=\"sha384-pmn3IM3nPXHdhYvfyIW11PrG4UhEkINrGQdzwi2vYl023Lomm9bRTeIpC4lh0lCl\" crossorigin=\"anonymous\"></script>\n<doxa-maps-spa profile-config='{\"profile\":\"doxa-maps-spa\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></doxa-maps-spa>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./doxa-maps-spa/doxa-maps-spa.js\" integrity=\"sha384-eUOhrqzBqC4p9TmS3XkDuKPTjgRooQV1WrhUFaa8p3NqmKUXmEfbwh8GxfB5AK90\" crossorigin=\"anonymous\"></script>\n<doxa-maps-spa profile-config='{\"profile\":\"doxa-maps-spa\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></doxa-maps-spa>"
},
"instances": [
{

File diff suppressed because one or more lines are too long