From 9a7c6304f7fcef4ef9c03f56f9a81225781dd0f5 Mon Sep 17 00:00:00 2001 From: Doxa Maps Date: Fri, 25 Sep 2026 23:37:31 +0000 Subject: [PATCH 1/3] =?UTF-8?q?README:=20what=20the=20library=20gives=20ev?= =?UTF-8?q?ery=20map=20=E2=80=94=20two=20engines,=20a=20key=20or=20a=20key?= =?UTF-8?q?=20service,=2011=20languages,=20web=20components,=20maps=20that?= =?UTF-8?q?=20talk=20to=20each=20other,=20tree-shaken=20bundles,=20one=20b?= =?UTF-8?q?uild,=20three=20outputs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index ed1700a29..d1333d720 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,19 @@ Made by Vibe Coders for Vibe Coders to prepare the way of the Lord and make His iframe, script embed, mobile app β€” or open every map at once as one app. Five plugin capabilities, three outputs, one shared library, one build. +### What the library gives every map + +| | | +|---|---| +| πŸ—ΊοΈ **Two map engines** | Every map draws with **Mapbox** or **MapLibre** (free, no key) β€” switch with `"provider"` in its settings or `?provider=maplibre` in the address. Mapbox is the default. | +| πŸ”‘ **A key or a key service** | Give Mapbox a public token, or the address of a token service that hands one out β€” set once in the Plugin Studio header. | +| 🌍 **11 languages** | `?lang=` or `"locale"`, with Arabic right to left β€” [Languages](#languages). | +| 🧩 **Web components** | Each map is its own HTML tag, like ``, that works on any page β€” no framework needed. | +| πŸ”— **Maps that talk to each other** | Maps share props and events, so one app can nest them and a click in one can move another. | +| πŸͺΆ **Small bundles** | Each map's file carries only the parts of the shared library it uses (tree-shaken). | +| βš™οΈ **One build step** | `bun run build` makes every map at once. | +| πŸ“¦ **Three outputs** | A web folder (iframe, script embed), a mobile app, a desktop app β€” [the 3 outputs](#what-the-build-makes--3-outputs). | + | What dx-maps set out to do | Proof | |---|---| | **One build β†’ one folder** you can drop on any host | `bun run check:drop` | From 87ebbace9bd94163b35f298c727168ee476c14ba Mon Sep 17 00:00:00 2001 From: Doxa Maps Date: Fri, 25 Sep 2026 23:42:05 +0000 Subject: [PATCH 2/3] Leaf pages load only the engine their provider asks for: no Mapbox requests under MapLibre MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A generated map leaf chose its engine at BUILD time and hard-linked it in : mapbox-gl.css, the geocoder css, mapbox-gl.js and the geocoder js from api.mapbox.com. The provider is chosen at RUNTIME (profile-config.provider β†’ ?provider= β†’ window.MAP_PROVIDER), so a partner embedding a leaf with ?provider=maplibre still had every visitor's browser make 4 requests to Mapbox. - generate-tree: no static engine tags on map leaves; the element's own runtime loaders (ensureEngineScript / ensureEngineCSS, the geocoder's on-demand loader) load exactly the engine asked for, the path composite leaves already ran on. The forge page drops its api.mapbox.com preconnect. A leaf opened with provider=maplibre (same ladder as resolveMapProvider) is token-free, so the public copy no longer asks a MapLibre visitor to paste a Mapbox token. - check:leaf-engine-per-provider (new, browser): every leaf at ?provider=maplibre makes 0 requests to *.mapbox.com and still draws; at its default every map leaf draws with the engine it drew with before (--baseline compares against another tree). - check:dashboard-geocoder-provider is wired into gates:post alongside it; it had been held out because this generator's tags made it red. - verify-map-paint: optional GATE_VIEWPORT for a phone pass. Measured before this landing (browser runs made before the headless rule): leaf-engine vs main's tree PASS, 56 leaves, 0 Mapbox requests under MapLibre, same default engine. Pins, region popup, PPLR search and key-cities PASS on both engines with 0 Mapbox requests under MapLibre. RTL shaping PASS on both engines wherever RTL labels rendered. Paint under MapLibre: the same 4 leaves fail as on main (country-posters-mfe, upg-100-mfe, regions-mfe, flower-mesh). Pre-existing, not caused here. Poster "Render" is missing on country-posters and doxa-research-map under BOTH engines. Pre-existing on main, read from source: countries-map.vue renders without v-model or getMap, and PosterDialog shows only when modelValue is true. Headless, on this commit's regenerated tree: 57 leaves, 0 carry api.mapbox.com engine tags (main's doxa-simple-map leaf carried 5), 56 carry the runtime provider check, check:sri PASS. --- internal/check-leaf-engine-per-provider.mjs | 138 ++++++++++++++++++ internal/generate-tree.mjs | 22 +-- internal/verify-map-paint.mjs | 4 +- package.json | 6 +- .../check-dashboard-geocoder-provider.mjs | 5 +- 5 files changed, 162 insertions(+), 13 deletions(-) create mode 100644 internal/check-leaf-engine-per-provider.mjs diff --git a/internal/check-leaf-engine-per-provider.mjs b/internal/check-leaf-engine-per-provider.mjs new file mode 100644 index 000000000..3c700e9b3 --- /dev/null +++ b/internal/check-leaf-engine-per-provider.mjs @@ -0,0 +1,138 @@ +#!/usr/bin/env node +/** + * check-leaf-engine-per-provider β€” a leaf loads the engine its PROVIDER asks for, and no other. + * + * THE BUG (card BUG-LEAF-PAGES-PRELOAD-MAPBOX-EVEN-UNDER-MAPLIBRE; measured by exp-shepherd at + * 577149af). generate-tree chose a leaf's engine peer at BUILD time, from the instance config, and + * baked it into as static tags: mapbox-gl.css, the geocoder css, mapbox-gl.js and the geocoder + * js, all from api.mapbox.com. The provider is chosen at RUNTIME (resolveMapProvider: + * profile-config.provider β†’ ?provider= β†’ window.MAP_PROVIDER β†’ mapbox). So a partner that embeds a + * leaf with ?provider=maplibre still made its visitors' browsers fetch 4 files from Mapbox, sending + * every visitor's IP to a company the partner chose not to use. No project rule permits that. + * + * WHAT IS ASSERTED, on THIS checkout's served tree (every leaf in the manifest, instances included): + * MAPLIBRE every leaf at ?provider=maplibre makes ZERO requests to any *.mapbox.com host; a MAP + * leaf must also still draw (a canvas with a live GL context, window.maplibregl defined), + * so a fix cannot pass by breaking the map. + * DEFAULT every MAP leaf at its default still DRAWS, with the SAME engine it drew with before. + * The engine is read from the canvas that drew (mapboxgl-canvas / maplibregl-canvas), not + * from which globals exist: some leaves are MapLibre by design (prayer-walk-planner, the + * ".../maplibre" instances), and a peer that merely loaded Mapbox proves nothing. + * --baseline measures that tree too (e.g. main, with the static peer) and requires, per + * leaf: the default pass draws with the baseline's engine, and the maplibre pass draws with + * MapLibre wherever the baseline drew a map at all. Without it, a map leaf must simply draw. + * Paint here is a floor, not the verdict on looks: verify-map-paint and check:gospel-map-paints judge + * the map itself. + * + * USAGE node internal/check-leaf-engine-per-provider.mjs [--dir ] [--only a/b,c/d] + * Run through internal/browser-slot.sh. + * @browser-slot-max-hold 2700 + * EXIT 0 pass Β· 1 a leaf calls Mapbox under maplibre, or a map stopped drawing Β· 75 not measured. + */ +import { chromium } from 'playwright' +import { spawn } from 'node:child_process' +import { createServer } from 'node:http' +import { readFileSync, existsSync } from 'node:fs' +import { resolve, dirname, join } from 'node:path' +import { fileURLToPath } from 'node:url' +import { TREE_NAME } from './_tree-name.mjs' + +const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..') +const argv = process.argv.slice(2) +const arg = (k, d) => { const i = argv.indexOf(k); return i >= 0 && argv[i + 1] ? argv[i + 1] : d } +const DIR = resolve(arg('--dir', resolve(ROOT, 'public'))) +const ONLY = arg('--only', '') ? new Set(arg('--only', '').split(',')) : null +const BASELINE = arg('--baseline', '') ? resolve(arg('--baseline', '')) : null +const CONCURRENCY = 3 +const MAPBOX_HOST = /(^|\.)mapbox\.com$/i + +const manifestPath = join(DIR, TREE_NAME, 'manifest.json') +if (!existsSync(manifestPath)) { console.log(`LEAF-ENGINE: NOT MEASURED β€” no ${manifestPath}`); process.exit(75) } +const manifest = JSON.parse(readFileSync(manifestPath, 'utf8')) +const leaves = [] +for (const b of manifest.bundles || []) for (const p of b.profiles || []) { + const base = `${b.bundle}/${p.profile}` + const add = (id) => { if ((!ONLY || ONLY.has(id)) && existsSync(join(DIR, TREE_NAME, id, 'index.html'))) leaves.push({ id, isMap: !!p.isMap }) } + add(base) + for (const inst of p.instances || []) add(`${base}/${inst.id}`) +} +if (!leaves.length) { console.log('LEAF-ENGINE: NOT MEASURED β€” no leaves matched'); process.exit(75) } + +async function serve(dir) { + const port = await new Promise((ok) => { const s = createServer().listen(0, '127.0.0.1', () => { const n = s.address().port; s.close(() => ok(n)) }) }) + const srv = spawn(process.execPath, [resolve(ROOT, 'internal/serve-strict.mjs'), '--port', String(port), '--directory', dir], { stdio: 'ignore' }) + let up = false + for (let i = 0; i < 40 && !up; i++) { await new Promise((r) => setTimeout(r, 150)); up = await fetch(`http://127.0.0.1:${port}/${TREE_NAME}/manifest.json`).then((r) => r.ok).catch(() => false) } + if (!up) { try { srv.kill() } catch { /* gone */ } return null } + return { port, stop: () => { try { srv.kill() } catch { /* already gone */ } } } +} +const cur = await serve(DIR) +const base = BASELINE ? await serve(BASELINE) : null +const stop = () => { cur && cur.stop(); base && base.stop() } +if (!cur || (BASELINE && !base)) { stop(); console.log(`LEAF-ENGINE: NOT MEASURED β€” could not serve ${!cur ? DIR : BASELINE}`); process.exit(75) } + +// A drawn map: a canvas, somewhere down the shadow roots, with a live WebGL context. +const DRAWN = `(() => { + const canvases = [] + const walk = (r) => { for (const e of r.querySelectorAll('*')) { if (e.tagName === 'CANVAS') canvases.push(e); if (e.shadowRoot) walk(e.shadowRoot) } } + walk(document) + const drawing = canvases.filter((c) => { if (c.width < 50 || c.height < 50) return false + const gl = c.getContext('webgl2') || c.getContext('webgl'); return !!gl && !gl.isContextLost() }) + const engines = [...new Set(drawing.map((c) => c.classList.contains('maplibregl-canvas') ? 'maplibre' : c.classList.contains('mapboxgl-canvas') ? 'mapbox' : 'other'))].sort() + return { canvases: canvases.length, live: drawing.length > 0, engine: engines.join('+') || 'none', mapboxgl: !!window.mapboxgl, maplibregl: !!window.maplibregl } +})()` + +const browser = await chromium.launch({ args: ['--use-gl=swiftshader', '--enable-webgl', '--ignore-gpu-blocklist'] }) +async function load(srvPort, id, provider) { + const ctx = await browser.newContext({ viewport: { width: 1280, height: 860 } }) + const page = await ctx.newPage() + const mapboxReqs = [] + page.on('request', (r) => { try { if (MAPBOX_HOST.test(new URL(r.url()).hostname)) mapboxReqs.push(r.url()) } catch { /* data: etc */ } }) + const url = `http://127.0.0.1:${srvPort}/${TREE_NAME}/${id}/index.html${provider === 'maplibre' ? '?provider=maplibre' : ''}` + try { + await page.goto(url, { waitUntil: 'load', timeout: 45000 }) + await page.waitForLoadState('networkidle', { timeout: 20000 }).catch(() => {}) + let d = null + for (let i = 0; i < 30; i++) { d = await page.evaluate(DRAWN).catch(() => null); if (d && d.live) break; await page.waitForTimeout(500) } + await page.waitForTimeout(1500) // late requests (a lazily injected geocoder) still count + return { ...d, mapboxReqs: [...mapboxReqs] } + } catch (e) { return { error: String(e.message || e).split('\n')[0], mapboxReqs: [...mapboxReqs] } } + finally { await ctx.close() } +} + +const rows = [] +const queue = leaves.slice() +await Promise.all(Array.from({ length: CONCURRENCY }, async () => { + while (queue.length) { + const L = queue.shift() + const ml = await load(cur.port, L.id, 'maplibre') + const mb = L.isMap ? await load(cur.port, L.id, 'default') : null + const bl = base && L.isMap ? await load(base.port, L.id, 'default') : null + const problems = [] + if (ml.error) problems.push(`maplibre load: ${ml.error}`) + if (ml.mapboxReqs.length) problems.push(`${ml.mapboxReqs.length} Mapbox request(s) under maplibre: ${[...new Set(ml.mapboxReqs.map((u) => new URL(u).pathname.split('/').slice(0, 3).join('/') || u))].slice(0, 3).join(', ')}`) + if (mb && mb.error) problems.push(`default load: ${mb.error}`) + if (bl && bl.error) problems.push(`baseline load: ${bl.error}`) + // What SHOULD draw: with a baseline, whatever drew there; without one, a map leaf draws something. + const baselineDrew = bl ? bl.live : (L.isMap ? true : false) + if (L.isMap && !ml.error && baselineDrew && !(ml.live && ml.engine.includes('maplibre'))) problems.push(`under maplibre the map does not draw with MapLibre (drew: ${ml.engine})`) + if (mb && !mb.error) { + if (bl && !bl.error) { + if (mb.engine !== bl.engine) problems.push(`the DEFAULT path changed engine: before ${bl.engine}, now ${mb.engine}`) + } else if (!mb.live) problems.push('the DEFAULT path draws no map') + } + rows.push({ id: L.id, isMap: L.isMap, mlReqs: ml.mapboxReqs.length, def: mb ? mb.engine : '', was: bl ? bl.engine : '', problems, unmeasured: !!(ml.error || (mb && mb.error) || (bl && bl.error)) }) + } +})) +await browser.close(); stop() + +rows.sort((a, b) => a.id.localeCompare(b.id)) +console.log(`LEAF-ENGINE: ${rows.length} leaves (${rows.filter((r) => r.isMap).length} maps) on ${DIR}${BASELINE ? ` β€” baseline ${BASELINE}` : ''}`) +for (const r of rows) console.log(` ${r.problems.length ? (r.unmeasured ? '?? ' : 'FAIL') : 'ok '} ${r.id.padEnd(46)} ${r.isMap ? 'map ' : ' '} mapbox reqs under maplibre: ${String(r.mlReqs).padStart(2)}${r.def ? ` default draws: ${r.def}${r.was ? ` (was ${r.was})` : ''}` : ''}${r.problems.length ? ' β€” ' + r.problems.join('; ') : ''}`) +const fails = rows.filter((r) => r.problems.length && !r.unmeasured) +if (fails.length) { + console.log(`LEAF-ENGINE: FAIL β€” ${fails.length} leaf/leaves: under ?provider=maplibre they still call Mapbox, or a map stopped drawing.`) + process.exit(1) +} +if (rows.some((r) => r.unmeasured)) { console.log('LEAF-ENGINE: NOT MEASURED β€” a leaf did not load.'); process.exit(75) } +console.log(`LEAF-ENGINE: PASS β€” no leaf calls Mapbox under maplibre; every map draws with MapLibre under maplibre and with ${BASELINE ? 'the same engine as the baseline' : 'an engine'} by default.`) diff --git a/internal/generate-tree.mjs b/internal/generate-tree.mjs index 8d8157ce3..15791e8a2 100644 --- a/internal/generate-tree.mjs +++ b/internal/generate-tree.mjs @@ -615,7 +615,6 @@ function forgeHtml(o) { ${esc(o.title)} - @@ -798,13 +797,14 @@ function mapPageHtml(opts) { ` - : ` - - - - - ` + : `` return ` @@ -868,7 +868,11 @@ ${apiScript} var BASE_CONFIG = ${JSON.stringify(pageConfig)}; // MapLibre map pages AND shell pages (app-shell / nested-mfe-spa) need NO token: // the ladder below is skipped entirely and the element mounts immediately. - var TOKEN_FREE = ${JSON.stringify(tokenFree)}; + // …and a Mapbox leaf opened with provider=maplibre (config, ?provider= or window.MAP_PROVIDER β€” + // the same ladder as resolveMapProvider) needs no Mapbox token either: without this, the public + // copy (no baked token) asked a MapLibre visitor to paste a Mapbox token it would never use. + var RUNTIME_PROVIDER = String((BASE_CONFIG && BASE_CONFIG.provider) || new URLSearchParams(location.search).get('provider') || window.MAP_PROVIDER || 'mapbox').toLowerCase(); + var TOKEN_FREE = ${JSON.stringify(tokenFree)} || RUNTIME_PROVIDER === 'maplibre'; // A token-free COMPOSITE still nests plugins, and a nested Mapbox map reads window.MAP_TOKEN // rather than this page's profile-config. With no baked token (the public copy), it must get // one from ?tk= or a pasted token, or be asked for one β€” see the rungs below. diff --git a/internal/verify-map-paint.mjs b/internal/verify-map-paint.mjs index 722cfdc2b..182f8b182 100644 --- a/internal/verify-map-paint.mjs +++ b/internal/verify-map-paint.mjs @@ -166,10 +166,12 @@ const splitErrors = (lines) => { return { env, real } } +// Opt-in viewport for a phone pass (GATE_VIEWPORT=390x844); the default is unchanged. +const GATE_VIEWPORT = (() => { const m = /^(\d+)x(\d+)$/.exec(process.env.GATE_VIEWPORT || ''); return m ? { width: +m[1], height: +m[2] } : null })() let bad = 0 for (const url of urls) { const name = url.split(`/${TREE_NAME}/`)[1] || url - const page = await browser.newPage({ viewport: { width: 1280, height: 800 } }) + const page = await browser.newPage({ viewport: GATE_VIEWPORT || { width: 1280, height: 800 } }) const errors = [] // ⚠ THE STYLESHEET COMPLAINT IS A WARNING, NOT AN ERROR, so filtering to type()==='error' above // meant this gate could never see it. Mapbox emits "This page appears to be missing CSS diff --git a/package.json b/package.json index bc71271f0..98622bd18 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "check:coverage-consistent:poison": "node internal/check-gospel-coverage-consistent.mjs --poison", "check:csv-shape": "node internal/check-csv-export-shape.mjs", "check:csv-shape:self-test": "node internal/check-csv-export-shape.mjs --self-test", + "check:dashboard-geocoder-provider": "node plugin-designs/research-dashboard/scripts/check-dashboard-geocoder-provider.mjs", "check:deck-click": "node internal/check-deck-click.mjs", "check:dep-names": "node internal/check-dependency-names.mjs", "check:descriptions": "node internal/check-profile-descriptions.mjs", @@ -87,6 +88,7 @@ "check:i18n-parity": "node internal/check-i18n-parity.mjs", "check:kpi-unit": "node internal/check-kpi-unit-once.mjs", "check:label-joins": "node internal/check-label-joins.mjs", + "check:leaf-engine-per-provider": "node internal/check-leaf-engine-per-provider.mjs", "check:leaf-english-leaks": "node internal/check-leaf-english-leaks.mjs", "check:leaf-english-leaks:self-test": "node internal/check-leaf-english-leaks.mjs --self-test", "check:leaves-mount": "node internal/check-gospel-leaves-mount.mjs", @@ -180,7 +182,7 @@ "dev": "bun x vite", "fix:sri": "node internal/fix-sri.mjs", "gallery": "node internal/generate-gallery.mjs", - "gates:post": "node internal/run-parallel.mjs --label=post -- 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: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", + "gates:post": "node internal/run-parallel.mjs --label=post -- 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: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", "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", "library-index": "node internal/generate-library-index.mjs", "packet-fixture": "node internal/generate-packet-fixture.mjs", @@ -243,6 +245,7 @@ "check:composite-locale-leaks": "a composite rendered in a non-English locale must not show a string the catalogue already translates. The criterion is a LOOKUP, not a category: English catalogue holds the string as a value, this locale holds a different value for that key, the page still renders English. The previous sweep split strings into chrome/data/third-party and scored single words like 'Region' and 'Prayer' as data, so 28 leaks on Big Picture and 41 on Simple Research counted as zero. Surfaces that a lookup cannot judge - vendor map chrome, table DATA cells, facet-rail values - are suppressed by NAME with a stated reason, never by word.", "check:coverage-consistent": "the gospel-resources surfaces must agree: no resource on an ISO RESERVED code (und/mul/mis/zxx), every resourced code NAMED, table reach recounted off the pins, no invented URL, tiers reconcile. Static, sub-second. Self-test: `bun run check:coverage-consistent:poison`, all 12 clauses (A-L) must fail. Clause L is the one that is not about consistency: it fails when the link-verification record goes stale, because 'link checked' is a claim about the present tense. Written after the same drift defect was found by hand three times.", "check:csv-shape": "the export must OPEN as a table: header on line 0, every row matching. Run the `check:csv-shape:self-test` script for the NEGATIVE test (forces the provenance comment back to line 0 IN MEMORY and requires this gate to REJECT it) β€” the header used to claim 'NEGATIVE-TESTED' with no way to re-run it.", + "check:dashboard-geocoder-provider": "the research dashboard leaf at ?provider=maplibre makes ZERO requests to api.mapbox.com, and its search bars still suggest (built tree; browser gate). It was kept out of gates:post while the generated leaf hard-linked the Mapbox engine; the leaf now loads only the engine it is asked for.", "check:dep-names": "every declared dep is a REAL package name (a rename once rewrote @capacitor/* and broke bun install in a clone)", "check:descriptions": "every shipped profile must say what it is (reads the BUILT manifest)", "check:done-evidence": "a card may not reach status:done with NO bluf AND NO fields β€” the Doxa team inherits this board and an evidence-less 'done' cannot be reconciled against the code. 10 pre-existing are baselined by id; the list should only ever SHRINK.", @@ -273,6 +276,7 @@ "check:heartbeat:self-test": "THREE regression cases, each required to fire ITS OWN assertion: [literal] resolver replaced by the old hard-coded array -> B; [shipped-default] dashboard.config.js heartbeatThreshold changed -> A2; [shipped-mode] heartbeatMode flipped to quantile -> A2. Poisons IN MEMORY, never the file. Testing that the FEATURE works is not testing that the REGRESSION is caught β€” this gate shipped with only the first and missed the second.", "check:host-locale-contract": "a HOST's profile-config.locale reaches every leaf: each leaf page is loaded with ONLY \"locale\":\"ar\" added to its profile-config (no ?lang=, the page keeps lang=\"en\") and must draw at least half the Arabic text the same page draws at ?lang=ar. regions-dashboard/regions-list is the positive control (NOT MEASURED if it does not enter Arabic). Guards ProfileLoader's inject('appI18n'): a bundle that never provides appI18n silently ignores the host. --dir for a before/after, --only a/b,c/d to narrow. Through browser-slot.sh.", "check:i18n-never-translated": "a key PRESENT in all 11 catalogs but IDENTICAL in all of them is copied, not translated. check:i18n-parity asserts presence; presence and translatedness are different properties. Carries an allowlist for keys correctly identical everywhere (pure format strings, brand names, ISO designations) β€” each with a reason, so the list records decisions rather than silencing findings.", + "check:leaf-engine-per-provider": "every leaf in the manifest at ?provider=maplibre makes ZERO requests to any *.mapbox.com host and a map leaf still draws; at its default every map leaf draws with the engine it drew with before (read from the canvas that drew). --baseline compares against another tree (e.g. main). Browser gate, @browser-slot-max-hold 2700.", "check:leaves-mount": "DID THE ELEMENT ACTUALLY MOUNT, on all five gospel leaves. Exists because a ReferenceError thrown during setup dies INSIDE the custom-element boundary: the page loads, every asset returns 200, pageerror fires zero times, and the element sits there with an empty shadow root. A healthy-looking dead page. This asks the cheap unambiguous question the rich gates are too slow to ask on every leaf β€” is anything there β€” in five steps that each name their own failure: tag defined, host present, shadowRoot attached, shadow root non-empty, visible text rendered. It does NOT trust the absence of console errors; zero errors is not the same as working. --poison for the negative test.", "check:legend-export-reachable": "the Export affordance must be reachable at BOTH widths. Selects a leaf row first (the control is gated on selectedNode) and opens the mobile sheet first (it starts closed). Asserts a REAL BOX, not CSS visibility β€” on mobile the elements report visibility:visible at 0x0. PASSES at both widths since the mobile sheet was given :export-enabled and :tabs. SKIPs when nothing is served.", "check:legend-source": "the numbers the maps SHOW must match the numbers the live SOURCE has. Every other gate checks INTERNAL consistency β€” a page can agree with itself perfectly while its legend has stopped describing reality. Offline SKIPs (exit 0); only a reachable endpoint that disagrees fails.", diff --git a/plugin-designs/research-dashboard/scripts/check-dashboard-geocoder-provider.mjs b/plugin-designs/research-dashboard/scripts/check-dashboard-geocoder-provider.mjs index 1384ced8d..0a4e22988 100644 --- a/plugin-designs/research-dashboard/scripts/check-dashboard-geocoder-provider.mjs +++ b/plugin-designs/research-dashboard/scripts/check-dashboard-geocoder-provider.mjs @@ -17,8 +17,9 @@ * * i.e. the leaf ships Mapbox GL and the Mapbox geocoder to every visitor regardless of the engine * it was asked for. That is the leaf GENERATOR's doing, not this plugin's, and it is why this gate - * is deliberately NOT wired into gates:post yet: it would be a red gate about somebody else's file. - * Run it by name; it names the exact URLs. + * was kept out of gates:post until the generator stopped hard-linking the engine (2026-09-25: the leaf + * now loads only the engine its provider asks for). It is wired into gates:post in that same landing, + * and still names the exact URLs when it fails. * * β˜… ZERO IS THE ASSERTION, not "it rendered". A build that quietly kept the Mapbox geocoder would * look identical: the bars appear, suggestions work, nothing errors. Only the request log tells the From 13e0e02c9dccad9a214957162c616b023433c83b Mon Sep 17 00:00:00 2001 From: Doxa Maps Date: Fri, 25 Sep 2026 23:49:02 +0000 Subject: [PATCH 3/3] Rebuild the consumers main left stale (build owner, README rule 5) check:composite-freshness found stale on main: none. Rebuilt, tree + SRI regenerated; the landing precondition (fresh, one folder, SRI) passed in main-capture scan. --- .../all-people-groups/index.html | 21 ++++++++++++------- .../big-picture/big-picture/index.html | 6 +++++- .../big-picture/frontier/index.html | 6 +++++- .../big-picture/big-picture/index.html | 6 +++++- .../connectivity-list/index.html | 6 +++++- .../connectivity-map/index.html | 21 ++++++++++++------- .../cities-internet/data-tables/index.html | 6 +++++- .../country-posters/countries-map/index.html | 21 ++++++++++++------- .../country-poster-wall/index.html | 6 +++++- .../country-posters-mfe/index.html | 21 ++++++++++++------- .../dashboard-plus-grid/default/index.html | 21 ++++++++++++------- .../dashboard-plus-grid/index.html | 21 ++++++++++++------- public/dx-maps/demo-map/demo-map/index.html | 21 ++++++++++++------- .../demo-map/demo-map/maplibre/index.html | 6 +++++- .../doxa-maps-spa/app/index.html | 6 +++++- .../doxa-maps-spa/doxa-maps-spa/index.html | 6 +++++- .../doxa-research-map/research-map/index.html | 21 ++++++++++++------- .../research-map/maplibre/index.html | 6 +++++- .../doxa-simple-map/adoption/index.html | 21 ++++++++++++------- .../doxa-simple-map/engagement/index.html | 21 ++++++++++++------- .../doxa-simple-map/index.html | 21 ++++++++++++------- .../doxa-simple-map/prayer/index.html | 21 ++++++++++++------- .../doxa-vision-card/default/index.html | 6 +++++- .../doxa-vision-card/frontier/index.html | 6 +++++- .../doxa-vision-card/index.html | 6 +++++- .../golden-path-check/index.html | 6 +++++- .../gospel-resources/charts/index.html | 6 +++++- .../gospel-resources/data-tables/index.html | 6 +++++- .../gospel-dashboard/index.html | 21 ++++++++++++------- .../resources-list/index.html | 6 +++++- .../gospel-resources/resources-map/index.html | 21 ++++++++++++------- .../my-upg-100-list/poster-wall/index.html | 6 +++++- .../my-upg-100-list/upg-100-list/index.html | 21 ++++++++++++------- .../my-upg-100-list/upg-100-mfe/index.html | 21 ++++++++++++------- .../people-groups-grid/driven/index.html | 6 +++++- .../people-groups-grid/index.html | 6 +++++- .../people-groups-grid/index.html | 6 +++++- .../plugin-studio/plugin-studio/index.html | 6 +++++- .../plugin-studio/studio/index.html | 6 +++++- .../pplr-data-maps/pplr-data-maps/index.html | 21 ++++++++++++------- .../prayer-walk-planner/index.html | 21 ++++++++++++------- .../region-dashboards/index.html | 21 ++++++++++++------- .../regions-dashboard/regions-list/index.html | 6 +++++- .../regions-dashboard/regions-mfe/index.html | 21 ++++++++++++------- .../research-dashboard/index.html | 21 ++++++++++++------- .../research-map-plus-grid/default/index.html | 6 +++++- .../research-map-plus-grid/index.html | 6 +++++- .../dx-maps/stack-demo/stack-demo/index.html | 6 +++++- .../stack-demo/stack-demo/pair/index.html | 6 +++++- .../upg-neighbor/bloom-cockpit/index.html | 21 ++++++++++++------- .../upg-neighbor/flower-mesh/index.html | 21 ++++++++++++------- .../upg-neighbor/upg-neighbor/index.html | 21 ++++++++++++------- .../upg-neighbor/lang-cluster/index.html | 21 ++++++++++++------- .../upg-neighbor/send-map/index.html | 21 ++++++++++++------- .../upg-neighbor/upg-neighbors/index.html | 21 ++++++++++++------- .../wall-of-people-groups/index.html | 6 +++++- 56 files changed, 496 insertions(+), 245 deletions(-) diff --git a/public/dx-maps/all-people-groups/all-people-groups/index.html b/public/dx-maps/all-people-groups/all-people-groups/index.html index a6082a2dd..671e9a6e7 100644 --- a/public/dx-maps/all-people-groups/all-people-groups/index.html +++ b/public/dx-maps/all-people-groups/all-people-groups/index.html @@ -5,13 +5,14 @@ all-people-groups Β· all-people-groups - - - - - - +