Studio and stack-demo: forward the host's engine and token too

Extending the composite embed fix to the two remaining composites that a partner
can mount, plus two red gates that arrived on main.

plugin-studio already threaded tk, tokenUrl, locale and treeBase into every pane
it mounts — but not `provider`, so a studio embedded with provider=maplibre
showed mapbox panes and, on a keyless host, panes asking for a token. It now
forwards the engine the same way, validated to the two known values, and accepts
it as an attribute as well (a host showing plugins to partners wants to flip both
at once).

stack-demo declared no props at all, the same hole the two shipped composites
had: <stack-demo profile-config='{"tk":"…"}'> had its token read by nobody, and
on a page with no baked token the nested dashboard reported "No Mapbox access
token provided". It now parses the host config, publishes the token page-wide and
threads tk + provider into the child it mounts by tag.

MEASURED on a foreign origin, minimal config: stack-demo [tk] canvas 1 with 13
api.mapbox.com requests; stack-demo [provider] canvas 1 with ZERO. plugin-studio
[tk] canvas 1 with 19.

★ ONE LEVEL DEEPER, NOT MINE, MEASURED AND REPORTED: plugin-studio still shows
mapbox tiles under provider=maplibre, and the pane chain says why — the studio
hands provider=maplibre to <doxa-maps-spa> correctly, and the SPA then mounts
<dashboard-plus-grid> with a config carrying tk and NO provider. The cap-5 shell
drops it. Same three lines fix it there; routed to its owner rather than reached
into.

Also fixed two gates that were red on main and blocking every builder:
  check:script-flags       check:gallery-embed advertised --mode/--only in an
                           inline comment its command ends with, so the flags
                           could never arrive and the gate would run its default
                           and exit 0 looking like success. Gave the modes their
                           own entries and stopped the parent advertising what it
                           cannot receive.
  check:capability-exemplars  the generated table had drifted from the manifest
                           (a legacy bundle arrived); regenerated, 35 rows match.
This commit is contained in:
doxa-maps-88655c70 2026-09-24 19:39:59 +00:00
parent e565c58ca0
commit 4727005aba
12 changed files with 77 additions and 30 deletions

View file

@ -63,7 +63,6 @@ Generated from 22 bundle(s).
| `doxa-simple-map` | `doxa-simple-map` | `parameterized` |
| `doxa-vision-card` | `doxa-vision-card` | `parameterized` |
| `people-groups-grid` | `people-groups-grid` | `parameterized` |
| `upg-neighbor` | `upg-neighbor` | `parameterized` |
## Capability 3 — multi-view-element (monolithic)
@ -71,6 +70,7 @@ Generated from 22 bundle(s).
|---|---|---|
| `doxa-research-map` | `research-map` | `monolithic` |
| `research-dashboard` | `research-dashboard` | `monolithic` |
| `upg-neighbor` | `upg-neighbor` | `monolithic` |
## Capability 4 — composite-element (nested-element SPA)

View file

@ -146,7 +146,9 @@
"check:synced-css": "node internal/check-synced-css-copies.mjs",
"check:composite-reg": "node internal/check-composite-registration.mjs",
"check:filter-width": "node internal/check-filter-width-stable.mjs",
"check:gallery-embed": "node internal/check-research-gallery-embed.mjs # the cap-5 Research Gallery must embed on a FOREIGN ORIGIN (a stand-in host page on localhost, the built tree on 127.0.0.1) in BOTH modes — bare <script>+<doxa-maps-spa> and <iframe> — with the layout inside the host box and every curated tab painting a map. Starts its own servers on OS-assigned ports; needs only a built tree. --mode js|iframe, --only <tab,tab>. Exit 2 = SKIP (no built tree / no playwright).",
"check:gallery-embed": "node internal/check-research-gallery-embed.mjs # the cap-5 Research Gallery must embed on a FOREIGN ORIGIN (a stand-in host page on localhost, the built tree on 127.0.0.1) in BOTH modes — bare <script>+<doxa-maps-spa> and <iframe> — with the layout inside the host box and every curated tab painting a map. Starts its own servers on OS-assigned ports; needs only a built tree. Runs both modes; for one mode use the sibling entries check:gallery-embed:js and check:gallery-embed:iframe, and to narrow to specific tabs invoke internal/check-research-gallery-embed.mjs directly. Naming the flags HERE made check:script-flags red: this command ends in a # comment, so anything appended lands inside the comment and the shell drops it, and the gate would run its default and exit 0 looking like success. Exit 2 = SKIP (no built tree / no playwright).",
"check:gallery-embed:js": "node internal/check-research-gallery-embed.mjs --mode js # the JS-embed half of check:gallery-embed, as its own entry. The flag cannot reach the gate through `bun run check:gallery-embed --mode js`: that script ends in an inline # comment, so the shell drops anything appended after it and the gate silently runs its default and exits 0 — check:script-flags exists for exactly that trap.",
"check:gallery-embed:iframe": "node internal/check-research-gallery-embed.mjs --mode iframe # the iframe half, same reason.",
"check:spa-tab-order": "node internal/check-spa-tab-order.mjs # the cap-5 SPA must open on Big Picture and keep the Driver's order (Big Picture, UPG-100, Prayer Progress, Gospel Resources) — asserted as PRESENCE + tab-1 + RELATIVE order on the RENDERED rail, because the rail gained three sections and now renders 12 rows with others between them. ⚠ NOT WAVE-WIRED: it FAILS rather than skips when the SPA is unreachable, deliberately (\"a gate that cannot reach its subject has not passed, it has not run\") — correct standalone, but gates:post runs on checkouts with no server. Needs `bun run serve` on 4173.",
"check:backlog-status": "node internal/check-backlog-status-enum.mjs",
"check:mjs-parse": "node internal/check-mjs-parses.mjs",

