Some checks are pending
build-gate / build (push) Waiting to run
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.
182 lines
12 KiB
JavaScript
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})`)
|