my-upg-100-list joins the provider switch, and drops its search bar
The last plugin on the rollout list, and the only one the switch could not
reach: it builds its map RAW (new gl.Map on window.mapboxgl) instead of
through useMapInstance, so resolving the provider inside the factory --
which fixed every other bundle for free -- did nothing here. MEASURED at
?provider=maplibre before this: still Mapbox, 19 requests to api.mapbox.com,
while five sibling leaves swapped cleanly.
Three things blocked the keyless engine, not one:
- the engine was hardcoded to window.mapboxgl. It now loads whatever the
provider selects, through ensureEngineScript/engineGlobal EXPORTED from
useMapInstance. Imported, not copied: a second ENGINES table living in a
profile is how two copies of one fact drift apart.
- a `if (!token) return` early exit. A token is a MAPBOX requirement, not
a map requirement -- left in place it would have refused to draw the
engine that needs no token, turning the keyless option into a blank
cell. Now mapbox-only.
- a hardcoded mapbox://styles/mapbox/light-v11. Now paired through
resolveNamedStyle('paper'), the registry entry that already pairs
light-v11 with OpenFreeMap positron, so both engines show one intent.
And the search bar follows the standing decision -- deferred to Mapbox,
because GeocoderComponent's maplibre path still geocodes through Mapbox, so
a keyless map with a search bar is not keyless. MapToolbar collapses its
reserved gap and the legend rises into the vacated space.
MEASURED on the built tree, both engines:
mapbox search@12 legend@58 19 mapbox requests (all unchanged)
maplibre search@NONE legend@8 5 mapbox requests maplibregl loaded
The remaining 5 are the leaf's own static preloads -- already filed, and
not something this file can reach.
Screenshot read, not just counted: positron basemap, region polygons, all
2107 pins, legend flush at the top, no gap and no collision.
22/22 bundles, 7/7 pre, 18/18 post, browser sweep of five StackLayout
consumers and all seven studio dashboards.
This commit is contained in:
parent
c36d431105
commit
4350113041
8 changed files with 89 additions and 52 deletions
|
|
@ -77,8 +77,14 @@ const OPENFREEMAP_DARK_STYLE = 'https://tiles.openfreemap.org/styles/fiord'
|
|||
import { resolveNamedStyle, applyStylePaint } from '../styles/registry.js'
|
||||
import { resolveMapProvider } from './useMapProvider.js'
|
||||
|
||||
/** The live engine global for a provider, or undefined if not yet loaded. */
|
||||
function engineGlobal(provider) {
|
||||
/**
|
||||
* The live engine global for a provider, or undefined if not yet loaded.
|
||||
* EXPORTED for the profiles that build their map RAW (new gl.Map) instead of through this
|
||||
* composable — my-upg-100-list is one. They still need the RIGHT engine for the selected provider,
|
||||
* and the alternative is a second copy of the ENGINES table living in a profile, which is how two
|
||||
* copies of one fact drift apart. Borrow the loader; do not re-declare the registry.
|
||||
*/
|
||||
export function engineGlobal(provider) {
|
||||
const name = ENGINES[provider]?.global || ENGINES.mapbox.global
|
||||
return typeof window !== 'undefined' ? window[name] : undefined
|
||||
}
|
||||
|
|
@ -88,7 +94,7 @@ function engineGlobal(provider) {
|
|||
* Idempotent — resolves immediately if the global already exists (preloaded via
|
||||
* index.html), otherwise injects the script and awaits its load event.
|
||||
*/
|
||||
async function ensureEngineScript(provider) {
|
||||
export async function ensureEngineScript(provider) {
|
||||
const cfg = ENGINES[provider] || ENGINES.mapbox
|
||||
if (engineGlobal(provider)) return // already loaded — no-op
|
||||
if (typeof document === 'undefined') return
|
||||
|
|
|
|||
|
|
@ -70,6 +70,9 @@ import ThemeToggleButton from '@map/components/map-controls/ThemeToggleButton.vu
|
|||
// `__upgKind` to route PG→detail / zone→fitBounds / territory→fitBounds. Never
|
||||
// edits src/ (zero blast radius — md5 unchanged). (research-map.vue:141,1815.)
|
||||
import GeocoderComponent from '@map/components/map-controls/GeocoderComponent.vue'
|
||||
import { resolveMapProvider } from '@map/composables/useMapProvider.js'
|
||||
import { ensureEngineScript, engineGlobal } from '@map/composables/useMapInstance.js'
|
||||
import { resolveNamedStyle } from '@map/styles/registry.js'
|
||||
// Named imports: only the list arrays ship in the bundle; the file's bookkeeping fields stay out.
|
||||
import { lists as clusterLists, asia_lists as clusterAsiaLists, uncovered_lists as clusterUncoveredLists } from './data/clusters.json'
|
||||
const clusters = { lists: clusterLists, asia_lists: clusterAsiaLists, uncovered_lists: clusterUncoveredLists }
|
||||
|
|
@ -100,6 +103,23 @@ import { buildSearchIndex, buildGeocoderFeed, matchSearch } from './data/search-
|
|||
const mapboxToken = inject('mapboxToken', ref(''))
|
||||
const profileConfig = inject('profileConfig', ref(null))
|
||||
|
||||
// ── 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
|
||||
// move: MEASURED at ?provider=maplibre it still loaded Mapbox and made 19 requests to
|
||||
// api.mapbox.com while five sibling leaves swapped cleanly.
|
||||
// The engine loader is IMPORTED from useMapInstance rather than re-declared here — a second copy of
|
||||
// the ENGINES table in a profile is how two copies of one fact drift apart.
|
||||
const upgProvider = resolveMapProvider(profileConfig)
|
||||
const isMaplibre = upgProvider === 'maplibre'
|
||||
// The geocoder is deferred to Mapbox by decision (Driver 2026-09-24): GeocoderComponent's maplibre
|
||||
// path still calls makeMapboxForwardGeocode(accessToken), so the SEARCH REQUEST goes to Mapbox
|
||||
// whichever engine draws the tiles — a keyless map with a search bar is not keyless.
|
||||
const hasGeocoder = computed(() => {
|
||||
if (profileConfig?.value?.search === false) return false
|
||||
return upgProvider === 'mapbox'
|
||||
})
|
||||
|
||||
// EDGE SCROLL AFFORDANCE — the gray accessibility side bars that tell you the page
|
||||
// scrolls PAST the map. Feature-flagged per embed, because it is a promise: a map
|
||||
// with nothing beneath it must not advertise a scroll escape that goes nowhere.
|
||||
|
|
@ -1278,24 +1298,31 @@ onMounted(async () => {
|
|||
// hidden) to explain it. Sibling poster-wall.vue survives the same early return
|
||||
// precisely because its content does not depend on state onMounted populates.
|
||||
bootstrapLegend()
|
||||
const gl = window.mapboxgl
|
||||
// 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 */ }
|
||||
const gl = engineGlobal(upgProvider) || window.mapboxgl
|
||||
const token = mapboxToken?.value || profileConfig?.value?.tk || ''
|
||||
// Degraded exits: keep the terse telemetry in `status`, put the human-readable
|
||||
// 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.
|
||||
if (!gl) {
|
||||
status.value = '⚠ mapbox-gl not loaded'
|
||||
mapError.value = 'mapbox-gl did not load. The list and legend are unaffected.'
|
||||
status.value = '⚠ map engine not loaded'
|
||||
mapError.value = 'The map engine did not load. The list and legend are unaffected.'
|
||||
enrichLegendFromRecords()
|
||||
return
|
||||
}
|
||||
if (!token) {
|
||||
status.value = '⚠ no mapbox token'
|
||||
mapError.value = 'No Mapbox token configured. The list and legend are unaffected.'
|
||||
enrichLegendFromRecords()
|
||||
return
|
||||
// ★ A TOKEN IS A MAPBOX REQUIREMENT, NOT A MAP REQUIREMENT. This gate returned early whenever
|
||||
// there was no token, which under maplibre would have refused to draw a map that needs no
|
||||
// token at all — turning the keyless option into a blank cell. MapLibre skips it entirely.
|
||||
if (!isMaplibre) {
|
||||
if (!token) {
|
||||
status.value = '⚠ no mapbox token'
|
||||
mapError.value = 'No Mapbox token configured. The list and legend are unaffected.'
|
||||
enrichLegendFromRecords()
|
||||
return
|
||||
}
|
||||
gl.accessToken = token
|
||||
}
|
||||
gl.accessToken = token
|
||||
// P0 fix (LESSON-03 L4): mapbox-gl.css must live INSIDE the shadow root —
|
||||
// the host page's <link> cannot penetrate it. Without it the geocoder's
|
||||
// .mapboxgl-ctrl-top-left container is position:static and the search pill
|
||||
|
|
@ -1316,7 +1343,11 @@ onMounted(async () => {
|
|||
}
|
||||
} catch (_) { /* CSS injection is best-effort; map still works without it */ }
|
||||
map = new gl.Map({
|
||||
container: mapEl.value, style: 'mapbox://styles/mapbox/light-v11',
|
||||
container: mapEl.value,
|
||||
// 'paper' is the registry's near-white minimal entry — mapbox light-v11 paired with
|
||||
// OpenFreeMap positron — so the two engines show the same intent instead of this file
|
||||
// hardcoding one engine's URL. Falls back to the literal if the name ever stops resolving.
|
||||
style: resolveNamedStyle('paper', upgProvider).style || 'mapbox://styles/mapbox/light-v11',
|
||||
center: [35, 20], zoom: 1.5, projection: 'mercator',
|
||||
})
|
||||
mapRef.value = map // expose the reactive instance to the controls
|
||||
|
|
@ -1755,7 +1786,7 @@ onBeforeUnmount(() => {
|
|||
`.value`; template scope must not. -->
|
||||
|
||||
<GeocoderComponent
|
||||
v-if="mapRef"
|
||||
v-if="mapRef && hasGeocoder"
|
||||
ref="geocoderRef"
|
||||
:map-instance="mapRef"
|
||||
:access-token="mapboxToken || profileConfig?.tk || ''"
|
||||
|
|
@ -1770,7 +1801,7 @@ onBeforeUnmount(() => {
|
|||
<!-- Shared control rail — same set + ordering as research-map. MapToolbar
|
||||
self-positions top-right; the status badge is bottom-right (no collision).
|
||||
Gated on mapRef so the buttons only mount once the map instance exists. -->
|
||||
<MapToolbar v-if="mapRef">
|
||||
<MapToolbar v-if="mapRef" :has-geocoder="hasGeocoder">
|
||||
<ZoomInButton :map="mapRef" :is-dark="isDark" />
|
||||
<ZoomOutButton :map="mapRef" :is-dark="isDark" />
|
||||
<LocationButton :map="mapRef" :is-dark="isDark" />
|
||||
|
|
|
|||
2
public/doxa-maps/index.html
generated
2
public/doxa-maps/index.html
generated
File diff suppressed because one or more lines are too long
8
public/doxa-maps/manifest.json
generated
8
public/doxa-maps/manifest.json
generated
|
|
@ -640,7 +640,7 @@
|
|||
"bundle": "my-upg-100-list",
|
||||
"scriptUrl": "./my-upg-100-list/my-upg-100-list.js",
|
||||
"output": "doxa-maps/my-upg-100-list/my-upg-100-list.js",
|
||||
"integrity": "sha384-wsI/kC7ernWrD4UZydHEUyyjK65h9NRs50CbZAWYAFMziSSuCdeIsTleezVVbsyV",
|
||||
"integrity": "sha384-JhtuB6o4/AnwqNK5oJ7/pf95ao5S3mr66nLgJ1tpB0n1IJDFiyxHkUK+WXUm62Z4",
|
||||
"elements": [
|
||||
"my-upg-100-list-map"
|
||||
],
|
||||
|
|
@ -660,7 +660,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-wsI/kC7ernWrD4UZydHEUyyjK65h9NRs50CbZAWYAFMziSSuCdeIsTleezVVbsyV\" 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-JhtuB6o4/AnwqNK5oJ7/pf95ao5S3mr66nLgJ1tpB0n1IJDFiyxHkUK+WXUm62Z4\" 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>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -674,7 +674,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": "<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-wsI/kC7ernWrD4UZydHEUyyjK65h9NRs50CbZAWYAFMziSSuCdeIsTleezVVbsyV\" 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": "<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-JhtuB6o4/AnwqNK5oJ7/pf95ao5S3mr66nLgJ1tpB0n1IJDFiyxHkUK+WXUm62Z4\" 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>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -688,7 +688,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": "<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-wsI/kC7ernWrD4UZydHEUyyjK65h9NRs50CbZAWYAFMziSSuCdeIsTleezVVbsyV\" 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": "<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-JhtuB6o4/AnwqNK5oJ7/pf95ao5S3mr66nLgJ1tpB0n1IJDFiyxHkUK+WXUm62Z4\" 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>"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
58
public/doxa-maps/my-upg-100-list/my-upg-100-list.js
generated
58
public/doxa-maps/my-upg-100-list/my-upg-100-list.js
generated
File diff suppressed because one or more lines are too long
|
|
@ -29,7 +29,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-wsI/kC7ernWrD4UZydHEUyyjK65h9NRs50CbZAWYAFMziSSuCdeIsTleezVVbsyV" crossorigin="anonymous"></script>
|
||||
<script src="../my-upg-100-list.js" integrity="sha384-JhtuB6o4/AnwqNK5oJ7/pf95ao5S3mr66nLgJ1tpB0n1IJDFiyxHkUK+WXUm62Z4" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -29,7 +29,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-wsI/kC7ernWrD4UZydHEUyyjK65h9NRs50CbZAWYAFMziSSuCdeIsTleezVVbsyV" crossorigin="anonymous"></script>
|
||||
<script src="../my-upg-100-list.js" integrity="sha384-JhtuB6o4/AnwqNK5oJ7/pf95ao5S3mr66nLgJ1tpB0n1IJDFiyxHkUK+WXUm62Z4" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -40,7 +40,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-wsI/kC7ernWrD4UZydHEUyyjK65h9NRs50CbZAWYAFMziSSuCdeIsTleezVVbsyV" crossorigin="anonymous"></script>
|
||||
<script src="../my-upg-100-list.js" integrity="sha384-JhtuB6o4/AnwqNK5oJ7/pf95ao5S3mr66nLgJ1tpB0n1IJDFiyxHkUK+WXUm62Z4" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
Loading…
Reference in a new issue