This gate printed nine metrics, folded six into `const bad = a || b || ...` — so a
failure said "FAIL" without naming which — and left three unchecked: page size and
the two headings, which are the very strings its own header claims it covers ("the
gate reports which headings came back translated"). Reports, not asserts.
★ IT LOOKED LIKE IT HAD FOUND A PRODUCT BUG, AND HAD NOT. Under ?lang=es it printed
an EMPTY wheel heading; under hi it blanked BOTH — while the catalog holds "Rueda de
Oración" and "प्रार्थना चक्र". That reads exactly like a broken i18n path, and it
PASSED anyway, because nothing asserted the values.
The cause was the harness. wheelTitle was innerText.match(/^.*Wheel.*$/m) and
pointsTitle /Prayer Points|Puntos/ — neither can match Hindi, and /Wheel/ cannot
match Spanish. The product was correct in every locale all along: verified after the
fix in en/es/hi/ar/zh, RTL and Devanagari included. Had I reported that as a defect
I would have been wrong.
mark5 carried the same fault: innerText.includes('Gadarenes'), an English proper
noun that goes RED the moment point4_passage is translated. Now asserted by
.p4-passage length — the same fix already applied in check-packet-render.
Headings are now compared to the CATALOG entry for the packet's language, with
per-key fallback to English exactly as the renderer resolves it. packet-render.js
gains class="points-title"/"wheel-title" as TEST HOOKS: both were inline-styled with
no selector, which is what forced the English greps in the first place.
MUTATION-TESTED: a renderer forced to ignore the language FAILS with both headings
named and the expected values shown; a renderer emitting an empty heading FAILS
("a blank heading is worse than an English one").
★ ONE MUTATION CORRECTLY PASSED, and it marks a real limit: reverting es
wheel_title to English IN THE CATALOG passes, because rendered and expected then
legitimately agree. This gate cannot tell a translated catalog from an untranslated
one — that is check:packet-strings-parity's job, and it does assert it. Two gates,
two questions.
Partial translation re-verified through the BROWSER path: a 1-of-21-key Swahili
block renders "Mambo 12 ya Maombi" with the wheel heading falling back to "Prayer
Wheel" — translated where present, English where not, neither blank.
That makes four gates with one root cause (tabs-localized, packet-render,
deck-click, and this one twice). Any new gate matching on user-visible text should
resolve it from the catalog.
|
||
|---|---|---|
| .. | ||
| api | ||
| colors | ||
| components | ||
| composables | ||
| constants | ||
| data | ||
| i18n | ||
| layouts | ||
| 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.