Fetch the catalogue for a host-supplied locale, and hold RTL text while it lands

A locale can arrive two ways. ?lang and <html lang> are read at script
evaluation, and the preload in i18n/index.js covers them. profile-config —
the channel a host page uses — reaches ProfileLoader's watchEffect and
nowhere else, which is the channel's whole purpose; the comment there
already records that an earlier document-level fix never saw it. So with
the build shipping one locale, a host-configured embed set its locale,
mirrored its direction, and then rendered English with no error anywhere.
One line beside the setRuntimeLocale that marks that path.

The RTL hold is the second half. When the catalogue is fetched rather than
inlined, nine locales get a re-render — the words change, nothing moves —
and Arabic gets a layout flip: English left to right, then the whole chrome
mirrors. So the text waits and the map does not.

Not a v-if on the profile: <component :is="ProfileComponent"> is the entire
plugin, map included, and gating it would hold the one thing that must
never wait. A data attribute on the existing root plus a scoped rule over
named chrome classes leaves every component mounted, re-enters nothing, and
lets the map paint on its own schedule. The classes are named rather than
matched by a blanket descendant rule because two of the text-bearing
components also carry map objects. visibility rather than display, so the
chrome keeps its box and nothing reflows when the text appears — a reflow at
swap time is the same defect as the flip, only quieter.

The gate asserts the seam. "ProfileLoader mentions loadLocale" would pass on
an import nothing calls, so the watchEffect's own body is read by brace
depth and the call has to sit inside it. It also refuses a hold that names
anything map-shaped, a hold applied to the whole subtree, and a fetch that
could run without a pin. Poisoned both halves: removing the call and
pointing the hold at a map container each fail with the message that names
the defect.

(cherry picked from commit 5c54750d58)
This commit is contained in:
Doxa Maps 2026-09-29 05:23:57 +00:00
parent ae0fad7516
commit 9a83077061
3 changed files with 148 additions and 3 deletions

View file

