Reworked to the consuming dashboard's contract. It emits a zone id or null on zone-change, not a selector: an id is serialisable into a query parameter, a profile config and a deep link, which is the chain that dashboard already resolves its list from, and a predicate is none of those. It also keeps the component promotable -- a selector would have to know what it selects over, and the consumer holds four different row shapes across its tabs. The join stays in one place, where the diacritic folding and the province bridge already are. A leaf emits its zone id; a region row emits the region's id and the consumer expands it, because that is where the zone data lives. And it cannot silently drop a zone. Four joinable zones are absent from the region file today, one of them the consumer's largest at a hundred groups, so grouping by region loses them with no error and no empty state -- just a shorter tree. Given the full zone set, the legend renders the difference in an explicit bucket, each one selectable. That makes the drop structurally impossible rather than something to keep checking. Omit the set and the tree is untouched, so the guard is additive. Its check includes a negative control that the unaugmented tree really does omit the largest zone, so the bucket is demonstrably not decoration. |
||
|---|---|---|
| .. | ||
| 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.