UPG-100: test handles are also keyed by instance
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions

AUDIT34 card 9 (LOW): window.__UPG_MAP__, __UPG_SELECT__ and __UPG_SEARCH__ are
last-writer-wins. Embed two copies of this map on one page and the second mount
overwrites the first, so a gate driving 'the' map silently drives whichever
happened to mount last.

Adds __UPG_MAPS__, __UPG_SELECTS__ and __UPG_SEARCHES__, each keyed by the
embedder's instanceId (the same field poster-wall.vue:55 keys on), falling back
to the profile name.

THE BARE HANDLES ARE KEPT AND STILL POINT AT THE LAST MOUNT, deliberately. Every
existing consumer reads them directly -- internal/check-map-data-painted.mjs plus
~10 call sites across data/visual-gate.mjs and data/palette-preview.mjs -- and
those are browser gates. Removing the bare handles would break them to fix a case
they never hit, since a gate page mounts exactly one copy. This change is purely
additive: nothing reads the registries yet, so no consumer behaviour moves.

The honest limit, stated in the code rather than papered over: two copies with NO
instanceId and the SAME profile still collide, because nothing in the embed
contract distinguishes them. A mount counter would give them unique keys but make
the key unpredictable between mounts, so a gate could not name the instance it
wants -- worse than a key that repeats.
This commit is contained in:
Doxa Maps 2026-09-28 05:23:13 +00:00
parent 7b7b8a4ac3
commit f3e29d026b
7 changed files with 153 additions and 123 deletions

View file

