Merge origin/development into studio/deep-research

This commit is contained in:
Doxa Maps 2026-09-29 19:34:32 +00:00
commit cff06fb8e6
10 changed files with 537 additions and 72 deletions

View file

@ -0,0 +1,280 @@
#!/usr/bin/env node
/**
* check-pane-config-reforward — a composite's panes keep following the outer config for the whole
* session, and a locale change never rewrites a filter into a display name.
*
* ★ THE BUG THIS GUARDS, measured by rega on development 92ca8b6e6 across FOUR composites
* (regions-mfe, upg-100-mfe, country-posters-mfe, research-map-plus-grid). The outer element's
* profile-config reached locale 'ar' by +1.5 s — attribute AND property, same node — while every
* NESTED host carried NO locale key at boot and still none at +15 s. Text per composite at +15 s:
* the chrome switched (3 Arabic / 0 Latin) and the panes did not (0/205, 0/190, 0/618, 0/43).
* The cause is one line per composite: the pane's profile-config is written ONCE, in onMounted, so
* the pane holds a JSON STRING and every later change stops at the composite. The leaf's own chain
* is fully reactive and was never at fault.
*
* ★ IT RUNS THE SHIPPED HELPER WITH REAL VUE. usePaneConfig is imported, not re-typed, and driven
* by real refs through a real watchEffect — the dependency-tracking hole this gate's case 3 covers
* (a plain array leaves a LATER entry's getter untracked) is invisible to any test that fakes
* reactivity, and it was found by studio only because their test used real Vue.
*
* ★ AND IT ASSERTS THE DOM, NOT THE RETURN VALUE. Every case reads the attribute back off the
* element, because "the helper decided to write" and "the pane was told" are different claims and
* only the second one is the fix.
*
* Exit 0 pass · 1 fail.
*/
import { ref, reactive, nextTick } from 'vue'
import { usePaneConfig } from '../library/composables/usePaneConfig.js'
let bad = 0
const ok = (cond, what) => { if (!cond) bad++; console.log(` ${cond ? 'ok ' : 'FAIL'} ${what}`) }
/**
* The smallest element that can hold attributes and answer contains(). Not a DOM shim in general —
* only the surface the helper actually touches, so a change in what it touches breaks loudly here
* rather than passing against a stub that pretends to be a browser.
*/
function makeEl(name = 'pane') {
const attrs = new Map()
return {
name,
isConnected: true,
writes: 0,
getAttribute: (k) => (attrs.has(k) ? attrs.get(k) : null),
setAttribute(k, v) { attrs.set(k, String(v)); this.writes++ },
}
}
const makeHost = (children) => ({ contains: (el) => children.includes(el) })
// ★ NULL-SAFE DELIBERATELY. When the re-forward is broken the attribute is absent, and a gate that
// CRASHES on that reports "something went wrong somewhere" instead of naming the pane that stopped
// following. Verified against the plain-array mutation: it turns a TypeError into a named FAIL.
const cfgOf = (el) => {
const raw = el.getAttribute('profile-config')
if (!raw) return {}
try { return JSON.parse(raw) } catch { return {} }
}
// ── 1. THE MEASURED DEFECT. A locale change after mount must reach the pane.
{
const outer = ref({ locale: 'en', apiBaseUrl: '/api' })
const el = makeEl()
const host = makeHost([el])
const paneConfig = usePaneConfig(() => host)
// Mount-time write, exactly as the composite does it before registering.
el.setAttribute('profile-config', JSON.stringify({ ...outer.value, profile: 'regions-list' }))
paneConfig.register(el, () => ({ ...outer.value, profile: 'regions-list' }))
await nextTick()
const atMount = el.writes
ok(cfgOf(el).locale === 'en', 'at mount the pane has the outer locale')
outer.value = { locale: 'ar', apiBaseUrl: '/api' }
await nextTick()
ok(cfgOf(el).locale === 'ar', 'after a live locale switch the pane has been told (this is the bug: it used to stay en)')
ok(el.writes === atMount + 1, `exactly one re-write, not a loop (${el.writes - atMount})`)
ok(cfgOf(el).profile === 'regions-list', "the pane's own keys survive the re-forward")
paneConfig.dispose()
}
// ── 1b. NO LOCALE KEY AT BOOT IS THE REAL BEFORE-STATE, not a stale one. rega measured the nested
// hosts carrying no locale key at all, so the first re-forward is absent -> 'ar'.
{
const outer = ref({ apiBaseUrl: '/api' })
const el = makeEl()
const paneConfig = usePaneConfig(() => makeHost([el]))
el.setAttribute('profile-config', JSON.stringify({ ...outer.value }))
paneConfig.register(el, () => ({ ...outer.value }))
await nextTick()
ok(cfgOf(el).locale === undefined, 'the measured boot state: no locale key on the nested host')
outer.value = { apiBaseUrl: '/api', locale: 'ar' }
await nextTick()
ok(cfgOf(el).locale === 'ar', 'an ABSENT locale key is still a change, so the write fires')
paneConfig.dispose()
}
// ── 2. NO WRITE WHEN NOTHING CHANGED. Setting profile-config re-parses it in the child and can
// remount what it drives, so an unconditional write on every reactive tick is a rerender loop.
{
const outer = ref({ locale: 'en' })
const unrelated = ref(0)
const el = makeEl()
const paneConfig = usePaneConfig(() => makeHost([el]))
el.setAttribute('profile-config', JSON.stringify(outer.value))
paneConfig.register(el, () => ({ ...outer.value, tick: unrelated.value && 0 }))
await nextTick()
const before = el.writes
unrelated.value = 7 // reactive churn that does not change the config
await nextTick()
ok(el.writes === before, `an unchanged config writes nothing (${el.writes - before} writes)`)
paneConfig.dispose()
}
// ── 3. EVERY PANE FOLLOWS, NOT ONLY THE FIRST. ★ THE REGRESSION STUDIO FOUND. With a plain array
// the watchEffect tracks only the getters reachable on its FIRST run, so pane 2's source is never a
// dependency: changing it produced ZERO writes. That is this very bug, reappearing inside its own
// fix, for any adopter whose panes read different sources.
// It is also plugin-studio's load-bearing property, recorded at plugin-studio.vue:897: "EVERY
// mounted pane, not only the one on screen: a kept-alive pane showed the old language when the
// reader came back to it."
{
const a = ref('en')
const b = ref('en')
const elA = makeEl('a')
const elB = makeEl('b')
const paneConfig = usePaneConfig(() => makeHost([elA, elB]))
paneConfig.register(elA, () => ({ locale: a.value }))
paneConfig.register(elB, () => ({ locale: b.value }))
await nextTick()
b.value = 'ar' // ONLY the second pane's source
await nextTick()
ok(cfgOf(elB).locale === 'ar', 'the SECOND registered pane follows its own source (a plain array left this untracked)')
ok(cfgOf(elA).locale === 'en', 'and the first pane is not disturbed by it')
a.value = 'ro'
await nextTick()
ok(cfgOf(elA).locale === 'ro', 'the first pane still follows after the second has fired')
paneConfig.dispose()
}
// ── 4. A DETACHED PANE IS DROPPED, not written to. A pane column rebuild replaces the elements.
{
const outer = ref({ locale: 'en' })
const kept = makeEl('kept')
const gone = makeEl('gone')
let children = [kept, gone]
const paneConfig = usePaneConfig(() => makeHost(children))
paneConfig.register(kept, () => ({ ...outer.value }))
paneConfig.register(gone, () => ({ ...outer.value }))
await nextTick()
ok(paneConfig.count() === 2, 'both panes registered')
children = [kept] // the column was rebuilt; `gone` is no longer under it
gone.isConnected = false
const goneWrites = gone.writes
outer.value = { locale: 'ar' }
await nextTick()
ok(gone.writes === goneWrites, 'a detached pane is not written to')
ok(paneConfig.count() === 1, `and it is PRUNED rather than re-tested forever (count ${paneConfig.count()})`)
ok(cfgOf(kept).locale === 'ar', 'the surviving pane still follows')
paneConfig.dispose()
}
// ── 5. ONE PANE'S THROWING GETTER DOES NOT TAKE THE OTHERS DOWN. Inside a watchEffect an exception
// stops every LATER entry in the same pass — which would silently freeze panes that are fine.
{
const outer = ref({ locale: 'en' })
const boom = makeEl('boom')
const fine = makeEl('fine')
const paneConfig = usePaneConfig(() => makeHost([boom, fine]))
const realError = console.error
let errors = 0
console.error = () => { errors++ }
paneConfig.register(boom, () => { throw new Error('pane getter exploded') })
paneConfig.register(fine, () => ({ ...outer.value }))
await nextTick()
outer.value = { locale: 'ar' }
await nextTick()
console.error = realError
ok(cfgOf(fine).locale === 'ar', 'a pane registered AFTER a throwing one still follows the outer config')
ok(errors >= 1, `and the throw is reported rather than swallowed (${errors})`)
ok(errors <= 2, `but not once per reactive tick forever (${errors})`)
paneConfig.dispose()
}
// ── 6. registerAttrs: THE SECOND PANE SHAPE. people-groups-grid takes three separate attributes
// rather than one JSON blob (research-map-plus-grid.vue:430-435). Same list, same effect, same
// write-on-change — the point of generalising rather than letting the second adopter fork.
{
const linked = reactive({ region: '', religion: '', language: 'arb' })
const grid = makeEl('grid')
const paneConfig = usePaneConfig(() => makeHost([grid]))
paneConfig.registerAttrs(grid, () => ({
'wagf-region': linked.region,
'religion-family': linked.religion,
'primary-language': linked.language,
}))
await nextTick()
ok(grid.getAttribute('primary-language') === 'arb', 'an attribute-configured pane is written at registration')
const before = grid.writes
linked.region = 'africa'
await nextTick()
ok(grid.getAttribute('wagf-region') === 'africa', 'a changed attribute is written')
ok(grid.writes === before + 1, `ONLY the changed attribute is written, not all three (${grid.writes - before})`)
ok(grid.getAttribute('religion-family') === '', 'an empty value is written as empty, never the string "undefined"')
paneConfig.dispose()
}
// ── 6b. A KEY THE GETTER LATER OMITS IS CLEARED, not left applied. A stale filter silently
// narrowing the grid is the same class of lie as the frozen config this helper exists to fix.
{
const linked = reactive({ region: 'africa', present: true })
const grid = makeEl('grid')
const paneConfig = usePaneConfig(() => makeHost([grid]))
paneConfig.registerAttrs(grid, () => (linked.present
? { 'wagf-region': linked.region, 'religion-family': 'islam' }
: { 'religion-family': 'islam' }))
await nextTick()
ok(grid.getAttribute('wagf-region') === 'africa', 'the filter is applied')
linked.present = false
await nextTick()
ok(grid.getAttribute('wagf-region') === '', 'an omitted key CLEARS its attribute rather than leaving a stale filter')
paneConfig.dispose()
}
// ── 7. ★ LOCALE-INVARIANCE OF A FILTER VALUE (sr's property, SR-ADOPT-ANSWERS). A re-forward
// triggered by a LOCALE change must produce the SAME primary-language string it produced before.
// The hazard is real and general even though it does not bite today: research-map-plus-grid's
// gridLanguage() falls back to `linked.language.value || linked.language.label`, and `label` is a
// DISPLAY NAME. The grid's valueMatches() compares whole normalised values, so a name fetched in
// one language matches no row in another — the failure that took the grid 2,107 -> 0 and read as
// "no results" (rega, 2026-09-26). This asserts the CLASS, not today's instance.
{
const locale = ref('en')
// A language whose display label follows the locale, as the API returns it, but whose code does not.
const NAMES = { en: 'Arabic, Sudanese', ar: 'العربية السودانية' }
const linkedLanguage = () => ({ codes: ['apd'], value: 'apd', label: NAMES[locale.value] })
const gridLanguage = () => {
const l = linkedLanguage()
const codes = Array.isArray(l.codes) ? l.codes : []
if (codes.length > 1) return codes.join('|')
return l.value || l.label
}
const grid = makeEl('grid')
const paneConfig = usePaneConfig(() => makeHost([grid]))
paneConfig.registerAttrs(grid, () => ({ 'primary-language': gridLanguage(), 'wagf-region': `r-${locale.value === 'x' ? 1 : 0}` }))
await nextTick()
const beforeSwitch = grid.getAttribute('primary-language')
locale.value = 'ar'
await nextTick()
ok(grid.getAttribute('primary-language') === beforeSwitch,
`a locale change leaves primary-language unchanged ('${beforeSwitch}' -> '${grid.getAttribute('primary-language')}')`)
ok(!/[؀-ۿ]/.test(grid.getAttribute('primary-language')),
'the filter is a CODE, never a localised display name')
paneConfig.dispose()
}
// ── 8. DISPOSE STOPS THE EFFECT. A composite unmounted and left watching is a leak that keeps
// writing to detached elements.
{
const outer = ref({ locale: 'en' })
const el = makeEl()
const paneConfig = usePaneConfig(() => makeHost([el]))
paneConfig.register(el, () => ({ ...outer.value }))
await nextTick()
paneConfig.dispose()
const after = el.writes
outer.value = { locale: 'ar' }
await nextTick()
ok(el.writes === after, 'after dispose() the outer config is no longer followed')
ok(paneConfig.count() === 0, 'and the entry list is emptied')
}
// ── 9. IT DOES NOT THROW OUTSIDE setup(). Every case above runs outside a component instance, which
// is the un-guarded onBeforeUnmount hazard studio raised — if that guard regressed, this file would
// have failed at the first register() rather than here. Stated so the reason is on the record.
ok(true, 'the whole file ran outside setup(): the getCurrentInstance() guard holds')
if (bad) {
console.error(`\nPANE-CONFIG-REFORWARD: FAIL — ${bad} case(s). A composite's panes no longer follow the`)
console.error('outer profile-config, so a live locale switch leaves every nested pane in the old language.')
process.exit(1)
}
console.log('\nPANE-CONFIG-REFORWARD: PASS — panes follow the outer config for the session, and a locale change never rewrites a filter value.')

