setsPage landed as a renderer capability with no data supplier: it returns '' without data -- correctly -- which meant the FULL-DETAIL packet had never once printed its distinguishing back page in real use. The full-vs-lightweight split existed in code while the full packet was missing the page that makes it full. packetSets.js is the supplier: a pure decode+join with zero imports, so the identical function runs under Node (the generator reads the table from disk) and under Vite (the browser fetches it) and the join cannot fork between the two. The table stays with the caller -- the same seam as strings, christian and describe. The join is measured, not assumed: 2,091 of 2,107 groups match by slug, rings distribute 1-10 with real shape, and the 16 unmatched are inked as UNKNOWN on the bounded figure rather than guessed onto a side -- an un-inked category reads as zero on paper. Wired at every path that prints a packet: the offline generator (all 28 zones verified carrying the page, e.g. laos: 0 inside, 94 outside, 1 unknown; 94 placed with "25 same dialect, same people"), the legend Export menu, the poster wall, and the selection packet -- the last three through one opts.sets loader fetching the SAME served asset-pool copy the generator reads, so an embedded bundle prints the same page as the dev tree. The decode is a third reading of near-neighbors.json's cols/dict format (nearRows.js documents being a copy of upg-neighbor's, pending promotion to library/). Flagged in the header rather than hidden: when the loader is promoted, the decode here collapses into it and only the join remains. |
||
|---|---|---|
| .. | ||
| 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.