MapLibre: reading the projection threw, so a region dashboard never framed

Driver, on MapLibre: opening a region dashboard does not zoom to the region the
way Mapbox does. This is why.

fitTheirWay read `m.getProjection().name` OUTSIDE its try. The two engines
disagree about both halves of that API:
  Mapbox    getProjection() -> { name: 'globe' }    setProjection('mercator')
  MapLibre  getProjection() -> { type: 'globe' } | undefined
                                                  setProjection({ type: 'mercator' })
MEASURED in maplibre-gl@5's own source, which we load from jsDelivr:
Map.getProjection() returns `this.stylesheet?.projection`, and the OpenFreeMap
style we hand MapLibre declares no projection key at all — its top level is
version, sources, sprite, glyphs, layers. So getProjection() is UNDEFINED there
and `.name` threw a TypeError.

Because the read sat outside the try, the throw escaped fitTheirWay AND escaped
applySubject(), of which it is the last statement. The camera was never framed:
the map stayed wherever it was instead of flying to the region.

The read is now defensive and accepts both shapes, and the WRITE is
engine-correct: Mapbox takes the bare string, MapLibre reads e?.type, so handing
it a string would set undefined rather than fail loudly.

It was wrong in a second way worth recording: on a MapLibre style that DOES set
globe, the old read returned null and silently skipped the mercator correction
that exists to stop the fit pulling back. So it both crashed on one shape and
under-corrected on another.

Proven headless per the Playwright policy, with the old expression run against
the same stubs as the control: it THROWS TypeError on the real MapLibre shape and
returns null where it should see globe, while the new one returns 'globe',
'mercator', 'globe', null, null across the five engine shapes. A check that
cannot be shown to fail proves nothing, so the check fails if the old expression
ever stops throwing.

Needs eyes: MapLibre, region dashboard — does it now fly to the region.
This commit is contained in:
Doxa Maps 2026-09-26 00:19:26 +00:00
parent 8dcd407e29
commit 0f839f1071
7 changed files with 95 additions and 74 deletions

View file

@ -506,17 +506,38 @@ function chooserZoom(m) {
* Both setProjection calls land in ONE synchronous tick, so no frame renders in mercator; the
* probe asserts the projection is still 'globe' afterwards rather than trusting that.
*/
// The two engines disagree about BOTH halves of the projection API, and this read used to assume
// Mapbox's:
// Mapbox getProjection() -> { name: 'globe' } setProjection('mercator')
// MapLibre getProjection() -> { type: 'globe' } | undefined
// setProjection({ type: 'mercator' })
// MEASURED in maplibre-gl@5's own source: Map.getProjection() returns `this.stylesheet?.projection`,
// and the OpenFreeMap style we hand MapLibre declares no `projection` key at all — its top level is
// version, sources, sprite, glyphs, layers. So getProjection() is UNDEFINED there, and reading
// `.name` off it threw a TypeError. That read sat OUTSIDE the try, so the throw escaped
// fitTheirWay, escaped applySubject() — of which it is the last statement — and the camera was
// never framed. Opening a region dashboard under MapLibre therefore left the map wherever it was
// instead of flying to the region, which is exactly what the Driver reported.
const projectionOf = (m) => {
try { const p = m.getProjection && m.getProjection(); return (p && (p.name || p.type)) || null } catch (_) { return null }
}
// Mapbox takes the bare string; MapLibre v5 takes { type }. Writing the wrong shape is not a
// no-op — maplibre reads e?.type and would set undefined.
const setProjectionOn = (m, name) => {
try { m.setProjection(isMaplibreProvider ? { type: name } : name) } catch (_) {}
}
function fitTheirWay(m, bounds) {
let cam = null
const proj = (m.getProjection && m.getProjection().name) || null
const proj = projectionOf(m)
try {
if (proj && proj !== 'mercator') m.setProjection('mercator')
if (proj && proj !== 'mercator') setProjectionOn(m, 'mercator')
cam = m.cameraForBounds(bounds, FIT_OPTS)
} catch (e) {
console.warn('[RegionCountryMap] mercator fit unavailable, falling back to fitBounds:', e && e.message)
} finally {
// Restore in `finally` so a throw inside cameraForBounds can never leave the map flat.
try { if (proj && proj !== 'mercator') m.setProjection(proj) } catch (_) {}
if (proj && proj !== 'mercator') setProjectionOn(m, proj)
}
if (cam && cam.center && typeof cam.zoom === 'number') m.easeTo({ center: cam.center, zoom: cam.zoom, duration: 600 })
else m.fitBounds(bounds, { ...FIT_OPTS, duration: 600 })

View file

@ -888,7 +888,7 @@
"bundle": "regions-dashboard",
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
"output": "dx-maps/regions-dashboard/regions-dashboard.js",
"integrity": "sha384-0VXlux60qvoDiJCNedP6W33hhrWeghLzYSBkR8zK7bblXC74i4XL14Gsw+b/Z0la",
"integrity": "sha384-lpzd66s+yTDop/akosXlKIXuiOCv3r2RMUOWBcj217m3HvxSQ2UtPHIHqx4xwVAK",
"elements": [
"regions-dashboard"
],
@ -908,7 +908,7 @@
"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": "<!-- Mapbox engine, fetched early; remove this line if you set \"provider\":\"maplibre\" -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-0VXlux60qvoDiJCNedP6W33hhrWeghLzYSBkR8zK7bblXC74i4XL14Gsw+b/Z0la\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
"script": "<!-- Mapbox engine, fetched early; remove this line if you set \"provider\":\"maplibre\" -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-lpzd66s+yTDop/akosXlKIXuiOCv3r2RMUOWBcj217m3HvxSQ2UtPHIHqx4xwVAK\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
}
},
{
@ -922,7 +922,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-0VXlux60qvoDiJCNedP6W33hhrWeghLzYSBkR8zK7bblXC74i4XL14Gsw+b/Z0la\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"regions-list\",\"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-lpzd66s+yTDop/akosXlKIXuiOCv3r2RMUOWBcj217m3HvxSQ2UtPHIHqx4xwVAK\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"regions-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
}
},
{
@ -936,7 +936,7 @@
"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": "<!-- Mapbox engine, fetched early; remove this line if you set \"provider\":\"maplibre\" -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-0VXlux60qvoDiJCNedP6W33hhrWeghLzYSBkR8zK7bblXC74i4XL14Gsw+b/Z0la\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
"script": "<!-- Mapbox engine, fetched early; remove this line if you set \"provider\":\"maplibre\" -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-lpzd66s+yTDop/akosXlKIXuiOCv3r2RMUOWBcj217m3HvxSQ2UtPHIHqx4xwVAK\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
}
}
]

View file

@ -888,7 +888,7 @@
"bundle": "regions-dashboard",
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
"output": "dx-maps/regions-dashboard/regions-dashboard.js",
"integrity": "sha384-0VXlux60qvoDiJCNedP6W33hhrWeghLzYSBkR8zK7bblXC74i4XL14Gsw+b/Z0la",
"integrity": "sha384-lpzd66s+yTDop/akosXlKIXuiOCv3r2RMUOWBcj217m3HvxSQ2UtPHIHqx4xwVAK",
"elements": [
"regions-dashboard"
],
@ -908,7 +908,7 @@
"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": "<!-- Mapbox engine, fetched early; remove this line if you set \"provider\":\"maplibre\" -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-0VXlux60qvoDiJCNedP6W33hhrWeghLzYSBkR8zK7bblXC74i4XL14Gsw+b/Z0la\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
"script": "<!-- Mapbox engine, fetched early; remove this line if you set \"provider\":\"maplibre\" -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-lpzd66s+yTDop/akosXlKIXuiOCv3r2RMUOWBcj217m3HvxSQ2UtPHIHqx4xwVAK\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
}
},
{
@ -922,7 +922,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-0VXlux60qvoDiJCNedP6W33hhrWeghLzYSBkR8zK7bblXC74i4XL14Gsw+b/Z0la\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"regions-list\",\"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-lpzd66s+yTDop/akosXlKIXuiOCv3r2RMUOWBcj217m3HvxSQ2UtPHIHqx4xwVAK\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"regions-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
}
},
{
@ -936,7 +936,7 @@
"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": "<!-- Mapbox engine, fetched early; remove this line if you set \"provider\":\"maplibre\" -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-0VXlux60qvoDiJCNedP6W33hhrWeghLzYSBkR8zK7bblXC74i4XL14Gsw+b/Z0la\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
"script": "<!-- Mapbox engine, fetched early; remove this line if you set \"provider\":\"maplibre\" -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-lpzd66s+yTDop/akosXlKIXuiOCv3r2RMUOWBcj217m3HvxSQ2UtPHIHqx4xwVAK\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
}
}
]

View file

@ -30,7 +30,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-0VXlux60qvoDiJCNedP6W33hhrWeghLzYSBkR8zK7bblXC74i4XL14Gsw+b/Z0la" crossorigin="anonymous"></script>
<script src="../regions-dashboard.js" integrity="sha384-lpzd66s+yTDop/akosXlKIXuiOCv3r2RMUOWBcj217m3HvxSQ2UtPHIHqx4xwVAK" 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

@ -26,7 +26,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-0VXlux60qvoDiJCNedP6W33hhrWeghLzYSBkR8zK7bblXC74i4XL14Gsw+b/Z0la" crossorigin="anonymous"></script>
<script src="../regions-dashboard.js" integrity="sha384-lpzd66s+yTDop/akosXlKIXuiOCv3r2RMUOWBcj217m3HvxSQ2UtPHIHqx4xwVAK" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -41,7 +41,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-0VXlux60qvoDiJCNedP6W33hhrWeghLzYSBkR8zK7bblXC74i4XL14Gsw+b/Z0la" crossorigin="anonymous"></script>
<script src="../regions-dashboard.js" integrity="sha384-lpzd66s+yTDop/akosXlKIXuiOCv3r2RMUOWBcj217m3HvxSQ2UtPHIHqx4xwVAK" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order: