Comments, messages and docs only; no check logic changes. - The role-word gate is renamed check-no-internal-role-nouns (and its baseline fixture). Same logic: it builds the word from parts so the file does not carry it, and a planted line in title case and in capitals still fails it while WebDriver and "GPU driver" do not. - public-hygiene-check: the two role patterns are built from parts (the regexes are byte- identical), the header loses the forge and date wording, and the prayer comment loses its decision date. Its output on the tree is identical in normal and --strict modes. - landing-preconditions, check-no-public-binds, check-published-tree-weight: no fleet tool name, no "this box", no forge name in comments and messages. - Removed: check-backlog-refs, check-backlog-status-enum, check-done-evidence, check-verification-tags and check-dxlife-untouched (they police a private backlog and workspace), with their scripts, docs, gate-list entries and orphan-baseline entry, and the committed needs-eyes pages (check-embedded-rtl --emit writes them on demand). - package.json docs: no agent names. Docs: the stale check mentions and the snapshot references are gone (the snapshots themselves leave in the studio P commit). gates:pre: PASS 115, FAIL 0, NOT MEASURED 1 (browser seat).
4.2 KiB
Architecture — UPG-100 poster generation
The simple overview is in
library/scripts/posters/README.md. This is the full reference for the generator: files, environment, pipeline, and the gotchas that have bitten before.
The poster generator for the my-upg-100-list profile renders one 3ft × 3ft print poster per zone (28 today) from the profile's own data.
It lives inside doxa-maps on purpose. It used to sit outside the repo in a sibling generator
folder and reach in for data via a UPG100_DATA_PATH env var. That cost real bugs: forget the var
and it silently rendered a stale duplicate of clusters.json — which is how Nepal, and later
Bangladesh + Bhutan, vanished from posters that looked fine otherwise. The generator and the data
it reads now ship together.
Run it
bun run posters # all 28 posters -> out/posters/*.html
ONLY=asia-india-tamil-nadu bun run posters:one # just one (fast, ~15s)
bun run posters:shoot # HTML -> PNG @3000x3000 (Playwright/Firefox)
bun run posters:gallery # review pages -> out/index.html + out/hd.html
bun run posters:export # region folders -> ../../../upg-100-posters/
The full pipeline, in order:
posters -> posters:shoot -> (convert to WebP previews) -> bun run build
The WebP step feeds plugin-designs/my-upg-100-list/assets/previews/, which is what the poster wall
shows in the app. A template change is not visible in the app until previews are regenerated and
the app is rebuilt.
Files
| File | What it does |
|---|---|
generate.mjs |
THE template. Reads the profile data, writes one HTML poster per zone. |
shoot.mjs |
Screenshots each poster to PNG (Firefox — never Chromium; privacy-first policy). |
poster-regions.mjs |
The region map (India, Africa, …). Shared by both gallery builders. |
build-gallery.mjs |
out/index.html — browsable preview grid, sectioned by region. |
build-hd-gallery.mjs |
out/hd.html — full-resolution review + copy-to-clipboard. |
export-public.mjs |
Copies PNGs into region folders OUTSIDE the repo (see below). |
Environment
| Var | Default | Notes |
|---|---|---|
SPARSE=1 |
— | Required. Selects the approved template. Without it you get the older full-vision layout. |
FORMAT=3x3 |
3x4 |
1500×1500 square sheet. |
OFFLINE=1 |
— | Use the cached out/_records.json instead of calling the API. |
VITE_API_BASE_URL |
— | Satisfies the fail-closed config gate. No request is made under OFFLINE=1. |
ONLY |
— | Build a single zone by zoneKey. |
UPG100_DATA_PATH |
the profile in this repo | Only override to test against other data. |
SERIES_INDEX / SERIES_NAME |
derived / List of 100 UPG's |
Series line on the sheet. |
Two things that will bite you
1. The exported PNGs must NOT go in public/. capacitor.config.json sets webDir: "public"
and Capacitor packages that directory wholesale — no per-path exclude. The export is ~52 MB, and
the per-bundle vite build also fans the served root into every MFE, so it duplicated 11×
(631 MB) when it lived there. export-public.mjs therefore writes to ../../../upg-100-posters/, a
sibling of the repo. Desktop/web can serve from there or a CDN; the phone gets posters from a CDN
or generates on device.
2. Fail-loud guards, not silent overlaps. generate.mjs throws if the header would be drawn
over the map frame, or if the below-map block would run off the sheet. Raise the constant or tighten
the copy — never delete the guard. Both have caught real regressions (west-africa, sudan,
gujarat, oceania-indonesia).
Measurement discipline
SVG <text> does not word-wrap, so all multi-column setting is measured in the generator via
CHADV -> textW -> wrapPx -> paraInto. That estimator is calibrated for Lato at body sizes and is
wrong elsewhere — measured drift: Georgia bold display type runs ~1.35× wider than predicted, bold
digits ~0.98em against an assumed 0.63em, mixed bold/regular runs ~10% narrower. Every size that
must fit a box is therefore taken from the rendered artefact (getBBox), not from the formula.
When adding new display text, render it and measure before trusting a computed size.