doxa-maps/internal/check-research-gallery-embed.mjs
doxa-maps-88655c70 25e3edec8f
Some checks are pending
build-gate / build (push) Waiting to run
upg-100: no mapbox stylesheet under maplibre, and assert the preview exists
Two things, both found by other agents' reports rather than by my own gates.

rega found upg-100-list.vue:1464 injecting mapbox-gl.css v3.3.0 into the shadow
root unconditionally, so a keyless maplibre embed still pulled a stylesheet from
api.mapbox.com. Token-free and harmless to render, but wrong-engine leakage on a
page whose point is having no Mapbox dependency. Gated on isMaplibre, which
already existed at line 119.

This commit is ATOMIC by design: the fix, the rebuilt bundle, and the deletion
of rega's MAPBOX_KNOWN exemption land together. Either half alone leaves a
window — delete first and main red-gates on its own known bug; fix first and a
stale exemption sits there hiding the next one.

Proven before deleting, because the exemption is keyed on a URL STRING while the
defect is a network REQUEST: the built bundle still contains the v3.3.0 literal
(the constant remains; the guard stops the fetch), so a source-grep gate would
have been misled. rega's gate measures requests, and it reported
"provider=maplibre made 0 request(s) to api.mapbox.com" in BOTH js and iframe
modes with no KNOWN line printed at all — i.e. the exemption was already dead
code. Re-proven after deletion: still 0 requests, still PASS.

The leaf-head preloads are explicitly NOT covered and the reason is recorded in
the gate: mapbox-gl.css v3.24.0 and mapbox-gl-geocoder v5.0.0 are a different
version and a different owner (generate-tree, bundler-1's lane), already tracked
separately.

Second: the Driver's report had two halves — "it is supposed to show the
previews" and the print options overlapping. I fixed and gated the overlap and
left the first unasserted. The over-preview check was written as `if (img)`,
which means a card with NO preview satisfies it perfectly: nothing can overlap
an image that is not there.

The gate now counts previews. Mutation-tested by aborting every previews/**
request: the product degrades well — all 27 cards swap to a "poster not
generated yet" placeholder with no broken-image icons and no <img> at all, which
is precisely why an image-health check cannot see it. So the fallback itself is
asserted, and all-cards-placeholder now fails.

A first version of that assertion was wrong and the reason is recorded in the
code: it called 18 lazy-loaded previews broken. Every img carries loading=lazy,
the first unpainted one sat 3926px down an 844px viewport, and no HTTP response
failed. Deferred is not broken; it now judges only in-viewport images.
2026-09-24 20:28:45 +00:00

182 lines
12 KiB
JavaScript

#!/usr/bin/env node
/**
* check-research-gallery-embed.mjs — the cap-5 Research Gallery (<doxa-maps-spa>) must embed on
* SOMEONE ELSE'S page, in both embed modes, with every nested map painting.
*
* WHAT THIS PROTECTS. The gallery is going onto the DX-life research page. Every other gate loads
* the gallery on its OWN generated leaf page, which bakes the three things a nested map needs —
* window.MAP_APP_API_URL, window.MAP_TOKEN and a correct assets path — and pins the tag
* position:fixed. A foreign page supplies none of that, so those gates cannot see the embed failing.
* Measured 2026-09-24 before the fix, from a second origin: the gallery drew OVER the host's header
* (the element had no :host box, so the layout's position:absolute root anchored to the viewport),
* and 8 of 13 tabs showed an error card because every data call hit the host's origin.
*
* HOW. Serves the built tree with internal/serve-strict.mjs (production parity: strict directories,
* ACAO:*) on 127.0.0.1, and a stand-in host page on `localhost` — a DIFFERENT ORIGIN, so CORS is
* genuinely exercised. The host page has its own header and footer around the embed. Then, per mode:
* js — `<script src=…/doxa-maps-spa.js>` + a bare `<doxa-maps-spa>` tag, NO attributes: the
* zero-config embed the docs promise.
* iframe — an <iframe> at the gallery's leaf page.
* and for every tab in the rail: select it, wait for it to mount, then assert.
*
* ASSERTIONS (a FAIL names the tab and the reason):
* 1. js mode: the layout root sits INSIDE the element's box (not at the viewport origin).
* 2. every tab reaches state 'ready' (never 'error', never stuck 'idle'/'loading').
* 3. every CURATED tab paints a WebGL canvas — they are all maps or map composites.
* Experimental tabs need only mount: some are cards or legacy frames with no canvas.
* 4. zero failed requests to the TREE's own origin (a 404/CORS on our assets is our bug; third-
* party tile/glyph hiccups are reported, not failed — they are not this repo's to fix).
*
* PRECONDITION: a built tree in public/doxa-maps (this starts its own servers; no dev server).
* Usage: node internal/check-research-gallery-embed.mjs [--mode js|iframe] [--only id,id] [--provider maplibre]
* Exit: 0 pass · 1 fail · 2 SKIP (no built tree / no browser)
*/
import { createServer } from 'node:http'
import { spawn } from 'node:child_process'
import { existsSync } from 'node:fs'
import { resolve, dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..')
const argv = process.argv.slice(2)
const argOf = (k, d) => { const i = argv.indexOf(k); return i >= 0 && argv[i + 1] ? argv[i + 1] : d }
const MODES = argOf('--mode', '') ? [argOf('--mode', '')] : ['js', 'iframe']
const ONLY = argOf('--only', '') ? argOf('--only', '').split(',') : null
// --provider maplibre: the KEYLESS embed. The host asks for maplibre (attribute in js mode, ?provider=
// in iframe mode) and every tab must still paint — with ZERO requests to api.mapbox.com. That is the
// promise a keyless partner embed depends on, and the shell once broke it by not forwarding the engine.
const PROVIDER = argOf('--provider', '')
// KNOWN, ROUTED exemptions from the zero-Mapbox rule — each names its owner. Printed on every run so
// it cannot become invisible; DELETE the entry when the owner lands the fix (the gate then enforces it).
const MAPBOX_KNOWN = {
// EMPTY ON PURPOSE. The one entry here — my-upg-100-list/upg-100-list.vue:1464 pinning
// mapbox-gl.css v3.3.0 regardless of engine — was fixed and the exemption deleted in the SAME
// COMMIT as the fix, so main never had a window where the gate red-flagged its own known bug and
// never had a window where a stale exemption hid a live one. Proven before deletion: this gate
// reported "provider=maplibre made 0 request(s) to api.mapbox.com" in BOTH js and iframe modes,
// with no KNOWN line printed at all.
//
// ⚠ NOT COVERED BY THIS MAP, and deliberately so: the generated leaf HEAD still preloads
// mapbox-gl.css v3.24.0 and mapbox-gl-geocoder v5.0.0. Different versions, different owner —
// that is generate-tree (bundler-1's lane), tracked as
// BUG-LEAF-PAGES-PRELOAD-MAPBOX-EVEN-UNDER-MAPLIBRE. This gate embeds the GALLERY, which does not
// load those leaves, which is why it reads zero today.
}
// ★ FREE PORTS, ASKED OF THE OS — never fixed ones. A stray server left on a fixed port by an
// earlier crashed run answered in place of the tree under test, and this gate PASSED a known-broken
// tree (caught by its own negative control, 2026-09-24): spawn failed with EADDRINUSE silently and
// the old server served the fixed build. A port the OS just handed out cannot be someone else's.
const freePort = () => new Promise(ok => { const s = createServer().listen(0, '127.0.0.1', () => { const p = s.address().port; s.close(() => ok(p)) }) })
const TREE_PORT = await freePort()
const HOST_PORT = await freePort()
const TREE = `http://127.0.0.1:${TREE_PORT}/doxa-maps`
if (!existsSync(resolve(ROOT, 'public/doxa-maps/doxa-maps-spa/doxa-maps-spa.js'))) {
console.log('SKIP check-research-gallery-embed: no built gallery at public/doxa-maps/doxa-maps-spa/')
process.exit(2)
}
let chromium
try { ({ chromium } = await import('playwright')) } catch {
console.log('SKIP check-research-gallery-embed: playwright not installed'); process.exit(2)
}
let mode = 'js'
const hostPage = () => {
const embed = mode === 'iframe'
? `<iframe src="${TREE}/doxa-maps-spa/doxa-maps-spa/index.html${PROVIDER ? '?provider=' + PROVIDER : ''}" style="width:100%;height:760px;border:0"></iframe>`
: `<script src="${TREE}/doxa-maps-spa/doxa-maps-spa.js"></script><doxa-maps-spa${PROVIDER ? ` provider="${PROVIDER}"` : ''}></doxa-maps-spa>`
return `<!doctype html><html><head><meta charset=utf-8><title>host</title>
<style>body{margin:0} header,footer{height:60px;background:#eee}</style></head>
<body><header id=hdr>HOST HEADER</header><main style="max-width:1200px;margin:0 auto">${embed}</main><footer>HOST FOOTER</footer></body></html>`
}
const tree = spawn(process.execPath, ['internal/serve-strict.mjs', '--port', String(TREE_PORT)], { cwd: ROOT, stdio: 'ignore' })
const host = createServer((_, r) => { r.writeHead(200, { 'content-type': 'text/html' }); r.end(hostPage()) }).listen(HOST_PORT, 'localhost')
const cleanup = () => { try { tree.kill() } catch {} try { host.close() } catch {} }
await new Promise(r => setTimeout(r, 800))
const browser = await chromium.launch({ args: ['--enable-unsafe-swiftshader', '--use-gl=angle', '--use-angle=swiftshader'] })
const fails = []
const checked = {} // mode -> tabs actually driven; printed, and 0 is a FAIL (see the summary)
try {
for (mode of MODES) {
const page = await browser.newPage({ viewport: { width: 1400, height: 900 } })
const own = new Set(), foreign = new Set(), mapbox = new Set()
let current = '(page load)'
const mapboxBy = {} // tab id (the one open when the request fired) -> distinct mapbox URLs
page.on('request', q => {
if (!/^https:\/\/api\.mapbox\.com\//.test(q.url())) return
const u = q.url().split('?')[0]
mapbox.add(u); (mapboxBy[current] = mapboxBy[current] || new Set()).add(u)
})
const note = (url, why) => (url.startsWith(`http://127.0.0.1:${TREE_PORT}/`) ? own : foreign).add(why + ' ' + url.split('?')[0])
page.on('response', r => { if (r.status() >= 400) note(r.url(), String(r.status())) })
page.on('requestfailed', q => { const e = (q.failure() && q.failure().errorText) || ''; if (!/ABORTED/.test(e)) note(q.url(), e) })
await page.goto(`http://localhost:${HOST_PORT}/`)
const frame = mode === 'iframe' ? await (await page.waitForSelector('iframe')).contentFrame() : page.mainFrame()
await frame.waitForFunction(() => { const s = document.querySelector('doxa-maps-spa'); return s && s._instance && s._instance.proxy.tabs.some(t => t.resolved) }, null, { timeout: 30000 })
if (mode === 'js') {
const g = await page.evaluate(() => {
const s = document.querySelector('doxa-maps-spa'), v = s.shadowRoot.querySelector('.vtl')
const a = s.getBoundingClientRect(), b = v.getBoundingClientRect()
return { host: [a.top, a.left, a.width, a.height].map(Math.round), vtl: [b.top, b.left, b.width, b.height].map(Math.round) }
})
const inside = g.vtl[0] >= g.host[0] - 1 && g.vtl[1] >= g.host[1] - 1 && g.vtl[2] <= g.host[2] + 1 && g.vtl[3] <= g.host[3] + 1 && g.host[3] >= 400
console.log(`${inside ? 'PASS' : 'FAIL'} [js] layout inside the host box — host ${g.host} layout ${g.vtl}`)
if (!inside) fails.push(`[js] layout escaped the <doxa-maps-spa> box (host ${g.host}, layout ${g.vtl})`)
}
const tabs = await frame.evaluate(() => document.querySelector('doxa-maps-spa')._instance.proxy.tabs.map(t => ({ id: t.id, discovered: !!t.discovered })))
for (const { id, discovered } of tabs) {
if (ONLY && !ONLY.includes(id)) continue
current = id
await frame.evaluate(id => document.querySelector('doxa-maps-spa')._instance.proxy.onSelect(id), id)
let v
for (let k = 0; k < 50; k++) {
await page.waitForTimeout(400)
v = await frame.evaluate(id => {
const vm = document.querySelector('doxa-maps-spa')._instance.proxy
const i = vm.tabs.findIndex(x => x.id === id), t = vm.tabs[i]
const el = vm._slots && vm._slots[i] && vm._slots[i].firstElementChild
let canvas = 0
const walk = r => { for (const n of r.querySelectorAll('*')) { if (n.tagName === 'CANVAS') canvas++; if (n.shadowRoot) walk(n.shadowRoot) } }
if (el) { walk(el); if (el.shadowRoot) walk(el.shadowRoot) }
return { state: t.state, error: t.error, canvas }
}, id)
if (v.state === 'error' || (v.state === 'ready' && (v.canvas || discovered && k > 5))) break
}
const needCanvas = !discovered
const ok = v.state === 'ready' && (!needCanvas || v.canvas > 0)
const why = v.state !== 'ready' ? `state ${v.state}${v.error ? ' — ' + v.error : ''}` : (needCanvas && !v.canvas ? 'mounted but painted no map canvas' : '')
checked[mode] = (checked[mode] || 0) + 1
console.log(`${ok ? 'PASS' : 'FAIL'} [${mode}] ${id}${why ? ' — ' + why : ''}`)
if (!ok) fails.push(`[${mode}] ${id}: ${why}`)
}
for (const f of own) { console.log(`FAIL [${mode}] tree request failed: ${f}`); fails.push(`[${mode}] ${f}`) }
if (PROVIDER === 'maplibre') {
for (const u of [...mapbox]) if (MAPBOX_KNOWN[u]) { console.log(`KNOWN [${mode}] ${u} — owner: ${MAPBOX_KNOWN[u]}`); mapbox.delete(u); for (const k in mapboxBy) { mapboxBy[k].delete(u); if (!mapboxBy[k].size) delete mapboxBy[k] } }
const ok = mapbox.size === 0
console.log(`${ok ? 'PASS' : 'FAIL'} [${mode}] provider=maplibre made ${mapbox.size} request(s) to api.mapbox.com`)
if (!ok) {
for (const [tab, urls] of Object.entries(mapboxBy)) {
console.log(` while ${tab} was open: ${urls.size} — ${[...urls].slice(0, 3).join(' , ')}`)
fails.push(`[${mode}] provider=maplibre: ${tab} hit api.mapbox.com (${urls.size} distinct URL(s))`)
}
}
}
for (const f of foreign) console.log(`note [${mode}] third-party request failed (not gated): ${f}`)
await page.close()
}
} finally { await browser.close(); cleanup() }
// ★ A MODE THAT RAN NOTHING HAS NOT PASSED. A flag lost on the way in (package.json once ended
// this entry in an inline # comment, so an appended --mode landed inside the comment) must not
// read as "both modes green": each requested mode has to have driven at least one tab.
for (const m of MODES) if (!checked[m]) fails.push(`[${m}] checked 0 tabs — this mode did not run`)
const tally = MODES.map(m => `${m} ${checked[m] || 0} tab(s)`).join(', ')
if (fails.length) {
console.log(`\nchecked: ${tally}`)
console.log(`\ncheck-research-gallery-embed: FAIL (${fails.length})`); for (const f of fails) console.log(' - ' + f)
process.exit(1)
}
console.log(`\ncheck-research-gallery-embed: PASS — the Research Gallery embeds on a foreign origin (${tally})`)