A page that asks for the keyless engine never silently gets the token-gated one

`const gl = engineGlobal(upgProvider) || window.mapboxgl` — and that fallback can
ONLY fire when the requested provider is 'maplibre' and MapLibre failed to load.
engineGlobal already resolves mapbox's own global for the mapbox case
(useMapInstance.js:115 falls back to ENGINES.mapbox.global only for an UNKNOWN
provider NAME, not for a missing engine). So the `||` silently handed MAPBOX to a
page that asked for the KEYLESS engine, breaking the keyless contract that
research-map.instances.json documents. Found by regions' C3 read, routed by
dx-shepherd (END-SHEP-C3C).

⚠ AND THE FALLBACK COULD NOT EVEN WORK. `gl.accessToken = token` a few lines below
is gated on `if (!isMaplibre)`, so a maplibre request that fell through to Mapbox
would get Mapbox WITHOUT a token — 401s on every tile instead of a legible
failure. Two defects in one expression: a silent engine switch, and a substitute
that was broken by construction.

THE FIX IS A DELETION, not a new path: the existing `if (!gl)` branch already
fails visibly and well — it sets status + mapError, calls
enrichLegendFromRecords() so the curated list and legend survive, and returns. It
just was not being reached. Removing the `||` lets it run.

MADE THE FAILURE DIAGNOSABLE while I was there. It said "The map engine did not
load" — true but useless: a reader could not tell whether the keyless engine they
asked for was even attempted, which is exactly the ambiguity a silent downgrade
creates. It now NAMES the engine, so a partner who set ?provider=maplibre and sees
"MapLibre did not load" knows their request was honoured and the script is the
problem. Plus a console.warn stating the refusal to substitute — the notice is for
the reader, the warn is for whoever is debugging the embed.
Both strings go through messages.* in eleven locales rather than adding to the
English-literal debt I cleared earlier this tick.

VERIFIED HEADLESS ACROSS BOTH PROVIDERS x THREE LOAD STATES, which is the mutation
test this needed: the ONLY behaviour that changes is provider=maplibre with
MapLibre absent, where old=MB (silent Mapbox) becomes new=undefined (the visible
failure path). mapbox/both, mapbox/only-mapbox, mapbox/none, maplibre/both and
maplibre/none are all byte-identical to before. So the fix is surgical rather than
a behaviour change dressed as a bugfix.
check:i18n-parity PASS. BUNDLE=my-upg-100-list vite build clean.
This commit is contained in:
Doxa Maps 2026-09-26 16:54:12 +00:00
parent 1e08837756
commit ade12e4c0b
13 changed files with 87 additions and 46 deletions

View file

@ -14,5 +14,7 @@
"selectionPosterFailed": "فشل إنشاء ملصق التحديد: {reason}", "selectionPosterFailed": "فشل إنشاء ملصق التحديد: {reason}",
"mapUnavailable": "الخريطة غير متاحة", "mapUnavailable": "الخريطة غير متاحة",
"copied": "تم النسخ!", "copied": "تم النسخ!",
"copyFailed": "فشل النسخ — اضغط Ctrl/Cmd-C" "copyFailed": "فشل النسخ — اضغط Ctrl/Cmd-C",
"engineNotLoaded": "لم يتم تحميل محرك الخريطة",
"engineNotLoadedDetail": "لم يتم تحميل محرك الخريطة {engine}. القائمة ووسيلة الإيضاح غير متأثرتين."
} }

View file

@ -14,5 +14,7 @@
"selectionPosterFailed": "Plakat der Auswahl fehlgeschlagen: {reason}", "selectionPosterFailed": "Plakat der Auswahl fehlgeschlagen: {reason}",
"mapUnavailable": "Karte nicht verfügbar", "mapUnavailable": "Karte nicht verfügbar",
"copied": "Kopiert!", "copied": "Kopiert!",
"copyFailed": "Kopieren fehlgeschlagen — Strg/Cmd-C drücken" "copyFailed": "Kopieren fehlgeschlagen — Strg/Cmd-C drücken",
"engineNotLoaded": "Karten-Engine nicht geladen",
"engineNotLoadedDetail": "Die Karten-Engine {engine} wurde nicht geladen. Liste und Legende sind nicht betroffen."
} }

View file

@ -14,5 +14,7 @@
"selectionPosterFailed": "Selection poster failed: {reason}", "selectionPosterFailed": "Selection poster failed: {reason}",
"mapUnavailable": "Map unavailable", "mapUnavailable": "Map unavailable",
"copied": "Copied!", "copied": "Copied!",
"copyFailed": "Copy failed — press Ctrl/Cmd-C" "copyFailed": "Copy failed — press Ctrl/Cmd-C",
"engineNotLoaded": "map engine not loaded",
"engineNotLoadedDetail": "The {engine} map engine did not load. The list and legend are unaffected."
} }

