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.
118 lines
6.2 KiB
Vue
118 lines
6.2 KiB
Vue
<script setup>
|
|
/**
|
|
* stack-demo.vue — Capability-4 composite demonstrating the shared StackLayout (scroll-stack).
|
|
*
|
|
* @element stack-demo
|
|
* @description Groups TWO independent elements TOP-TO-BOTTOM so you can embed the pair on a page and
|
|
* SCROLL down through all of it (the doxa-life / nuxtinator page-flow model), instead of switching
|
|
* between them with tabs. Section 1 = the rich <research-dashboard-map> (facets + map + charts +
|
|
* table); section 2 = the cap-2 <people-groups-grid> (live pray.doxa.life). Each renders at its
|
|
* NATURAL height; the host page scrolls.
|
|
*
|
|
* This is the STACK counterpart to big-picture (which TABS/couples). Both reuse the shared library
|
|
* 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.
|
|
// ── 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`
|
|
|
|
// ★ 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',
|
|
sub: 'facets · map · charts · table (live)',
|
|
element: 'research-dashboard-map',
|
|
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
|
|
// structural check said "present" — while ProfileLoader rendered "missing or invalid
|
|
// profile-config prop" where the dashboard should be. The grid section below was fine, which is
|
|
// what made it survive: half the page worked.
|
|
// Shape copied verbatim from this bundle's own generated leaf (its BASE_CONFIG), so the nested
|
|
// and standalone mounts ask for the identical thing. Now gated by check:composite-srcs.
|
|
attrs: {
|
|
'profile-config': JSON.stringify({
|
|
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'
|
|
})
|
|
}
|
|
},
|
|
{
|
|
key: 'grid',
|
|
title: 'People Groups',
|
|
sub: 'the full searchable list (live)',
|
|
element: 'people-groups-grid',
|
|
src: bundleSrc('people-groups-grid', 'people-groups-grid')
|
|
}
|
|
])
|
|
</script>
|
|
|
|
<template>
|
|
<StackLayout title="Stacked view" :sections="sections">
|
|
<template #meta>scroll ↓</template>
|
|
</StackLayout>
|
|
</template>
|
|
|
|
<style>
|
|
/* stack-demo is a thin composition: the shell + its scroll behaviour live in the shared StackLayout
|
|
(which self-injects its own shadow CSS). Nothing to add here beyond making the host fill width. */
|
|
:host { display:block; width:100%; }
|
|
</style>
|