Print files: a stale cached copy of a pinned file is fetched once more, fresh

The print-time files are pinned (poster-render.js and its classic copy,
packet-strings.json, ne-places.json, india-states.json) and were fetched with
cache: 'force-cache', which returns any cached copy, however old. After a
re-pin, a reader who had printed before kept the previous copy: it failed its
pin on every print and the print fell back without a word. Page 1 became the
stored poster, a non-English packet would print in English, and India's state
cards would be lost.

sha384.js fetchPinned fetches force-cache first (fast, and offline once
cached) and, when the copy is not served or fails its pin, once more with
cache: 'reload', which skips and refreshes the HTTP cache; only that answer
decides. posterSheets (the generator and the places table), packetStrings and
indiaStates all fetch through it. The classic-script path refreshes the cache
the same way and retries a refused tag once.

check:pinned-fetch-fresh models the HTTP cache with stale copies: page 1 still
draws, the packet keeps every language, India keeps its 36 states; a matching
cached copy is used with one request; wrong bytes are refused. The previous
loaders fail it. Self-test poisons: no fresh refetch, never the cache, a
mismatch accepted, a loader fetching its pinned file directly.
This commit is contained in:
Doxa Maps 2026-10-02 19:05:37 +00:00
parent a1bb974651
commit ef439a5d8e
6 changed files with 218 additions and 29 deletions

View file

