diff --git a/internal/check-leaf-hints.mjs b/internal/check-leaf-hints.mjs
index 8a9085e51..7a49e9656 100644
--- a/internal/check-leaf-hints.mjs
+++ b/internal/check-leaf-hints.mjs
@@ -36,13 +36,37 @@ const ENGINE_ORIGINS = [ORIGIN(CDN.mapboxGlJs.url), ORIGIN(CDN.maplibreGlJs.url)
const STYLE_ORIGINS = ['https://tiles.openfreemap.org', ORIGIN(CDN.mapboxGlJs.url)]
const MAP_ORIGINS = new Set([...ENGINE_ORIGINS, ...STYLE_ORIGINS].filter(Boolean))
-// Markers generate-tree writes into a leaf's
(mapPageHtml peerBlock):
-const NON_MAP = 'NON-MAP leaf' // grid/card/chart-table/app-shell/composite → no peer
-const MAPLIBRE_PEER = 'MapLibre peer' // provider:'maplibre' static engine
-const AUTO_NOTE = 'NO STATIC ENGINE TAGS' // the 'auto' leaf: engine chosen at runtime
const HINTS = 'A9 hints'
+const ENGINE_RE = /useMapInstance|useMapProvider|mapbox-gl|maplibre-gl|window\.(?:mapboxgl|maplibregl)/
-// Every leaf index.html under the tree (a profile or instance page).
+// The rule the generator applies (mapPageHtml → drawsMapInPage): a leaf gets A9 hints iff its OWN page
+// loads a map engine — a real map leaf (`isMap`), OR a cap-4 `nested-elements-mfe` composite whose bundle
+// mounts a map inline (`bundleLoadsEngine`) — and it is not a cap-5 `nested-mfe-spa` shell (its maps are
+// iframe children). A plain non-map leaf that merely SHARES a bundle with a map leaf gets none. The gate
+// reads the same authoritative facts: profile `type`/`isMap` from the manifest, and the built bundle. So
+// the gate and the generator cannot drift.
+const manifest = JSON.parse(readFileSync(join(TREE, 'manifest.json'), 'utf8'))
+const engineBundles = new Map()
+function bundleLoadsEngine(bundle) {
+ if (!engineBundles.has(bundle)) {
+ let hit = false
+ try { hit = ENGINE_RE.test(readFileSync(join(TREE, bundle, `${bundle}.js`), 'utf8')) } catch { hit = false }
+ engineBundles.set(bundle, hit)
+ }
+ return engineBundles.get(bundle)
+}
+// Expected hint state per profile, and the leaf dirs it renders to (profile page + any instance pages).
+const expect = new Map() // "/" -> boolean shouldHint
+for (const b of manifest.bundles || []) {
+ for (const p of b.profiles || []) {
+ const shell = p.type === 'nested-mfe-spa'
+ const composite = p.type === 'nested-elements-mfe'
+ const shouldHint = (!!p.isMap || (composite && bundleLoadsEngine(b.bundle))) && !shell
+ expect.set(`${b.bundle}/${p.profile}`, shouldHint)
+ }
+}
+
+// Every leaf index.html under the tree, mapped back to its "/".
function leaves(dir, out = []) {
for (const e of readdirSync(dir)) {
const p = join(dir, e)
@@ -51,32 +75,36 @@ function leaves(dir, out = []) {
}
return out
}
+// rel is "/[/]"; the profile key is the first two segments.
+const keyOf = (rel) => rel.split('/').slice(0, 2).join('/')
const fails = []
-let maps = 0
-let nonMaps = 0
+let hinted = 0
+let bare = 0
+let unclassified = 0
for (const file of leaves(TREE)) {
const html = readFileSync(file, 'utf8')
const rel = file.slice(TREE.length + 1)
- const isMapLeaf = html.includes(MAPLIBRE_PEER) || html.includes(AUTO_NOTE)
- const isNonMap = html.includes(NON_MAP)
- // Engine/style-origin preconnects and engine preloads present in this page.
+ const key = keyOf(rel)
+ if (!expect.has(key)) { unclassified += 1; continue } // a non-profile page (e.g. tree root) — not our subject
+ const shouldHint = expect.get(key)
+ const hasHints = html.includes(HINTS)
const preconnects = [...html.matchAll(/ m[1])
const enginePreload = / MAP_ORIGINS.has(o)) || enginePreload
- if (isMapLeaf) {
- maps += 1
- if (!html.includes(HINTS)) fails.push(`${rel}: MAP leaf with no A9 hints (expected preconnect + preload)`)
- // Every preconnect on a map leaf must name an origin the code uses (no stale host).
+ if (shouldHint) {
+ if (!hasHints) fails.push(`${rel}: loads a map engine in its own page but has NO A9 hints`)
+ else hinted += 1
for (const o of preconnects) {
if (!MAP_ORIGINS.has(o) && !/^https:\/\/[a-z0-9.-]+$/i.test(o)) fails.push(`${rel}: malformed preconnect origin ${o}`)
}
- } else if (isNonMap) {
- nonMaps += 1
- if (mapHint) fails.push(`${rel}: NON-MAP leaf carries a map-engine hint (a non-map leaf loads no peer)`)
+ } else {
+ if (mapHint || hasHints) fails.push(`${rel}: does not load a map engine in its own page, yet carries a map-engine hint`)
+ else bare += 1
}
}
+const maps = hinted, nonMaps = bare
if (maps === 0) fails.push('no map leaves found in the tree: the classifier or the tree is wrong')
diff --git a/internal/generate-tree.mjs b/internal/generate-tree.mjs
index dea0c586d..13ed3c5f3 100644
--- a/internal/generate-tree.mjs
+++ b/internal/generate-tree.mjs
@@ -881,8 +881,17 @@ function resourceHints({ isMapLibre, config, apiBaseUrl, tokenUrlExpr, bakedToke
return lines.length ? ` \n${lines.join('\n')}\n` : ''
}
+// Does a bundle's BUILT .js reference a map engine? The directly verifiable signal for A9 leaf hints
+// (below), read from public///.js with the same regex the source classifier uses.
+// This catches a composite whose own bundle mounts a map inline even when its profile is isMap:false;
+// it does not catch one whose engine loads only in a lazily-fetched child bundle (see the hint comment).
+function builtBundleLoadsEngine(bundleName) {
+ try { return MAP_ENGINE_RE.test(readFileSync(join(DEFAULT_ROOT, bundleName, `${bundleName}.js`), 'utf8')) }
+ catch { return false }
+}
+
function mapPageHtml(opts) {
- const { title, tag, config, jsSrc, apiBaseUrl, tokenUrlExpr, bakedToken, integrity, shellTokenFree, isMap, scrollPage, isComposite } = opts
+ const { title, tag, config, jsSrc, apiBaseUrl, tokenUrlExpr, bakedToken, integrity, shellTokenFree, isMap, scrollPage, isComposite, bundleLoadsEngine } = opts
const apiScript = apiBaseUrl
? `\n `
: ''
@@ -924,7 +933,24 @@ function mapPageHtml(opts) {
// hardcoded: preconnect the token-implied engine + the style host + the data origin, and — only
// where the leaf emits no engine
regions-mfe · regions-dashboard
+
+
+
+
+