@ -124,6 +124,20 @@ import { buildSearchIndex, buildGeocoderFeed, matchSearch } from './data/search-
const mapboxToken = inject('mapboxToken', ref(''))
const profileConfig = inject('profileConfig', ref(null))
// ★ THE KEY FOR THE PER-INSTANCE TEST HANDLES (AUDIT34 card 9). The bare window.__UPG_MAP__ /
// __UPG_SELECT__ are last-writer-wins: two copies of this map on one page and the second mount
// overwrites the first, so a gate driving "the" map drives whichever mounted last.
// The embedder's own instanceId when it set one — the same field poster-wall.vue:55 keys on —
// falling back to the profile name.
// ⚠ TWO COPIES WITH NO instanceId AND THE SAME PROFILE STILL COLLIDE. That is the honest limit of
// what the embed contract gives us: nothing in it distinguishes them. Stated rather than papered
// over with a counter, because a key that changes between mounts would be worse than one that
// repeats — a gate could not name the instance it wants.
// A function, not a computed: it is read twice inside the map-ready path, never rendered.
function testHandleKey() {
return profileConfig.value?.instanceId || profileConfig.value?.profile || 'upg-100-list'
}
// ── THIS PROFILE BUILDS ITS MAP RAW, SO IT HAS TO HONOUR THE SWITCH ITSELF ───
// Every other map bundle reaches the provider switch for free because useMapInstance resolves it.
// This one calls `new gl.Map` directly (see onMounted), so it was the ONE map the switch could not
@ -983,6 +997,8 @@ function buildSearch(records) {
return fn(q).map((f) => f && f.place_name)
},
}
// Mirrored per instance, same reason and shape as __UPG_MAPS__ (AUDIT34 card 9).
;(window.__UPG_SEARCHES__ ||= {})[testHandleKey()] = window.__UPG_SEARCH__
}
}
@ -2147,7 +2163,19 @@ onMounted(async () => {
// 09b-6 §1g: expose the map instance for the Playwright-Firefox visual gate so
// it can assert tapestry fill features via map.queryRenderedFeatures (no visual
// diffing — a reviewer eyeballs the PNGs). Test-only handle; harmless in prod.
if (typeof window !== 'undefined') window.__UPG_MAP__ = map
// ★ ALSO KEYED BY INSTANCE, because the bare handle is LAST-WRITER-WINS: embed two copies of this
// map on one page and the second mount overwrites the first, so a gate driving "the" map silently
// drives whichever happened to mount last (AUDIT34 card 9).
// ⚠ THE BARE HANDLE IS KEPT AND STILL POINTS AT THE LAST MOUNT, deliberately. Every existing
// consumer reads window.__UPG_MAP__ directly — internal/check-map-data-painted.mjs plus ~10 call
// sites across data/visual-gate.mjs and data/palette-preview.mjs — and those are browser gates
// that are not mine to rewrite. Removing it would break them to fix a case they never hit, since
// a gate page mounts exactly one copy. The registry is ADDITIVE: a caller that knows which
// instance it wants can ask for it; a caller that does not keeps today's behaviour.
if (typeof window !== 'undefined') {
window.__UPG_MAP__ = map
;(window.__UPG_MAPS__ ||= {})[testHandleKey()] = map
}
// R1.2 PIVOT: NO keyboard listener (D-8 — modifier-key gestures removed for
// mobile parity). Expose a tiny test handle so the firefox gate can drive batch
@ -2245,6 +2273,8 @@ onMounted(async () => {
return node ? [...descendantKeys(node)] : null
},
}
// Mirrored per instance, same reason and same additive shape as __UPG_MAPS__ above.
;(window.__UPG_SELECTS__ ||= {})[testHandleKey()] = window.__UPG_SELECT__
// Legend tree + selectors are already built (bootstrapLegend() at the top of
// onMounted, before the map guards — P3-1). Nothing to do here.

View file

@ -830,7 +830,7 @@
"bundle": "my-upg-100-list",
"scriptUrl": "./my-upg-100-list/my-upg-100-list.js",
"output": "dx-maps/my-upg-100-list/my-upg-100-list.js",
"integrity": "sha384-pP5oqR725UhonKjefd23vSzweJ8EzbyRoIBaPsGXLn5SlvI7Zr6YG0CT8RqSydGz",
"integrity": "sha384-ETHwvtaRVEZAh1AOVLvjXlV2aHwF3JEksRnKK/IWpoHYeZtcrokIeHmEnM+Yih8g",
"elements": [
"my-upg-100-list-map"
],
@ -852,7 +852,7 @@
"scriptUrl": "./my-upg-100-list/my-upg-100-list.js",
"embed": {
"iframe": "<iframe src=\"./my-upg-100-list/poster-wall/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=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-pP5oqR725UhonKjefd23vSzweJ8EzbyRoIBaPsGXLn5SlvI7Zr6YG0CT8RqSydGz\" crossorigin=\"anonymous\"></script>\n<my-upg-100-list-map profile-config='{\"profile\":\"poster-wall\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-ETHwvtaRVEZAh1AOVLvjXlV2aHwF3JEksRnKK/IWpoHYeZtcrokIeHmEnM+Yih8g\" crossorigin=\"anonymous\"></script>\n<my-upg-100-list-map profile-config='{\"profile\":\"poster-wall\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
}
},
{
@ -867,7 +867,7 @@
"scriptUrl": "./my-upg-100-list/my-upg-100-list.js",
"embed": {
"iframe": "<iframe src=\"./my-upg-100-list/upg-100-list/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\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<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-pP5oqR725UhonKjefd23vSzweJ8EzbyRoIBaPsGXLn5SlvI7Zr6YG0CT8RqSydGz\" crossorigin=\"anonymous\"></script>\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\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<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-ETHwvtaRVEZAh1AOVLvjXlV2aHwF3JEksRnKK/IWpoHYeZtcrokIeHmEnM+Yih8g\" crossorigin=\"anonymous\"></script>\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
}
},
{
@ -883,7 +883,7 @@
"scriptUrl": "./my-upg-100-list/my-upg-100-list.js",
"embed": {
"iframe": "<iframe src=\"./my-upg-100-list/upg-100-mfe/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\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<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-pP5oqR725UhonKjefd23vSzweJ8EzbyRoIBaPsGXLn5SlvI7Zr6YG0CT8RqSydGz\" crossorigin=\"anonymous\"></script>\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-mfe\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\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<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-ETHwvtaRVEZAh1AOVLvjXlV2aHwF3JEksRnKK/IWpoHYeZtcrokIeHmEnM+Yih8g\" crossorigin=\"anonymous\"></script>\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-mfe\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
}
}
]

View file

@ -830,7 +830,7 @@
"bundle": "my-upg-100-list",
"scriptUrl": "./my-upg-100-list/my-upg-100-list.js",
"output": "dx-maps/my-upg-100-list/my-upg-100-list.js",
"integrity": "sha384-pP5oqR725UhonKjefd23vSzweJ8EzbyRoIBaPsGXLn5SlvI7Zr6YG0CT8RqSydGz",
"integrity": "sha384-ETHwvtaRVEZAh1AOVLvjXlV2aHwF3JEksRnKK/IWpoHYeZtcrokIeHmEnM+Yih8g",
"elements": [
"my-upg-100-list-map"
],
@ -852,7 +852,7 @@
"scriptUrl": "./my-upg-100-list/my-upg-100-list.js",
"embed": {
"iframe": "<iframe src=\"./my-upg-100-list/poster-wall/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=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-pP5oqR725UhonKjefd23vSzweJ8EzbyRoIBaPsGXLn5SlvI7Zr6YG0CT8RqSydGz\" crossorigin=\"anonymous\"></script>\n<my-upg-100-list-map profile-config='{\"profile\":\"poster-wall\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-ETHwvtaRVEZAh1AOVLvjXlV2aHwF3JEksRnKK/IWpoHYeZtcrokIeHmEnM+Yih8g\" crossorigin=\"anonymous\"></script>\n<my-upg-100-list-map profile-config='{\"profile\":\"poster-wall\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
}
},
{
@ -867,7 +867,7 @@
"scriptUrl": "./my-upg-100-list/my-upg-100-list.js",
"embed": {
"iframe": "<iframe src=\"./my-upg-100-list/upg-100-list/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\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<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-pP5oqR725UhonKjefd23vSzweJ8EzbyRoIBaPsGXLn5SlvI7Zr6YG0CT8RqSydGz\" crossorigin=\"anonymous\"></script>\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\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<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-ETHwvtaRVEZAh1AOVLvjXlV2aHwF3JEksRnKK/IWpoHYeZtcrokIeHmEnM+Yih8g\" crossorigin=\"anonymous\"></script>\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
}
},
{
@ -883,7 +883,7 @@
"scriptUrl": "./my-upg-100-list/my-upg-100-list.js",
"embed": {
"iframe": "<iframe src=\"./my-upg-100-list/upg-100-mfe/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\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<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-pP5oqR725UhonKjefd23vSzweJ8EzbyRoIBaPsGXLn5SlvI7Zr6YG0CT8RqSydGz\" crossorigin=\"anonymous\"></script>\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-mfe\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\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<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-ETHwvtaRVEZAh1AOVLvjXlV2aHwF3JEksRnKK/IWpoHYeZtcrokIeHmEnM+Yih8g\" crossorigin=\"anonymous\"></script>\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-mfe\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
}
}
]

File diff suppressed because one or more lines are too long

View file

@ -32,7 +32,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <my-upg-100-list-map> custom element. -->
<script src="../my-upg-100-list.js" integrity="sha384-pP5oqR725UhonKjefd23vSzweJ8EzbyRoIBaPsGXLn5SlvI7Zr6YG0CT8RqSydGz" crossorigin="anonymous"></script>
<script src="../my-upg-100-list.js" integrity="sha384-ETHwvtaRVEZAh1AOVLvjXlV2aHwF3JEksRnKK/IWpoHYeZtcrokIeHmEnM+Yih8g" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -36,7 +36,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <my-upg-100-list-map> custom element. -->
<script src="../my-upg-100-list.js" integrity="sha384-pP5oqR725UhonKjefd23vSzweJ8EzbyRoIBaPsGXLn5SlvI7Zr6YG0CT8RqSydGz" crossorigin="anonymous"></script>
<script src="../my-upg-100-list.js" integrity="sha384-ETHwvtaRVEZAh1AOVLvjXlV2aHwF3JEksRnKK/IWpoHYeZtcrokIeHmEnM+Yih8g" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -47,7 +47,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <my-upg-100-list-map> custom element. -->
<script src="../my-upg-100-list.js" integrity="sha384-pP5oqR725UhonKjefd23vSzweJ8EzbyRoIBaPsGXLn5SlvI7Zr6YG0CT8RqSydGz" crossorigin="anonymous"></script>
<script src="../my-upg-100-list.js" integrity="sha384-ETHwvtaRVEZAh1AOVLvjXlV2aHwF3JEksRnKK/IWpoHYeZtcrokIeHmEnM+Yih8g" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order: