gospel found a latent ambiguity in the emit contract while wiring, and checked it instead of building past it. selectedId emits one bare string with no kind, and region ids and zone ids share a namespace. Re-measured here rather than taken on report: 33 zone ids, 7 region ids, and the intersection is exactly europe, whose region is itself. So the string "europe" cannot tell a consumer whether the Europe region row was clicked, meaning every European zone, or the Europe zone leaf, meaning that one. It is not live today, and only for a reason that is a coincidence rather than a design: region europe contains exactly one zone, so both readings give the same set. It goes live the day a second European zone is filed, and then it fails the quiet way this whole file exists to catch — a smaller result, no error, no empty state. So this is an alarm, not a fix. The consumer builds the tree, so only the consumer knows which rows are regions, and gospel is namespacing theirs. Two arms: the colliding set is pinned and says re-derive if it changes, and each colliding id's region must still contain exactly one zone. The second fails with the instruction rather than the symptom, naming the zones and telling whoever hits it to namespace their region rows. Both falsified before trusting them: filing a second European zone fires the ambiguity arm with both zones named, and adding a second colliding id fires the drift arm with both ids listed. Data restored, clean diff. The same note is on selectedId, because the next consumer to build a tree from this zone data inherits the collision, and the doc as written invited it. |
||
|---|---|---|
| .. | ||
| api | ||
| colors | ||
| components | ||
| composables | ||
| constants | ||
| data | ||
| 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.