View file

@ -0,0 +1,169 @@
/**
* usePaneConfig — keep a composite's panes' profile-config in step with the outer element's.
*
* ★ THE BUG THIS EXISTS FOR. A composite builds each pane by creating the child custom element and
* writing its profile-config ONCE, inside onMounted:
*
* el.setAttribute('profile-config', JSON.stringify(pcfg)) // and never again
*
* That is correct at mount and wrong for the rest of the session. The pane is handed a JSON STRING,
* not a reactive object, so a later change to the OUTER element's profile-config updates the
* composite's own `cfg` and reaches nothing below it. MEASURED on Regions: a live locale switch
* en -> ar left the panes in English for the whole session, because regions-dashboard is kept alive
* by v-show rather than remounted — the one code path that would have rewritten the attribute never
* runs again. The leaf's own chain is fully reactive and was never at fault: it is reactive to its
* own prop, and its prop is frozen.
*
* ★ WHY A HELPER RATHER THAN THREE FIXES. regions-mfe, upg-100-mfe and country-posters-mfe all build
* their panes the same way — assemble a config object, stringify it, set the attribute once. They
* differ only in WHICH keys they inherit. So the composite keeps its own INHERITED_PAGE_KEYS and its
* own assembly, and hands the result here; this owns the part that was missing everywhere.
*
* ★ IT WRITES ONLY WHEN THE STRING CHANGES. Setting profile-config re-parses it in the child and can
* remount what it drives, so an unconditional write on every reactive tick would be a rerender loop.
* The previous value is remembered per element and compared before touching the DOM.
*
* const panes = usePaneConfig(() => stage.value, TAG)
* panes.register(el, () => paneConfigFor(pane)) // at mount, after appendChild
*
* Re-forwarding happens inside a watchEffect, so any reactive source the getter reads — the outer
* config, a computed spec, the pane's own config — triggers it.
*
* ★ NOT EVERY PANE IS CONFIGURED BY profile-config. people-groups-grid takes its filters as THREE
* separate attributes (wagf-region, religion-family, primary-language) rather than one JSON blob, so
* research-map-plus-grid has one pane of each kind. registerAttrs covers that second shape:
*
* panes.registerAttrs(grid, () => ({ 'wagf-region': region.value, 'religion-family': fam.value }))
*
* It shares this list, this watchEffect, this prune and this write-only-on-change — deliberately, so
* the second adopter rides ONE mechanism instead of forking a second. A helper with a single caller
* has not been shown to generalise at all; this is the shape that showed it.
*/
import { watchEffect, onBeforeUnmount, shallowReactive, getCurrentInstance } from 'vue'
/**
* @param {() => (Element|null|undefined)} hostGetter the element the panes were appended under; used
* only to drop registrations whose element has left the DOM (a pane column can be rebuilt).
* ★ IT MUST BE IN THE SAME TREE AS THE PANES. Element.contains() does NOT cross a shadow
* boundary, so passing the custom element while its panes live in its shadow root would make
* every pane look detached and skip every write, silently.
* @returns {{ register: (el: Element, configGetter: () => object) => void,
* registerAttrs: (el: Element, attrsGetter: () => Record<string,unknown>) => void,
* dispose: () => void, count: () => number }}
*/
export function usePaneConfig(hostGetter) {
// ★ shallowReactive, NOT a plain array. The watchEffect below runs once, at the FIRST register();
// with a plain array a LATER entry's getter is never tracked, so its pane stays frozen until
// something the first entry reads happens to change. MEASURED with real Vue by studio: two panes
// reading different refs, changing only pane 2's source produced ZERO writes. That is the very bug
// this helper exists to fix, reappearing for any adopter whose panes read different sources.
// Making the list reactive re-runs the effect on register() and re-tracks every getter.
/**
* One entry per registered pane. `attrs` is the ATTRIBUTE NAMES this entry owns: the single-key
* ['profile-config'] for a JSON-configured pane, or the caller's list for an attribute-configured
* one. `last` holds the previously written value PER ATTRIBUTE, so a getter that changes one of
* three attributes writes one of three.
* @type {Array<{el: Element, get: () => object, attrs: string[]|null, last: Record<string,string|null>, warned: boolean}>}
*/
const entries = shallowReactive([])
let stop = null
const alive = (el) => {
if (!el || !el.isConnected) return false
const host = typeof hostGetter === 'function' ? hostGetter() : hostGetter
// A pane column rebuild replaces the elements; an entry whose element is no longer under the
// current host is stale and must not be written to.
return !host || host.contains(el)
}
/** Add an entry and make sure the one shared effect is running. */
function add(entry) {
entries.push(entry)
if (stop) return
stop = watchEffect(() => {
// PRUNE rather than skip: an adopter that rebuilds its pane column would otherwise keep
// detached elements in the list and re-run their getters on every pass, forever.
for (let i = entries.length - 1; i >= 0; i--) if (!alive(entries[i].el)) entries.splice(i, 1)
for (const e of entries) forward(e)
})
}
/** Re-evaluate one entry's getter and write only what changed. */
function forward(entry) {
let value
// A getter that throws must not take the other panes down with it: this runs inside a
// watchEffect, where an exception would stop every later entry in the same pass. It is
// reported ONCE per entry -- silence here would recreate the symptom this helper fixes.
try { value = entry.get() } catch (e) {
if (!entry.warned) { entry.warned = true; console.error('[usePaneConfig] pane config getter threw; this pane will not follow the outer config:', e) }
return
}
if (!entry.attrs) {
// JSON pane: the whole config is one attribute.
let next
try { next = JSON.stringify(value) } catch (e) {
if (!entry.warned) { entry.warned = true; console.error('[usePaneConfig] pane config did not stringify; this pane will not follow the outer config:', e) }
return
}
if (next === entry.last['profile-config']) return
entry.last['profile-config'] = next
entry.el.setAttribute('profile-config', next)
return
}
// Attribute pane: each named attribute is compared and written independently, so a getter whose
// region changed does not rewrite (and so re-trigger) the two that did not.
const bag = value && typeof value === 'object' ? value : {}
for (const name of entry.attrs) {
// An absent/null/undefined value is written as '' rather than the string "undefined" -- the
// grid reads these as plain filter strings and "undefined" would be a filter that matches
// nothing, which is the empty-grid failure this repo has already paid for once.
const raw = bag[name]
const next = raw === undefined || raw === null ? '' : String(raw)
if (next === entry.last[name]) continue
entry.last[name] = next
entry.el.setAttribute(name, next)
}
}
function register(el, configGetter) {
if (!el || typeof configGetter !== 'function') return
// The attribute the caller already set at mount is the baseline: recording it here means the
// first watchEffect pass does not rewrite an identical value.
add({ el, get: configGetter, attrs: null, last: { 'profile-config': el.getAttribute('profile-config') }, warned: false })
}
/**
* The attribute-configured sibling. `attrsGetter` returns an object keyed by ATTRIBUTE NAME
* (kebab-case, as written in the DOM), e.g. { 'wagf-region': 'africa' }.
* ★ THE NAME LIST IS FIXED AT REGISTRATION, from the getter's first result. A pane's attribute
* surface is static -- it is the element's contract -- and pinning it means a later getter that
* omits a key CLEARS that attribute rather than silently leaving a stale filter applied, which is
* the same class of lie as the frozen config this helper exists to fix.
*/
function registerAttrs(el, attrsGetter) {
if (!el || typeof attrsGetter !== 'function') return
let first
try { first = attrsGetter() } catch (e) {
console.error('[usePaneConfig] attrs getter threw at registration; this pane is not registered:', e)
return
}
const attrs = Object.keys(first && typeof first === 'object' ? first : {})
if (!attrs.length) {
console.error('[usePaneConfig] attrs getter returned no attribute names; this pane is not registered.')
return
}
const last = {}
for (const name of attrs) last[name] = el.getAttribute(name)
add({ el, get: attrsGetter, attrs, last, warned: false })
}
// ★ GUARDED, the house pattern (useMapData.js:144). onBeforeUnmount only binds inside setup(); a
// caller outside one gets a Vue warning and no auto-cleanup. `dispose` is returned so such a
// caller has something to call -- silently leaking a watchEffect would be the worse half.
const dispose = () => { if (stop) { stop(); stop = null } entries.length = 0 }
if (getCurrentInstance()) onBeforeUnmount(dispose)
return { register, registerAttrs, dispose, count: () => entries.length }
}
export default usePaneConfig

