Commit graph

663 commits

Author SHA1 Message Date
doxa-maps-88655c70
81cb8ad235 Merge branch 'main' of https://git.nuxtbase.app/doxa-life/doxa-maps into bundler-1/work
Some checks are pending
build-gate / build (push) Waiting to run
2026-09-24 06:30:46 +00:00
doxa-maps-88655c70
a5628171ea Merge main into bundler-1/work (regenerate the tree, re-hash) 2026-09-24 06:30:29 +00:00
doxa-maps-88655c70
cf33a79c32 Merge origin/main into the studio branch
Some checks failed
build-gate / build (push) Has been cancelled
The two repos carry the same work under different commit hashes, so this
branch had drifted 30 commits behind while looking 8 ahead. Pushing it would
have DELETED 315 lines of newer upstream work, including
check-maplibre-layer-drops.mjs entirely and the current versions of six
library/ files.

Both conflicts were textual, not semantic: package.json's script map is
key-for-key identical to remote's (verified before resolving, not assumed),
and check-packet-render.mjs is byte-identical on both sides — add/add
artifacts of two repos that built the same file in separate histories.

This also retires the divergence finding committed a moment ago. It was
measured against this repo's STALE checkout rather than against main; the
merge brings library/ to byte-identical with wt-packet and
check:poster-prints — which failed with "printPacket is not a function" —
now passes. The durable part of that finding is the absolute-path bug, and
that is already recorded where it matters, in the gates themselves.

check:i18n-parity, check:locale-reachable, check:poster-prints,
check:tabs-localized and check:no-abs-paths all pass on the merged tree.
2026-09-24 06:29:57 +00:00
doxa-maps-88655c70
51a5636601 Make the last two gates repo-relative; report the library divergence they expose
check-locale-reachable and check-poster-prints defaulted REPO to a hardcoded
wt-packet path, so like check-i18n-parity before them they graded the OTHER
repo's files and printed PASS for a tree they never opened. These two are the
third and fourth instance of that pattern. Now repo-relative, so
check:no-abs-paths — which DOES gate this repo's build — stays green.

Reading its own tree, check-poster-prints now fails honestly:
usePosterPrints.js here has no printPacket. That is real, and it is NOT fixed
here, because the fix that worked for the locale catalogs would be destructive
for this code. All six diverged library/ files have lines that exist only in
this repo — two-way divergence, not staleness — so a wholesale copy would
delete studio-side work. What made the catalog sync safe was the measurement
that nothing would be lost, not the resemblance.

Recorded in internal/FINDING-library-diverged-from-wt-packet.md with the
per-file line counts and the decision a resolver needs to make. None of these
four gates run in this repo's gates:post, so nothing is newly blocked; they are
runnable by hand and now honest, which beats passing by reading another repo.
2026-09-24 06:27:48 +00:00
doxa-maps-88655c70
52e6f9ca2e Merge main into bundler-1/work (regenerate the tree, re-hash)
Six conflicts, all generated artifacts, resolved by rebuilding the
PosterWall consumers and regenerating the tree.

Main brought two things that land on this branch's work: another
builder made check:i18n-parity's DOXA_REPO default portable, which is
the wt-packet default this branch flagged last tick, so the gate now
measures the repo it lives in; and a new check:tabs-localized proves a
catalog is actually REACHED rather than merely symmetric. That gate
covers SemanticTreeLegend's tabs.json, not this element's SELECT VIEW
strip, so the strip's three strings remain uncovered and untranslated.
2026-09-24 06:26:43 +00:00
doxa-maps-88655c70
e8e2be1498 Merge main into bundler-2/work
Some checks are pending
build-gate / build (push) Waiting to run
# Conflicts:
#	package.json
#	public/doxa-maps/all-people-groups/all-people-groups.js
#	public/doxa-maps/all-people-groups/all-people-groups/index.html
#	public/doxa-maps/index.html
#	public/doxa-maps/manifest.json
2026-09-24 06:25:29 +00:00
doxa-maps-88655c70
2ddb09b42c Regions: RTL kept three rules on the wrong side, and the tab strip never localised
Every vs-live diff so far ran at LTR, so RTL was the next coverage gap —
and it is where layout actually breaks, because a physical `left`
silently survives a direction flip while a logical
`inset-inline-start` does not. Their CSS uses the logical form; ours had
physical `left` in three direction-sensitive places.

The new probe compares each element's offset from the INLINE-START edge
— the left edge in LTR, the right edge in RTL — which is the one number
that goes wrong when a physical property survives. Measured against
their live /ar/regions/cameroon:

- .search-icon sat 566px from the inline-start edge against their
  19.5px: it had stayed on the physical left, which is the inline END in
  Arabic. Replaced with their rule verbatim (blocks/_form.scss:119 —
  inset-inline-start:1em, top:0/bottom:0, margin:auto 0, 1em square),
  which also fixed a second difference, since our fixed 18px square
  could not track the input's type where theirs is 1em. Ours now
  measures their exact 19.5px.
- The country bullet kept left:-16px, so in RTL it detached on the
  wrong side — computed right 256.938px instead of a -16px overhang.
- The chooser's region legend was pinned to the physical left. That one
  is a sanctioned addition rather than a clone, but a legend stuck on
  the physical left in an RTL layout reads as broken whoever designed
  it.

Reading the Arabic screenshot then showed every heading, strapline and
map note in Arabic and RTL while the SELECT VIEW strip still read
"Regions List" — the one piece of chrome that never localised, because
regions-mfe hardcoded its labels and imported no i18n at all. Now
routed through t(), with the legend going through ViewSelect's existing
prop so the shared component and its three consumers stay untouched.

No catalogue keys were added, deliberately. Their common.json has
"Regions" and "View" but none of the composites, so there is nothing to
copy; translating our own additions is not this agent's call; and
check:i18n-parity tracks a per-locale debt ceiling — it/ro/zh sit at
exactly 94 missing because DX-life translates 8 of our 11 locales — so
English-only keys would push them to 97 and fail it. The shim falls
back to the key, so today's rendering is byte-identical. Proven rather
than assumed: a sentinel injected into ar/regions.json rendered on tab
1 at locale=ar while tab 2 stayed English, then was reverted.

Two of my own assertions were wrong first. The probe set
profile-config.locale on every custom element, which on the composite
means three regions-dashboard elements — host and both panes — and
writing a fresh {locale:'ar'} onto a pane that had inherited its config
by provide() clobbered it, leaving every root LTR; I nearly "fixed" RTL,
which works on all three leaves. And checking inset-inline-start in an
LTR context is meaningless, since it merely mirrors left.

Worth flagging: check:i18n-parity's DOXA_REPO defaults to wt-packet,
another worktree, so it must be pointed at this repo to mean anything.

Regression: 22 suites green.
2026-09-24 06:24:45 +00:00
doxa-maps-88655c70
9056b9329c All 12,379 pins were missing on MapLibre, and now a gate says so
Having found that MapLibre drops a whole layer for one unrecognised paint
property, I swept all 17 isMap leaves under ?provider=maplibre listening to
the map's own error event -- the only place this class surfaces. One more
real instance:

    layers.apg-pins.paint.circle-sort-key: unknown property

circle-sort-key is a LAYOUT property, written into paint. Mapbox tolerated
the misplacement; MapLibre rejected the layer, so every pin on
all-people-groups was absent under the keyless engine while the page looked
perfectly healthy. Moved to `layout`; the sort order itself is unchanged.
Screenshots read on both engines: 12,379 pins, identical.

NEW GATE check:maplibre-drops loads every isMap leaf under maplibre and
fails on the layer/source-dropping error family. It skips cleanly when the
server is unreachable, because an UNMEASURED check is not a FAILED one --
the flaw already filed against check:filter-width.

★ THE NEGATIVE CONTROL WAS VACUOUS TWICE BEFORE IT WAS REAL, and that is
worth more than the fix. The first poison renamed the layout block, which
does not reproduce the bug -- the property simply vanishes, no error. The
second poison was faithful, circle-sort-key back in paint, and the gate
STILL printed PASS. Cause: I rebuilt with `BUNDLE=... vite build` and did
not run `bun run tree`, so the leaf carried stale SRI, the browser REFUSED
the script, no map mounted, and the gate saw zero errors. A check that ran
correctly and measured nothing, reading as green -- the first entry in
AGENT-BRIEF's troubleshooting table, walked into anyway.

After `bun run tree` (check:sri 0) the gate FAILED on the real bug, naming
the exact property, and PASSED again once restored. Both directions seen on
this codebase, which is the only thing that makes its green mean anything.

gates:pre 0, gates:post 17/18 (pre-existing verify/tables-complete),
check:doc-scripts 0, check:no-orphan-gates 0.
2026-09-24 06:24:40 +00:00
doxa-maps-88655c70
b0a267c894 Sync the locale tree forward from wt-packet; stop grading another repo's locales
This repo's check-i18n-parity hardcoded an absolute path to wt-packet, so it
read THAT repo's locales and reported PASS on a tree it never opened. Making it
repo-relative revealed this tree was 103 keys behind in it/ro/zh and short in
every other locale too — including buttons.json:share and shareSteps, the exact
orphan the gate's own header describes it as having been written to catch. The
bug it was built for had reappeared in a second repo, invisible.

The tree is a strict subset of wt-packet's: zero keys exist only here, zero
translations disagree. So this is a lossless forward copy, not a merge. All 11
locales now carry 10 of 11 areas; regions.json in it/ro/zh is the only debt.

Adds check-tabs-localized, which renders the tab labels and asserts they CHANGE
with the locale — key-parity alone cannot distinguish a shipped translation from
a dead catalog.
2026-09-24 06:23:39 +00:00
doxa-maps-88655c70
4cb04480cc Localize tab labels for it/ro/zh, and prove the catalog is actually read
Some checks are pending
build-gate / build (push) Waiting to run
120 keys across three locales: legend.json (12 each), search.json kinds (10
each) and tabs.json (18 each, previously absent entirely). Each locale follows
its own established vocabulary rather than a parallel one — Popolo/Popor/民族,
matching the terms already used in that catalog's detail and legend strings.
zh stays Simplified here, as the rest of this tree is, even though the prayer
packet's zh block is Traditional: a reader sees 民族 next to 祷告进度, never
the two files side by side.

Ceilings drop 94 -> 54 per locale. regions.json is the only i18n debt left in
the app: 54 keys of full sentences, three locales.

WHY THE NEW GATE. check:i18n-parity compares catalogs TO EACH OTHER, so it
certifies a file no t() call ever reaches — perfect symmetry, zero consumers,
green forever. Grepping for t('tabs. found ZERO call sites and tabs.json was
nearly recorded as dead. It is not: the only consumer builds the key
dynamically (SemanticTreeLegend.vue:40, 'tabs.' + (tab.i18nKey || tab.id)),
which no literal-prefix grep can see. The reverse error appeared in the same
pass — regions. and options. matched only poster-regions.mjs and a JS options
parameter. Static reachability is unavailable in both directions, so
check:tabs-localized renders the labels and reads them.

It asserts DIFFERENCE from English, not mere presence: te() falls back to the
hardcoded English label, so "shows some text" passes on the exact failure
being tested. Mutation-tested three ways — missing catalog FAILs, an all-English
catalog FAILs (first draft PASSed it, taking a per-pair exemption seven times),
one legitimate collision still passes with a note.

Both i18n gates now run in gates:post; neither was wired into the build before.

Also fixed, found by the build rather than sought:
- check-i18n-parity hardcoded an absolute path, so the studio copy silently
  graded wt-packet's locales and reported PASS on a tree it never read. Now
  repo-relative. That exposed the studio's own locale tree as a strict subset,
  103 keys behind including buttons.json:share — the very orphan this gate's
  header says it was written to catch, reappeared unseen in a second repo.
  Synced forward: zero studio-only keys, zero text conflicts, lossless.
- index.json still declared field-access-visas with rows:1146, a leftover from
  the commit that removed that table as private. The generator already filters
  private tables; the committed artifact predated the filter. Removing the
  declaration also removes the row-count leak. Copying the file in would have
  reopened the leak.
- check:sfc-parse needs an installed node_modules and is the first pre-gate to
  fail without one, so a dependency-less worktree reads as a parse error.
  Documented, with the reason switching it to bun only appears to fix it.
2026-09-24 06:23:30 +00:00
doxa-maps-88655c70
66c4d3163b Merge main into bundler-1/work (regenerate the tree, re-hash)
Some checks are pending
build-gate / build (push) Waiting to run
Six conflicts, all generated artifacts under public/, resolved by
rebuilding the three PosterWall consumers and regenerating the tree
rather than hand-merging built files.

Main brought the i18n catalogues to parity, and another builder has
translated this branch's three poster-dropdown strings into all seven
non-English locales — so the 'en only' caveat on that feature is now
closed. Main's two new gates, check:i18n-parity and
check:locale-reachable, both pass against the merged catalogue.
2026-09-24 06:01:41 +00:00
doxa-maps-88655c70
0fffb90cca test(i18n): ratchet down again — seven locales must now stay complete
Some checks are pending
build-gate / build (push) Waiting to run
344 -> 282 keys. de and hi reached zero and join ar/es/fr/pt/ru as entries
DELETED from the DEBT map: the gate treats an absent locale as "must be
complete", so removal is what makes a finished translation permanent.

The three that remain (it, ro, zh) are each missing WHOLE catalog files rather
than stray keys, which is worth stating in the file — it tells the next person
these are real translation work, not the propagation gaps the last two ticks
closed.

Confirmed the guarantee before relying on it: deleting one Hindi tab key reports
"hi is missing 1/176 key(s) and has no DEBT entry" rather than passing.
2026-09-24 05:58:55 +00:00
doxa-maps-88655c70
10f83a943b Regions: links we render as buttons had lost their anchor styling
Found by a COVERAGE AUDIT, not a screenshot. region-vs-live diffs
/regions/africa and country-vs-live diffs only UupgsList's nine
elements, so the country page's head and the region card had no
property diff at all. Adding both produced four defects sharing one
root cause: DX-life renders these as <a>, we render them as <button>
because this element has no router, and a button inherits neither the
UA underline nor their `a` rule (_globals.scss:132 — color
var(--color-brand-light), font-weight medium,
text-decoration-thickness 2px, text-underline-offset 4px).

- The dashboard back-link was #4e594f with no underline against their
  #cbc5b9 underlined. Their .light-link recolours to
  --color-brand-secondary = $color-beige-300 = #cbc5b9, exactly the
  measured rgb(203, 197, 185). Wrong on BOTH our region and country
  pages.
- The region-card link was font-weight 400 against their 500.
- Its icon spacing came from gap:10px where theirs comes from
  .with-icon .icon.right { margin-inline-start: 1rem } — the same 10px
  on screen, but their rule would never have applied to our markup, so
  the mechanism is now theirs.
- The country entries were rgb(42, 51, 42), our body ink, against
  their rgb(78, 89, 79) = $color-green-400 = --color-brand-light.
  Their country links carry no class at all — they simply take the `a`
  rule, which is why '.country-link' matched nothing on their side and
  the element read as unmeasured until the selector became
  '.countries-grid a'.

Coverage added so none of this can come back: a new
_probe-country-head-vs-live.mjs at 29/29 (back-link, h1, strapline, map
note, search-box container); the back-link added to region-vs-live,
which went 73 to 79 IDENTICAL; the search-box CONTAINER added to
country-vs-live, 28 to 32, so the 50ch cap cannot regress; the region
card added to list-vs-live at 30/30.

Three of my own assertions were wrong before the code was: I omitted
fontWeight and the underline geometry from the back-link spec while
their `a` sets all three; I compared `gap` on an element where theirs
never sets it; and my sub-pixel tolerance used Number('246.938px'),
which is NaN, so NaN <= 0.5 was false and it silently never fired.

Regression: 22 suites green, 9 gates green.
2026-09-24 05:58:43 +00:00
doxa-maps-88655c70
68963c5e1e test(i18n): ratchet down again — seven locales must now stay complete
344 -> 282 keys. de and hi reached zero and join ar/es/fr/pt/ru as entries
DELETED from the DEBT map: the gate treats an absent locale as "must be
complete", so removal is what makes a finished translation permanent.

The three that remain (it, ro, zh) are each missing WHOLE catalog files rather
than stray keys, which is worth stating in the file — it tells the next person
these are real translation work, not the propagation gaps the last two ticks
closed.

Confirmed the guarantee before relying on it: deleting one Hindi tab key reports
"hi is missing 1/176 key(s) and has no DEBT entry" rather than passing.
2026-09-24 05:58:25 +00:00
doxa-maps-88655c70
7580274ed4 feat(i18n): German and Hindi reach complete — 7 of 11 locales done
Some checks are pending
build-gate / build (push) Waiting to run
Continues paying the debt the parity gate measured, cheapest first.

German and Hindi carried an IDENTICAL 22-key gap — the same legend and search
keys, added to English after those two catalogs were last touched. One batch of
work closed German entirely and took Hindi to 18, which was its whole missing
tabs.json. Both are now complete: 344 -> 282 keys.

Terminology follows each catalog's OWN existing vocabulary rather than inventing
a second one for the same concepts — de keeps Volk/Sprachfamilien, hi keeps
जनसमूह/भाषा परिवार. Tab labels stay SHORT because they sit in a narrow rail:
Spanish already uses "Pueblos" rather than a literal rendering, so Hindi does
the same. UUPG, WAGF and DOXA remain acronyms, as in every locale.

Seven locales (ar es fr pt ru de hi) are now DELETED from the gate's DEBT map
rather than set to zero — an absent entry means "must be complete", so deletion
is what converts finished work into a permanent guarantee. Verified: removing
one Hindi tab key reports "hi is missing 1/176 key(s) and has no DEBT entry".

Verified at the RENDERED layer: "View country posters" comes back as
"Länderposter ansehen" (de) and "देश के पोस्टर देखें" (hi), zero page errors —
which also confirms Devanagari shapes correctly in the APP, not only in the
prayer packet where the font was first vendored.

The three remaining (it, ro, zh at 94 each) are missing WHOLE FILES —
regions.json and tabs.json — so they are real translation work rather than the
propagation gaps closed here.
2026-09-24 05:58:16 +00:00
doxa-maps-88655c70
b4902a7fed test(i18n): ratchet down — five locales now must stay complete
Some checks are pending
build-gate / build (push) Waiting to run
The ceilings drop as the debt is paid: 429 -> 344 keys, and ar/es/fr/pt/ru
reached zero.

Those five are REMOVED from the DEBT map rather than set to 0. The gate already
treats a locale with no entry as "must be complete", so deletion is what
converts a finished translation into a permanent guarantee — setting 0 would
read the same but leaves a line inviting someone to raise it.

Confirmed the guarantee works before relying on it: deleting one Spanish key
reports "es is missing 1/176 key(s) and has no DEBT entry" rather than passing.
2026-09-24 05:53:31 +00:00
doxa-maps-88655c70
8cef804bae test(i18n): ratchet down — five locales now must stay complete
The ceilings drop as the debt is paid: 429 -> 344 keys, and ar/es/fr/pt/ru
reached zero.

Those five are REMOVED from the DEBT map rather than set to 0. The gate already
treats a locale with no entry as "must be complete", so deletion is what
converts a finished translation into a permanent guarantee — setting 0 would
read the same but leaves a line inviting someone to raise it.

Confirmed the guarantee works before relying on it: deleting one Spanish key
reports "es is missing 1/176 key(s) and has no DEBT entry" rather than passing.
2026-09-24 05:53:03 +00:00
doxa-maps-88655c70
a487e48cbf feat(i18n): five locales reach complete — popup.json and six orphaned keys
Some checks are pending
build-gate / build (push) Waiting to run
Working the 429-key debt the parity gate surfaced last tick, cheapest first.

popup.json existed ONLY in English and French — the country popup's four labels
(WAGF Region, WAGF Bloc, Country, UUPG Count), which is what a reader sees when
they click a country on the map. Now in all 11. WAGF and UUPG stay as acronyms
in every language, following what French already did: they are proper nouns of
the dataset, not words to translate.

Then six keys that were missing from EVERY non-English locale identically —
added to English at some point and never propagated: legend header.default,
search sections.within/allData, and the three country-poster strings in
regions.json. The {selection} placeholder is preserved.

Result: ar, es, fr, pt and ru are now COMPLETE, and 429 -> 344 keys of debt.
Those five are deliberately REMOVED from the gate's DEBT map rather than set to
zero — a locale with no entry must be complete, so none of them can silently
regress. Verified: deleting one Spanish key reports "es is missing 1/176 key(s)
and has no DEBT entry".

Verified at the RENDERED layer, not by counting files: "View country posters"
comes back as "Ver los carteles del país" under ?lang=es and "Показать плакаты
страны" under ?lang=ru, zero page errors.

★ One key I translated turned out to be DEAD: legend.header.default is consumed
by nothing — my first probe appeared to test it and was actually matching the
substring "Legend" inside this profile's hardcoded LEGEND_TITLE ("My UPG 100
List — Legend"). A probe that matches an artifact proves nothing; the assertion
has to be a string only the translated key can produce.
2026-09-24 05:52:53 +00:00
doxa-maps-88655c70
ce750f5b55 test(i18n): a key in English must exist in every locale — ratcheted
Some checks are pending
build-gate / build (push) Waiting to run
The failure here is silent BY DESIGN. vue-i18n falls back to English for a
missing key, which is the right runtime behaviour and exactly why catalogs drift
unnoticed: the app looks fine, nothing errors, and a reader who chose Spanish
just gets English words with no signal anything is missing.

Measured when this was written: buttons.json had 21 keys in English and 19 in
every other locale. The orphans were `share` and `shareSteps` — the Share
button's title and its explainer, rendered on EVERY map. Ten locales had been
shipping an English Share button since those keys landed.

★ IT FOUND MUCH MORE THAN THE BUG THAT PROMPTED IT: 429 missing keys across ten
locales, with popup.json existing only in English and it/ro/zh also lacking
regions.json and tabs.json entirely — 101 of 176 keys each.

So it RATCHETS rather than blocks. Each locale has a ceiling; the gate fails
when one gets WORSE and prints a nudge when one gets better so the ceiling can
be lowered. Failing outright on 429 keys of debt this gate did not create would
block every build, and a gate everyone disables protects nothing. A locale with
no ceiling entry must be complete, so a NEW locale cannot be added half-done.

Keys are compared FLATTENED, so a nested object or an array is checked
key-by-key rather than counted as one present key.

Confirmed to fail before acceptance: deleting two French keys reports
"fr regressed: 8 missing key(s), was 6" and names them.
2026-09-24 05:46:13 +00:00
doxa-maps-88655c70
e69c76f5d4 chore(i18n): regenerate the tree after rebasing onto main
Some checks are pending
build-gate / build (push) Waiting to run
Generated output only — three bundles rebuilt and the tree regenerated so the
SRI hashes match the shipped scripts. Re-verified rather than assumed:
check:i18n-parity and check:deck-click both pass afterwards.
2026-09-24 05:45:57 +00:00
doxa-maps-88655c70
7fd1c6bcd1 feat(i18n): the poster wall's own labels now translate
Last tick made the 11 locales selectable. That immediately exposed what was
sitting behind the door: the wall's labels — Poster Map, Prayer List, Card Deck,
Print Options, Show on map, Close — were hardcoded English, so a reader who
chose Spanish got Spanish page furniture around English buttons.

All six are now keys in buttons.json, translated across all 11 locales, plus the
aria-label which interpolates the item name rather than concatenating it.

★ AND IT CLOSED TWO OLDER ORPHANS. buttons.json had 21 keys in English and 19
everywhere else: `share` and `shareSteps` — the Share button's title and its
five-step explainer, rendered on EVERY map. Ten locales had been shipping an
English Share button since those keys were added, invisibly, because vue-i18n
falls back to English rather than erroring. Both are translated now.

Verified in a real browser across four languages: the wall's buttons read
"Lista de oración | Mapa del cartel" under ?lang=es, "Liste de prière | Carte de
l'affiche" under ?lang=fr, and the Arabic text renders with dir=rtl — zero page
errors in each. The Share button's title follows too: Compartir, Поделиться.

The wall's `t` and `locale` come from the SAME useI18n instance, so a label and
a printed card deck can never disagree about which language the reader chose.
2026-09-24 05:44:06 +00:00
doxa-maps-88655c70
986b83ee77 test(i18n): a key in English must exist in every locale — ratcheted
The failure here is silent BY DESIGN. vue-i18n falls back to English for a
missing key, which is the right runtime behaviour and exactly why catalogs drift
unnoticed: the app looks fine, nothing errors, and a reader who chose Spanish
just gets English words with no signal anything is missing.

Measured when this was written: buttons.json had 21 keys in English and 19 in
every other locale. The orphans were `share` and `shareSteps` — the Share
button's title and its explainer, rendered on EVERY map. Ten locales had been
shipping an English Share button since those keys landed.

★ IT FOUND MUCH MORE THAN THE BUG THAT PROMPTED IT: 429 missing keys across ten
locales, with popup.json existing only in English and it/ro/zh also lacking
regions.json and tabs.json entirely — 101 of 176 keys each.

So it RATCHETS rather than blocks. Each locale has a ceiling; the gate fails
when one gets WORSE and prints a nudge when one gets better so the ceiling can
be lowered. Failing outright on 429 keys of debt this gate did not create would
block every build, and a gate everyone disables protects nothing. A locale with
no ceiling entry must be complete, so a NEW locale cannot be added half-done.

Keys are compared FLATTENED, so a nested object or an array is checked
key-by-key rather than counted as one present key.

Confirmed to fail before acceptance: deleting two French keys reports
"fr regressed: 8 missing key(s), was 6" and names them.
2026-09-24 05:43:32 +00:00
doxa-maps-88655c70
24a4520bc0 Fix the import I omitted in the locale-reachable portability fix
Some checks are pending
build-gate / build (push) Waiting to run
My previous commit changed the REPO fallback to
resolve(dirname(fileURLToPath(import.meta.url)), '..') but did not add the
fileURLToPath import, so the gate died at load with

    ReferenceError: fileURLToPath is not defined

and I pushed it. node --check passed because that is a RUNTIME error, not a
syntax one, and check:no-abs-paths went green because the offending literal
really was gone -- so both of the checks I ran said yes while the gate
itself could no longer run at all. The script's own exit code printed 1 in
the same output I pushed from, and I did not stop on it.

Verified this time by running the gate, not around it:
check:locale-reachable 0, check:no-abs-paths 0.
2026-09-24 05:40:58 +00:00
doxa-maps-88655c70
05bc4c45c5 locale-reachable: default to this repo, not one author's worktree
Some checks are pending
build-gate / build (push) Waiting to run
Fourth instance of one pattern in this flight, after check-zone-census,
check-share-policy and check-poster-prints: a new internal/ gate hardcodes
its author's worktree (/home/.../wt-packet) as the REPO fallback, and
check:no-abs-paths -- which runs inside build:unlocked -- turns red for
every other builder on the next merge.

DOXA_REPO still wins and is untouched; only the default becomes portable,
via the idiom check-no-absolute-paths.mjs prints in its own failure text.
Verified both ways: check:no-abs-paths 0, check:locale-reachable 0 on the
derived default.

Four occurrences is a template problem, not four accidents.
2026-09-24 05:40:06 +00:00
doxa-maps-88655c70
27e397225e Merge main into bundler-2/work
Some checks are pending
build-gate / build (push) Waiting to run
# Conflicts:
#	public/doxa-maps/country-posters/countries-map/index.html
#	public/doxa-maps/country-posters/country-poster-wall/index.html
#	public/doxa-maps/country-posters/country-posters-mfe/index.html
#	public/doxa-maps/country-posters/country-posters.js
#	public/doxa-maps/index.html
#	public/doxa-maps/manifest.json
#	public/doxa-maps/regions-dashboard/region-dashboards/index.html
#	public/doxa-maps/regions-dashboard/regions-dashboard.js
#	public/doxa-maps/regions-dashboard/regions-list/index.html
#	public/doxa-maps/regions-dashboard/regions-mfe/index.html
2026-09-24 05:39:04 +00:00
doxa-maps-88655c70
b2f441b075 MapLibre drops a layer whose paint it does not recognise
The region fills did not paint under MapLibre, and three ticks of
instrumenting addLayer() found nothing because the failure never travels
through addLayer's return path. Listening to the map's own `error` EVENT --
the only place it surfaces -- gave it in one run:

    layers.regions-fill.paint.fill-emissive-strength: unknown property
    layers.regions-border.paint.line-emissive-strength: unknown property

*-emissive-strength is a Mapbox GL v3 lighting property. MapLibre rejects a
layer whose paint carries an unknown property and DROPS THE WHOLE LAYER:
addLayer() accepts the call, emits an error event, and silently omits the
layer. getLayer() then returns undefined and the map renders without it, so
it reads as "the fills do not paint" rather than as a failure.

★ THE REPO ALREADY KNEW THIS, FOR CIRCLES ONLY. useMapProvider.emissivePaint()
exists and its own docstring says "the whole layer is dropped -> invisible
pins" -- but it was hardcoded to 'circle', while the fill, line and
clustering layers still wrote the literal and kept being dropped for exactly
the documented reason. Generalised to emissivePaint(map, kind) and applied
at all five literal sites: useMapLayers x3, useCountryOutline,
useMapClustering. One helper, one rule, no sixth copy.

A second defect was hiding behind the first: regions-border still declared
'source-layer' on the geojson source ("Source layer country_boundaries does
not exist on source"), which only became visible once the paint error
stopped masking it. Fixed the same way as regions-fill.

MEASURED on the built tree: 0 error events, both layers present, and the
screenshot shows the WAGF fills painting under MapLibre -- green, blue,
purple, red, orange -- matching the Mapbox control, which is itself
re-shot and unchanged. Provider sweep 15/17 (the 2 render no map at all),
gates:pre 0, gates:post 17/18 with only the pre-existing
verify/tables-complete failure.
2026-09-24 05:38:29 +00:00
doxa-maps-88655c70
39ec4b42bd test(i18n): every packet language must be SELECTABLE, not merely present
Some checks are pending
build-gate / build (push) Waiting to run
The bug this gates is unreachability, not incorrectness. The packet shipped 11
complete translations while detectLocale() read only <html lang>, which every
generated leaf page hardcodes to "en" — so ten of them could not be reached by
any reader on the static tree. Nothing was wrong; nothing was visible.

Two assertions, because they fail separately:

  RESOLUTION    ?lang= / ?locale= selects a supported locale, an unsupported
                value falls back to English, a region tag (pt-BR) resolves to its
                base, and with no param the host page still wins.
  REACHABILITY  the packet's language list and SUPPORTED_LOCALES cannot drift
                apart, and the leaf pages still declare a lang — so if someone
                later drops that attribute, the host-page path does not silently
                become unavailable while the tests stay green.

★ It evaluates the SHIPPED detectLocale rather than reimplementing it. A gate
that reimplements the thing it tests only ever proves the copy agrees with
itself — which is how a fix can be reverted in the source while the gate keeps
passing.

Confirmed to fail before acceptance: removing the URL branch reports all four
param cases resolving to "en".
2026-09-24 05:36:45 +00:00
doxa-maps-88655c70
e2a47b1366 chore(i18n): regenerate the tree after rebasing onto main
Some checks are pending
build-gate / build (push) Waiting to run
Generated output only. Regenerating rather than hand-merging keeps the tree a
true function of the merged source — and refreshes the SRI hashes, which is not
optional: a stale hash makes the browser refuse the script and the page mounts
NOTHING, silently. Re-verified afterwards rather than assumed: check:locale-reachable
and check:deck-click both pass.
2026-09-24 05:36:30 +00:00
doxa-maps-88655c70
f7f4b8c1e8 fix(i18n): let a reader actually CHOOSE one of the 11 locales
The packet ships in 11 languages, every string translated. detectLocale() read
ONLY document.documentElement.lang — and EVERY generated leaf page in this repo
hardcodes lang="en" (measured: 20 of 20, later 34 of 34 after the tree grew).

So on the built static tree — the jamstack case these bundles exist for — ten of
the eleven translations could not be reached by any reader. Correct, complete,
and invisible. Several ticks of translation work landed behind a door with no
handle.

?lang= / ?locale= is now consulted FIRST, then the host page, then English. A
query parameter is the smallest thing that makes them reachable: no per-language
page, linkable and shareable, and the WordPress/Polylang path through <html lang>
is untouched — a gate assertion keeps the leaf pages declaring a lang so that
path cannot silently become the only way in. Both spellings are accepted because
both are in use here (`lang` in profile configs, `locale` in the i18n layer) and
guessing wrong is a silent no-op.

⚠ AND MAKING ARABIC REACHABLE EXPOSED THE NEXT PROBLEM. Only ONE component reads
RTL_LOCALES today, so selecting ar would have given Arabic TEXT inside a
left-to-right layout everywhere else — readable glyphs in the wrong place, which
reads as a styling quirk rather than a bug. createAppI18n now reflects an RTL
locale onto document.dir. It only ever SETS it, never clears it: a host page that
already declares dir owns that decision, and stomping it to "ltr" for an English
map embedded in an Arabic page would break the page around us.

Verified: ?lang=ar sets dir=rtl in a real browser; ?lang=zz correctly falls
through to English. detectLocale is exercised against its SHIPPED source across
7 cases including pt-BR -> pt.

★ One thing this tick taught me the hard way: rebuilding a bundle WITHOUT
regenerating the tree leaves the leaf page carrying the OLD SRI hash, so the
browser refuses the script and NOTHING MOUNTS — silently. I spent three probes
reading an empty page before checking the integrity attribute.
2026-09-24 05:35:11 +00:00
doxa-maps-88655c70
d0ecd2f780 test(i18n): every packet language must be SELECTABLE, not merely present
The bug this gates is unreachability, not incorrectness. The packet shipped 11
complete translations while detectLocale() read only <html lang>, which every
generated leaf page hardcodes to "en" — so ten of them could not be reached by
any reader on the static tree. Nothing was wrong; nothing was visible.

Two assertions, because they fail separately:

  RESOLUTION    ?lang= / ?locale= selects a supported locale, an unsupported
                value falls back to English, a region tag (pt-BR) resolves to its
                base, and with no param the host page still wins.
  REACHABILITY  the packet's language list and SUPPORTED_LOCALES cannot drift
                apart, and the leaf pages still declare a lang — so if someone
                later drops that attribute, the host-page path does not silently
                become unavailable while the tests stay green.

★ It evaluates the SHIPPED detectLocale rather than reimplementing it. A gate
that reimplements the thing it tests only ever proves the copy agrees with
itself — which is how a fix can be reverted in the source while the gate keeps
passing.

Confirmed to fail before acceptance: removing the URL branch reports all four
param cases resolving to "en".
2026-09-24 05:34:47 +00:00
doxa-maps-88655c70
ba6b9229f3 Merge main into bundler-1/work (regenerate the tree, re-hash)
Some checks are pending
build-gate / build (push) Waiting to run
Six conflicts, all in generated artifacts under public/, including this
bundle's own — another builder's tree regeneration had rebuilt it.
Regenerated rather than hand-merged: the three PosterWall consumers
rebuilt, then the tree and the hashes.

PosterWall.vue itself auto-merged with both sides intact — this
branch's embedded/sectionHeads props and main's usePosterPrints
refactor — and the four suites that exercise it through the regions
poster dropdown were re-run green after the merge, since their change
lands inside the component this feature renders.
2026-09-24 05:33:32 +00:00
doxa-maps-88655c70
9e8d53bf3b test(poster): assert the shared PACKET action's shape, not just its bytes
Some checks are pending
build-gate / build (push) Waiting to run
The packet is the product's core artefact, and it moved into the shared
composable this tick. The gate now drives printPacket and asserts what came out:
4 sheets for 2 groups (map one-pager + one people page + prayer points + prayer
wheel), 12 wheel segments, Mark 5:1-20 present. Confirmed to fail before
acceptance — removing the wheel page reports 3 sheets and 0 segments.

⚠ THE HARNESS HAD TO RESOLVE THE RENDERERS, and the first run exposed why. The
composable imports them by RELATIVE path; under a blob URL there is no directory
to resolve '../packet/...' against, so the action failed with "Failed to resolve
module specifier" and printed nothing — reported as 0 sheets, which reads like a
product bug. It is not: Vite resolves them and the built bundle inlines both
renderers (verified in the shipped my-upg-100-list.js). The harness now
substitutes blob URLs for packet-render, card-render, qr and the strings catalog,
so the gate exercises the REAL renderers rather than a stub.
2026-09-24 05:23:05 +00:00
doxa-maps-88655c70
a0910bcdef refactor(packet): the legend's packet export uses the shared action
Some checks are pending
build-gate / build (push) Waiting to run
Continues the consolidation. The card deck moved to usePosterPrints last tick;
the PACKET export in upg-100-list.vue was still a second hand-maintained copy of
the same shape — its own renderer import, its own strings fetch, its own
language resolution. Two copies of one fact drift.

printPacket now lives in the composable, so the browser export and the
build-time generator share one renderer AND one calling convention. What stays
in the bundle is what genuinely cannot be shared:

  packetRecords  who is in this list — the people-groups API, the selector
                 evaluator, and the pg_slugs special case that 12 of the 27
                 lists need (the evaluator intentionally THROWS on that type)
  packetMapSvg   where this bundle's posters are served from. Absent is fine:
                 the packet prints a cover without the map rather than failing

⚠ AND THE MIGRATION WOULD HAVE MADE THE PACKET PRINT SILENTLY. The composable
reports through its own exportState; this profile has no HUD for that and shows
`status` instead — exportState appears ZERO times in the file. Without a bridge
there would be no "building…", and a failure would vanish with nothing said: the
mechanism working while the feedback does not, which is the exact trap the wall
consolidation hit from the other direction. A watcher now maps the composable's
phases onto `status`.

Verified: check:poster-prints drives the shared action and asserts the packet's
SHAPE — 4 sheets for 2 groups (map + people page + points + wheel), 12 wheel
segments, Mark 5:1-20 present — not merely that bytes were produced. Confirmed
to fail before acceptance by removing the wheel page (3 sheets, 0 segments). The
build-time generator still passes check:packet-render at 8/8, and
check:deck-click still prints 116 cards through the real button.
2026-09-24 05:22:29 +00:00
doxa-maps-88655c70
248c2cc6d6 test(poster): assert the shared PACKET action's shape, not just its bytes
The packet is the product's core artefact, and it moved into the shared
composable this tick. The gate now drives printPacket and asserts what came out:
4 sheets for 2 groups (map one-pager + one people page + prayer points + prayer
wheel), 12 wheel segments, Mark 5:1-20 present. Confirmed to fail before
acceptance — removing the wheel page reports 3 sheets and 0 segments.

⚠ THE HARNESS HAD TO RESOLVE THE RENDERERS, and the first run exposed why. The
composable imports them by RELATIVE path; under a blob URL there is no directory
to resolve '../packet/...' against, so the action failed with "Failed to resolve
module specifier" and printed nothing — reported as 0 sheets, which reads like a
product bug. It is not: Vite resolves them and the built bundle inlines both
renderers (verified in the shipped my-upg-100-list.js). The harness now
substitutes blob URLs for packet-render, card-render, qr and the strings catalog,
so the gate exercises the REAL renderers rather than a stub.
2026-09-24 05:22:17 +00:00
doxa-maps-88655c70
63fad20739 Regions: their worldmap watermark, and two defects it uncovered
Measured first, as the Oswald lesson requires, and this one was real:
their UupgsList root carries worldmap.svg inline (UupgsList.vue:451),
and it genuinely renders on their live country page — HTTP 200, 62,740
bytes over the wire, computed background-size 60%, position 50% 0%,
repeat no-repeat, min-height 400px. Confirmed by screenshotting the
ELEMENT, not the viewport: the watermark sits below the fold, so a page
screenshot shows nothing at all.

public/assets/images/worldmap.svg is their file byte-for-byte — 311KB on
disk, small against this tree's existing assets, which run 644KB to
42MB. The inline background-image takes its base from a new imagesBase
prop, since a dropped bundle cannot assume their absolute /assets path.

generate-tree's staged allowlist needed 'images' added — the same trap
that silently broke the vendored fonts. A dir absent from the allowlist
404s, and an unfetched background-image is quieter than the font
failure was: nothing paints and nothing reports. So the probe asserts
the REQUEST returned 200 rather than that the CSS names the file.

Then, reading the screenshot after every number was already green, two
more defects:

- A CLASS WITHOUT ITS RULE. The search label uses their class name,
  sr-only — the right instinct — but their rule
  (utilities/_visibility.scss:2) was never ported, so the class matched
  nothing and the label rendered as a visible line of text above the
  input, duplicating its own placeholder. Borrowing a class name only
  works if its declarations travel with it, and a class matching no rule
  is invisible to a property diff. Now 1x1px and clipped, still in the
  DOM and still announced.

- A PIXEL CAP WHERE THEIRS IS RELATIVE. .search-box was capped at a
  hardcoded max-width:480px; theirs composes .max-width-md, which is
  max-width:50ch (utilities/_sizes.scss:11). Wrong value and wrong unit —
  ch tracks the input's font, px does not — and ours truncated the
  placeholder mid-word. 50ch now resolves to exactly 600px, their
  measured width.

Regression: 18 suites green, including a new _probe-watermark.mjs at
15/15.
2026-09-24 05:15:06 +00:00
doxa-maps-88655c70
bc18753985 Merge main into bundler-2/work
Some checks are pending
build-gate / build (push) Waiting to run
2026-09-24 05:14:55 +00:00
doxa-maps-88655c70
e263991dc5 test(deck): click the REAL button and read what it prints
Some checks are pending
build-gate / build (push) Waiting to run
Every other packet/card gate drives the RENDERERS. This one drives the UI: it
finds the Card Deck button in the shadow root, clicks it, and captures the
document that reaches the print frame. That is the only path proving the wiring
— records provider, stock picker, language, shared composable, print frame — is
connected end to end. A renderer can be perfect while the button calling it
passes the wrong stock, or nothing at all.

⚠ It intercepts the printed HTML rather than reading the iframe back.
openPrintFrame calls window.print(), which BLOCKS in headless Chromium, and the
frame self-removes on afterprint — reading it back measures a frame that has
already gone and reports 0 cards for a correct deck.

Measured through the real UI: 116 cards at 2.5in 3.5in with 116 QR codes, and
3.5in 2.5in when the stock picker is switched to landscape. Confirmed to fail on
a wrong expected page size.

Also updates the share policy: country-posters moves from 'own-button' to
'header'. Its two-tab MFE now owns sharing, exactly as my-upg-100-list's does —
the gate caught the bundle briefly carrying two affordances after the MFE
landed, which is the regression it was written for.
2026-09-24 05:13:03 +00:00
doxa-maps-88655c70
571670f1d3 chore(poster): regenerate the tree after rebasing onto main
Some checks are pending
build-gate / build (push) Waiting to run
Generated output only — built bundles, SRI hashes, leaf pages and the manifest —
conflicted because another agent's work landed in the same files. Resolving
those by hand yields a tree matching neither agent's source, so this takes
main's versions and regenerates. Re-verified rather than assumed: check:deck-click
and check:mfe-two-tab both pass afterwards.
2026-09-24 05:12:41 +00:00
doxa-maps-88655c70
09460e1777 poster-prints: default to this repo, not one author's worktree
Third instance of one pattern in this flight, after check-zone-census and
check-share-policy: a new internal/ gate authored in one worktree hardcodes
THAT worktree (/home/.../wt-packet) as its REPO fallback, and
check:no-abs-paths -- which runs inside build:unlocked -- turns red for
every other builder on the next merge.

Same minimal fix each time: DOXA_REPO still wins and is untouched; only the
default becomes portable, via the idiom check-no-absolute-paths.mjs prints
in its own failure text. Verified both directions: check:no-abs-paths 0, and
check:poster-prints itself 0 on the derived default.

Three occurrences is a pattern, not bad luck. Whoever is generating these
gates should take the portable default as the template, or the next one
lands red too.
2026-09-24 05:11:10 +00:00
doxa-maps-88655c70
6c32623856 refactor(poster): the 100-list wall stops carrying its own copies
Finishes the half this card left open last tick. The shared print composable
existed but the 903-line 100-list wall did not use it, so the card deck lived in
TWO places — I wrote both this flight, and they could already drift.

Two duplicates removed:

  printCardDeck   now delegates to usePosterPrints, sharing THIS wall's
                  exportState so the HUD keeps rendering one surface. The bundle
                  still owns what cannot be shared — fetchListRecords knows the
                  people-groups API, the selector evaluator and the pg_slugs
                  special case — and hands it over as the `records` provider. The
                  preview-only "no API configured" hint is preserved, because the
                  shared action cannot know about it.

  openPrintWindow was a local reimplementation of printInPlace.js — same
                  off-screen iframe, same doc.write, same autoprint script,
                  differing only in an unused `title` argument. A FOURTH copy of
                  one mechanism is how a fix to the blank-first-page behaviour
                  reaches one caller and not the others. Now a one-line adapter.

★ AND check:share-policy CAUGHT A REGRESSION I HAD CAUSED. Adding the
country-posters two-tab MFE last tick gave that bundle a second ShareButton —
the BUG-UPG100-DOUBLE-SHARE state the 100-list already fixed once. Fixed the
same way the 100-list fixed it: the MAP's button is REMOVED and the MFE header
owns sharing. Removed, not flag-gated — a `v-if="false"` button is dead code
that still reads as an affordance to every reader and every counter, which is
exactly why the gate counts tags rather than renders.

Verified by clicking the REAL button in a browser, not by driving the renderer:
116 cards at 2.5in 3.5in with 116 QR codes, and 3.5in 2.5in when the stock
picker is switched to landscape — so records provider, picker, language, shared
composable and print frame are connected end to end. check:mfe-two-tab still
reports exactly one share affordance on the country-posters MFE.
2026-09-24 05:10:44 +00:00
doxa-maps-88655c70
394741bf7a test(deck): click the REAL button and read what it prints
Every other packet/card gate drives the RENDERERS. This one drives the UI: it
finds the Card Deck button in the shadow root, clicks it, and captures the
document that reaches the print frame. That is the only path proving the wiring
— records provider, stock picker, language, shared composable, print frame — is
connected end to end. A renderer can be perfect while the button calling it
passes the wrong stock, or nothing at all.

⚠ It intercepts the printed HTML rather than reading the iframe back.
openPrintFrame calls window.print(), which BLOCKS in headless Chromium, and the
frame self-removes on afterprint — reading it back measures a frame that has
already gone and reports 0 cards for a correct deck.

Measured through the real UI: 116 cards at 2.5in 3.5in with 116 QR codes, and
3.5in 2.5in when the stock picker is switched to landscape. Confirmed to fail on
a wrong expected page size.

Also updates the share policy: country-posters moves from 'own-button' to
'header'. Its two-tab MFE now owns sharing, exactly as my-upg-100-list's does —
the gate caught the bundle briefly carrying two affordances after the MFE
landed, which is the regression it was written for.
2026-09-24 05:10:17 +00:00
doxa-maps-88655c70
916ef35ee8 Merge main into bundler-2/work
Some checks are pending
build-gate / build (push) Waiting to run
# Conflicts:
#	public/doxa-maps/country-posters/countries-map/index.html
#	public/doxa-maps/country-posters/country-poster-wall/index.html
#	public/doxa-maps/country-posters/country-posters-mfe/index.html
#	public/doxa-maps/country-posters/country-posters.js
#	public/doxa-maps/index.html
#	public/doxa-maps/manifest.json
2026-09-24 05:09:42 +00:00
doxa-maps-88655c70
fc69adbbd5 Country boundaries load on MapLibre in the shared library, not per plugin
main added country-posters-mfe (isMap=true). It inherits the engine swap
correctly -- 14 -> 4 requests to api.mapbox.com, maplibregl loaded -- but
threw

    Fetch API cannot load mapbox://mapbox.country-boundaries-v1.
    URL scheme "mapbox" is not supported

A new map arriving is exactly what the rollout has to cover, so this was
worth chasing rather than noting.

TWO shared library sites, not one, which only measuring found. Fixing
useCountryOutline.js (consumers: doxa-simple-map, countries-map,
research-map) cleaned two of the four leaves and left countries-map -- and
the new element nesting it -- still throwing, from a SECOND site:
useMapLayers.js addRegionsLayer. Both now branch per engine, using the
keyless assets/boundaries/world-adm0.geojson the tree already ships, with
iso3 normalised once to iso_3166_1_alpha_3 so every downstream expression
keeps reading the name it always read, and 'source-layer' set for the
vector-tile path alone.

The engine is read off the LIVE map -- its container carries
maplibregl-map -- rather than from config, because both composables are
called with nothing but a map getter.

MEASURED: 0 mapbox:// errors across country-posters-mfe, countries-map,
doxa-simple-map, research-map and demo-map; the adm0 fetch confirmed firing;
provider sweep 15/17. The Mapbox path is unchanged -- its control screenshot
still shows the coloured WAGF region fills.

Also fixes check:descriptions, which the new profile red-gated by shipping
an empty description. Written from the component's own header, not a
placeholder: 34/34 profiles described.

NOT FIXED, and recorded in the ledger rather than glossed: under MapLibre
countries-map still paints NO coloured region fills. The error is gone and
the geojson loads, but the fill does not appear. That is a separate defect
from the source failing, and claiming this commit finishes it would be
false.
2026-09-24 05:09:12 +00:00
doxa-maps-88655c70
7f8b75d661 Experimental shelf: Kingdom-impact order + curated exclusions
Some checks are pending
build-gate / build (push) Waiting to run
The Experimental folder now presents most-impactful first per the
finalized roundup ranking (EXPERIMENTAL-MAPS-ROUNDUP.md): Prayer Walk
Planner, the rescued legacy set (entry: send-map), All People Groups,
Country Posters, Vision Card -- rank is editorial curation and lives
beside TAB_DEF; unranked bundles still discover and sort A-Z after.
Verified in the built shell rail.

Exclusions tightened: demo-map + golden-path-check (scaffolding /
authoring tooling -- the studio keeps them), doxa-research-map (the
part already composed into Simple Research), stack-demo (broken and
deferred; a broken tab on the giveaway shelf reads as damage).
Wrapper description corrected: the legacy IIFE holds FIVE maps.
2026-09-24 05:02:21 +00:00
doxa-maps-88655c70
449adfe515 test(poster): the shared print actions must produce real documents
Some checks are pending
build-gate / build (push) Waiting to run
Gates usePosterPrints.js — the print actions now shared by every poster wall —
by driving them in a browser with a fake records provider, so the assertion is
about the DOCUMENTS rather than any bundle's API being reachable.

★ BOTH DIRECTIONS, because "the button is there" and "the button works" are
different claims and this flight has repeatedly found the first true while the
second was false. With a provider: a real table, correct row count, the prayer
checkbox column present. WITHOUT one: phase "error" naming the missing provider,
not a silent no-op and not a throw.

⚠ IT CAPTURES THE PRINTED HTML INSTEAD OF READING THE IFRAME BACK, and finding
that out cost a wrong first result. openPrintFrame calls window.print() under
autoprint, which BLOCKS in headless Chromium, and the frame self-removes on
afterprint — so reading it back measured a frame that had already gone and
reported 0 rows for a perfectly correct document. Intercepting what the
composable hands to the frame measures what actually gets printed.

Confirmed to fail before being accepted: deleting the checkbox column from the
row template reports "the list print has no prayer checkbox column".
2026-09-24 04:59:45 +00:00
doxa-maps-88655c70
5c192ea0e2 chore(poster): regenerate the tree after rebasing onto main
Some checks are pending
build-gate / build (push) Waiting to run
The rebase conflicted only in GENERATED output — the built bundles, their SRI
hashes, the leaf pages and the manifest — because another agent's regions work
landed in the same files. My source file applied cleanly.

Resolving generated files by hand produces a tree matching neither agent's
source, so this takes main's versions and REGENERATES: four bundle builds plus
`npm run tree`. Re-verified rather than assumed — check:poster-prints and
check:mfe-two-tab both pass after the regeneration.
2026-09-24 04:59:23 +00:00
doxa-maps-88655c70
04f165efd3 feat(poster): share the print actions, keep the record fetching per bundle
The poster wall was generalised into library/components/poster/PosterWall.vue
and two bundles migrated onto it — but the ORIGINAL, my-upg-100-list's 903-line
wall, was left on its own copy. So the one wall carrying the real print actions
is the one wall nothing else benefits from, and the shared component's packet
path was still a "coming soon" stub. Every print fix made in the 100-list this
flight (the card deck, the stock picker, the image_url fetch) was invisible to
the three bundles on the shared component, and vice versa.

★ THE SEAM IS WHAT MADE THIS SHAREABLE AT ALL. The PRINT ACTIONS are common; the
RECORD FETCHING is not. fetchListRecords in the 100-list knows the people-groups
API, the selector evaluator and the pg_slugs special case; Key Cities and Country
Posters have entirely different data shapes. usePosterPrints.js therefore takes a
`records(item)` PROVIDER and owns everything after it — the HUD state machine,
the in-place print frame, and the documents.

That also keeps the bundles honest. A wall that CAN supply rows passes the prop
and gets the list + card-deck prints; one that cannot omits it, the Card Deck
button is not rendered, and Prayer List keeps its truthful "coming soon" instead
of a button that fails at click time. Country Posters shows exactly that today:
175 cards, Prayer List and Poster Map, no Card Deck — because it has no provider
yet, not because something broke.

⚠ ONE exportState, not two. The composable owns a ref by default, and a wall
rendering its OWN state while these actions wrote to a private one would show no
progress and swallow every error — the mechanism working and the feedback not,
which is the failure mode this flight keeps finding. PosterWall hands the
composable its own ref so both write to the surface the reader is looking at.

Prints IN PLACE via the shared openPrintFrame, never window.open() — the
mechanism the Driver asked for by name: "not supposed to open up a new browser,
but should open up the print dialog directly in the browser".

NOT DONE, and deliberately: the 100-list wall is NOT yet migrated onto the shared
component. Its template is 107 lines against the shared 72 with a different
structure, and it is a Driver-reviewed surface — that is a rewrite whose risk
does not fit in one tick alongside this. The shared half lands first and is
verified; the migration stays open on the card with this composable now waiting
for it.

Verified: all three bundles on the shared wall build; country-poster-wall renders
175 cards with zero page errors; check:poster-prints drives the composable in a
browser and asserts BOTH directions.
2026-09-24 04:56:31 +00:00
doxa-maps-88655c70
c1ab013f9e Merge main into bundler-1/work (regenerate the tree, re-hash)
Some checks are pending
build-gate / build (push) Waiting to run
The one conflict was in public/doxa-maps/index.html, which generate-tree
writes. Built files are regenerated, not hand-merged: country-posters
was rebuilt because main changed its source (a new two-tab MFE),
regions-dashboard because this branch did, then the tree and the hashes.
2026-09-24 04:54:42 +00:00
doxa-maps-88655c70
c268d13a42 Regions: the list tab was half-localised, and Oswald must not be vendored
Two findings, both from measuring at locale=ru instead of trusting the
plan.

DO NOT VENDOR OSWALD. The work list called for it for Russian headings.
Their rule is [lang="ru-RU"] on h1/h2/h3 (_globals.scss:40) — but the
live document's lang is "ru", so the selector never matches. It is dead
code on their own site. Oswald never loads there either:
document.fonts reports it "loading"/"unloaded" while Bebas, Poppins and
Brandon all report "loaded", and the glyph oracle agrees — Oswald
renders "HAMBURGEFONS" at 855 against sans-serif's 856.36 (0.2%, noise),
where Bebas moves it to 486 (43.2%). Bebas itself has no Cyrillic (501
vs 501.42), so their Russian headings fall back to the system face at
letter-spacing normal. Vendoring Oswald would have made us DIFFER.
_probe-ru-parity.mjs now asserts that parity instead of assuming it:
10/10, both sides falling back identically at 444.88.

The list tab was half-localised, and three of the four defects were
invisible in English:

- .rl-title forced text-transform:uppercase. Their h1 is none on both
  locales. Invisible in English because Bebas is an all-caps face, and
  wrong in Russian, where theirs reads "Регионы" and ours read
  "РЕГИОНЫ". This file's own comment already recorded the correct
  finding for .region-card__title; this rule was missed.
- summaryLine() was a hardcoded English template where theirs is
  t('%count% people groups in %countries% countries'). The Russian
  string was already in our catalogue — copied, never wired.
- dashboardLink() likewise: their ru value is "Открыть панель: %region%",
  a different word order no English template could be patched into.
- Nothing refetched on a locale change. The region and country labels
  come from the API's ?lang=, and their site serves Азия/Африка/
  Азербайджан on /ru/regions. region-dashboards.vue already had
  watch(languageCode, loadRegions); this tab did not, so a locale switch
  translated the chrome and left every label English.

Why none of it was caught: _probe-region-vs-live.mjs does compare
textTransform and reports 73/73 — but it diffs the DASHBOARD tab's
.rd-title. Nothing ever property-diffed the LIST tab's head against
/regions. The gap was coverage, not the property list.
_probe-list-vs-live.mjs closes it at 8/8.

The loading state now uses their own 'Loading results...' key rather
than new English, since that string is already translated in all eight
locales.

Regression: 18 suites green.
2026-09-24 04:53:36 +00:00
doxa-maps-88655c70
e1553df458 test(provider): ?provider=maplibre must swap the ENGINE, measured by request host
Some checks are pending
build-gate / build (push) Waiting to run
Gates BUG-UPG100-IGNORES-THE-PROVIDER-SWITCH. my-upg-100-list builds its map RAW
with `new gl.Map` instead of going through useMapInstance, so the page-level
switch — which resolves inside that composable — could not reach it. The card
measured 19 requests to api.mapbox.com at ?provider=maplibre while five sibling
leaves swapped cleanly.

★ IT COUNTS NETWORK REQUESTS BY HOST, not globals. `window.mapboxgl` can be
present because the leaf page preloads it while the map is actually DRAWN by
maplibre, and the reverse is equally possible. What the switch exists for is not
loading a proprietary engine and not calling its servers, and only the request
log shows that.

The allowance is DERIVED from the leaf's own HTML — count the distinct
api.mapbox.com urls the generated page declares, plus one for a stylesheet that
gets fetched twice — rather than hardcoded. Asserting merely "fewer than the
default" would pass a half-fix at 18 of 19; hardcoding 5 would pass a leaf that
preloads 4 and leaks 1 runtime call.

★ THE GATE'S OWN FIRST VERSION MEASURED NOTHING AND PASSED. `const URL =
process.argv[2]` shadowed the global URL constructor, so every
`new URL(request.url())` threw, the host tally stayed empty, and it cheerfully
reported "0 mapbox requests" on a page making 19. It now names the variable LEAF,
counts what it could not parse, and FAILS if the default run records no requests
at all — because a silently empty tally is indistinguishable from success.

Passing on three leaves today: upg-100-list 19 -> 5, doxa-simple-map 21 -> 4,
countries-map 24 -> 4, with every residual being the page's own <link>/<script>
preloads. Confirmed to fail when the allowance is tightened by one.
2026-09-24 04:47:54 +00:00