The Driver, 2026-09-28: an export-poster button must open the PRINT DIALOG with a poster preview, never a direct download. Simple Research was doing exactly the forbidden thing — render a PNG, hand it to a download helper — so a reader got doxa-prayer-poster.png in their downloads folder with no preview, no paper size, and no way to print it without opening it somewhere else first. The dialog IS the preview. printImageBlob() puts the rendered poster on a sheet of exactly the spec's size (@page 8.5in x 11in, margin 0 — the renderer already composed the margins in) and raises the dialog over the map. It goes in library/composables/printInPlace.js, which is THE print mechanism here and already carries the Driver's earlier ruling that killed the pop-up path: an iframe cannot be blocked and needs no user gesture, so the same call works for a human click and for an agent. The ellipsis comes back on the label. It was stripped while the button handed over a file directly, and an ellipsis promises a dialog — which is now what happens. ★ AND THE GATE ASSERTED THE OLD RULING, which is the part worth naming. check-export-one-click passed while the button did the thing the Driver has now ruled against: it waited for a download and checked the PNG's dimensions. A gate that encodes a superseded ruling does not just miss the defect, it defends it — the next person to "fix" this button would have had to break a green gate to do it. It is replaced by check-export-poster-prints, same four cases (leaf and gallery, en and ar), asserting the new contract: one click, some frame calls window.print() within 120s, the printing document is the 1700x2200 poster on an 8.5in x 11in sheet, and NO download arrives. window.print is stubbed in every frame — the native dialog is modal and would hang the run — so this measures everything up to the dialog and not the browser's own chrome, which is the right boundary for a headless gate. Why the download half is asserted separately: a button that opens the dialog AND saves a file still breaks the rule, and a download that lands late is still a download, so it is judged over the whole run rather than at a moment. Verified: gates:pre 12/12, check:doc-scripts and check:no-credentials-omit PASS with the rename. NOT verified here: the new gate needs a browser, and this agent does not start one — rega runs it. |
||
|---|---|---|
| .. | ||
| api | ||
| colors | ||
| components | ||
| composables | ||
| constants | ||
| data | ||
| i18n | ||
| layouts | ||
| lists | ||
| maps | ||
| packet | ||
| scripts | ||
| stores | ||
| styles | ||
| utils | ||
| vendor/cldr | ||
| index.js | ||
| ProfileLoader.vue | ||
| README.md | ||
library/ (@map) — the shared map template
The other half of the architecture (see ../plugin-designs/README.md):
every map in plugin-designs/<your-map>/ composes the reusable, parameterized pieces that live here.
Fix something here and every map benefits on the next build; break something here and every map
breaks — read components/WARNING.md before touching shared pieces.
| Folder | What lives there |
|---|---|
ProfileLoader.vue |
The root of every bundle: parses profile-config, loads the profile, provides the stores + config |
components/ |
Shared UI — map, legends, controls, popup (PeopleGroupDetail), tables, charts, hints (README) |
composables/ |
Shared logic — map engine (useMapInstance), provider switch, shadow-DOM styles, theming |
stores/ |
Per-instance Pinia stores (ui / map / data) — provided by ProfileLoader, injected by children |
api/ |
Data-source registry (sources.json) + fetch layer — the JAMstack runtime wiring |
colors/ |
Color sets maps opt into via profile-config.colorSet |
i18n/ |
The shared string catalogs (all maps speak the same languages) |
data/ |
Small shared datasets baked at build time (big served data lives in ../data/) |
constants/ |
Shared constants |
scripts/ |
Maintainer pipelines (posters, research) — not part of any bundle |
The rule of thumb (same as WARNING.md): custom code in your app-profile, reusable code here — and things only move from the first to the second once they've earned it.