View file

@ -237,6 +237,7 @@
"check:poster-action-print": "node internal/check-poster-action-print.mjs",
"check:popup-href-escaped": "node internal/check-popup-href-escaped.mjs",
"check:popup-pan-controls": "node internal/check-popup-pan-clears-controls.mjs",
"check:pane-config-reforward": "node internal/check-pane-config-reforward.mjs",
"check:control-inset": "node internal/check-control-inset-measured.mjs",
"check:viewport-meta": "node internal/check-viewport-meta.mjs",
"check:viewport-meta:self-test": "VIEWPORT_META_SELFTEST=1 node internal/check-viewport-meta.mjs",
@ -340,7 +341,7 @@
"fix:sri": "node internal/fix-sri.mjs",
"gallery": "node internal/generate-gallery.mjs",
"gates:post": "node internal/run-parallel.mjs --label=post -- check:capability-derived check:drop check:parse verify check:sri check:public-hygiene check:label-joins check:facet-cascade check:facet-one-field check:heartbeat check:kpi-unit check:composite-reg check:descriptions check:no-abs-paths check:no-codename check:dep-names check:composite-srcs check:embed-targets check:nested-token check:i18n-parity check:tabs-localized check:packets-all check:packet-strings-parity check:zone-census check:zoning-declared check:share-policy check:library-index check:csv-shape check:capability-citations check:catalog-identity check:no-orphan-gates check:quoted-scripts check:done-evidence check:script-flags check:backlog-status check:capability-exemplars check:packet-fixture check:packet-browser check:provider-switch check:locale-reachable check:url-resolver check:manifest-covers check:one-palette check:pin-dim-slider check:legend-source check:backlog-refs check:clustering-modes check:composite-embed-knobs check:composite-locale-leaks check:linked-vocabulary check:wp-embed check:dashboard-geocoder-provider check:leaf-engine-per-provider check:auto-engine check:engine-contract check:map-props-unit check:composites-mount check:composite-interop check:no-free-identifiers check:map-fly-reduced-motion check:mst-clustering check:list-prop-unit check:profiles-mount check:cdn-pins check:no-eval check:popup-escaping check:i18n-treeshake check:pinned-umd check:one-palette:self-test check:no-cross-plugin-imports check:no-cross-plugin-imports:self-test check:map-attribution check:studio-rail check:approx-pin-note check:embedder-keys check:provider-for check:legend-logical-insets check:legend-logical-insets:self-test check:optional-t-equivalence check:text-highlight check:religion-label-not-value check:i18n-stopped-scope check:no-silent-engine check:no-silent-engine:self-test check:regions-uses-library check:shadow-css-parses check:square-map-controls check:preset-config-merge check:instance-config-keys check:frozen-labels check:plural-forms check:region-fill-repaint check:region-paint-single-source check:poster-print-fallback check:posters-exportable check:country-names check:reduced-motion check:no-dead-library check:universal-props check:readme-size check:tree-relative check:leaf-hints check:snippet-engine check:snippet-engine:self-test check:embed-base check:link-coverage check:no-public-binds check:shared-reached check:tree-weight",
"gates:pre": "node internal/run-parallel.mjs --label=pre -- check:template-literal check:mjs-parse check:sfc-parse check:tab-radius check:synced-css check:doc-scripts check:setup-order check:package-json-unique-keys check:no-repo-pb-hooks check:no-credentials-omit check:no-credentials-omit:self-test check:shadow-reduced-motion check:poster-action-print check:targets check:targets:self-test check:browser-gates-guarded check:pin-selection check:facet-shape check:hardcoded-english:self-test check:list-selection check:selector-fields check:upg-zone-joins check:gospel-set-charts check:gospel-set-fit check:describe-group check:one-scroll-container check:packet-charts check:composite-token-url check:language-tree-layout check:i18n-keys-exist check:i18n-never-translated check:packet-variants check:profile-table check:element-bootstrap check:embed-url-absolute check:frame-target check:language-tree-model check:map-menu-anchor check:selection-kinds check:tiered-points check:whiteboard-projection check:mercator-slice check:ring-vocabulary check:map-props-unit check:mobile-gating check:provide-shadowing check:legend-top-offset check:cdn-cache check:plural-forms check:select-zone-race check:script-registry check:map-control-overlap check:poster-attribution check:board-map-options check:gospel-whiteboard check:packet-map-missing-loud check:poster-layout-optin check:ported-globals-identical check:ring-bands check:packet-a1-structure check:zone-title-tier-a check:evangelical-share check:map-labels check:packet-offline check:help-reachable check:gated-not-in-tree check:window-globals-released check:legend-data-contract check:no-new-driver-references check:poster-fetch-guard check:people-group-records check:sentence-slots check:popup-href-escaped check:popup-pan-controls check:viewport-meta check:viewport-meta:self-test check:label-key-coverage check:poster-browser-equiv check:poster-render-pinned check:host-locale-fetch check:print-frame-reclaim check:click-sheet check:coverage-consistent:chained check:coverage-consistent:poison check:dashboard-filter-a11y check:filter-multivalue check:hover-channel check:kit-options-reachable check:layout-key-namespaced check:legend-mirrors check:map-unmount-race check:no-conflict-markers check:series-isolation check:snapshot-guard check:stale-sweep-claim check:surfaces-read-the-tier check:translator-contacts check:dxlife check:wall-wysiwyg check:wall-print-watch check:wall-no-bg-photos check:template-i18n check:list-pattern-doc check:shadow-style-labels-unique check:grid-image-loading check:retry-fetch",
"gates:pre": "node internal/run-parallel.mjs --label=pre -- check:template-literal check:mjs-parse check:sfc-parse check:tab-radius check:synced-css check:doc-scripts check:setup-order check:package-json-unique-keys check:no-repo-pb-hooks check:no-credentials-omit check:no-credentials-omit:self-test check:shadow-reduced-motion check:poster-action-print check:targets check:targets:self-test check:browser-gates-guarded check:pin-selection check:facet-shape check:hardcoded-english:self-test check:list-selection check:selector-fields check:upg-zone-joins check:gospel-set-charts check:gospel-set-fit check:describe-group check:one-scroll-container check:packet-charts check:composite-token-url check:language-tree-layout check:i18n-keys-exist check:i18n-never-translated check:packet-variants check:profile-table check:element-bootstrap check:embed-url-absolute check:frame-target check:language-tree-model check:map-menu-anchor check:selection-kinds check:tiered-points check:whiteboard-projection check:mercator-slice check:ring-vocabulary check:map-props-unit check:mobile-gating check:provide-shadowing check:legend-top-offset check:cdn-cache check:plural-forms check:select-zone-race check:script-registry check:map-control-overlap check:poster-attribution check:board-map-options check:gospel-whiteboard check:packet-map-missing-loud check:poster-layout-optin check:ported-globals-identical check:ring-bands check:packet-a1-structure check:zone-title-tier-a check:evangelical-share check:map-labels check:packet-offline check:help-reachable check:gated-not-in-tree check:window-globals-released check:legend-data-contract check:no-new-driver-references check:poster-fetch-guard check:people-group-records check:sentence-slots check:popup-href-escaped check:popup-pan-controls check:pane-config-reforward check:viewport-meta check:viewport-meta:self-test check:label-key-coverage check:poster-browser-equiv check:poster-render-pinned check:host-locale-fetch check:print-frame-reclaim check:click-sheet check:coverage-consistent:chained check:coverage-consistent:poison check:dashboard-filter-a11y check:filter-multivalue check:hover-channel check:kit-options-reachable check:layout-key-namespaced check:legend-mirrors check:map-unmount-race check:no-conflict-markers check:series-isolation check:snapshot-guard check:stale-sweep-claim check:surfaces-read-the-tier check:translator-contacts check:dxlife check:wall-wysiwyg check:wall-print-watch check:wall-no-bg-photos check:template-i18n check:list-pattern-doc check:shadow-style-labels-unique check:grid-image-loading check:retry-fetch",
"gen:iso3-to-alpha2": "node library/scripts/generate-iso3-to-alpha2.mjs",
"internet:adm1": "node plugin-designs/cities-internet/scripts/build-adm1-internet.mjs",
"library-index": "node internal/generate-library-index.mjs",

View file

@ -25,6 +25,7 @@
* wall's 'show-on-map', and it keeps each pane under its own ProfileLoader/Pinia.
*/
import { ref, computed, inject, onMounted, onBeforeUnmount } from 'vue'
import { usePaneConfig } from '@map/composables/usePaneConfig.js'
import { buildPaneColumn, PANE_GRID_STYLES } from '@map/layouts/pane-wrapper/pane-wrapper.js'
import { useShadowStyles } from '@map/composables/useShadowStyles.js'
import { SHADOW_BOX_SIZING_RESET } from './src/shadowReset.js'
@ -113,16 +114,27 @@ let offScroll = null
// below), and this was the one genuinely missing.
const INHERITED_PAGE_KEYS = ['assetsBase', 'apiBaseUrl', 'prayBaseUrl', 'tokenUrl', 'dataSource', 'locale', 'lang', 'languageCode', 'isDark', 'qa', 'pinHint', 'urlSync', 'provider', 'list']
function paneElement(pane) {
const el = document.createElement(TAG)
// ★ SPLIT OUT SO IT CAN BE RE-EVALUATED. This reads cfg.value and spec.value, so calling it again
// after the outer profile-config changes yields the NEW pane config. It used to be inline in
// paneElement, which ran once per pane in onMounted and never again -- that is the whole defect:
// the pane is handed a JSON STRING, so a later locale change reached this element and stopped.
// stage is the element the panes are appended UNDER, in the same tree as them -- contains() does
// not cross a shadow boundary, so the host must not be the custom element itself.
const paneConfig = usePaneConfig(() => stage.value)
function paneConfigFor(pane) {
const host = cfg.value || {}
const inherited = {}
for (const k of INHERITED_PAGE_KEYS) {
if (host[k] !== undefined && host[k] !== null && host[k] !== '') inherited[k] = host[k]
}
if (inherited.locale === undefined && inherited.lang !== undefined) inherited.locale = inherited.lang
const pcfg = { ...inherited, ...spec.value.config, ...pane.config }
el.setAttribute('profile-config', JSON.stringify(pcfg))
return { ...inherited, ...spec.value.config, ...pane.config }
}
function paneElement(pane) {
const el = document.createElement(TAG)
el.setAttribute('profile-config', JSON.stringify(paneConfigFor(pane)))
return el
}
@ -160,7 +172,10 @@ onMounted(() => {
if (!column) return
for (const pane of column.panes) {
if (!pane.config) continue
column.mountEl(pane.id)?.appendChild(paneElement(pane))
const el = paneElement(pane)
column.mountEl(pane.id)?.appendChild(el)
// Keep this pane's profile-config in step with the outer element's for the rest of the session.
paneConfig.register(el, () => paneConfigFor(pane))
}
// Tabs are a readout as well as a control — follow the user's scroll.
offScroll = column.onScroll(() => {

View file

@ -1126,7 +1126,7 @@
"bundle": "regions-dashboard",
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
"output": "dx-maps/regions-dashboard/regions-dashboard.js",
"integrity": "sha384-+u2vDSnYni3VeNKSYdNWfw5bjKtriFjik/k10lugA0nw6Ozfi/vKUYp3a9sbMAp6",
"integrity": "sha384-YNNxkm78b9Z8oKetaufWw+ajPKAEqk89gSrY5imw8fbVMMAyVvg6hU+9E+osig8s",
"elements": [
"regions-dashboard"
],
@ -1149,8 +1149,8 @@
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
"embed": {
"iframe": "<iframe src=\"./regions-dashboard/region-dashboards/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-+u2vDSnYni3VeNKSYdNWfw5bjKtriFjik/k10lugA0nw6Ozfi/vKUYp3a9sbMAp6\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>",
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-+u2vDSnYni3VeNKSYdNWfw5bjKtriFjik/k10lugA0nw6Ozfi/vKUYp3a9sbMAp6\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-YNNxkm78b9Z8oKetaufWw+ajPKAEqk89gSrY5imw8fbVMMAyVvg6hU+9E+osig8s\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>",
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-YNNxkm78b9Z8oKetaufWw+ajPKAEqk89gSrY5imw8fbVMMAyVvg6hU+9E+osig8s\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
}
},
{
@ -1166,7 +1166,7 @@
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
"embed": {
"iframe": "<iframe src=\"./regions-dashboard/regions-list/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=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-+u2vDSnYni3VeNKSYdNWfw5bjKtriFjik/k10lugA0nw6Ozfi/vKUYp3a9sbMAp6\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-list\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-YNNxkm78b9Z8oKetaufWw+ajPKAEqk89gSrY5imw8fbVMMAyVvg6hU+9E+osig8s\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-list\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
}
},
{
@ -1182,8 +1182,8 @@
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
"embed": {
"iframe": "<iframe src=\"./regions-dashboard/regions-mfe/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-+u2vDSnYni3VeNKSYdNWfw5bjKtriFjik/k10lugA0nw6Ozfi/vKUYp3a9sbMAp6\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>",
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-+u2vDSnYni3VeNKSYdNWfw5bjKtriFjik/k10lugA0nw6Ozfi/vKUYp3a9sbMAp6\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-YNNxkm78b9Z8oKetaufWw+ajPKAEqk89gSrY5imw8fbVMMAyVvg6hU+9E+osig8s\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>",
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-YNNxkm78b9Z8oKetaufWw+ajPKAEqk89gSrY5imw8fbVMMAyVvg6hU+9E+osig8s\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
}
}
]

View file

@ -1126,7 +1126,7 @@
"bundle": "regions-dashboard",
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
"output": "dx-maps/regions-dashboard/regions-dashboard.js",
"integrity": "sha384-+u2vDSnYni3VeNKSYdNWfw5bjKtriFjik/k10lugA0nw6Ozfi/vKUYp3a9sbMAp6",
"integrity": "sha384-YNNxkm78b9Z8oKetaufWw+ajPKAEqk89gSrY5imw8fbVMMAyVvg6hU+9E+osig8s",
"elements": [
"regions-dashboard"
],
@ -1149,8 +1149,8 @@
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
"embed": {
"iframe": "<iframe src=\"./regions-dashboard/region-dashboards/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-+u2vDSnYni3VeNKSYdNWfw5bjKtriFjik/k10lugA0nw6Ozfi/vKUYp3a9sbMAp6\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>",
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-+u2vDSnYni3VeNKSYdNWfw5bjKtriFjik/k10lugA0nw6Ozfi/vKUYp3a9sbMAp6\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-YNNxkm78b9Z8oKetaufWw+ajPKAEqk89gSrY5imw8fbVMMAyVvg6hU+9E+osig8s\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>",
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-YNNxkm78b9Z8oKetaufWw+ajPKAEqk89gSrY5imw8fbVMMAyVvg6hU+9E+osig8s\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
}
},
{
@ -1166,7 +1166,7 @@
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
"embed": {
"iframe": "<iframe src=\"./regions-dashboard/regions-list/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=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-+u2vDSnYni3VeNKSYdNWfw5bjKtriFjik/k10lugA0nw6Ozfi/vKUYp3a9sbMAp6\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-list\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-YNNxkm78b9Z8oKetaufWw+ajPKAEqk89gSrY5imw8fbVMMAyVvg6hU+9E+osig8s\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-list\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
}
},
{
@ -1182,8 +1182,8 @@
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
"embed": {
"iframe": "<iframe src=\"./regions-dashboard/regions-mfe/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-+u2vDSnYni3VeNKSYdNWfw5bjKtriFjik/k10lugA0nw6Ozfi/vKUYp3a9sbMAp6\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>",
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-+u2vDSnYni3VeNKSYdNWfw5bjKtriFjik/k10lugA0nw6Ozfi/vKUYp3a9sbMAp6\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-YNNxkm78b9Z8oKetaufWw+ajPKAEqk89gSrY5imw8fbVMMAyVvg6hU+9E+osig8s\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>",
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-YNNxkm78b9Z8oKetaufWw+ajPKAEqk89gSrY5imw8fbVMMAyVvg6hU+9E+osig8s\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
}
}
]

View file

@ -70,7 +70,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <regions-dashboard> custom element. -->
<script src="../regions-dashboard.js" integrity="sha384-+u2vDSnYni3VeNKSYdNWfw5bjKtriFjik/k10lugA0nw6Ozfi/vKUYp3a9sbMAp6" crossorigin="anonymous"></script>
<script src="../regions-dashboard.js" integrity="sha384-YNNxkm78b9Z8oKetaufWw+ajPKAEqk89gSrY5imw8fbVMMAyVvg6hU+9E+osig8s" 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

@ -61,7 +61,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <regions-dashboard> custom element. -->
<script src="../regions-dashboard.js" integrity="sha384-+u2vDSnYni3VeNKSYdNWfw5bjKtriFjik/k10lugA0nw6Ozfi/vKUYp3a9sbMAp6" crossorigin="anonymous"></script>
<script src="../regions-dashboard.js" integrity="sha384-YNNxkm78b9Z8oKetaufWw+ajPKAEqk89gSrY5imw8fbVMMAyVvg6hU+9E+osig8s" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -77,7 +77,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <regions-dashboard> custom element. -->
<script src="../regions-dashboard.js" integrity="sha384-+u2vDSnYni3VeNKSYdNWfw5bjKtriFjik/k10lugA0nw6Ozfi/vKUYp3a9sbMAp6" crossorigin="anonymous"></script>
<script src="../regions-dashboard.js" integrity="sha384-YNNxkm78b9Z8oKetaufWw+ajPKAEqk89gSrY5imw8fbVMMAyVvg6hU+9E+osig8s" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order: