Follow @map/ imports when deciding a profile owns a map
The map-ness scan follows a profile's own imports, which was the fix for a map-less list inheriting map-ness from its bundle. It follows RELATIVE imports only, so a component that moved into library/ became invisible: the profile reaches it through @map/..., the alias resolves nowhere in the walk, and the profile reports no map. MEASURED on the seven curated tabs, alias imports resolved and re-tested: exactly one was wrong. regions-dashboard/regions-mfe draws a real map through @map/components/maps/RegionCountryMap.vue, which does match the engine test -- it imports useMapInstance and useMapProvider -- while region-dashboards.vue holds no engine token of its own. The other five map tabs carry an engine token in their own files too, which is why only this one showed the symptom. It is not a recent break: regions-mfe is isMap=false in all eight manifest revisions in history. The consequence was a blind gate. check-mapbox-drew takes its targets from this flag, so it measured six of the seven tabs and silently omitted the one with an open map bug. Map leaves go 6 -> 7. country-map also flips to true, correctly: it renders the same component. A stricter test applies once the walk crosses into library/. The engine pattern matches the bare string maplibre-gl, which in a shared library is mostly prose and CSS class names -- 12+ library files match it while using no engine. The one that bit: wall-of-people-groups, a D3 wall with no map, imports src/wallD3.js, which imports library/constants/cdn.js, a pinned URL and SRI table that only NAMES the engine CDNs. With the loose test that wall flipped to isMap=true and would have baked ~400 KB it never uses, which is the regression the per-profile scan exists to prevent. Inside library/ a file now counts only if it imports the package, calls useMapInstance/useMapProvider, or touches a map global. Plugin files keep the original test, so no in-bundle result moves. Probe in _qa-cycle: nine cases including two reverse poisons -- regions-list and wall-of-people-groups must stay false. It reads the shipping functions out of this file rather than reimplementing them, and it fails on the code as it stood before this change. gates:pre 65/66. The one red is check:poster-action-print, which wants gospel's GiTree fix that is not on this branch yet.
This commit is contained in:
parent
d7de2a2e00
commit
38f027bac4
3 changed files with 73 additions and 16 deletions
|
|
@ -298,6 +298,53 @@ function ownsAMapDeeply(profileDir) {
|
|||
* An explicit boolean `isMap` in <profile>.meta.json wins over the scan.
|
||||
*/
|
||||
const RELATIVE_IMPORT_RE = /(?:import[^'"`;]*?from\s*|import\s*\(\s*|import\s+)['"`](\.{1,2}\/[^'"`]+)['"`]/g
|
||||
/**
|
||||
* ★ AND THE @map/ ALIAS TOO (BUG-ALIAS-IMPORT-HIDES-A-MAP, measured 2026-09-29).
|
||||
*
|
||||
* Following only RELATIVE imports is what made the regions-list fix above correct, and it is also
|
||||
* what made a map INVISIBLE once the component holding it moved into library/. MEASURED: the seven
|
||||
* curated production tabs, alias imports resolved and re-tested — exactly ONE is misclassified.
|
||||
* regions-dashboard/regions-mfe renders a real map through
|
||||
* @map/components/maps/RegionCountryMap.vue — that file matches MAP_ENGINE_RE (it imports
|
||||
* useMapInstance and useMapProvider), but region-dashboards.vue holds no engine token of its own,
|
||||
* so the profile resolved isMap=FALSE. The other five map tabs happen to carry an engine token in
|
||||
* their own files as well, which is why only this one shows the symptom.
|
||||
*
|
||||
* THE MEASURED CONSEQUENCE. check-mapbox-drew takes its targets from this flag, so it was silently
|
||||
* measuring 6 of the 7 tabs — omitting the one tab with an open map bug. A gate that cannot see a
|
||||
* page is worse than no gate, because its PASS reads as coverage.
|
||||
*
|
||||
* WHY THIS DOES NOT REOPEN THE regions-list FALSE POSITIVE. That bug came from asking "is there a
|
||||
* map anywhere under this BUNDLE", which let a map-less sibling inherit map-ness. This still walks
|
||||
* the PROFILE'S OWN import graph and only adds one more edge to it: an alias import is a real
|
||||
* import, exactly as a relative one is. regions-list imports no map component and still resolves
|
||||
* false — asserted in the self-test below, because that is the regression worth pinning.
|
||||
*
|
||||
* BOUNDED THE SAME WAY. Only the @map/ prefix (there is one alias and it is fixed, so no bundler
|
||||
* resolution is needed), only into library/, only .vue/.js/.mjs. An @map/ import that escapes
|
||||
* library/ via .. is refused by the same startsWith check the relative arm uses.
|
||||
*
|
||||
* ★ AND A STRICTER TEST ONCE INSIDE library/, BECAUSE MAP_ENGINE_RE IS TOO LOOSE THERE. It matches
|
||||
* the BARE STRING 'maplibre-gl', which in a shared library is mostly prose and CSS: comments, and
|
||||
* selectors like .maplibregl-ctrl-bottom-left. MEASURED while building this: 12+ library files match
|
||||
* it without using an engine at all. The one that bit: wall-of-people-groups (a D3 wall, no map)
|
||||
* imports src/wallD3.js, which imports library/constants/cdn.js — a pinned URL + SRI table that
|
||||
* merely NAMES the engine CDNs. Following that edge with the loose test flipped a map-less leaf to
|
||||
* isMap=true, which is the exact 400 KB regression the per-profile fix above was written to stop.
|
||||
* So inside library/ a file counts only if it really DEPENDS on an engine: it imports the package,
|
||||
* or calls useMapInstance()/useMapProvider, or touches a global map object. Plugin files keep the
|
||||
* original loose test, so no existing in-bundle classification moves.
|
||||
*/
|
||||
const Q = '[\'"\\x60]' // a quote: ' " or a backtick, written by code point so this line stays backtick-free
|
||||
const LIBRARY_ENGINE_RE = new RegExp(
|
||||
`from\\s*${Q}(?:maplibre-gl|mapbox-gl)${Q}` + // import … from 'maplibre-gl'
|
||||
`|import\\s*\\(\\s*${Q}(?:maplibre-gl|mapbox-gl)${Q}` + // await import('mapbox-gl')
|
||||
'|useMapInstance\\s*\\(' + // the composable, actually called
|
||||
`|from\\s*${Q}[^'"\\x60]*useMapProvider` + // … from '@map/composables/useMapProvider.js'
|
||||
'|window\\.(?:mapboxgl|maplibregl)' +
|
||||
'|new\\s+(?:mapboxgl|maplibregl)\\.Map')
|
||||
const ALIAS_IMPORT_RE = /(?:import[^'"`;]*?from\s*|import\s*\(\s*|import\s+)['"`](@map\/[^'"`]+)['"`]/g
|
||||
const ALIAS_ROOT = resolve(__dirname, '..', 'library') // vite.config.js:180 — '@map' is exactly this
|
||||
function profileReachesAMap(dir, file) {
|
||||
const seen = new Set()
|
||||
const stack = [join(dir, file)]
|
||||
|
|
@ -307,14 +354,16 @@ function profileReachesAMap(dir, file) {
|
|||
seen.add(f)
|
||||
let src
|
||||
try { src = readFileSync(f, 'utf8') } catch { continue }
|
||||
if (MAP_ENGINE_RE.test(src)) return true
|
||||
for (const m of src.matchAll(RELATIVE_IMPORT_RE)) {
|
||||
const base = resolve(dirname(f), m[1])
|
||||
if (!base.startsWith(dir)) continue // stay inside this bundle
|
||||
// A library file has to really depend on an engine; a plugin file keeps the original test.
|
||||
if ((f.startsWith(ALIAS_ROOT) ? LIBRARY_ENGINE_RE : MAP_ENGINE_RE).test(src)) return true
|
||||
const push = (base, root) => {
|
||||
if (!base.startsWith(root)) return // stay inside the tree we are allowed to walk
|
||||
for (const cand of [base, base + '.vue', base + '.js', base + '.mjs', join(base, 'index.js')]) {
|
||||
if (NESTED_SCAN_EXT.test(cand) && existsSync(cand) && statSync(cand).isFile()) { stack.push(cand); break }
|
||||
}
|
||||
}
|
||||
for (const m of src.matchAll(RELATIVE_IMPORT_RE)) push(resolve(dirname(f), m[1]), dir)
|
||||
for (const m of src.matchAll(ALIAS_IMPORT_RE)) push(resolve(ALIAS_ROOT, m[1].slice('@map/'.length)), ALIAS_ROOT)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,10 +11,14 @@
|
|||
<script>window.MAP_OWNS_PAGE = true;</script>
|
||||
<title>country-map · country-map</title>
|
||||
<link rel="icon" href="data:,">
|
||||
<!-- NON-MAP leaf (app-shell / composite / grid / card / chart-table): NO map peer.
|
||||
This element draws no map tiles, so it needs no Mapbox/MapLibre engine and MUST NOT
|
||||
gate on a token. Any nested MAP (in a composite/shell) renders in its own iframe leaf,
|
||||
which loads its own map peer + token. A peer here would be dead weight + imply a gate. -->
|
||||
<!-- NO STATIC ENGINE TAGS (2026-09-25). This leaf used to hard-link mapbox-gl.css, the
|
||||
geocoder css, mapbox-gl.js and the geocoder js from Mapbox's CDN, chosen at BUILD time —
|
||||
so a partner embedding it with ?provider=maplibre still sent every visitor's IP to Mapbox
|
||||
(4 requests per leaf, measured). The provider is chosen at RUNTIME (profile-config.provider
|
||||
→ ?provider= → window.MAP_PROVIDER → auto), and the element's own runtime loaders load
|
||||
exactly that engine: ensureEngineScript / ensureEngineCSS (useMapInstance) and the
|
||||
geocoder's on-demand loader — the path composite leaves already run on. Gate:
|
||||
check:leaf-engine-per-provider (0 Mapbox requests under maplibre; Mapbox unchanged). -->
|
||||
<style>
|
||||
/* BARE full-page map — no chrome. The custom element IS the page, so the
|
||||
browser URL shares the map itself. */
|
||||
|
|
@ -57,7 +61,7 @@
|
|||
</head>
|
||||
<body>
|
||||
<div id="slot"></div>
|
||||
<div id="status">loading…</div>
|
||||
<div id="status">loading token…</div>
|
||||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <doxa-country-map> custom element. -->
|
||||
|
|
@ -81,7 +85,7 @@
|
|||
// 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.
|
||||
// (A leaf whose engine is MapLibre at RUNTIME joins them — see PROVIDER below.)
|
||||
var TOKEN_FREE = true;
|
||||
var TOKEN_FREE = false;
|
||||
// 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.
|
||||
|
|
|
|||
|
|
@ -11,10 +11,14 @@
|
|||
<script>window.MAP_OWNS_PAGE = true;</script>
|
||||
<title>region-dashboards · regions-dashboard</title>
|
||||
<link rel="icon" href="data:,">
|
||||
<!-- NON-MAP leaf (app-shell / composite / grid / card / chart-table): NO map peer.
|
||||
This element draws no map tiles, so it needs no Mapbox/MapLibre engine and MUST NOT
|
||||
gate on a token. Any nested MAP (in a composite/shell) renders in its own iframe leaf,
|
||||
which loads its own map peer + token. A peer here would be dead weight + imply a gate. -->
|
||||
<!-- NO STATIC ENGINE TAGS (2026-09-25). This leaf used to hard-link mapbox-gl.css, the
|
||||
geocoder css, mapbox-gl.js and the geocoder js from Mapbox's CDN, chosen at BUILD time —
|
||||
so a partner embedding it with ?provider=maplibre still sent every visitor's IP to Mapbox
|
||||
(4 requests per leaf, measured). The provider is chosen at RUNTIME (profile-config.provider
|
||||
→ ?provider= → window.MAP_PROVIDER → auto), and the element's own runtime loaders load
|
||||
exactly that engine: ensureEngineScript / ensureEngineCSS (useMapInstance) and the
|
||||
geocoder's on-demand loader — the path composite leaves already run on. Gate:
|
||||
check:leaf-engine-per-provider (0 Mapbox requests under maplibre; Mapbox unchanged). -->
|
||||
<style>
|
||||
/* BARE full-page map — no chrome. The custom element IS the page, so the
|
||||
browser URL shares the map itself. */
|
||||
|
|
@ -57,7 +61,7 @@
|
|||
</head>
|
||||
<body>
|
||||
<div id="slot"></div>
|
||||
<div id="status">loading…</div>
|
||||
<div id="status">loading token…</div>
|
||||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <regions-dashboard> custom element. -->
|
||||
|
|
@ -81,7 +85,7 @@
|
|||
// 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.
|
||||
// (A leaf whose engine is MapLibre at RUNTIME joins them — see PROVIDER below.)
|
||||
var TOKEN_FREE = true;
|
||||
var TOKEN_FREE = false;
|
||||
// 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.
|
||||
|
|
|
|||
Loading…
Reference in a new issue