@ -0,0 +1,88 @@
#!/usr/bin/env node
/**
* check-host-locale-fetch — a locale supplied through profile-config must fetch its catalogue.
*
* ★ WHAT THIS CAUGHT. When the build ships one locale (DX_SHIP_ONE_LOCALE), the preload in
* library/i18n/index.js fires once at script evaluation from detectLocale(), which reads only the
* URL and <html lang>. A locale arriving through profile-config — the HOST channel, the one a
* partner page uses — reaches ProfileLoader's watchEffect and nowhere else. Without a loadLocale()
* call there, that embed sets its locale, mirrors its direction, and then renders English, with no
* error anywhere: the silent-fallback shape this gate family exists to refuse.
*
* ⚠ ASSERTED ON THE SEAM, NOT ON A STRING. "ProfileLoader mentions loadLocale" would pass on an
* import that nothing calls, and on a call in a branch that never runs. So the watchEffect's own
* body is read, and the call has to sit beside the setRuntimeLocale() that marks the host-channel
* path — the two belong together because they answer the same question for different consumers.
*
* Reads real source. Headless, no browser, no network. Exit 0 pass · 1 fail.
*/
import { resolve, dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
import { readFileSync } from 'node:fs'
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..')
const read = (p) => readFileSync(resolve(ROOT, p), 'utf8')
const fails = []
const ok = (cond, msg) => { if (!cond) fails.push(msg) }
const PL = read('library/ProfileLoader.vue')
const I18N = read('library/i18n/index.js')
// ── 1. THE HOST CHANNEL EXISTS AND STILL LOOKS THE WAY THIS GATE ASSUMES ───────────────────────
ok(/config\.value\?\.locale \|\| config\.value\?\.lang/.test(PL),
'ProfileLoader no longer reads the locale from profile-config — this gate is judging a path that moved')
// ── 2. THE WATCHEFFECT THAT APPLIES IT ALSO FETCHES IT ─────────────────────────────────────────
// The body is taken by brace depth from the watchEffect that calls setRuntimeLocale, so a call
// sitting elsewhere in the file cannot satisfy this.
const at = PL.indexOf('watchEffect(() => {\n if (!lang.value) return')
ok(at >= 0, 'the locale watchEffect was not found — it was renamed or restructured')
if (at >= 0) {
const open = PL.indexOf('{', PL.indexOf('=>', at))
let depth = 0, end = open
for (let i = open; i < PL.length; i++) {
if (PL[i] === '{') depth++
else if (PL[i] === '}' && --depth === 0) { end = i; break }
}
const body = PL.slice(at, end + 1)
ok(/setRuntimeLocale\(lang\.value\)/.test(body),
'the watchEffect no longer publishes the locale — this gate is anchored to the wrong effect')
ok(/loadLocale\([^)]*lang\.value\)/.test(body),
'the locale watchEffect does not call loadLocale(), so a locale set through profile-config never '
+ 'fetches its catalogue: the embed mirrors its direction and renders English with no error')
}
ok(/import \{[^}]*\bloadLocale\b[^}]*\} from '\.\/i18n\/index\.js'/.test(PL),
'ProfileLoader does not import loadLocale from the i18n module')
// ── 3. THE FETCH IS PINNED, AND UNREACHABLE WITHOUT A PIN ──────────────────────────────────────
// Keying on the hash map is what keeps the flag-OFF build off the network: a bundle with no areas
// inlines nothing, so a messages[] test would let the preload request a file that was never emitted.
ok(/if \(!LOCALE_SRI\[c\]\) return/.test(I18N),
'loadLocale no longer refuses to fetch without a pin — the flag-off build would hit the network, '
+ 'and any fetch that did happen would be unpinned')
ok(/fetch\(url, \{ integrity: LOCALE_SRI\[c\] \}\)/.test(I18N),
'the catalogue fetch does not pass its integrity hash unconditionally')
// ── 4. THE RTL HOLD NEVER REACHES THE MAP ──────────────────────────────────────────────────────
// A blanket rule on descendants would hide the map subtree, which is the outcome the hold exists to
// avoid: the map must paint while only text waits.
const held = [...PL.matchAll(/\[data-locale-pending\] :deep\(\.([a-z0-9-]+)\)/g)].map((m) => m[1])
ok(held.length > 0, 'no chrome classes are held while an RTL catalogue is in flight — the hold is not wired')
ok(!/\[data-locale-pending\]\s*\{/.test(PL) && !/\[data-locale-pending\] :deep\(\*\)/.test(PL),
'the hold is applied to the whole subtree rather than to named chrome, so it would hide the map')
for (const cls of held) {
ok(!/^(map|mapbox|maplibre|canvas)/.test(cls),
`the hold names .${cls}, which looks like a map container — only text chrome may ever wait`)
}
ok(/\[data-locale-pending\][\s\S]{0,900}visibility: hidden/.test(PL),
'the hold uses something other than visibility — display:none would reflow the chrome back in when '
+ 'the text lands, which is the same visual defect as the flip')
if (fails.length) {
console.error('HOST-LOCALE-FETCH: FAIL')
for (const f of fails) console.error(` ✗ ${f}`)
process.exit(1)
}
console.log(`HOST-LOCALE-FETCH: PASS — a profile-config locale fetches its catalogue, the fetch is `
+ `unreachable without a pin, and the RTL hold covers ${held.length} named chrome class(es) and no map.`)

View file

@ -17,7 +17,7 @@
import { computed, provide, inject, watchEffect, watch, ref, onBeforeUnmount, useHost } from 'vue' import { computed, provide, inject, watchEffect, watch, ref, onBeforeUnmount, useHost } from 'vue'
import { useSharedMapToken, publishMapToken, fetchMapToken } from './composables/useSharedMapToken.js' import { useSharedMapToken, publishMapToken, fetchMapToken } from './composables/useSharedMapToken.js'
import { retryableAsyncComponent } from './composables/retryableAsyncComponent.js' import { retryableAsyncComponent } from './composables/retryableAsyncComponent.js'
import { SUPPORTED_LOCALES, RTL_LOCALES, setRuntimeLocale, localeFromUrl } from './i18n/index.js' import { SUPPORTED_LOCALES, RTL_LOCALES, setRuntimeLocale, localeFromUrl, loadLocale, localeChromeReady } from './i18n/index.js'
// QA corner: COMPILE-TIME gated (__DOXA_QA__, vite define). In public builds // QA corner: COMPILE-TIME gated (__DOXA_QA__, vite define). In public builds
// the flag is false → this whole branch (and the widget's code, including its // the flag is false → this whole branch (and the widget's code, including its
// platform references) is eliminated from the bundle. Platform builds // platform references) is eliminated from the bundle. Platform builds
@ -182,6 +182,14 @@ watchEffect(() => {
// t(); tStatic reads the URL and the document and would still say English. A bundle that installs // t(); tStatic reads the URL and the document and would still say English. A bundle that installs
// no i18n at all reaches this line and nothing else, which is the case it exists for. // no i18n at all reaches this line and nothing else, which is the case it exists for.
setRuntimeLocale(lang.value) setRuntimeLocale(lang.value)
// ★ THE HOST CHANNEL NEEDS ITS CATALOGUE FETCHED. When the build ships one locale, the preload in
// i18n/index.js fires once at script evaluation from detectLocale() — which reads only the URL and
// <html lang>. A locale arriving through profile-config gets here and nowhere else (that is the
// whole point of the channel, and the comment below records that an earlier document-level fix
// never saw it), so without this line a host-configured embed sets the locale, mirrors its
// direction, and then renders English with no error. No-op when the locale is inlined, already
// fetched, or unpinned — see loadLocale.
loadLocale(appI18n, lang.value).catch(() => { /* stays on the English fallback */ })
}) })
// Effective locale for non-i18n descendants (the geocoder reads useI18n directly). // Effective locale for non-i18n descendants (the geocoder reads useI18n directly).
const effectiveLocale = computed(() => lang.value || appI18n?.global?.locale?.value || 'en') const effectiveLocale = computed(() => lang.value || appI18n?.global?.locale?.value || 'en')
@ -252,6 +260,30 @@ const blockedOnThisScreen = computed(() => mobileFlag.value === 'desktop-only' &
const adviseOnThisScreen = computed(() => mobileFlag.value === 'larger-screens' && narrow.value && !hostedInGallery && !noticeDismissed.value) const adviseOnThisScreen = computed(() => mobileFlag.value === 'larger-screens' && narrow.value && !hostedInGallery && !noticeDismissed.value)
const mobileLabel = computed(() => String(config.value?.mobileLabel || config.value?.profile || '')) const mobileLabel = computed(() => String(config.value?.mobileLabel || config.value?.profile || ''))
// ── HOLD THE TEXT FOR AN RTL LOCALE, NEVER THE MAP ──────────────────────────
// When the catalogue is fetched rather than inlined, a non-English reader sees English until it
// lands. For nine of the ten that is a re-render — the words change and nothing moves. For Arabic it
// is a LAYOUT FLIP: English left-to-right, then the whole chrome mirrors. So the text waits and the
// map does not.
//
// ⚠ NOT A v-if ON THE PROFILE. <component :is="ProfileComponent"> is the ENTIRE plugin, map
// included, so gating it would hold the one thing that must never wait. This is an ATTRIBUTE on the
// existing root and a scoped rule that hides named chrome classes — every component stays mounted,
// nothing is re-entered, and the map paints on its own schedule.
//
// ⚠ visibility, NOT display: the chrome keeps its box, so nothing reflows when the text appears. A
// reflow at swap time is the same defect as the flip, only quieter.
const localePending = ref(false)
watchEffect((onCleanup) => {
const loc = effectiveLocale.value
if (!RTL_LOCALES.has(loc)) { localePending.value = false; return }
let live = true
onCleanup(() => { live = false })
localePending.value = true
// Always resolves — on the catalogue, or on the timeout inside localeChromeReady.
localeChromeReady(loc).finally(() => { if (live) localePending.value = false })
})
// ─── Instance ID ───────────────────────────────────────────────────────────── // ─── Instance ID ─────────────────────────────────────────────────────────────
// Optional in profile-config: { "instanceId": "my-map" } // Optional in profile-config: { "instanceId": "my-map" }
// If not provided, a random ID is generated (stable for the element's lifetime). // If not provided, a random ID is generated (stable for the element's lifetime).
@ -327,7 +359,7 @@ provide('dataStore', dataStore)
</script> </script>
<template> <template>
<div class="profile-loader" :class="{ 'has-screen-notice': adviseOnThisScreen }" :dir="dirAttr"> <div class="profile-loader" :class="{ 'has-screen-notice': adviseOnThisScreen }" :dir="dirAttr" :data-locale-pending="localePending ? '' : null">
<!-- Error: no profile-config prop --> <!-- Error: no profile-config prop -->
<div v-if="!config" class="profile-error"> <div v-if="!config" class="profile-error">
<p>⚠️ <strong>doxa-map</strong>: missing or invalid <code>profile-config</code> prop.</p> <p>⚠️ <strong>doxa-map</strong>: missing or invalid <code>profile-config</code> prop.</p>
@ -357,6 +389,29 @@ provide('dataStore', dataStore)
</template> </template>
<style scoped> <style scoped>
/* ── the RTL chrome hold (see localePending above) ─────────────────────────────
Text chrome is hidden while an RTL catalogue is in flight, so a reader never sees English
left-to-right and then a mirror. NAMED CLASSES, not a blanket rule on descendants: two of the
text-bearing components also carry the map (LegendMobile, RegionCountryMap), and hiding a map
subtree is the one outcome this is supposed to prevent.
:deep() because those elements belong to child components and this block is scoped.
visibility keeps each box in the layout, so the text appearing causes no reflow. The state is
always temporary — localeChromeReady resolves on the catalogue or on its own timeout — so there
is no path where this leaves the chrome hidden. */
.profile-loader[data-locale-pending] :deep(.stl-tabs),
.profile-loader[data-locale-pending] :deep(.stl-title),
.profile-loader[data-locale-pending] :deep(.stl-rows),
.profile-loader[data-locale-pending] :deep(.legend-title),
.profile-loader[data-locale-pending] :deep(.legend-content),
.profile-loader[data-locale-pending] :deep(.legend-reopen-label),
.profile-loader[data-locale-pending] :deep(.lt-btn-text),
.profile-loader[data-locale-pending] :deep(.rd-topbar),
.profile-loader[data-locale-pending] :deep(.rd-exportbar) {
visibility: hidden;
}
.profile-loader { .profile-loader {
/* Pass the host's rounded corners down. The host page rounds the custom element; this /* Pass the host's rounded corners down. The host page rounds the custom element; this
wrapper sits between the host and the profile, so without this every descendant — wrapper sits between the host and the profile, so without this every descendant —

View file

@ -218,6 +218,7 @@
"check:packet-strings-parity": "node internal/check-packet-strings-parity.mjs", "check:packet-strings-parity": "node internal/check-packet-strings-parity.mjs",
"check:packet-variants": "node internal/check-packet-variants.mjs", "check:packet-variants": "node internal/check-packet-variants.mjs",
"check:label-key-coverage": "node internal/check-label-key-coverage.mjs", "check:label-key-coverage": "node internal/check-label-key-coverage.mjs",
"check:host-locale-fetch": "node internal/check-host-locale-fetch.mjs",
"check:packet-a1-structure": "node internal/check-packet-a1-structure.mjs", "check:packet-a1-structure": "node internal/check-packet-a1-structure.mjs",
"check:zone-title-tier-a": "node internal/check-zone-title-tier-a.mjs", "check:zone-title-tier-a": "node internal/check-zone-title-tier-a.mjs",
"check:evangelical-share": "node internal/check-evangelical-share.mjs", "check:evangelical-share": "node internal/check-evangelical-share.mjs",
@ -324,7 +325,7 @@
"fix:sri": "node internal/fix-sri.mjs", "fix:sri": "node internal/fix-sri.mjs",
"gallery": "node internal/generate-gallery.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: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: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: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", "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: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: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: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",
"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: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: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:label-key-coverage check:poster-browser-equiv check:poster-render-pinned", "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: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: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:label-key-coverage check:poster-browser-equiv check:poster-render-pinned check:host-locale-fetch",
"gen:iso3-to-alpha2": "node library/scripts/generate-iso3-to-alpha2.mjs", "gen:iso3-to-alpha2": "node library/scripts/generate-iso3-to-alpha2.mjs",
"internet:adm1": "node plugin-designs/cities-internet/scripts/build-adm1-internet.mjs", "internet:adm1": "node plugin-designs/cities-internet/scripts/build-adm1-internet.mjs",
"library-index": "node internal/generate-library-index.mjs", "library-index": "node internal/generate-library-index.mjs",
@ -505,6 +506,7 @@
"check:packet-strings-parity": "the PACKET catalog (packet-strings.json) must be whole in every language: all string keys, all 12 prayer points with every field, all 12 wheel rhythms. check:i18n-parity covers library/i18n/locales only — six gates READ this file and none checked it. Asserts PRESENCE, not translatedness, with point4_passage declared-absent on record.", "check:packet-strings-parity": "the PACKET catalog (packet-strings.json) must be whole in every language: all string keys, all 12 prayer points with every field, all 12 wheel rhythms. check:i18n-parity covers library/i18n/locales only — six gates READ this file and none checked it. Asserts PRESENCE, not translatedness, with point4_passage declared-absent on record.",
"check:packet-variants": "the poster wall's Prayer List prints the PACKET (not the columnar list), and every variant it offers exists in packet-render's VARIANTS. A variant may drop fixed pages but never people: lightweight keeps every group. By the UPG-100 owner; mutation-tested. Headless; in gates:pre.", "check:packet-variants": "the poster wall's Prayer List prints the PACKET (not the columnar list), and every variant it offers exists in packet-render's VARIANTS. A variant may drop fixed pages but never people: lightweight keeps every group. By the UPG-100 owner; mutation-tested. Headless; in gates:pre.",
"check:label-key-coverage": "node internal/check-label-key-coverage.mjs", "check:label-key-coverage": "node internal/check-label-key-coverage.mjs",
"check:host-locale-fetch": "node internal/check-host-locale-fetch.mjs",
"check:packet-a1-structure": "the prayer packet is built to its specified structure, read page by page from each sheet's CONTENT: full detail = map, triangle, life calendar, % evangelical, profiles, bounded set, centered set, wheel, points; lightweight = map, triangle, profiles, wheel, points. Page 1 places a map-only vertical sheet on the packet's own margin and says it is one of the UPG 100 prayer lists only when a count is supplied; page 2 is one inverted triangle whose packets tier and \"- N\" steps appear only where true; the wheel carries the Metacamp Module 1 PDF's colours and opens at nine o'clock; all 28 legend zones resolve, Deaf included.", "check:packet-a1-structure": "the prayer packet is built to its specified structure, read page by page from each sheet's CONTENT: full detail = map, triangle, life calendar, % evangelical, profiles, bounded set, centered set, wheel, points; lightweight = map, triangle, profiles, wheel, points. Page 1 places a map-only vertical sheet on the packet's own margin and says it is one of the UPG 100 prayer lists only when a count is supplied; page 2 is one inverted triangle whose packets tier and \"- N\" steps appear only where true; the wheel carries the Metacamp Module 1 PDF's colours and opens at nine o'clock; all 28 legend zones resolve, Deaf included.",
"check:tree-relative": "the committed tree names no host of its own. With embedBaseUrl empty in the build config, the manifest must say embedBase \".\" and every scriptUrl, iframeUrl and embed snippet src must be relative. check:drop cannot see this: it looks for root-relative \"/...\" references and skips https:// ones, so a tree built with EMBED_BASE_URL (a deploy build) would pass it and pin every snippet to one host, the way the retired CDN route broke. Data addresses (runtime.apiBaseUrl) are not checked. SKIPs when the config sets embedBaseUrl. Static; takes an optional manifest path for poison tests.", "check:tree-relative": "the committed tree names no host of its own. With embedBaseUrl empty in the build config, the manifest must say embedBase \".\" and every scriptUrl, iframeUrl and embed snippet src must be relative. check:drop cannot see this: it looks for root-relative \"/...\" references and skips https:// ones, so a tree built with EMBED_BASE_URL (a deploy build) would pass it and pin every snippet to one host, the way the retired CDN route broke. Data addresses (runtime.apiBaseUrl) are not checked. SKIPs when the config sets embedBaseUrl. Static; takes an optional manifest path for poison tests.",
"check:universal-props": "every plugin reads the props the Plugin Studio header sets for all of them through the library: map engine, locale, Mapbox token and token URL, and the people-group list. It reads each bundle's source AND its shipped bundle and fails on a bypass (a mapbox:// style literal, a private token global, a list fetched around the library); a by-design exception is ALLOWed by name with its reason. --report also prints the matrix. Headless.", "check:universal-props": "every plugin reads the props the Plugin Studio header sets for all of them through the library: map engine, locale, Mapbox token and token URL, and the people-group list. It reads each bundle's source AND its shipped bundle and fails on a bypass (a mapbox:// style literal, a private token global, a list fetched around the library); a by-design exception is ALLOWed by name with its reason. --report also prints the matrix. Headless.",