The five are NOT one shape, and treating them as one would have changed what readers see. Measured before touching them: ApproxPinNote, FilterDropdown fall back to a literal map SemanticTreeLegend, PosterWall fall back to humanizeKey() — 'legend.columns.name' renders 'Name' PinHint falls back to a one-key map SemanticTreeLegend, PinHint also capture i18n.te (used 3x and 1x) PosterWall also captures i18n.locale So useOptionalT grew an optional `fallback` hook, and useOptionalI18n was added for the callers that need te and locale. Every site passes the fallback it already had, so not one rendered string changes. Routing them through the catalogue instead would have altered what seven bare-host bundles print, and — because tStatic honours the runtime locale — would have started translating strings that are English today. That may be an improvement; it is not this commit's to make. Changing a default is not a migration. te keeps its existing contract too: FALSE on a bare host, never "true because the catalogue has it". PinHint and SemanticTreeLegend both record why — te is how their callers know to use their own defaults. THE CHECK NOW LIVES ON MAIN, which is the point of this commit as much as the migration is. dx-shepherd: the 18-assertion version was in _qa-cycle, so it would never have run again. internal/check-optional-t-equivalence.mjs is registered as check:optional-t-equivalence — script line, //scripts note and gates:post token, all inserted by hand rather than by re-serialising package.json. It carries 46 assertions: the helper's contract across all three host states, a branch-for- branch equivalence against the old hand-written code, and a PER-SITE REGISTER that every later C4 wave appends a row to. Negative-tested from outside: strip one site's own fallback and it fails naming that site. check:library-index went red on the new composable and is green again after `bun run library-index`. That is the staleness gate working, not a flake. |
||
|---|---|---|
| .. | ||
| api | ||
| colors | ||
| components | ||
| composables | ||
| constants | ||
| data | ||
| i18n | ||
| layouts | ||
| lists | ||
| maps | ||
| packet | ||
| scripts | ||
| stores | ||
| styles | ||
| utils | ||
| 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.