View file

@ -14,5 +14,7 @@
"selectionPosterFailed": "Error al generar el cartel de la selección: {reason}", "selectionPosterFailed": "Error al generar el cartel de la selección: {reason}",
"mapUnavailable": "Mapa no disponible", "mapUnavailable": "Mapa no disponible",
"copied": "¡Copiado!", "copied": "¡Copiado!",
"copyFailed": "Error al copiar: pulsa Ctrl/Cmd-C" "copyFailed": "Error al copiar: pulsa Ctrl/Cmd-C",
"engineNotLoaded": "motor de mapas no cargado",
"engineNotLoadedDetail": "El motor de mapas {engine} no se cargó. La lista y la leyenda no se ven afectadas."
} }

View file

@ -14,5 +14,7 @@
"selectionPosterFailed": "Échec de l’affiche de la sélection : {reason}", "selectionPosterFailed": "Échec de l’affiche de la sélection : {reason}",
"mapUnavailable": "Carte indisponible", "mapUnavailable": "Carte indisponible",
"copied": "Copié !", "copied": "Copié !",
"copyFailed": "Échec de la copie — appuyez sur Ctrl/Cmd-C" "copyFailed": "Échec de la copie — appuyez sur Ctrl/Cmd-C",
"engineNotLoaded": "moteur cartographique non chargé",
"engineNotLoadedDetail": "Le moteur cartographique {engine} ne s’est pas chargé. La liste et la légende ne sont pas affectées."
} }

View file

@ -14,5 +14,7 @@
"selectionPosterFailed": "चयन का पोस्टर विफल: {reason}", "selectionPosterFailed": "चयन का पोस्टर विफल: {reason}",
"mapUnavailable": "मानचित्र अनुपलब्ध", "mapUnavailable": "मानचित्र अनुपलब्ध",
"copied": "कॉपी हो गया!", "copied": "कॉपी हो गया!",
"copyFailed": "कॉपी विफल — Ctrl/Cmd-C दबाएँ" "copyFailed": "कॉपी विफल — Ctrl/Cmd-C दबाएँ",
"engineNotLoaded": "मानचित्र इंजन लोड नहीं हुआ",
"engineNotLoadedDetail": "{engine} मानचित्र इंजन लोड नहीं हुआ। सूची और लेजेंड अप्रभावित हैं।"
} }

View file

@ -14,5 +14,7 @@
"selectionPosterFailed": "Manifesto della selezione non riuscito: {reason}", "selectionPosterFailed": "Manifesto della selezione non riuscito: {reason}",
"mapUnavailable": "Mappa non disponibile", "mapUnavailable": "Mappa non disponibile",
"copied": "Copiato!", "copied": "Copiato!",
"copyFailed": "Copia non riuscita — premi Ctrl/Cmd-C" "copyFailed": "Copia non riuscita — premi Ctrl/Cmd-C",
"engineNotLoaded": "motore di mappe non caricato",
"engineNotLoadedDetail": "Il motore di mappe {engine} non si è caricato. L’elenco e la legenda non sono interessati."
} }

View file

@ -14,5 +14,7 @@
"selectionPosterFailed": "Falha ao gerar o cartaz da seleção: {reason}", "selectionPosterFailed": "Falha ao gerar o cartaz da seleção: {reason}",
"mapUnavailable": "Mapa indisponível", "mapUnavailable": "Mapa indisponível",
"copied": "Copiado!", "copied": "Copiado!",
"copyFailed": "Falha ao copiar — pressione Ctrl/Cmd-C" "copyFailed": "Falha ao copiar — pressione Ctrl/Cmd-C",
"engineNotLoaded": "motor de mapas não carregado",
"engineNotLoadedDetail": "O motor de mapas {engine} não carregou. A lista e a legenda não são afetadas."
} }

View file

@ -14,5 +14,7 @@
"selectionPosterFailed": "Afișul selecției a eșuat: {reason}", "selectionPosterFailed": "Afișul selecției a eșuat: {reason}",
"mapUnavailable": "Harta indisponibilă", "mapUnavailable": "Harta indisponibilă",
"copied": "Copiat!", "copied": "Copiat!",
"copyFailed": "Copierea a eșuat — apasă Ctrl/Cmd-C" "copyFailed": "Copierea a eșuat — apasă Ctrl/Cmd-C",
"engineNotLoaded": "motorul de hărți nu s-a încărcat",
"engineNotLoadedDetail": "Motorul de hărți {engine} nu s-a încărcat. Lista și legenda nu sunt afectate."
} }

View file

@ -14,5 +14,7 @@
"selectionPosterFailed": "Не удалось создать плакат выбора: {reason}", "selectionPosterFailed": "Не удалось создать плакат выбора: {reason}",
"mapUnavailable": "Карта недоступна", "mapUnavailable": "Карта недоступна",
"copied": "Скопировано!", "copied": "Скопировано!",
"copyFailed": "Не удалось скопировать — нажмите Ctrl/Cmd-C" "copyFailed": "Не удалось скопировать — нажмите Ctrl/Cmd-C",
"engineNotLoaded": "движок карты не загружен",
"engineNotLoadedDetail": "Движок карты {engine} не загрузился. Список и легенда не затронуты."
} }

View file

@ -14,5 +14,7 @@
"selectionPosterFailed": "所选内容的海报生成失败:{reason}", "selectionPosterFailed": "所选内容的海报生成失败:{reason}",
"mapUnavailable": "地图不可用", "mapUnavailable": "地图不可用",
"copied": "已复制!", "copied": "已复制!",
"copyFailed": "复制失败——请按 Ctrl/Cmd-C" "copyFailed": "复制失败——请按 Ctrl/Cmd-C",
"engineNotLoaded": "地图引擎未加载",
"engineNotLoadedDetail": "{engine} 地图引擎未能加载。列表和图例不受影响。"
} }

View file

@ -1859,7 +1859,18 @@ onMounted(async () => {
bootstrapLegend() bootstrapLegend()
// Load the engine the PROVIDER selects, not the one this file used to assume. // Load the engine the PROVIDER selects, not the one this file used to assume.
try { await ensureEngineScript(upgProvider) } catch (_) { /* fall through to the !gl notice below */ } try { await ensureEngineScript(upgProvider) } catch (_) { /* fall through to the !gl notice below */ }
const gl = engineGlobal(upgProvider) || window.mapboxgl // ★ NEVER SWITCH ENGINES SILENTLY. This read `engineGlobal(upgProvider) || window.mapboxgl`, and that
// fallback can ONLY fire when the requested provider is 'maplibre' and MapLibre failed to load —
// engineGlobal already resolves mapbox's own global for the mapbox case (useMapInstance.js:115 falls back
// to ENGINES.mapbox.global only for an UNKNOWN provider name, not for a missing engine). So the `||`
// silently handed back MAPBOX to a page that asked for the KEYLESS engine, breaking the keyless contract
// that research-map.instances.json documents.
// ⚠ AND THE FALLBACK COULD NOT EVEN WORK: `gl.accessToken = token` a few lines below is gated on
// `if (!isMaplibre)`, so a maplibre request that fell through to Mapbox would get Mapbox WITHOUT a token
// — 401s on every tile instead of a legible failure. Two defects in one expression.
// Found by regions' C3 read, routed via dx-shepherd (END-SHEP-C3C). Same shape in all three raw-map
// plugins.
const gl = engineGlobal(upgProvider)
// ★ SHAPE RTL BASEMAP LABELS — HERE, not in index.js, and not after the map exists. // ★ SHAPE RTL BASEMAP LABELS — HERE, not in index.js, and not after the map exists.
// This profile builds its map RAW (`new gl.Map` below) instead of going through // This profile builds its map RAW (`new gl.Map` below) instead of going through
// useMapInstance, so it inherits none of the entry's side effects. It appeared to work only // useMapInstance, so it inherits none of the entry's side effects. It appeared to work only
@ -1882,8 +1893,16 @@ onMounted(async () => {
// notice in the MAP CELL, and still enrich the legend with live population data // notice in the MAP CELL, and still enrich the legend with live population data
// (a plain fetch — it needs no map). The list stays fully usable without tiles. // (a plain fetch — it needs no map). The list stays fully usable without tiles.
if (!gl) { if (!gl) {
status.value = '⚠ map engine not loaded' // FAIL VISIBLY AND NAME THE ENGINE. The old copy said "The map engine" — true but undiagnosable:
mapError.value = 'The map engine did not load. The list and legend are unaffected.' // a reader could not tell whether the keyless engine they asked for was even attempted. It now says
// WHICH engine, so a partner who set ?provider=maplibre and sees "MapLibre did not load" knows their
// request was honoured and the script is the problem, rather than suspecting a silent downgrade.
// A console.warn too, because the on-screen notice is for the reader and this line is for whoever is
// debugging the embed.
console.warn(`[upg-100-list] ${upgProvider} engine global is missing — refusing to substitute another `
+ 'engine. A page that asked for the keyless engine must not silently receive the token-gated one.')
status.value = `⚠ ${t('messages.engineNotLoaded')}`
mapError.value = t('messages.engineNotLoadedDetail', { engine: isMaplibre ? 'MapLibre' : 'Mapbox' })
enrichLegendFromRecords() enrichLegendFromRecords()
return return
} }

File diff suppressed because one or more lines are too long