The agreed ring vocabulary now lives in two catalogues in eleven locales each -- gospelInternet.json for the screen legend, packet-strings.json for the printed sheet -- and nothing held them together. They legitimately want different SHAPES (the screen renders the axes as standalone labels, the packet composes them into one string per ring), so neither can import the other; but the WORDS must match. The failure that makes this worth a gate is quiet: one cell is provisional on the Driver's row 89, and it appears in rings 1, 4 and 7. Answer it in one file and not the other and the sheet and the screen state different things about the same ring, each correct in isolation, with no error anywhere. Only a reader holding both would notice. Measuring first showed 36 of 77 cells already differed, in two classes. Most were grammar -- the packet's culture terms follow a comma mid-sentence, so they are lowercase -- but some were genuinely different words: "Same family" against "Same language family", German "verwandte" against "Nahe", Hindi "वही जन" against "वही जनसमूह". Those are drift, and the screen's wording wins because it is what readers already see. The packet now adopts every term verbatim, lowercasing only the first character of the culture half, which is a no-op in zh/ar/hi and leaves German common nouns capitalised. That adoption caught a bug in my own assertion: I had hardcoded ring 7-9 as "Same family" from CenteredSetTarget.vue's header, while the shipping string is "Same language family". A summary in a comment is a description of the data, never a second source of it. The gate also refuses to pass when it compared nothing -- rename a key on either side and every lookup returns undefined, every pair is skipped, and a silent check reports success. Mutation-tested both ways: answering row 89 on the screen only, and renaming a packet key. |
||
|---|---|---|
| .. | ||
| data | ||
| fixtures | ||
| snapshots | ||
| build-gallery.mjs | ||
| build-hd-gallery.mjs | ||
| emit-countries.mjs | ||
| export-public.mjs | ||
| generate.mjs | ||
| make-previews.py | ||
| osm-basemap.mjs | ||
| packet-strings.json | ||
| packet.mjs | ||
| poster-regions.mjs | ||
| publish-html.mjs | ||
| README.md | ||
| series.json | ||
| serve-persist.sh | ||
| serve.sh | ||
| shoot.mjs | ||
| verify-posters.mjs | ||
library/scripts/posters/ — UPG-100 poster generator
Renders one 3ft × 3ft print poster per zone (28 today) for the my-upg-100-list profile, from the profile's own data. It ships inside the repo alongside that data on purpose — reaching for the data via an env var once silently rendered stale posters (Nepal, Bangladesh, Bhutan vanished).
bun run posters -> posters:shoot -> (WebP previews) -> bun run build
HTML per zone PNG @3000² assets/previews/ app shows them
generate.mjsis the template;shoot.mjsscreenshots to PNG (Firefox only — privacy-first policy).- A template change is not visible in the app until previews are regenerated and the app rebuilt.
- Exported PNGs go to a sibling folder (
../../../upg-100-posters/), neverpublic/— Capacitor packagespublic/wholesale. snapshots/holds frozen template versions (v5 is the approved current); each has its own README.
Full reference — files, env vars, gotchas, measurement discipline: ../../../documentation/architecture/poster-generation.md.