On mobile, selecting countries and pressing Prayer list reported zero UPGs and would not generate a prayer list. selectedTerritories holds three kinds -- 'zone' (a legend row), 'adm0' (a country tapped on the map) and 'adm1' (an India state / China province) -- and selectedLists() kept only 'zone'. So a map selection resolved to an empty list and selectionAsList threw "nothing selected", while the HUD, which sums t.count straight off the selection, showed a correct non-zero total. Two sources of truth for one selection is why it read as a nonsense error rather than an empty state: the app said 87 and the generator said zero. The adm0 key already carries the ISO3, which IS country_code on a people-group row, so a tapped country becomes a country_codes selector the evaluator already handles. No new selector type, and a mixed selection unions and de-duplicates for free. The cover label now names the countries too -- built from lists alone it was empty, and reduce() on an empty array throws, so the packet would have died on its cover even after the selector was fixed. adm1 cannot resolve and that is a data fact, not an oversight: a people-group row has no province field and the evaluator has no adm1 case. Folding it into the country union would print a packet for the whole COUNTRY while the reader had selected one STATE -- a plausible packet that is quietly wrong. So it is refused BY NAME, which "nothing selected" was not: that is a lie the reader can see is false with their selection highlighted on the map. Also, nothing showed that the action was running: batchBusy only DISABLED the buttons, and a disabled button is visually indistinguishable from an unresponsive one, so on a phone you tap, see nothing change, and tap again. busyAction names which action is running so only that button relabels, and it is cleared in every finally -- otherwise a failed print leaves the button reading "Working..." forever. check-selection-kinds.mjs reads the kinds OUT of resolveTerritory rather than listing them, because listing them is how this survived: 'adm0' existed in the resolver and nowhere in the consumer, and a test written from the same mental list as the consumer would have missed it too. Mutation-tested: reverting the guard, dropping the selector, and leaving busyAction uncleared each fail with their own message. |
||
|---|---|---|
| .. | ||
| 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.