diff --git a/documentation/readme-images/cap1-single-element.png b/documentation/readme-images/cap1-single-element.png index 70fdca5b3..4cc997683 100644 Binary files a/documentation/readme-images/cap1-single-element.png and b/documentation/readme-images/cap1-single-element.png differ diff --git a/documentation/readme-images/cap2-adoption.png b/documentation/readme-images/cap2-adoption.png index b2b2c035e..f71b092ad 100644 Binary files a/documentation/readme-images/cap2-adoption.png and b/documentation/readme-images/cap2-adoption.png differ diff --git a/documentation/readme-images/cap2-engagement.png b/documentation/readme-images/cap2-engagement.png index 43aaaeb37..abd8317a4 100644 Binary files a/documentation/readme-images/cap2-engagement.png and b/documentation/readme-images/cap2-engagement.png differ diff --git a/documentation/readme-images/cap2-prayer.png b/documentation/readme-images/cap2-prayer.png index 1a54497ec..3e68bc5b8 100644 Binary files a/documentation/readme-images/cap2-prayer.png and b/documentation/readme-images/cap2-prayer.png differ diff --git a/documentation/readme-images/cap3-monolithic-research.png b/documentation/readme-images/cap3-monolithic-research.png index c5f045b9b..b08a03de2 100644 Binary files a/documentation/readme-images/cap3-monolithic-research.png and b/documentation/readme-images/cap3-monolithic-research.png differ diff --git a/documentation/readme-images/cap4-upg100-list.png b/documentation/readme-images/cap4-upg100-list.png index 6dd93ce0a..c8b27b92c 100644 Binary files a/documentation/readme-images/cap4-upg100-list.png and b/documentation/readme-images/cap4-upg100-list.png differ diff --git a/documentation/readme-images/cap4-upg100-mfe.png b/documentation/readme-images/cap4-upg100-mfe.png index cc1d713b7..77d77fd24 100644 Binary files a/documentation/readme-images/cap4-upg100-mfe.png and b/documentation/readme-images/cap4-upg100-mfe.png differ diff --git a/documentation/readme-images/cap5-doxa-maps-spa.png b/documentation/readme-images/cap5-doxa-maps-spa.png index a6a484645..53d033308 100644 Binary files a/documentation/readme-images/cap5-doxa-maps-spa.png and b/documentation/readme-images/cap5-doxa-maps-spa.png differ diff --git a/documentation/readme-images/cap5-plugin-studio.png b/documentation/readme-images/cap5-plugin-studio.png index 3860b4b7c..7e0cde12a 100644 Binary files a/documentation/readme-images/cap5-plugin-studio.png and b/documentation/readme-images/cap5-plugin-studio.png differ diff --git a/documentation/readme-images/deploy1-iframe.png b/documentation/readme-images/deploy1-iframe.png index 7c5ce5aea..0abc9e3dd 100644 Binary files a/documentation/readme-images/deploy1-iframe.png and b/documentation/readme-images/deploy1-iframe.png differ diff --git a/documentation/readme-images/deploy2-mobile-spa.png b/documentation/readme-images/deploy2-mobile-spa.png index 16f33c5c8..8a79670f8 100644 Binary files a/documentation/readme-images/deploy2-mobile-spa.png and b/documentation/readme-images/deploy2-mobile-spa.png differ diff --git a/documentation/readme-images/deploy3-research-dashboard.png b/documentation/readme-images/deploy3-research-dashboard.png index 56bf264f3..c4a96aea8 100644 Binary files a/documentation/readme-images/deploy3-research-dashboard.png and b/documentation/readme-images/deploy3-research-dashboard.png differ diff --git a/internal/check-shot-fidelity.mjs b/internal/check-shot-fidelity.mjs index 85bd306cc..d3dda88ab 100644 --- a/internal/check-shot-fidelity.mjs +++ b/internal/check-shot-fidelity.mjs @@ -159,16 +159,57 @@ function tileDependentMapBundles() { return out } +/** + * ★ ASK THE TOKEN, DO NOT ASSERT ABOUT IT (added 2026-09-23). + * + * The two tileset findings below used to fire whenever any file referenced a `mapbox://` tileset, + * and printed a hardcoded "127.0.0.1 -> 403" as though it were a live measurement. It WAS one, on + * 2026-09-21, against the token that shipped then. The baked token has since been swapped + * (commit 1b6316a) to one that is NOT URL-restricted, and the old sentences kept printing — + * warning about a blank void while the shots came out fully painted. + * + * A gate that states a measured fact must re-measure it, or it becomes a confident lie at exactly + * the moment someone fixes the thing it describes. So: fetch one real tile with the token the + * bundles actually ship, sending no Referer, which is precisely what this shot machine does. 200 + * means the tileset findings do not apply and are suppressed; 403 means they do. + * + * Network failure is NOT treated as 403. An offline box cannot distinguish "restricted" from + * "unreachable", and reporting a restriction that may not exist is the failure being fixed here. + */ +async function tilesetReachability() { + let token = '' + try { + const cfg = JSON.parse(readFileSync(join(ROOT, 'vite.doxa-maps-build-config.json'), 'utf8')) + if (typeof cfg.token === 'string' && cfg.token.startsWith('pk.')) token = cfg.token + } catch { /* no build config — fall through to unknown */ } + if (!token) return { state: 'unknown', why: 'no pk. token baked in vite.doxa-maps-build-config.json' } + const url = 'https://api.mapbox.com/v4/mapbox.country-boundaries-v1/3/4/3.vector.pbf?access_token=' + token + try { + const r = await fetch(url, { signal: AbortSignal.timeout(12000) }) + if (r.status === 200) return { state: 'reachable', why: 'country-boundaries-v1 -> 200 with no Referer, on the baked token' } + if (r.status === 401 || r.status === 403) return { state: 'restricted', why: `country-boundaries-v1 -> ${r.status} with no Referer` } + return { state: 'unknown', why: `country-boundaries-v1 -> HTTP ${r.status}` } + } catch (e) { + return { state: 'unknown', why: `tile probe did not complete (${e?.name || 'error'}) — offline is not a restriction` } + } +} + const bundles = emojiBundles() const fonts = emojiFontCount() -const tilesetFiles = restrictedTilesetFiles() -const tileBundles = tileDependentMapBundles() +const tiles = await tilesetReachability() +const tilesetFiles = tiles.state === 'reachable' ? [] : restrictedTilesetFiles() +const tileBundles = tiles.state === 'reachable' ? [] : tileDependentMapBundles() const findings = [] // every condition reports; no early exit hides a later one console.log(`SHOT-FIDELITY: ${bundles.length} bundle(s) render emoji; emoji fonts on this box: ` + `${fonts === null ? 'UNKNOWN (no fc-list)' : fonts}`) -console.log(`SHOT-FIDELITY: ${tilesetFiles.length} file(s) source a Mapbox-hosted tileset ` + - `(mapbox://mapbox.*), which a URL-restricted token cannot fetch off its allowed origin.`) +console.log(`SHOT-FIDELITY: Mapbox tilesets are ${tiles.state.toUpperCase()} from this box — ${tiles.why}.`) +if (tiles.state === 'reachable') { + console.log(' So the tileset findings do not apply: shots taken here get a real basemap, not a blank void.') +} else { + console.log(` ${tilesetFiles.length} file(s) source a Mapbox-hosted tileset (mapbox://mapbox.*), which a`) + console.log(' URL-restricted token cannot fetch off its allowed origin.') +} if (fonts === null) { console.log(' i fc-list not available — cannot verify glyph coverage. Screenshots UNASSESSED.') @@ -181,10 +222,10 @@ if (tileBundles.length) { 'as a blank void when the token cannot fetch tilesets from this origin:', ` ${tileBundles.join(', ')}`, '', - ' Measured: the STYLE document returns 200 everywhere (so the map boots, the legend and', - ' controls paint, the data layer is correct) while mapbox-streets-v8 / bathymetry-v2 /', - ' country-boundaries-v1 all return 403 off doxa.life. A booted map showing its own UI and', - ' its own data looks like a working map — only the coastlines and country fills are gone.', + ' The STYLE document returns 200 everywhere (so the map boots, the legend and controls paint,', + ' and the data layer is correct) while the TILESETS are refused. A booted map showing its own', + ' UI and its own data looks like a working map — only the coastlines and country fills are', + ` gone. Measured this run: ${tiles.why}.`, '', ' Bundles that ship their own world-adm0.geojson are UNAFFECTED, which is why one cap-4 shot', ' looks perfect beside four blank ones. That contrast is a red herring, not a per-bundle bug.', @@ -197,8 +238,7 @@ if (tilesetFiles.length) { 'SHOT-FIDELITY: a tileset-backed map pane can shoot BLANK while the page reports OK.', ` files sourcing a mapbox:// tileset: ${tilesetFiles.join(', ')}`, '', - ' The shared pk. token is URL-restricted. Measured against country-boundaries-v1:', - ' Referer doxa.life -> 200 (3/3) · 127.0.0.1 -> 403 · no referer -> 403', + ` Measured THIS RUN against country-boundaries-v1 with the baked token: ${tiles.why}.`, ' The STYLE endpoint returns 200 from every origin, so every map BOOTS and only the TILES are', ' missing — which is why this first looked like "that surface has no map".', '', diff --git a/internal/readme-shots.mjs b/internal/readme-shots.mjs index bc59aad22..4a13e7939 100644 --- a/internal/readme-shots.mjs +++ b/internal/readme-shots.mjs @@ -45,13 +45,36 @@ const server = createServer(async (req, res) => { await new Promise(r => server.listen(0, '127.0.0.1', r)) const BASE = `http://127.0.0.1:${server.address().port}` -// Live token so maps paint fully. +// ★ SHOOT WITH THE TOKEN THE BUNDLES ACTUALLY SHIP, not one fetched over the network. +// +// This used to fetch https://doxa.life/api/maps/token unconditionally. That token belongs to the +// Mapbox account `doxalife` and is URL-restricted to doxa.life, which is why the shot harness had +// to spoof a Referer to make tiles load at all. Meanwhile the BUILT BUNDLES carry a different +// token — `gosystems`, baked in at build time from vite.doxa-maps-build-config.json — which is not +// URL-restricted. Measured 2026-09-23 with no Referer at all: mapbox-streets-v8, +// country-boundaries-v1 and bathymetry-v2 all return 200 on the baked token and 403 on the fetched +// one. So the harness was doing extra work to use the weaker of the two. +// +// Two reasons to prefer the baked token beyond it simply working. A README image is evidence about +// the SHIPPED artifact, so it should render with the credential the artifact ships with; rendering +// with a different token means the picture no longer proves what a reader downloads. And reading +// the build config is local and deterministic — the network path degrades silently, setting TK to +// '' when doxa.life is slow, after which every shot is taken token-limited and still written to +// disk. The fetch is kept only as a fallback for a tree whose build config has no baked token. let TK = '' +let TK_SRC = '' try { - const r = await fetch('https://doxa.life/api/maps/token', { signal: AbortSignal.timeout(8000) }) - TK = r.ok ? ((await r.json()).token || '') : '' + const cfg = JSON.parse(await readFile(new URL('../vite.doxa-maps-build-config.json', import.meta.url), 'utf8')) + if (typeof cfg.token === 'string' && cfg.token.startsWith('pk.')) { TK = cfg.token; TK_SRC = 'build config (the token the bundles ship)' } } catch {} -console.log(TK ? `token: acquired (${TK.length} chars) — maps will paint` : 'token: NONE — maps token-limited') +if (!TK) { + try { + const r = await fetch('https://doxa.life/api/maps/token', { signal: AbortSignal.timeout(8000) }) + TK = r.ok ? ((await r.json()).token || '') : '' + if (TK) TK_SRC = 'doxa.life/api/maps/token (fallback — needs the Referer header below)' + } catch {} +} +console.log(TK ? `token: ${TK.length} chars from ${TK_SRC}` : 'token: NONE — maps token-limited') // ★ SwiftShader — WITHOUT THESE ARGS EVERY MAP SHOT IS TAKEN WITH NO GL CONTEXT. // This script was written under the belief "this VPS has no WebGL", which was a LAUNCH-FLAG