@ -0,0 +1,163 @@
#!/usr/bin/env bun
/**
* check-pinned-fetch-fresh.mjs — a STALE cached copy of a pinned file never decides a print.
*
* THE DEFECT (2026-10-02, on a phone). The print-time files are pinned (poster-render.js, its classic copy,
* packet-strings.json, ne-places.json, india-states.json) and fetched with cache: 'force-cache', which returns ANY
* cached copy, however old. After a re-pin, a reader who had printed before kept the previous copy: it failed its pin
* on every print and the print fell back without a word. Page 1 became the stored poster (the phone's packet), a
* non-English packet would print in English, and India's state cards would be lost. library/packet/sha384.js
* fetchPinned fetches force-cache first and, on a copy that is not served or does not match, ONCE more with cache:
* 'reload'; every pinned loader calls it.
*
* A browser's HTTP cache is modelled: force-cache answers from it when it holds the URL, reload always asks the
* server and refreshes it. The SERVER holds the current files (public/dx-maps/assets); the CACHE holds a stale copy.
* A. fetchPinned: a stale cached copy → the current text, after exactly one fresh refetch;
* B. a cached copy that matches is used as is: ONE request, force-cache (the fast, offline-friendly path stays);
* C. a server that also serves the wrong bytes → rejected, err.integrity, after one fresh refetch;
* D. a file that is not served → rejected (not an integrity failure);
* E. loadPacketStrings with a stale cached table → the full table in every language (not the English fallback);
* F. loadIndiaStates with a stale cached table → India's 36 states (not a rejection);
* G. generateSheet with a stale cached generator → a drawn sheet (not an integrity refusal);
* H. wiring: every pinned loader fetches through fetchPinned with its own pin, and none fetches its pinned file
* directly; the classic-script path refreshes the HTTP cache and retries a refused tag once.
* `--self-test`: fetchPinned without its fresh refetch, one that never uses the cache, one that accepts a
* mismatch, and a loader that fetches its pinned file directly must each FAIL.
* Two-sided by hand at the change: the previous loaders (the pilot before this) fail E, F and G.
*/
import { readFileSync, existsSync } from 'node:fs'
import { join } from 'node:path'
const REPO = new URL('..', import.meta.url).pathname
const ASSETS = join(REPO, 'public/dx-maps/assets')
const SHA_SRC = join(REPO, 'library/packet/sha384.js')
const LOADERS = {
'library/packet/posterSheets.js': ['POSTER_RENDER_SHA384', 'NE_PLACES_SHA384'],
'library/packet/packetStrings.js': ['PACKET_STRINGS_SHA384'],
'library/data/indiaStates.js': ['INDIA_STATES_SHA384'],
}
const PINNED_FILES = ['lib/poster-render.js', 'lib/packet-strings.json', 'boundaries/ne-places.json', 'people-groups/india-states.json']
const served = (p) => { const f = join(ASSETS, p); return existsSync(f) ? readFileSync(f, 'utf8') : null }
const asModule = (src) => import('data:text/javascript;base64,' + Buffer.from(src).toString('base64'))
const realFetch = globalThis.fetch
// The browser, modelled: { cached: path -> stale text } in the HTTP cache, { wrong: path -> text } served instead of
// the real file, { missing: [path] } not served at all.
function world(host, { cached = {}, wrong = {}, missing = [] } = {}) {
const http = new Map(Object.entries(cached).map(([p, t]) => [`${host}/${p}`, t]))
const requests = []
globalThis.fetch = async (input, init = {}) => {
const url = String(input)
if (!url.startsWith(host + '/')) return realFetch(input, init)
const path = url.slice(host.length + 1).split('?')[0]
const mode = init.cache || 'default'
requests.push({ path, mode })
let text = null
if (mode !== 'reload' && mode !== 'no-store' && http.has(url)) text = http.get(url)
else {
text = missing.includes(path) ? null : (path in wrong ? wrong[path] : served(path))
if (text !== null) http.set(url, text)
}
return text === null ? new Response('not found', { status: 404 }) : new Response(text, { status: 200 })
}
return { requests }
}
const stale = (p) => `/* an older build */\n${served(p)}`
async function auditFetchPinned(shaSrc) {
const fails = []
const { fetchPinned, sha384 } = await asModule(shaSrc)
if (typeof fetchPinned !== 'function') return ['A: library/packet/sha384.js exports no fetchPinned']
const P = 'lib/packet-strings.json', current = served(P), pin = await sha384(current)
// A
{ const w = world('https://a.test', { cached: { [P]: stale(P) } }); let got = null, err = null
try { got = await fetchPinned(`https://a.test/${P}`, pin) } catch (e) { err = e }
if (got !== current) fails.push(`A: a stale cached copy did not give the current file (${err ? `rejected: ${err.message}` : 'returned other bytes'})`)
else if (w.requests.length !== 2 || w.requests[0].mode !== 'force-cache' || w.requests[1].mode !== 'reload') fails.push(`A: expected force-cache then ONE reload, got ${JSON.stringify(w.requests)}`) }
// B
{ const w = world('https://b.test', { cached: { [P]: current } }); let got = null
try { got = await fetchPinned(`https://b.test/${P}`, pin) } catch { /* reported below */ }
if (got !== current || w.requests.length !== 1 || w.requests[0].mode !== 'force-cache') fails.push(`B: a matching cached copy should be used with ONE force-cache request, got ${JSON.stringify(w.requests)}`) }
// C
{ const bad = current.replace('"en"', '"en" ') + ' '
const w = world('https://c.test', { cached: { [P]: stale(P) }, wrong: { [P]: bad } }); let got = null, err = null
try { got = await fetchPinned(`https://c.test/${P}`, pin) } catch (e) { err = e }
if (got !== null || !err || err.integrity !== true) fails.push(`C: wrong bytes on the server were not refused as an integrity failure (${got !== null ? 'ACCEPTED' : err?.message})`)
else if (w.requests.length !== 2) fails.push(`C: expected exactly one fresh refetch before refusing, got ${w.requests.length} request(s)`) }
// D
{ world('https://d.test', { missing: [P] }); let err = null
try { await fetchPinned(`https://d.test/${P}`, pin) } catch (e) { err = e }
if (!err || err.integrity) fails.push(`D: an unserved file should reject as not served (${err ? err.message : 'it resolved'})`) }
globalThis.fetch = realFetch
return fails
}
async function auditLoaders() {
const fails = []
// E: the packet text
{ world('https://e.test', { cached: { 'lib/packet-strings.json': stale('lib/packet-strings.json') } })
const { loadPacketStrings } = await import(join(REPO, 'library/packet/packetStrings.js'))
const warn = console.warn; console.warn = () => {}
const t = await loadPacketStrings('https://e.test').finally(() => { console.warn = warn })
const src = JSON.parse(served('lib/packet-strings.json'))
if (Object.keys(t).join() === 'en' || t.zh?.zone_affinity_A017 !== src.zh?.zone_affinity_A017) fails.push(`E: a stale cached packet-strings.json made the packet English (got languages: ${Object.keys(t).join(',')})`) }
// F: India's states
{ world('https://f.test', { cached: { 'people-groups/india-states.json': stale('people-groups/india-states.json') } })
const { loadIndiaStates } = await import(join(REPO, 'library/data/indiaStates.js'))
let n = -1, err = null
try { n = (await loadIndiaStates('https://f.test')).states.length } catch (e) { err = e }
const want = JSON.parse(served('people-groups/india-states.json')).states.length
if (n !== want) fails.push(`F: a stale cached india-states.json lost India's states (${err ? `rejected: ${err.message}` : `${n} of ${want}`})`) }
// G: page 1's generator
{ world('https://g.test', { cached: { 'lib/poster-render.js': stale('lib/poster-render.js'), 'boundaries/ne-places.json': stale('boundaries/ne-places.json') } })
const { generateSheet, countryZone } = await import(join(REPO, 'library/packet/posterSheets.js'))
const zone = countryZone({ id: 'country-tcd', iso3: 'TCD', label: 'Chad', count: 1, region: 'Africa' })
const pins = [[12.1, 15.0], [13.8, 20.8], [9.1, 18.4]].map(([lat, lng], i) => ({ name: `Probe ${i + 1}`, lat, lng, slug: `probe-${i}`, country_code: 'TCD', population: 1000 }))
const adm0 = JSON.parse(served('boundaries/world-adm0.geojson'))
let html = '', err = null
const warn = console.warn; console.warn = () => {}
try { html = await generateSheet({ zone, pins, format: '3x3', seriesId: 'country', adm0, assetsBase: 'https://g.test', date: '2026-09-29' }) } catch (e) { err = e }
console.warn = warn
if (!(typeof html === 'string' && /<svg\b/.test(html))) fails.push(`G: a stale cached generator stopped the sheet from drawing (${err ? err.message.slice(0, 110) : 'no sheet'})`) }
globalThis.fetch = realFetch
return fails
}
function auditWiring(srcOf) {
const fails = []
for (const [file, pins] of Object.entries(LOADERS)) {
const s = srcOf(file)
if (!/import \{[^}]*\bfetchPinned\b[^}]*\} from '[^']*sha384\.js'/.test(s)) fails.push(`H: ${file} does not import fetchPinned`)
for (const pin of pins) if (!new RegExp(`fetchPinned\\([^)]*,\\s*${pin}\\)`).test(s)) fails.push(`H: ${file} does not fetch its ${pin} file through fetchPinned`)
if (/fetch\(url,\s*\{\s*cache:\s*'force-cache'\s*\}\)/.test(s)) fails.push(`H: ${file} still fetches a pinned file directly with force-cache`)
}
const ps = srcOf('library/packet/posterSheets.js')
const classic = (ps.match(/function loadClassic\(url\) \{[\s\S]*?\n\}/) || [''])[0]
if (!/fetch\(url, \{ cache: 'reload' \}\)/.test(classic) || !/return tag\(\)\s*\n?\s*\}\)/.test(classic)) fails.push('H: the classic-script path does not refresh the HTTP cache and retry a refused tag once')
for (const p of PINNED_FILES) if (served(p) === null) fails.push(`H: ${p} is not in the served tree (the gate would measure nothing)`)
return fails
}
const shaSrc = readFileSync(SHA_SRC, 'utf8')
const read = (f) => readFileSync(join(REPO, f), 'utf8')
if (process.argv.includes('--self-test')) {
const FOR = "for (const cache of ['force-cache', 'reload']) {"
const poisons = [
['fetchPinned without its fresh refetch', () => auditFetchPinned(shaSrc.replace(FOR, "for (const cache of ['force-cache']) {")), /^A: /],
['fetchPinned that never uses the cache', () => auditFetchPinned(shaSrc.replace(FOR, "for (const cache of ['reload', 'reload']) {")), /^B: /],
['fetchPinned that accepts a mismatch', () => auditFetchPinned(shaSrc.replace(' if (got === pin) return text\n', ' return text\n')), /^C: /],
['a loader that fetches its pinned file directly', () => auditWiring((f) => (f === 'library/packet/packetStrings.js'
? read(f).replace('fetchPinned(url, PACKET_STRINGS_SHA384)', "(await fetch(url, { cache: 'force-cache' })).text()") : read(f))), /^H: library\/packet\/packetStrings\.js/],
]
let missed = 0
for (const [name, run, rx] of poisons) {
const f = await run(); const hit = f.find((x) => rx.test(x))
console.log(hit ? `✓ caught: ${name} -> ${hit}` : `✗ MISSED: ${name}${f.length ? ` (failed instead: ${f[0]})` : ''}`)
if (!hit) missed++
}
if (missed) process.exitCode = 1
} else {
const f = [...await auditFetchPinned(shaSrc), ...await auditLoaders(), ...auditWiring(read)]
if (f.length) { for (const x of f) console.log('✗ ' + x); console.log(`FAIL check:pinned-fetch-fresh (${f.length})`); process.exitCode = 1 }
else console.log('PASS check:pinned-fetch-fresh — a stale cached copy of a pinned file is refetched once, fresh: page 1 still draws, the packet keeps its language, India keeps its states; a matching cached copy is used as is; wrong bytes are refused; every pinned loader goes through fetchPinned')
}

