The Prayer-list dropdown went into PosterWall's per-card print row. The profile people open -- upg-100-list -- does not render PosterWall at all; its print path is the legend's Export menu, which still offered two items and printed immediately. So the feature was built, landed and served, and invisible on the only screen it was asked for. Two surfaces legitimately offer printing and both carry a button labelled Prayer list, so the label was not enough to tell them apart. The grep that would have caught it -- does this profile mount that component -- takes five seconds and I never ran it. The legend's packet item is now a disclosure: it is named UPG 100, it explains what a UPG 100 list is, and it reveals Full detail and Lightweight. The explainer matters because this menu item is the only place a reader meets the idea, so it wraps rather than truncates. The submenu opens BELOW its trigger by construction, not by positioning: the menu is a flex column, so a block after the button flows underneath it. A panel that grows its parent cannot cover its parent -- which is exactly what the card-deck row could do, hanging out of its card at top:100% with nowhere to go on a short viewport. emitExport widens its existing string to packet:full / packet:lightweight rather than adding a second event; poster and a bare packet still work, so an older consumer keeps working and one event means nobody can handle only half the menu. printPrayerPacket takes the variant and forwards it to printPacket, which already accepts it. UPG 100 is allowed to be identical in all eleven locales, recorded in check:i18n-never-translated as a brand name rather than as debt: it is not waiting on a ruling, it IS the ruling, and the explainer beside it carries the meaning per locale. Inventing eleven names for one product would be the failure that gate exists to prevent. |
||
|---|---|---|
| .. | ||
| api | ||
| charts | ||
| colors | ||
| components | ||
| composables | ||
| constants | ||
| data | ||
| elements | ||
| i18n | ||
| layouts | ||
| lists | ||
| maps | ||
| packet | ||
| packets | ||
| 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.