A9 leaf hints: key on the bundle's engine, not the leaf's isMap flag
rega's A9NET run found two production composite tabs — regions-dashboard/ regions-mfe and research-map-plus-grid — getting NO hints, because my first cut gated on the leaf's isMap flag and both are isMap:false composites that mount a map child and load the engine at runtime. Same trap the check:leaf-engine-per-provider isMap gate fell into. The gate now keys on whether the leaf's OWN page loads a map engine: a real map leaf (isMap), OR a cap-4 nested-elements-mfe composite whose BUILT bundle references an engine (builtBundleLoadsEngine). It excludes a cap-5 nested-mfe-spa shell (maps are iframe children), a genuinely map-less composite (big-picture, stack-demo: 0 engine refs), and a plain non-map leaf sharing a bundle with a map (connectivity-list). regions-mfe (33 engine refs) is now hinted; research-map-plus-grid is not — its own bundle has 0 refs, the engine loads in a lazily-fetched child (recorded gap; the deeper cause is the upstream isMap classifier mislabelling these composites, which composes-by-tag where the child .vue filename differs). check:leaf-hints rebuilt to the same rule, read from the manifest (profile type + isMap) and the built bundle, so gate and generator cannot drift. PASS 37 hinted / 27 bare; both new poisons bite (composite stripped of hints; non-map leaf in a map bundle given a hint).
This commit is contained in:
parent
fcebf2c589
commit
71f36b5079
3 changed files with 82 additions and 19 deletions
|
|
@ -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 <head> (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() // "<bundle>/<profile>" -> 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 "<bundle>/<profile>".
|
||||
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 "<bundle>/<profile>[/<instance>]"; 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(/<link rel="preconnect" href="([^"]+)"/g)].map((m) => m[1])
|
||||
const enginePreload = /<link rel="preload" as="script" href="([^"]+maplibre-gl[^"]*|[^"]+mapbox-gl[^"]*)"/.test(html)
|
||||
const mapHint = preconnects.some((o) => 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')
|
||||
|
||||
|
|
|
|||
|
|
@ -881,8 +881,17 @@ function resourceHints({ isMapLibre, config, apiBaseUrl, tokenUrlExpr, bakedToke
|
|||
return lines.length ? ` <!-- A9 hints: the map's origins, so DNS/TLS + the engine start with the bundle, not after it (dx-base/a9-leaf-hints; engine derived from the token ladder). -->\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/<tree>/<bundle>/<bundle>.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 <script>window.MAP_APP_API_URL = ${JSON.stringify(apiBaseUrl)};</script>`
|
||||
: ''
|
||||
|
|
@ -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 <script> today (the 'auto' leaf) — preload the engine, its URL,
|
||||
// integrity and crossorigin from library/constants/cdn.js so it cannot drift from the runtime tag.
|
||||
const hintBlock = shellOnly ? '' : resourceHints({ isMapLibre, config, apiBaseUrl, tokenUrlExpr, bakedToken })
|
||||
// WHO GETS HINTS: any page whose OWN bundle loads a map engine — the directly verifiable signal
|
||||
// (`bundleLoadsEngine`, an engine reference in the built .js), NOT the leaf's `isMap` flag. rega's A9
|
||||
// net run found regions-mfe (isMap:false, but 33 engine refs in its bundle) opening cdn.jsdelivr.net +
|
||||
// tiles.openfreemap.org from the top page and getting no hints — my first cut keyed on isMap and missed
|
||||
// it, the same trap check:leaf-engine-per-provider's isMap gate fell into. Keying on the built bundle
|
||||
// hints regions-mfe and excludes the genuinely map-less composites (big-picture, stack-demo: 0 engine
|
||||
// refs), with no false positives. A cap-5 nested-mfe-spa shell (shellTokenFree) is excluded even if it
|
||||
// somehow references an engine: its nested maps render in their OWN iframe leaves, which carry hints.
|
||||
// KNOWN GAP: a composite whose engine loads only inside a LAZILY-fetched child bundle (research-map-plus-grid
|
||||
// composes doxa-research-map; its own bundle has 0 engine refs) is not hinted — the top page's own bytes
|
||||
// don't name the engine. Recorded in the A9 report; the deeper fix is the upstream isMap classifier,
|
||||
// which mislabels these composites isMap:false (composes-by-tag where the child .vue filename differs).
|
||||
// A plain non-map leaf (a list/card) can SHARE a bundle with a map leaf (connectivity-list rides the
|
||||
// cities-internet bundle, which loads an engine for connectivity-map) — so the bundle signal alone
|
||||
// over-hints it. The engine only mounts in-page for a real map leaf (drawsMap) or a cap-4 composite
|
||||
// (isComposite) that stacks a map child; a non-map, non-composite leaf never opens the engine itself.
|
||||
const drawsMapInPage = (drawsMap || (isComposite && bundleLoadsEngine)) && !shellTokenFree
|
||||
const hintBlock = drawsMapInPage ? resourceHints({ isMapLibre, config, apiBaseUrl, tokenUrlExpr, bakedToken }) : ''
|
||||
const peerBlock = shellOnly
|
||||
? `<!-- 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
|
||||
|
|
@ -1231,6 +1257,8 @@ function profileHtml(b, profile, tokenUrlExpr, bakedToken) {
|
|||
// Whether this leaf actually draws map tiles (computed in scanBundles → isMapLeaf). A
|
||||
// non-map leaf renders token-free with no map peer; only a real map runs the token ladder.
|
||||
isMap: profile.isMap,
|
||||
// Whether this bundle's OWN built .js loads a map engine — the A9-leaf-hint signal (mapPageHtml).
|
||||
bundleLoadsEngine: builtBundleLoadsEngine(b.bundle),
|
||||
// A cap-4 composite (nested-elements-mfe) is a StackLayout SCROLL-STACK: its leaf must let the
|
||||
// DOCUMENT own the scroll (height:auto, no position:fixed) so the page grows and a reader scrolls
|
||||
// PAST one section to the next. A fixed-viewport leaf traps a 100vh dashboard and nothing scrolls
|
||||
|
|
@ -1265,6 +1293,8 @@ function instanceHtml(b, profile, inst, tokenUrlExpr, bakedToken) {
|
|||
// Instance pages inherit the profile's map-ness: a preset of a non-map leaf is still a
|
||||
// non-map (token-free, no peer); a preset of a real map still runs the token ladder.
|
||||
isMap: profile.isMap,
|
||||
// Same bundle as the profile page, so the same engine signal (A9-leaf-hint, mapPageHtml).
|
||||
bundleLoadsEngine: builtBundleLoadsEngine(b.bundle),
|
||||
// A preset of a cap-4 composite scroll-stack is still a scroll page (see profileHtml).
|
||||
scrollPage: profile.type === 'nested-elements-mfe',
|
||||
})
|
||||
|
|
|
|||
|
|
@ -11,6 +11,11 @@
|
|||
<script>window.MAP_OWNS_PAGE = true;</script>
|
||||
<title>regions-mfe · regions-dashboard</title>
|
||||
<link rel="icon" href="data:,">
|
||||
<!-- A9 hints: the map's origins, so DNS/TLS + the engine start with the bundle, not after it (dx-base/a9-leaf-hints; engine derived from the token ladder). -->
|
||||
<link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin />
|
||||
<link rel="preconnect" href="https://tiles.openfreemap.org" crossorigin />
|
||||
<link rel="preconnect" href="https://pray.doxa.life" crossorigin />
|
||||
<link rel="preload" as="script" href="https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js" integrity="sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp" crossorigin="anonymous" />
|
||||
<!-- 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,
|
||||
|
|
|
|||
Loading…
Reference in a new issue