View file

@ -15,7 +15,7 @@
// t.byShapeName → Map(shapeName → entry) (the map's ADM1 features carry shapeName)
// t.byIso → Map(iso → entry)
import { INDIA_STATES_SHA384 } from './indiaStates.integrity.js'
import { sha384 } from '../packet/sha384.js'
import { fetchPinned } from '../packet/sha384.js'
const PAGE_BASE = globalThis.document?.baseURI || undefined
const cache = new Map()
@ -30,10 +30,11 @@ export function loadIndiaStates(assetsBase) {
try { url = indiaStatesUrl(assetsBase) } catch (e) { return Promise.reject(e) }
if (!cache.has(url)) {
const p = (async () => {
const res = await fetch(url, { cache: 'force-cache' })
if (!res.ok) throw new Error(`india-states.json not served (${res.status})`)
const text = await res.text()
if ((await sha384(text)) !== INDIA_STATES_SHA384) throw new Error('india-states.json failed its integrity check')
// force-cache, and once more fresh on a mismatch: a stale cached copy after a re-pin must not cost a returning
// reader India's state cards (../packet/sha384.js fetchPinned says why).
let text
try { text = await fetchPinned(url, INDIA_STATES_SHA384) }
catch (e) { throw new Error(`india-states.json ${e?.message || e}`) }
const data = JSON.parse(text)
const states = Array.isArray(data.states) ? data.states : []
return {

View file

@ -18,7 +18,7 @@
*/
import { en as EN } from '../scripts/posters/packet-strings.json'
import { PACKET_STRINGS_SHA384 } from './poster-render.integrity.js'
import { sha384 } from './sha384.js'
import { fetchPinned } from './sha384.js'
// Captured when the module evaluates, as in posterSheets.js: a relative asset base resolves against the page
// that loaded the map, even if a single-page host changes its URL before the click.
@ -33,11 +33,9 @@ export function loadPacketStrings(assetsBase) {
try { url = new URL(`${assetsBase}/lib/packet-strings.json`, PAGE_BASE).href } catch { return Promise.resolve(ENGLISH_ONLY) }
if (!cache.has(url)) {
const p = (async () => {
const res = await fetch(url, { cache: 'force-cache' })
if (!res.ok) throw new Error(`not served (${res.status})`)
const text = await res.text()
if ((await sha384(text)) !== PACKET_STRINGS_SHA384) throw new Error('failed its integrity check')
return JSON.parse(text)
// force-cache, and once more fresh on a mismatch: a stale cached copy after a re-pin must not turn a
// returning reader's packet English (sha384.js fetchPinned says why).
return JSON.parse(await fetchPinned(url, PACKET_STRINGS_SHA384))
})().catch((e) => {
console.warn(`[packetStrings] ${url}: ${e?.message || e} — printing in English`)
cache.delete(url) // a later print tries again rather than inheriting one failure

View file

@ -16,7 +16,7 @@
* ISO3_NAME, series: { totalGroups, totalLists, index } })
*/
import { POSTER_RENDER_SHA384, NE_PLACES_SHA384, POSTER_RENDER_GLOBAL_SHA384 } from './poster-render.integrity.js'
import { sha384 } from './sha384.js'
import { fetchPinned } from './sha384.js'
// ★ THE GENERATOR IS SERVED, NOT BUNDLED, AND IT IS INTEGRITY-PINNED. poster-render.js is served verbatim
// at <assets>/lib/poster-render.js (generate-tree) and loaded at the FIRST print: a single-file build
@ -33,9 +33,11 @@ const PAGE_BASE = globalThis.document?.baseURI || undefined
const abs = (u) => new URL(u, PAGE_BASE).href
// The classic-script path: the browser checks POSTER_RENDER_GLOBAL_SHA384 itself and refuses to run a
// file that does not match (onerror), so nothing unverified executes here either.
// A refused tag is tried ONCE more after a fetch with cache: 'reload' has refreshed the HTTP cache: a stale cached
// copy after a re-pin fails the browser's own check, exactly as it fails fetchPinned's (sha384.js says why).
function loadClassic(url) {
if (globalThis.DoxaPosterRender?.createPosterGenerator) return Promise.resolve(globalThis.DoxaPosterRender.createPosterGenerator)
return new Promise((resolve, reject) => {
const tag = () => new Promise((resolve, reject) => {
const el = document.createElement('script')
el.src = url
el.integrity = POSTER_RENDER_GLOBAL_SHA384
@ -44,9 +46,13 @@ function loadClassic(url) {
el.onload = () => (globalThis.DoxaPosterRender?.createPosterGenerator
? resolve(globalThis.DoxaPosterRender.createPosterGenerator)
: reject(new Error(`the sheet generator at ${url} loaded but did not define DoxaPosterRender`)))
el.onerror = () => reject(new Error(`the sheet generator at ${url} was refused (integrity or network) — not running it`))
el.onerror = () => { el.remove(); reject(new Error(`the sheet generator at ${url} was refused (integrity or network) — not running it`)) }
document.head.appendChild(el)
})
return tag().catch(async (first) => {
try { await fetch(url, { cache: 'reload' }) } catch { throw first }
return tag()
})
}
const generatorCache = new Map()
function loadGenerator(assetsBase) {
@ -54,13 +60,10 @@ function loadGenerator(assetsBase) {
const url = abs(`${assetsBase}/lib/poster-render.js`)
if (!generatorCache.has(url)) {
const p = (async () => {
const res = await fetch(url, { cache: 'force-cache' })
if (!res.ok) throw new Error(`the sheet generator is not served (${res.status}) at ${url}`)
const text = await res.text()
const got = await sha384(text)
if (got !== POSTER_RENDER_SHA384) {
throw new Error(`the sheet generator at ${url} failed its integrity check (expected ${POSTER_RENDER_SHA384}, got ${got}) — refusing to run it`)
}
// force-cache, and once more fresh on a mismatch (sha384.js fetchPinned says why); only a match runs.
let text
try { text = await fetchPinned(url, POSTER_RENDER_SHA384) }
catch (e) { throw new Error(`the sheet generator at ${url} ${e?.message || e} — refusing to run it`) }
const blobUrl = URL.createObjectURL(new Blob([text], { type: 'text/javascript' }))
try { return (await import(/* @vite-ignore */ blobUrl)).createPosterGenerator }
catch (e) {
@ -96,11 +99,10 @@ function loadPlaces(assetsBase) {
const url = abs(`${assetsBase}/boundaries/ne-places.json`)
if (!placesCache.has(url)) {
const p = (async () => {
const res = await fetch(url, { cache: 'force-cache' })
if (!res.ok) return []
const text = await res.text()
if ((await sha384(text)) !== NE_PLACES_SHA384) {
console.warn(`[posterSheets] ${url} failed its integrity check — drawing no city dots`)
let text
try { text = await fetchPinned(url, NE_PLACES_SHA384) }
catch (e) {
if (e?.integrity) console.warn(`[posterSheets] ${url} failed its integrity check, also when fetched fresh — drawing no city dots`)
return []
}
return JSON.parse(text).places || []

View file

@ -7,3 +7,25 @@ export async function sha384(text) {
for (const b of d) bin += String.fromCharCode(b)
return `sha384-${btoa(bin)}`
}
// ★ A PINNED FILE THAT FAILS ITS PIN IS FETCHED ONCE MORE, FRESH. The loaders fetch with cache: 'force-cache' (fast,
// and it works offline once cached), which returns ANY cached copy, however old. After a re-pin, a reader who had
// printed before kept the previous file: it failed its pin on every print, and the print fell back without a word
// (the stored poster for page 1, English for the packet text) until the browser evicted it. So a copy that is not
// served or does not match is fetched ONCE more with cache: 'reload', which skips the HTTP cache and refreshes it,
// and only that answer decides. Resolves with the verified text; rejects with the last reason (err.integrity is
// true when the bytes were wrong rather than missing).
export async function fetchPinned(url, pin) {
let last = null
for (const cache of ['force-cache', 'reload']) {
try {
const res = await fetch(url, { cache })
if (!res.ok) { last = new Error(`not served (${res.status})`); continue }
const text = await res.text()
const got = await sha384(text)
if (got === pin) return text
last = Object.assign(new Error(`failed its integrity check (expected ${pin}, got ${got})`), { integrity: true })
} catch (e) { last = e }
}
throw last
}

View file

@ -356,8 +356,8 @@
"dev": "bun x vite",
"fix:sri": "node internal/fix-sri.mjs",
"gallery": "node internal/generate-gallery.mjs",
"gates:post": "node internal/run-parallel.mjs --label=post -- check:capability-derived check:drop check:parse verify check:sri check:public-hygiene check:label-joins check:facet-cascade check:facet-one-field check:heartbeat check:kpi-unit check:composite-reg check:descriptions check:no-abs-paths check:no-codename check:dep-names check:composite-srcs check:embed-targets check:nested-token check:i18n-parity check:tabs-localized check:packets-all check:packet-strings-parity check:zone-census check:zoning-declared check:share-policy check:library-index check:csv-shape check:capability-citations check:catalog-identity check:no-orphan-gates check:quoted-scripts check:script-flags check:capability-exemplars check:packet-fixture check:packet-browser check:provider-switch check:locale-reachable check:url-resolver check:manifest-covers check:one-palette check:pin-dim-slider check:legend-source check:clustering-modes check:composite-embed-knobs check:composite-locale-leaks check:linked-vocabulary check:wp-embed check:dashboard-geocoder-provider check:leaf-engine-per-provider check:auto-engine check:engine-contract check:map-props-unit check:composites-mount check:composite-interop check:no-free-identifiers check:map-fly-reduced-motion check:mst-clustering check:list-prop-unit check:profiles-mount check:cdn-pins check:no-eval check:popup-escaping check:i18n-treeshake check:pinned-umd check:one-palette:self-test check:no-cross-plugin-imports check:no-cross-plugin-imports:self-test check:map-attribution check:studio-rail check:approx-pin-note check:embedder-keys check:provider-for check:legend-logical-insets check:legend-logical-insets:self-test check:optional-t-equivalence check:text-highlight check:religion-label-not-value check:i18n-stopped-scope check:no-silent-engine check:no-silent-engine:self-test check:regions-uses-library check:shadow-css-parses check:square-map-controls check:preset-config-merge check:instance-config-keys check:frozen-labels check:plural-forms check:region-fill-repaint check:region-paint-single-source check:poster-print-fallback check:posters-exportable check:country-names check:reduced-motion check:no-dead-library check:universal-props check:readme-size check:tree-relative check:leaf-hints check:snippet-engine check:snippet-engine:self-test check:embed-base check:link-coverage check:no-public-binds check:shared-reached check:tree-weight check:no-fleet-names:self-test check:pin-hint-floor:self-test check:poster-sizes:self-test check:shell-header:self-test check:preview-flag-dev-only:self-test check:poster-type-registry:self-test check:host-floor:self-test check:phone-print-keep:self-test",
"gates:pre": "node internal/run-parallel.mjs --label=pre -- check:template-literal check:mjs-parse check:sfc-parse check:tab-radius check:synced-css check:doc-scripts check:setup-order check:package-json-unique-keys check:no-repo-pb-hooks check:no-credentials-omit check:no-credentials-omit:self-test check:shadow-reduced-motion check:poster-action-print check:targets check:targets:self-test check:browser-gates-guarded check:pin-selection check:facet-shape check:hardcoded-english:self-test check:list-selection check:selector-fields check:upg-zone-joins check:gospel-set-charts check:gospel-set-fit check:describe-group check:one-scroll-container check:packet-charts check:pins-in-state check:fork-leaders check:circular-layout check:composite-token-url check:language-tree-layout check:i18n-keys-exist check:i18n-never-translated check:packet-variants check:profile-table check:element-bootstrap check:embed-url-absolute check:frame-target check:language-tree-model check:map-menu-anchor check:selection-kinds check:tiered-points check:whiteboard-projection check:mercator-slice check:ring-vocabulary check:map-props-unit check:mobile-gating check:provide-shadowing check:legend-top-offset check:cdn-cache check:plural-forms check:select-zone-race check:script-registry check:map-control-overlap check:poster-attribution check:board-map-options check:gospel-whiteboard check:packet-map-missing-loud check:poster-layout-optin check:ported-globals-identical check:ring-bands check:packet-a1-structure check:india-states check:batch-selection-export check:zone-title-tier-a check:evangelical-share check:map-labels check:packet-offline check:help-reachable check:gated-not-in-tree check:window-globals-released check:legend-data-contract check:no-internal-role-nouns check:poster-fetch-guard check:people-group-records check:sentence-slots check:popup-href-escaped check:popup-pan-controls check:country-map-refetch check:pane-config-reforward check:viewport-meta check:viewport-meta:self-test check:label-key-coverage check:poster-browser-equiv check:poster-render-pinned check:country-sheet-one-path check:country-sheet-one-path:self-test check:side-lanes check:side-lanes:self-test check:poster-section check:poster-section:self-test check:posterverse-shelf check:host-locale-fetch check:print-frame-reclaim check:click-sheet check:coverage-consistent:chained check:coverage-consistent:poison check:dashboard-filter-a11y check:filter-multivalue check:hover-channel check:kit-options-reachable check:layout-key-namespaced check:legend-mirrors check:map-unmount-race check:no-conflict-markers check:series-isolation check:snapshot-guard check:stale-sweep-claim check:surfaces-read-the-tier check:translator-contacts check:wall-wysiwyg check:wall-print-watch check:wall-no-bg-photos check:wall-pan check:template-i18n check:list-pattern-doc check:shadow-style-labels-unique check:grid-image-loading check:retry-fetch check:panel-sheets check:poster-wall-host-seams check:poster-sections check:poster-sections:self-test check:poster-design-catalogue check:poster-design-catalogue:self-test check:wall-photo-weight check:no-fleet-names check:pin-hint-floor check:poster-sizes check:upg-count check:shell-header check:pg-name-gate check:preview-flag-dev-only check:poster-type-registry check:host-floor check:phone-print-keep",
"gates:post": "node internal/run-parallel.mjs --label=post -- check:capability-derived check:drop check:parse verify check:sri check:public-hygiene check:label-joins check:facet-cascade check:facet-one-field check:heartbeat check:kpi-unit check:composite-reg check:descriptions check:no-abs-paths check:no-codename check:dep-names check:composite-srcs check:embed-targets check:nested-token check:i18n-parity check:tabs-localized check:packets-all check:packet-strings-parity check:zone-census check:zoning-declared check:share-policy check:library-index check:csv-shape check:capability-citations check:catalog-identity check:no-orphan-gates check:quoted-scripts check:script-flags check:capability-exemplars check:packet-fixture check:packet-browser check:provider-switch check:locale-reachable check:url-resolver check:manifest-covers check:one-palette check:pin-dim-slider check:legend-source check:clustering-modes check:composite-embed-knobs check:composite-locale-leaks check:linked-vocabulary check:wp-embed check:dashboard-geocoder-provider check:leaf-engine-per-provider check:auto-engine check:engine-contract check:map-props-unit check:composites-mount check:composite-interop check:no-free-identifiers check:map-fly-reduced-motion check:mst-clustering check:list-prop-unit check:profiles-mount check:cdn-pins check:no-eval check:popup-escaping check:i18n-treeshake check:pinned-umd check:one-palette:self-test check:no-cross-plugin-imports check:no-cross-plugin-imports:self-test check:map-attribution check:studio-rail check:approx-pin-note check:embedder-keys check:provider-for check:legend-logical-insets check:legend-logical-insets:self-test check:optional-t-equivalence check:text-highlight check:religion-label-not-value check:i18n-stopped-scope check:no-silent-engine check:no-silent-engine:self-test check:regions-uses-library check:shadow-css-parses check:square-map-controls check:preset-config-merge check:instance-config-keys check:frozen-labels check:plural-forms check:region-fill-repaint check:region-paint-single-source check:poster-print-fallback check:posters-exportable check:country-names check:reduced-motion check:no-dead-library check:universal-props check:readme-size check:tree-relative check:leaf-hints check:snippet-engine check:snippet-engine:self-test check:embed-base check:link-coverage check:no-public-binds check:shared-reached check:tree-weight check:no-fleet-names:self-test check:pin-hint-floor:self-test check:poster-sizes:self-test check:shell-header:self-test check:preview-flag-dev-only:self-test check:poster-type-registry:self-test check:host-floor:self-test check:phone-print-keep:self-test check:pinned-fetch-fresh:self-test",
"gates:pre": "node internal/run-parallel.mjs --label=pre -- check:template-literal check:mjs-parse check:sfc-parse check:tab-radius check:synced-css check:doc-scripts check:setup-order check:package-json-unique-keys check:no-repo-pb-hooks check:no-credentials-omit check:no-credentials-omit:self-test check:shadow-reduced-motion check:poster-action-print check:targets check:targets:self-test check:browser-gates-guarded check:pin-selection check:facet-shape check:hardcoded-english:self-test check:list-selection check:selector-fields check:upg-zone-joins check:gospel-set-charts check:gospel-set-fit check:describe-group check:one-scroll-container check:packet-charts check:pins-in-state check:fork-leaders check:circular-layout check:composite-token-url check:language-tree-layout check:i18n-keys-exist check:i18n-never-translated check:packet-variants check:profile-table check:element-bootstrap check:embed-url-absolute check:frame-target check:language-tree-model check:map-menu-anchor check:selection-kinds check:tiered-points check:whiteboard-projection check:mercator-slice check:ring-vocabulary check:map-props-unit check:mobile-gating check:provide-shadowing check:legend-top-offset check:cdn-cache check:plural-forms check:select-zone-race check:script-registry check:map-control-overlap check:poster-attribution check:board-map-options check:gospel-whiteboard check:packet-map-missing-loud check:poster-layout-optin check:ported-globals-identical check:ring-bands check:packet-a1-structure check:india-states check:batch-selection-export check:zone-title-tier-a check:evangelical-share check:map-labels check:packet-offline check:help-reachable check:gated-not-in-tree check:window-globals-released check:legend-data-contract check:no-internal-role-nouns check:poster-fetch-guard check:people-group-records check:sentence-slots check:popup-href-escaped check:popup-pan-controls check:country-map-refetch check:pane-config-reforward check:viewport-meta check:viewport-meta:self-test check:label-key-coverage check:poster-browser-equiv check:poster-render-pinned check:country-sheet-one-path check:country-sheet-one-path:self-test check:side-lanes check:side-lanes:self-test check:poster-section check:poster-section:self-test check:posterverse-shelf check:host-locale-fetch check:print-frame-reclaim check:click-sheet check:coverage-consistent:chained check:coverage-consistent:poison check:dashboard-filter-a11y check:filter-multivalue check:hover-channel check:kit-options-reachable check:layout-key-namespaced check:legend-mirrors check:map-unmount-race check:no-conflict-markers check:series-isolation check:snapshot-guard check:stale-sweep-claim check:surfaces-read-the-tier check:translator-contacts check:wall-wysiwyg check:wall-print-watch check:wall-no-bg-photos check:wall-pan check:template-i18n check:list-pattern-doc check:shadow-style-labels-unique check:grid-image-loading check:retry-fetch check:panel-sheets check:poster-wall-host-seams check:poster-sections check:poster-sections:self-test check:poster-design-catalogue check:poster-design-catalogue:self-test check:wall-photo-weight check:no-fleet-names check:pin-hint-floor check:poster-sizes check:upg-count check:shell-header check:pg-name-gate check:preview-flag-dev-only check:poster-type-registry check:host-floor check:phone-print-keep check:pinned-fetch-fresh",
"gen:iso3-to-alpha2": "node library/scripts/generate-iso3-to-alpha2.mjs",
"internet:adm1": "node plugin-designs/cities-internet/scripts/build-adm1-internet.mjs",
"library-index": "node internal/generate-library-index.mjs",
@ -436,9 +436,12 @@
"check:host-floor": "node internal/check-host-floor.mjs",
"check:host-floor:self-test": "node internal/check-host-floor.mjs --self-test",
"check:phone-print-keep": "node internal/check-phone-print-keep.mjs",
"check:phone-print-keep:self-test": "node internal/check-phone-print-keep.mjs --self-test"
"check:phone-print-keep:self-test": "node internal/check-phone-print-keep.mjs --self-test",
"check:pinned-fetch-fresh": "bun internal/check-pinned-fetch-fresh.mjs",
"check:pinned-fetch-fresh:self-test": "bun internal/check-pinned-fetch-fresh.mjs --self-test"
},
"//scripts": {
"check:pinned-fetch-fresh": "a STALE cached copy of a pinned print-time file never decides a print: library/packet/sha384.js fetchPinned fetches force-cache, and on a copy that is not served or fails its pin, ONCE more with cache:'reload'; every pinned loader (posterSheets poster-render.js + ne-places.json, packetStrings, indiaStates) calls it, and the classic-script path refreshes the cache and retries a refused tag once. With a modelled HTTP cache holding stale copies: the current file after one fresh refetch; a matching cached copy used with one force-cache request; wrong bytes refused; page 1 still draws (generateSheet), the packet keeps every language, India keeps its 36 states. Self-test poisons: no fresh refetch, never the cache, a mismatch accepted, a loader fetching its pinned file directly.",
"check:phone-print-keep": "on a PHONE the in-page print document (#dxm-print-root and its print style, library/composables/printInPlace.js) and printImageBlob's object URL outlive afterprint, because Android Chrome fires afterprint once the PREVIEW is built and renders a saved PDF later, from the page as it is then (a phone's saved packet was the bare host page). A timeline on fake timers and a stub DOM with an Android user agent: still mounted 30 s after afterprint; released once the reader is back (shown again after hidden, focused again after blur, or touched after print() but not in its first TOUCH_SETTLE_MS) plus BACK_GRACE_MS (60 s), or at the ceiling; going away again during the grace cancels the pending drop; the Print tap of a late print does not count; a new print replaces it; desktop is the frame path, verbatim. --impl=<path> runs it against another copy (the pre-fix file fails). Self-test poisons: the old drop at afterprint, no release when back, listeners armed at mount, no grace, printImageBlob revoking at afterprint, no ceiling, no cancel when away again, no touch settle window.",
"_": "What each script does. These notes used to sit inside the command strings as trailing \"# …\" comments, which made the shell discard any arguments passed after \"--\" (so a gate given --url silently measured its hardcoded default port — possibly another agent's server). Keep notes here, never in a command.",
"browser-gates": "lists every package.json script that launches a browser (the same detection run-parallel uses to route them through internal/browser-slot.sh). Run each as internal/browser-slot.sh bun run <name>.",