View file

@ -382,6 +382,10 @@ export default defineComponent({
props: {
treeBase: { type: String, default: '' },
profileConfig: { type: [String, Object], default: '' },
// The map engine, as an attribute as well as a profile-config key — the studio previews map
// plugins, so a host showing it to partners wants to flip both at once. Forwarded to every pane
// below; an unrecognised value is ignored there rather than passed on.
provider: { type: String, default: '' },
// NOT `title` — that name collides with the NATIVE HTMLElement.title property.
// defineCustomElement maps every prop to an attribute, and `title` already exists on every
// element (the tooltip), so Vue warned on EVERY mount:
@ -875,6 +879,13 @@ export default defineComponent({
const pc = { profile: row.label, instanceId: 'ps-' + row.key.replace(/\//g, '-'), locale: this.locale }
if (this.tk || this.cfg.tk) pc.tk = this.tk || this.cfg.tk
if (this.tokenUrl || this.cfg.tokenUrl) pc.tokenUrl = this.tokenUrl || this.cfg.tokenUrl
// ★ AND THE ENGINE, for the same reason tk rides along. resolveMapProvider's ladder starts
// at profile-config.provider, and a pane reads the config THIS file writes — so a studio
// embedded with provider=maplibre showed mapbox panes and, on a keyless host, panes that
// asked for a token. MEASURED on a foreign origin: 19 requests to api.mapbox.com for a
// maplibre request. Validated to the two known engines so a typo cannot select a third.
const wantProvider = this.provider || this.cfg.provider
if (wantProvider === 'maplibre' || wantProvider === 'mapbox') pc.provider = wantProvider
// PASS THE TREE BASE DOWN — a nested cap-5 shell cannot guess it.
// doxa-maps-spa carries its OWN hardcoded, TREE-RELATIVE tab list (doxa-maps-spa.vue:265-312,
// e.g. 'doxa-research-map/research-map/index.html') and resolves it against ITS treeBase,

View file

@ -13,7 +13,9 @@
* layouts in library/layouts/. StackLayout mounts each section's `element` eagerly (all visible to
* scroll) via the same element/src/attrs contract VerticalTabsLayout uses.
*/
import { computed } from 'vue'
import StackLayout from '@map/layouts/StackLayout.vue'
import { publishMapToken } from '@map/composables/useSharedMapToken.js'
// 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.
@ -42,7 +44,30 @@ import StackLayout from '@map/layouts/StackLayout.vue'
const bundleSrc = (bundle, file) =>
import.meta.env.DEV ? `/plugin-designs/${bundle}/index.js` : `../../${bundle}/${file}.js`
const sections = [
// ★ THE HOST'S OWN profile-config. This demo declared no props, so a partner embedding it with
// <stack-demo profile-config='{"tk":"pk...."}'> had that token read by nobody and the nested
// dashboard reported "No Mapbox access token provided" on a page with no baked token to fall back
// on. Same defect the two shipped composites had; check:composite-embed-knobs names it.
const props = defineProps({
profileConfig: { type: [String, Object], default: null },
})
const hostConfig = computed(() => {
const raw = props.profileConfig
if (!raw) return {}
if (typeof raw === 'object') return raw
try { return JSON.parse(raw) } catch { return {} }
})
const hostToken = computed(() => {
const tk = hostConfig.value?.tk
return typeof tk === 'string' && tk ? tk : ''
})
if (hostToken.value) publishMapToken(hostToken.value)
const hostProvider = computed(() => {
const p = hostConfig.value?.provider
return p === 'maplibre' || p === 'mapbox' ? p : ''
})
const sections = computed(() => [
{
key: 'dashboard',
title: 'Research Dashboard',
@ -62,6 +87,10 @@ const sections = [
profile: 'research-dashboard',
dataSource: 'pray-tools',
instanceId: 'stack-demo-research-dashboard',
// Spread only when the host supplied one, so the nested mount otherwise asks for exactly
// what its standalone leaf asks for.
...(hostToken.value ? { tk: hostToken.value } : {}),
...(hostProvider.value ? { provider: hostProvider.value } : {}),
assetsBase: '../../assets'
})
}
@ -73,7 +102,7 @@ const sections = [
element: 'people-groups-grid',
src: bundleSrc('people-groups-grid', 'people-groups-grid')
}
]
])
</script>
<template>

File diff suppressed because one or more lines are too long

View file

@ -799,7 +799,7 @@
"bundle": "plugin-studio",
"scriptUrl": "./plugin-studio/plugin-studio.js",
"output": "doxa-maps/plugin-studio/plugin-studio.js",
"integrity": "sha384-2RlLFRU2XdjBN7w+FPguj8INgj57JFg4IFT2AmdefGnR9QQLehbiiAffV45LcsFH",
"integrity": "sha384-EvwAOYsSENxqsDaAJcyANtACSyVvdFI7Eh3BiB4dHIFjln2tqx+G/VUpLC1zurMO",
"elements": [
"plugin-studio"
],
@ -827,7 +827,7 @@
"scriptUrl": "./plugin-studio/plugin-studio.js",
"embed": {
"iframe": "<iframe src=\"./plugin-studio/plugin-studio/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=\"./plugin-studio/plugin-studio.js\" integrity=\"sha384-2RlLFRU2XdjBN7w+FPguj8INgj57JFg4IFT2AmdefGnR9QQLehbiiAffV45LcsFH\" crossorigin=\"anonymous\"></script>\n<plugin-studio profile-config='{\"profile\":\"plugin-studio\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></plugin-studio>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./plugin-studio/plugin-studio.js\" integrity=\"sha384-EvwAOYsSENxqsDaAJcyANtACSyVvdFI7Eh3BiB4dHIFjln2tqx+G/VUpLC1zurMO\" crossorigin=\"anonymous\"></script>\n<plugin-studio profile-config='{\"profile\":\"plugin-studio\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></plugin-studio>"
},
"instances": [
{
@ -1006,7 +1006,7 @@
"bundle": "stack-demo",
"scriptUrl": "./stack-demo/stack-demo.js",
"output": "doxa-maps/stack-demo/stack-demo.js",
"integrity": "sha384-gnwM6s+puf1K60Nl7crQcC2QC+9BxwpkaTx4OepLZ8VUCraZc+aRlWpsb6nj6Gyf",
"integrity": "sha384-EI3V6JX9ws4fGxKBzMOgwhG5de8MvL6va3zqmB2gTXecd/m0mxNq8eUht/SGzH1B",
"elements": [
"stack-demo"
],
@ -1026,7 +1026,7 @@
"scriptUrl": "./stack-demo/stack-demo.js",
"embed": {
"iframe": "<iframe src=\"./stack-demo/stack-demo/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=\"./stack-demo/stack-demo.js\" integrity=\"sha384-gnwM6s+puf1K60Nl7crQcC2QC+9BxwpkaTx4OepLZ8VUCraZc+aRlWpsb6nj6Gyf\" crossorigin=\"anonymous\"></script>\n<stack-demo profile-config='{\"profile\":\"stack-demo\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></stack-demo>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./stack-demo/stack-demo.js\" integrity=\"sha384-EI3V6JX9ws4fGxKBzMOgwhG5de8MvL6va3zqmB2gTXecd/m0mxNq8eUht/SGzH1B\" crossorigin=\"anonymous\"></script>\n<stack-demo profile-config='{\"profile\":\"stack-demo\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></stack-demo>"
},
"instances": [
{

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 <plugin-studio> custom element. -->
<script src="../plugin-studio.js" integrity="sha384-2RlLFRU2XdjBN7w+FPguj8INgj57JFg4IFT2AmdefGnR9QQLehbiiAffV45LcsFH" crossorigin="anonymous"></script>
<script src="../plugin-studio.js" integrity="sha384-EvwAOYsSENxqsDaAJcyANtACSyVvdFI7Eh3BiB4dHIFjln2tqx+G/VUpLC1zurMO" 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 <plugin-studio> custom element. -->
<script src="../../plugin-studio.js" integrity="sha384-2RlLFRU2XdjBN7w+FPguj8INgj57JFg4IFT2AmdefGnR9QQLehbiiAffV45LcsFH" crossorigin="anonymous"></script>
<script src="../../plugin-studio.js" integrity="sha384-EvwAOYsSENxqsDaAJcyANtACSyVvdFI7Eh3BiB4dHIFjln2tqx+G/VUpLC1zurMO" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

File diff suppressed because one or more lines are too long

View file

@ -37,7 +37,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <stack-demo> custom element. -->
<script src="../stack-demo.js" integrity="sha384-gnwM6s+puf1K60Nl7crQcC2QC+9BxwpkaTx4OepLZ8VUCraZc+aRlWpsb6nj6Gyf" crossorigin="anonymous"></script>
<script src="../stack-demo.js" integrity="sha384-EI3V6JX9ws4fGxKBzMOgwhG5de8MvL6va3zqmB2gTXecd/m0mxNq8eUht/SGzH1B" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -37,7 +37,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <stack-demo> custom element. -->
<script src="../../stack-demo.js" integrity="sha384-gnwM6s+puf1K60Nl7crQcC2QC+9BxwpkaTx4OepLZ8VUCraZc+aRlWpsb6nj6Gyf" crossorigin="anonymous"></script>
<script src="../../stack-demo.js" integrity="sha384-EI3V6JX9ws4fGxKBzMOgwhG5de8MvL6va3zqmB2gTXecd/m0mxNq8eUht/SGzH1B" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order: