Commit graph

1368 commits

Author SHA1 Message Date
doxa-maps-88655c70
93c52cf498 Merge branch 'main' of https://git.nuxtbase.app/doxa-life/doxa-maps into bundler-1/work
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
2026-09-25 17:28:55 +00:00
doxa-maps-88655c70
357b050d6b Rebuild all bundles on the merged source 2026-09-25 17:27:50 +00:00
doxa-maps-88655c70
2a2ea36e98 A leak check that can be pointed at any leaf, and two false positives it taught me
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
Cell E verification for Big Picture and Simple Research needed the
gospel leak gate's method on two other bundles. That gate hardcodes its
four leaves, its element tag and its single catalogue file, so pointed
elsewhere it finds no shadow root and silently compares nothing. This
one takes the leaf, the tag and the locales as arguments and compares
against EVERY catalogue area — a COMPOSITE has no t() calls of its own,
so its chrome comes from the children it nests.

SIMPLE RESEARCH CELL E IS CLEAN, independently measured:
research-map-plus-grid in ar/zh/ru/hi, tree identity checked first, no
leaks, 0 page errors, and 86/34/27/43 comparable keys — so the
comparison was non-vacuous in every locale rather than "0 leaks over
nothing". A second instrument agreeing with the owner's reading is what
the verifier role is for.

★ TWO FALSE POSITIVES, BOTH OF WHICH LOOKED LIKE TEXTBOOK DEFECTS. The
first run reported 3 leaks x 4 locales on Big Picture, and all three
keys ARE translated in every catalogue, so the pages really were showing
English where a translation existed.

  DATA CELLS. "Unengaged" rendered in .rd-td inside .rd-grid — table
  data. people-groups-grid.vue:150 records the field as
  {value:'unengaged', label:'Unengaged'}, 100% populated, and
  dashboard.config.js:132 counts 2,081 Unengaged / 27 Engaged. It is an
  API VALUE that happens to equal a catalogue string, which is the
  population rega's method excludes by name. Whole-label matching stops
  a substring impersonating a label; it cannot stop a data cell
  rendering the same text.

  FACET VALUES. "Other" rendered in span.fr-lbl in the facet rail, which
  LOOKS like chrome. The template is
  `<span class="fr-lbl">{{ r.label }}</span>` with `{{ r.count }}`
  beside it, and the component's own comment calls it "the 'Other'
  bucket". The string comes from row data, not from t('regions.Other').

Both are excluded BY ROLE rather than by class. The facet exclusion keys
on the row carrying a COUNT, so the rail's own section headings stay in
scope and a real leak there is still caught — excluding .fr-lbl
wholesale would have blinded it.

⚠ NEITHER IS GOSPEL-SPECIFIC. Any bundle with a data grid or a facet
rail produces these, and whether "Unengaged" and "Other" as DATA should
be localised is a genuine product question — but a different one from
cell E, and filing it as a leak would send someone to fix correct code.

⚠ BIG PICTURE CELL E IS NOT CLAIMED CLEAN. The confirming run after the
facet exclusion came back RC=75 (no browser slot within 900s), and the
run before it still reported regions.Other. Whether the exclusion takes
effect is unverified, so the cell stays open rather than being marked
on an unconfirmed reading.
2026-09-25 17:26:29 +00:00
doxa-maps-88655c70
8a9130294a Rebuild all bundles on the merged source 2026-09-25 17:18:38 +00:00
doxa-maps-88655c70
b9dd5545c8 Merge branch 'main' of https://git.nuxtbase.app/doxa-life/doxa-maps into bundler-3/work
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
# Conflicts:
#	public/doxa-maps/doxa-maps-spa/manifest.json
#	public/doxa-maps/gospel-resources/charts/index.html
#	public/doxa-maps/gospel-resources/data-tables/index.html
#	public/doxa-maps/gospel-resources/gospel-dashboard/index.html
#	public/doxa-maps/gospel-resources/gospel-resources.js
#	public/doxa-maps/gospel-resources/resources-list/index.html
#	public/doxa-maps/gospel-resources/resources-map/index.html
#	public/doxa-maps/index.html
#	public/doxa-maps/manifest.json
2026-09-25 17:12:17 +00:00
doxa-maps-88655c70
d633f496d7 Gospel map: shape Arabic, Hebrew and Persian place names
The map is built raw, for the provider switch, so it never installed the RTL
text plugin — the same opt-out that cost it the engine stylesheet. Without the
plugin those labels draw as disconnected glyphs in the wrong order, which is
unreadable rather than merely ugly.

This was never an Arabic-locale bug, which is why it went unseen. The leaf does
not localise basemap labels, so every place is drawn in its own script: over
Arabia, 57 of 61 rendered symbols are Arabic for every viewer whatever their UI
language. An English reader sees broken Arabic they cannot evaluate.

It installs after the engine global resolves rather than at bundle entry. This
bundle injects its engine lazily, so at entry time there is no engine to install
into and the call would silently do nothing.

The gate pans to Arabia before judging. Registration alone proves little, since
the plugin is registered lazily and a freshly loaded map reports "deferred"
whether or not the shaper can actually be fetched; only real RTL text on screen
turns it "loaded". Asserting without panning would have failed a working map.
2026-09-25 17:10:04 +00:00
doxa-maps-88655c70
30a11a175c Merge main into bundler-2/work
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
# Conflicts:
#	public/doxa-maps/doxa-maps-spa/manifest.json
#	public/doxa-maps/gospel-resources/charts/index.html
#	public/doxa-maps/gospel-resources/data-tables/index.html
#	public/doxa-maps/gospel-resources/gospel-dashboard/index.html
#	public/doxa-maps/gospel-resources/gospel-resources.js
#	public/doxa-maps/gospel-resources/resources-list/index.html
#	public/doxa-maps/gospel-resources/resources-map/index.html
#	public/doxa-maps/index.html
#	public/doxa-maps/manifest.json
2026-09-25 17:09:33 +00:00
doxa-maps-88655c70
93ace277df Match the live page's wording where we differed only by case
The census against doxa.life/research found our grid saying "Full profile" and
"Load more" where the page we clone says "Full Profile" and "Load More". Case,
nothing else — the kind of difference that is invisible in review and obvious
side by side. Fixed in the English catalogue and in the fallback literals; the
other ten locales are untouched, since none of them has a capital to drift.

The filter toggle's "Hide Filters" is now SANCTIONED in the preset rather than
fixed, with the Driver's words, the date and the card: our filter panel is open
on load because the Driver reported not being able to find the dropdowns, so our
toggle offers to hide where theirs offers to show. Same control, other state.

Everything else the census found went to the Driver as
DECIDE-RESEARCH-CENSUS-DIFFERENCES rather than into the allowlist — the legend's
prayer-band wording, our four additions, and their page title.

★ AND THE CENSUS TURNED UP A REAL DEFECT WHILE CONFIRMING THE "DATA DRIFT".
The drift is OURS. Measured against pray.doxa.life/api/people-groups/list with
this repo's own thresholds (prayer-progress.js: 0 or null = Needs Prayer):

    API         Needs Prayer 1,162 (58.8M)    Has Prayer 945 (147.5M)
    doxa.life   Needs Prayer 1,162 (58.8M)    — matches the API exactly
    our legend  Needs Prayer 1,417 (69.1M)    Has Prayer 690 (137.1M)

254 people groups the API says have someone praying are drawn and counted by our
map as having nobody. Their snapshot is right and we are wrong. Recorded on the
card with a candidate cause (useMapLayers.js:226 reads only the camelCase
peoplePraying and defaults to 0, where getPrayerLevel also accepts the snake
_raw.people_praying) and passed to the shepherd for its own card rather than
fixed inside a wording commit.
2026-09-25 17:08:16 +00:00
doxa-maps-88655c70
52552e7c48 Pin popups ask the map which engine built it, instead of assuming Mapbox
RegionCountryMap built its popup from window.mapboxgl and returned SILENTLY when
that global was absent — the worst shape of failure, because a pin click would
simply do nothing and no error would appear. It has been passing only because
every generated leaf <head> statically loads mapbox-gl, so under provider=maplibre
a MapLibre map was opening a MAPBOX popup. studio is removing those static tags,
since a Mapbox script leaks the visitor's IP to Mapbox even for a partner who
deliberately chose MapLibre; the moment that lands, these popups vanish.

glFor(map) asks which namespace owns the map instance. It checks MapLibre FIRST
with an instanceof test, because a page can legitimately carry both globals and
only the instance knows which one made it.

The controls read is fixed with it. It was `window.mapboxgl || window.maplibregl`,
which happened to work through its fallback but preferred Mapbox even on a
MapLibre map — the identical bug one line away from the same silent failure.

The proof fails first by construction. The probe BLOCKS the Mapbox script, then
asserts window.mapboxgl is genuinely undefined BEFORE clicking, so the pass that
follows is a pass of the thing that used to break: with no peer, MapLibre present,
a pin click opens "Deaf Liechtensteiners … Population 400"; under Mapbox it opens
"Vinza … Population 33,000", unchanged.

glFor is copied, not imported: the same function is in another plugin's folder
(pplr-data-maps/src/utils/pplrData.js:64), and importing across plugin folders
would couple two plugins through a path neither owns. library/ is the right home
the moment a third consumer appears, and both copies should die in that commit.
2026-09-25 16:59:57 +00:00
doxa-maps-88655c70
2f204c8e30 Rebuild all bundles on the merged source; freshness and SRI pass 2026-09-25 16:43:15 +00:00
doxa-maps-88655c70
1870efa5c9 Merge branch 'main' of https://git.nuxtbase.app/doxa-life/doxa-maps into bundler-3/work
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
# Conflicts:
#	public/doxa-maps/gospel-resources/charts/index.html
#	public/doxa-maps/gospel-resources/data-tables/index.html
#	public/doxa-maps/gospel-resources/gospel-dashboard/index.html
#	public/doxa-maps/gospel-resources/gospel-resources.js
#	public/doxa-maps/gospel-resources/resources-list/index.html
#	public/doxa-maps/gospel-resources/resources-map/index.html
2026-09-25 16:39:46 +00:00
doxa-maps-88655c70
03a01f99bf Merge main into bundler-2/work
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
2026-09-25 16:37:11 +00:00
doxa-maps-88655c70
78021cd854 Census Simple Research against the live research page it clones
check:element-census-vs-live now has a preset for this lane, so the question
"what is on our page and not on theirs, and the reverse" has an answer that runs
instead of an opinion.

★ THE LEAF IS THE COMPOSITE, NOT THE GRID ALONE. people-groups-grid is a copy of
DX-life's UupgsList.vue, which is the BODY of doxa.life/research — but that page
is a map plus that body. MEASURED: censusing the grid leaf against /research
reports 45 omissions, and every one of them is the research map's chrome
(Clustering, Off/Native/MST/Network, the tab strip, the legend) — nothing to do
with the grid. A list that long and that wrong teaches a reader to ignore the
gate. research-map-plus-grid is map + grid, which is what /research IS, and it
lines up 98 strings against their 95.

The negative control is the pin hint ("someone still needs prayer"), and the run
confirms the census SEES it as ours-only — without that, every count it prints
would be worthless.

The sanctioned list is deliberately EMPTY. The first run's 14 additions and 14
omissions have gone to the shepherd as findings; an allowlist written by the
owner of the code to make its own gate green is not evidence. Most of the
omissions look like wording rather than absence — their "Full Profile" against
our "Full profile", their "Load More" against our "Load more", their legend's
"1+ People Praying" against our "Has Prayer" — but that is exactly the call the
Driver makes and not the one this file should quietly make for them.
2026-09-25 16:37:10 +00:00
doxa-maps-88655c70
e52150b500 Map paint gate: say which tree it measured
The gate defaults to the deployed site, so a failure line reads as "this code
is broken" when it means "this code is not published yet". That misread cost
two wrong diagnoses in one day: the resources map and the connectivity map both
reported the stylesheet absent while the fix for exactly that sat verified on
disk. Served locally, the same gate passes all thirteen leaves.

The identity guard cannot close this. It fingerprints the manifest to catch the
wrong worktree on a localhost port and deliberately passes a remote target,
because lagging behind a checkout is a deployment's normal state. The output
was the thing at fault: the one number that tells the two failures apart was
never on screen.
2026-09-25 16:36:54 +00:00
doxa-maps-88655c70
bd8935de84 Gospel map: one shared implementation of the engine stylesheet injection
The resources map builds its map raw, for the provider switch, and in doing so
opted out of the CSS injection useMapInstance does for every other leaf. That
was fixed here first with a local copy, because nothing shared needed changing
for it. The shared helper now exists for the same defect on the connectivity
map, so this drops the copy and calls it.

Theirs is the better one: it also waits on a link that is present but not yet
loaded, which the local copy skipped straight past.
2026-09-25 16:36:53 +00:00
doxa-maps-88655c70
09d03c60d2 A shared ?lang=ar link now translates the region names too, not just the chrome
The prayer API localises its labels from its own lang parameter, so the region
and country NAMES follow whatever languageCode resolves to. That computed read
props and profile-config only — never the URL. So a DIRECT LINK, which is the URL
a person actually sends someone, went through detectLocale() for the chrome and
then asked the API in English: Arabic headings around English region names.

MEASURED, and only visible because the two channels were compared rather than
each checked alone: region-dashboards rendered 597 Arabic chrome characters
through profile-config against 309 through ?lang=ar, and regions-mfe 2709 against
748. The host-locale gate passes both, because it asks host >= ref/2 and cannot
see an asymmetry in that direction. Diffing the two renders element by element
named the whole gap exactly: the seven region labels in the chooser legend
(.rd-regionkey__label) and the seven row links in the table (.rd-row-link).

The reader is EXTRACTED, not copied. library/i18n/index.js already parsed
?lang= / ?locale= inside detectLocale; it is now exported as localeFromUrl() and
used in both places, so the data call and the chrome can never disagree about
what the URL asked for. Re-implementing those two lines in the plugin would have
been a second copy of one fact, which is how the asset-path bug and the
PosterWall fallback table both went wrong.

After: ?lang=ar renders 30 Arabic elements against profile-config's 30 on
region-dashboards, and 42 against 42 on regions-mfe — 0 strings left English on
the shared-link path. All 23 bundles rebuilt because the library file reaches
them; composite freshness PASS, check:sri PASS.
2026-09-25 16:26:31 +00:00
doxa-maps-88655c70
3c3e357068 check-gospel-map-paints reads the tree's name from the build config too
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
It landed on main after the tree-name codemod ran and carried one 'doxa-maps' literal.
2026-09-25 16:26:03 +00:00
doxa-maps-88655c70
456b920fb6 browser-slot: a long gate declares its own hold limit; check:host-locale-contract takes 2700 s
A full check:host-locale-contract run loads every leaf twice and hit the 900 s default three times
(exit 75, NOT MEASURED). An env prefix in its package.json script cannot help: the wrapper reads its
limit before the script's environment exists. So a gate declares it in its header
(@browser-slot-max-hold <s>); the wrapper reads it for 'node <file>' and 'bun run <script>' alike.
The caller's BROWSER_MAX_HOLD still wins, a declaration is capped at 3600 s, and every other
command keeps the 900 s default. Tested on a private slot dir: declared 3 s -> killed at 3.1 s;
caller override 1 s -> 1.1 s; bun run form -> 3.2 s; 99999 -> capped to 3600.
2026-09-25 16:25:22 +00:00
doxa-maps-88655c70
412602cdef Five more gate scripts read the tree's name from the build config
The research verify-* scripts and research-map-plus-grid's two gates hardcoded 'doxa-maps' (11
literals); they now import TREE_NAME from internal/_tree-name.mjs like the rest of the tooling.
2026-09-25 16:25:22 +00:00
doxa-maps-88655c70
44ef31b2c6 Tooling reads the tree's name from the build config, in one place
The output tree's name has always been configurable (vite.doxa-maps-build-config.json "name"),
but 61 tooling files ignored it and hardcoded 'doxa-maps' (181 literals), so a rename would have
moved the build while the gates kept reading the old folder.

- internal/_tree-name.mjs reads it once (validated as one URL-safe path segment; falls back to the
  historical name) and exports TREE_NAME / TREE_NAME_RE; `node internal/_tree-name.mjs` prints it
  for shell scripts.
- A codemod rewrote 171 literals in internal/ to TREE_NAME; the regex literals, messages with
  backticks, atomic-public-swap.sh (fails loudly if the name cannot be read), build-lock.sh and
  vite.config.js (its duplicate readMfeName is gone) were done by hand.
- The name is unchanged: old and new tooling produce all 1408 files under public/ byte for byte.
  Code lines still naming the tree in internal/ + vite.config.js: 2, both intended (the helper's
  fallback, the private-repo-name pattern in public-hygiene-check).

This renames nothing visible; the eventual rename becomes a one-line config change.
2026-09-25 16:25:22 +00:00
doxa-maps-88655c70
577149afb4 check:experimental-shelf: test the partner embeds too, from another origin
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
Two surfaces join the leaf and the Gallery, each built from the manifest's own snippets so a
broken snippet fails as well:
  B  a host on a different origin iframes the leaf; the plugin must draw its data and the
     host's title, URL and scroll must not move.
  C  a host on a different origin pastes embed.script with its own token and provider. Under
     maplibre: 0 api.mapbox.com requests. Under mapbox, where the plugin draws a Mapbox map, a
     second run with a fake host token must show that token, and no other, in tile requests.
     MapLibre-only plugins take no token and report n/a.
The self-test now also poisons the bundle under the script embed. Rows print their seconds;
--surfaces A,B,C,D splits the ~26 min run. Measured: 7 bundles, 60 rows PASS, 3 n/a.
B does not assert 0 api.mapbox.com: every generated leaf head links Mapbox JS/CSS whatever
the provider, which is a template property reported separately.
2026-09-25 16:23:49 +00:00
doxa-maps-88655c70
d900edaab5 Rebuild cities-internet after the merge, so its committed bundle matches its source
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
Its output collided with this branch's during the integration; a build artifact
cannot be merged line by line, so it was rebuilt rather than resolved by hand.
2026-09-25 16:21:25 +00:00
doxa-maps-88655c70
8e8e3b3e82 Integrate main into bundler-2/work
# Conflicts:
#	public/doxa-maps/cities-internet/cities-internet.js
#	public/doxa-maps/cities-internet/connectivity-list/index.html
#	public/doxa-maps/cities-internet/connectivity-map/index.html
#	public/doxa-maps/cities-internet/data-tables/index.html
#	public/doxa-maps/doxa-maps-spa/manifest.json
#	public/doxa-maps/index.html
#	public/doxa-maps/manifest.json
2026-09-25 16:17:07 +00:00
doxa-maps-88655c70
849cd61022 Make the map's clustering picker real: four options, four different maps
Choosing "Native" in the research map's side menu drew nothing. So did MST, and
so did Network. A control that does nothing is worse than one that is absent:
it tells the reader the map cannot do what it can.

TWO CAUSES, AND FIXING EITHER ALONE WOULD HAVE LOOKED LIKE A FIX.

  1. mapStore never declared `clusteringMode`. The picker wrote to a property no
     reactive system was watching, so useMapClustering's `mode` kept its own
     internal default and the click changed nothing.
  2. The legend's Clusters checkbox force-set 'mst' "regardless of what's in the
     store", with the mode watcher "kept as a no-op for forwards compat" — its
     own words. Even with (1) fixed, one tick of that checkbox would have snapped
     Native back to MST: a control that works until you touch the other one.

So the two controls are now two views of one state. clusteringMode IS the state
('off' means disabled); the picker sets it; the checkbox maps onto it, promoting
only 'off' to 'mst' so the checkbox alone behaves exactly as it always has. They
can no longer show different things. The dim-slider's clusterPinOpacity got a
real home in the same pass, for the same reason it was missing.

★ THE GATE ASSERTS ON WHAT IS DRAWN, NOT ON WHAT IS SET. `clusteringMode ===
'mapbox'` would have passed the moment (1) was fixed, while the map still drew
MST lines — state was exactly the evidence that let an inert control ship. The
map now reports, per clustering layer, whether it exists, whether it is visible
and how many features are rendered; the gate clicks the real buttons and reads
that. MEASURED, before and after, on the same tree:

    before   off 0/0/0   native 0/0/0     mst 0/0/0       network 0/0/0
    after    off 0/0/0   native 37/37/0   mst 0/0/1700    network 0/0/23896
                        (circles/counts/lines)

It also asserts no orphan layers between modes, and that MST and Network differ
— two labels for one behaviour is the same defect as a dead option.

One measurement bug caught on the way, worth recording: a fixed 6-second pause
reported MST as drawing nothing while Network drew thousands of lines. MST over
~2,100 pins is quadratic and had simply not finished. The gate now waits for the
drawing to settle rather than guessing a duration — a gate that calls a slow
feature broken is worse than no gate.
2026-09-25 16:16:46 +00:00
doxa-maps-88655c70
7c9e3d9fc4 Take main
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
2026-09-25 15:54:57 +00:00
doxa-maps-88655c70
0574fe7ffa A gate that looks at the map, because every other one passed over a blank rectangle
Injecting mapbox-gl.css collapsed this profile's canvas to a white strip, and
every assertion I owned went green over it: verify-gospel-map counted 2,107 of
2,107 pins "rendered", leaves-mount found a full shadow root, the i18n gates
found every label translated and nothing clipped. queryRenderedFeatures does not
care whether anything is visible. I caught it by opening the screenshot, which is
not a system.

So: screenshot the map region and count distinct colours — a basemap has
hundreds, a blank rectangle one or two — and check the map still fills its
element.

THE POISON ARM CORRECTED THIS GATE TWICE, which is the argument for having one.

First it hid the canvas, and the region still showed 316 colours: the sample was
taken over the whole custom element, and the legend card is itself full of
colour. The sample is now clipped to the right of the legend, so it looks at map
alone.

Then it collapsed the container, and the gate still passed — because I was
comparing the canvas to its own container, and in the real incident BOTH
collapsed together, leaving that ratio at 1.0. The check would have passed the
exact defect it was written for. The ratio that matters is map-to-ELEMENT, and
the poison now reproduces the incident rather than approximating it: 1440x300
inside a 1440x900 host, caught at 33%.

Measured after: 644 distinct colours, map filling 100% of the element, and the
poison arm red.
2026-09-25 15:54:03 +00:00
doxa-maps-88655c70
737059467d The never-translated gate goes green on what it has settled
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
It reported four failures and could not be run in CI, so a real
regression would have landed behind an already-red gate. Two of the four
are not defects at all, and two are open in a way code cannot close.

PROPER NOUNS, now allowlisted with reasons. "Open Bible Stories" and
"Scripture Earth" are the names of the organisations that publish these
resources, not descriptions of them: a reader following the link meets
the English name on the destination site, so translating it would hide
the thing it identifies. They sit in the same res.* block as
`jesusFilm: 'JESUS Film'`, which the product already treats as a brand,
and resources-list.vue:234 maps the literal string 'Scripture Earth'
back to this key — it is an identity, not a label.

DRIVER-GATED, and allowlisted as a BOOKMARK rather than a verdict.
legend.columns.upgs and prayerLaps.upgs really are an English acronym
wedged into an otherwise localized row, in Arabic and Chinese included,
while `population` beside them translates normally. That stays true; it
is simply not resolvable here. Measured this tick: the column's own
tooltip (legend.sortByUpgs) is ALREADY translated in all 11 with only
the acronym left English — "Ordenar por UUPG", "按 UUPG 排序",
"ترتيب حسب UUPG" — and the widest candidate phrase is 28% of the 380px
panel (zh costs 9px). So neither engineering nor space is the blocker:
it is terminology only, and an invented acronym looks AUTHORITATIVE to a
reader who cannot check it.

★ "ALLOWED" IS NOT ONE THING, and collapsing the two would make this
gate lie. A proper noun is SETTLED — correct English forever. A
driver-gated key is an OPEN DEFECT parked so the gate can still catch
the next regression. Calling it "identical on purpose" would turn a
deferral into a verdict, which is exactly how an open item stops being
visible. The gate now separates them: the PASS line itself reads
"2 deferred pending a ruling" and names both keys above it.

MUTATION TESTED — making a NEW key identical in all 11 locales
(buttons.prayerList) still fails it, exit 1, naming the key. The
allowlist exempts without blinding.

The priced options are on
DECIDE-UUPGS-COLUMN-HEADER-IS-ENGLISH-IN-ALL-ELEVEN-LOCALES; these two
lines come out when the Driver rules.
2026-09-25 15:49:47 +00:00
doxa-maps-88655c70
eb0cb7d9de Rebuild cities-internet with the data-fitted opening view
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
2026-09-25 15:48:05 +00:00
doxa-maps-88655c70
258fb5006c Cities: open on the dots' own extent, and on a phone put the window where most of them are
A hard-coded opening zoom framed the world at one canvas size only. The map now fits the
people-group dots on first paint. A portrait phone cannot zoom out far enough to frame the
world in Mercator (floor 0.72 at 390x844), so when the fit is clamped the view slides to the
longitude window holding the most dots. Measured: desktop 1440x900 still 2107/2107 drawn;
phone 390x844 from 1599 to 1907 of 2107.
2026-09-25 15:47:35 +00:00
doxa-maps-88655c70
2624cf8ef5 Integrate main into bundler-2/work
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
# Conflicts:
#	public/doxa-maps/index.html
2026-09-25 15:16:03 +00:00
doxa-maps-88655c70
8b40f6c585 Big Picture: read the language the host asked for
Embedded with profile-config {"locale":"ar"} this leaf drew no Arabic at all,
while the same page reached with ?lang=ar drew 273 characters of it.

Its two siblings were fixed two different ways and neither applies here. This
bundle's entry calls defineCustomElement with no configureApp: there is no i18n
instance for install-time provide to reach, and no ProfileLoader beneath it to
drive one. Everything this element renders comes through tStatic — the grid's
`t` proxy, the header's labels — and tStatic resolves the locale from the URL
and the document, neither of which a host's profile-config touches.

So the only thing missing was somebody to say which language this page is being
read in, which is the same six lines the other two composites already carry.

MEASURED with check:host-locale-contract:
    big-picture/big-picture   host 0 of 273  ->  host 273 of 273
    whole fleet               1 failing      ->  PASS, 32 leaves
2026-09-25 15:15:55 +00:00
doxa-maps-88655c70
0f6d504915 An RTL locale turns the plugin, not the partner's page
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
Arabic text was rendering inside a left-to-right layout: readable glyphs
in the wrong place, which reads as a styling quirk rather than the bug
it is.

TWO DEFECTS IN ONE BLOCK (library/i18n/index.js, createAppI18n):

  TARGET — it wrote document.documentElement.dir. An EMBEDDED widget
  doing that flips the PARTNER'S WHOLE PAGE: their headings, their nav,
  their footer. The existing comment already worried about the host
  owning that decision, but guarded only against CLEARING dir, never
  against setting it.

  TIMING — it ran at createAppI18n() construction, reading
  detectLocale(), which sees only the URL and <html lang>. A locale
  arriving later through profile-config — the channel a HOST uses —
  never reached it. Measured across the previous landing: htmlDir null
  on every host-channel run, including leaves whose chrome was fully
  Arabic.

Direction now binds on ProfileLoader's own root, driven by
effectiveLocale — the locale actually in force AFTER profile-config, so
it re-evaluates when a host changes the language. This element cannot
tell "I am the page" from "I am a widget", and guessing wrong is exactly
the failure above, so it no longer touches the document at all.

MEASURED on a partner page at a FOREIGN ORIGIN with
profile-config.locale='ar' (same gate, before and after):

  before   upg-100-mfe   plugin root ltr, arabic 1765   FAIL
           poster-wall   plugin root ltr, arabic 1738   FAIL
  after    upg-100-mfe   plugin root RTL, arabic 1765   pass
           poster-wall   plugin root RTL, arabic 1738   pass
  partner <html>/<body>/<h1> stayed ltr throughout.

Standalone leaves are unchanged and still get <html dir="rtl"> from
?lang=ar — verified, both profiles — because a leaf page DOES own its
document. createAppI18n's document write therefore stays for now;
replacing it needs a page-ownership signal that touches every generated
leaf, which is its own change rather than a rider on this one.

⚠ THE GATE REFUSES TO JUDGE WHAT IT CANNOT SEE, and that guard earned
its place twice before this was measurable. Its first two runs reported
"the element never mounted" — a HARNESS gap, not the product: a partner
embed uses crossorigin="anonymous" (SRI requires it), so the tree must
send Access-Control-Allow-Origin, which `bun run serve` does not. The
gate now proxies the tree through its own CORS-enabled port. Without
that guard those runs would have read as fails-first evidence for a
defect nothing had actually exercised. It also fails if the locale
produced zero Arabic chrome, because direction cannot be assessed on a
page whose language never applied.
2026-09-25 15:14:20 +00:00
doxa-maps-88655c70
cc4670431e Merge main: rebuild regions-dashboard on the merged source; all 10 composites fresh
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
2026-09-25 14:57:45 +00:00
doxa-maps-88655c70
8620c382b3 Match their node shape for the people-group bloc, parens and all
UupgsList.vue:639 on their side is `</a> (<span v-html=... />)`: the parentheses
are TEXT SIBLINGS of a span, with no wrapper element around the group. Ours used
an outer <span>, and in the un-highlighted branch inlined the whole thing as
" ({{ label }})" — one text node where theirs has three, plus an element theirs
does not have. The element census against live measured it exactly: ours
"(Horn of Africa)" against theirs "(", "Horn of Africa", ")". <template> renders
no element, so both branches now produce their shape and agree with each other.

The v-if guard stays, and it is the one deliberate difference. Theirs has no
guard, so a group with no rop1 renders " ()". Measured on /regions/ethiopia,
all 24 of their cards carry a bloc, so the guard never diverges from anything
they actually show.

No visual change: 24 cards and 24 country lines on both sides, before and after.
2026-09-25 14:52:49 +00:00
doxa-maps-88655c70
519d5c80ef Merge main into bundler-2/work
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
# Conflicts:
#	public/doxa-maps/cities-internet/cities-internet.js
#	public/doxa-maps/cities-internet/connectivity-list/index.html
#	public/doxa-maps/cities-internet/connectivity-map/index.html
#	public/doxa-maps/cities-internet/data-tables/index.html
#	public/doxa-maps/doxa-maps-spa/manifest.json
#	public/doxa-maps/gospel-resources/charts/index.html
#	public/doxa-maps/gospel-resources/data-tables/index.html
#	public/doxa-maps/gospel-resources/gospel-dashboard/index.html
#	public/doxa-maps/gospel-resources/gospel-resources.js
#	public/doxa-maps/gospel-resources/resources-list/index.html
#	public/doxa-maps/gospel-resources/resources-map/index.html
#	public/doxa-maps/index.html
#	public/doxa-maps/manifest.json
2026-09-25 14:42:21 +00:00
doxa-maps-88655c70
c107e2f5b6 Rebuild the fleet after integrating main, so no bundle ships an older copy
The merge brought other lanes' source changes alongside the shared i18n change
in this branch, and a build artifact cannot be merged line by line. Every bundle
was rebuilt from the merged sources rather than resolved by hand.
check:composite-freshness and check:sri are both clean.
2026-09-25 14:40:54 +00:00
doxa-maps-88655c70
9f97c52994 Take main
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
# Conflicts:
#	public/doxa-maps/index.html
2026-09-25 14:38:04 +00:00
doxa-maps-88655c70
bccd2fabe0 Merge remote-tracking branch 'origin/main' into exp-shepherd/engine-css
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
2026-09-25 14:28:47 +00:00
doxa-maps-88655c70
b0427efcd4 Cities with Internet: the map's stylesheet reaches its shadow root
connectivity-map builds its map raw (new gl.Map), not through useMapInstance, so
nothing injected the engine stylesheet into its shadow root. A document-level
<link> never reaches it. Mapbox's own CSS canary reported "missing CSS
declarations", and controls and markers had no rules to position them.

useMapInstance's ensureEngineCSS is now exported, and connectivity-map calls it
with engineStylesheetUrl(provider) before new gl.Map, re-checking afterwards that
the pane is still mounted. Maps that already use useMapInstance are unchanged.
gospel-resources/resources-map has the same raw-map shape and can make the same
call; that is its owner's change.

Measured with verify-map-paint on a served built tree: main failed both leaves
(canary not salmon, "missing CSS declarations"). This build passes
connectivity-map (live GL, engine CSS applied, 0 unclassified errors).
resources-map still fails, as expected until it adds the call.
2026-09-25 14:28:42 +00:00
doxa-maps-88655c70
2c7813cfd6 Integrate main into bundler-2/work
# 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/doxa-maps-spa/manifest.json
#	public/doxa-maps/doxa-research-map/doxa-research-map.js
#	public/doxa-maps/doxa-research-map/research-map/index.html
#	public/doxa-maps/doxa-research-map/research-map/maplibre/index.html
#	public/doxa-maps/index.html
#	public/doxa-maps/manifest.json
#	public/doxa-maps/prayer-walk-planner/prayer-walk-planner.js
#	public/doxa-maps/prayer-walk-planner/prayer-walk-planner/index.html
#	public/doxa-maps/upg-neighbor/upg-neighbor.js
#	public/doxa-maps/upg-neighbor/upg-neighbor/bloom-cockpit/index.html
#	public/doxa-maps/upg-neighbor/upg-neighbor/flower-mesh/index.html
#	public/doxa-maps/upg-neighbor/upg-neighbor/index.html
#	public/doxa-maps/upg-neighbor/upg-neighbor/lang-cluster/index.html
#	public/doxa-maps/upg-neighbor/upg-neighbor/send-map/index.html
#	public/doxa-maps/upg-neighbor/upg-neighbor/upg-neighbors/index.html
2026-09-25 14:26:30 +00:00
doxa-maps-88655c70
5639dd1ccf Let a host set the language of a whole page, not half of it
Embedded with profile-config {"locale":"ar"}, these dashboards came back half
translated: the map's six tabs in Arabic and the grid's four dropdown labels in
English, on the same screen. That reads as "some strings were never translated",
and it sends whoever investigates to the catalogues — where every one of those
strings is already present and correct.

Two readers, one locale, and only one of them was being told.

  t()      — vue-i18n, follows the plugin's locale, which ProfileLoader already
             drove from the host's config.
  tStatic  — the plugin-free reader that components mounted as their own custom
             element must use (useI18n() throws at a custom-element root and
             takes the subtree with it). It resolved the locale from the URL and
             the document, which are the only two things a STATIC page can say.

So a locale set at RUNTIME moved every t() string and no tStatic string. Now the
i18n instance publishes its locale whenever it changes — one choke point, so
ProfileLoader, the SPA's picker and the studio's picker all work without any of
them remembering to tell the static reader as well.

★ ON THE WINDOW, NOT IN THIS MODULE, AND THE FIRST VERSION GOT THIS WRONG.
A composite loads SEVERAL bundles — research-map-plus-grid loads its own,
doxa-research-map's and people-groups-grid's — and each carries its own copy of
library/i18n. A module-level variable set inside the map's copy is invisible to
the grid's copy, so the first attempt translated the map and left the grid in
English: the very defect it was written to remove, reproduced one layer down.
Measured, then moved to a window-level store, the way the shared map token has
always crossed bundles. Module state is per-BUNDLE; a locale is per-PAGE.

Both composites also publish the locale themselves, because a composite sits
ABOVE every ProfileLoader on its page rather than under one.

MEASURED with check:host-locale-contract, on c7c2050d and on this commit:

    dashboard-plus-grid/dashboard-plus-grid      32 of 2121  ->  2121 of 2121
    research-dashboard/research-dashboard        75 of 1891  ->  1891 of 1891
    research-map-plus-grid                      489 of 1201  ->  1201 of 1201
    whole fleet                                  18 failing  ->     1 failing

The one left is big-picture/big-picture, which installs no i18n and mounts no
ProfileLoader at all — a different shape of gap, and not this lane's bundle.

Every bundle was rebuilt; composite-freshness and SRI are clean.
2026-09-25 14:26:02 +00:00
doxa-maps-88655c70
9f121c4aff Take main
# Conflicts:
#	public/doxa-maps/doxa-maps-spa/manifest.json
#	public/doxa-maps/gospel-resources/charts/index.html
#	public/doxa-maps/gospel-resources/data-tables/index.html
#	public/doxa-maps/gospel-resources/gospel-dashboard/index.html
#	public/doxa-maps/gospel-resources/gospel-resources.js
#	public/doxa-maps/gospel-resources/resources-list/index.html
#	public/doxa-maps/gospel-resources/resources-map/index.html
#	public/doxa-maps/index.html
#	public/doxa-maps/manifest.json
2026-09-25 14:19:07 +00:00
doxa-maps-88655c70
430ba6c167 Give this map the engine's own stylesheet, and state its size so it survives
verify-map-paint was right. research-dashboard goes through useMapInstance, whose
header promises it "injects Mapbox GL CSS into the root". This profile calls
new gl.Map directly — deliberately, for the provider switch — and in taking that
path it silently opted out of the CSS injection too. Mapbox's canary read
rgba(0,0,0,0) here and rgb(250,128,114) there, with "missing CSS declarations"
in the console: the stylesheet was in the leaf's DOCUMENT all along, which is why
tiles painted and nothing looked obviously wrong, but document CSS does not cross
a shadow boundary. Controls, geocoder and markers were positioned by no
stylesheet at all.

Fixed inside this profile rather than in library/: engineStylesheetUrl is already
exported, so the engine choice stays the registry's and no shared file changes.

THEN IT MADE THINGS WORSE BEFORE BETTER, TWICE, AND BOTH ARE WORTH KNOWING.

mapbox-gl.css carries .mapboxgl-map{position:relative}, which cancelled
.gm-map{position:absolute;inset:0} and collapsed the canvas to 300px on a 900px
viewport — a BLANK MAP that still reported 2,107 pins to queryRenderedFeatures,
because that API does not care whether anything is visible. The size is stated
outright now. Anyone else injecting this stylesheet into a shadow root should
expect the same and check the screenshot, not the pin count.

And the opening view was a guessed zoom of 1.3, which framed the world only while
the canvas was wrong. With the canvas correct it cut the visible band to
-23.8..52.0 and dropped Cape Town, Iceland and the Faroes — 21 of Doxa's groups
off the map. fitToPins frames the DATA's own extent on load, so the first view
cannot depend on a canvas size again.

Verified: canary rgb(250,128,114) with zero warnings, matching the passing leaf;
2,107 of 2,107 pins; all five leaves mount; and the attribution bar now renders,
which is a licence obligation that was not displaying correctly before.
2026-09-25 14:17:32 +00:00
doxa-maps-88655c70
069d75dba7 check:element-census-vs-live: what is on one side and not the other, ours vs the live page we clone
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
Every clone-fidelity gate compares properties of NAMED selectors, so none of them can see an element
that exists on only one side; regions found an extra control and an invented chart by eye, past eight
green gates. This executes the Driver's binary scope test (promoted from regions' probe).

- Harvests every visible text node through every shadow root on both sides and diffs the sets:
  OURS-ONLY unaccounted (scope violation) and THEIRS-ONLY (omission) fail; SANCTIONED entries must
  carry the Driver's words, date and card or the preset is refused; same-label digit drift is
  reported, not failed.
- The negative control is mandatory: a known ours-only string must land in the ours-only set, or the
  run prints CENSUS IS BLIND and exits 75. An unreachable live page is also 75.
- Presets live in internal/element-census/<name>.json; ad hoc via --leaf/--live/--neg.
- Measured: regions passes (list 229 vs 228, the only difference its sanctioned poster toggle; Africa
  97 vs 97 with 3 live-vs-snapshot figures). With the allowlist removed the toggle FAILS as
  unaccounted; a negative-control string not on the page gives CENSUS IS BLIND, exit 75.
2026-09-25 14:15:19 +00:00
doxa-maps-88655c70
e1a3cf36d9 Rebuild the label-font consumers on the merged main
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
2026-09-25 13:56:37 +00:00
doxa-maps-88655c70
8e5fa066f6 Merge remote-tracking branch 'origin/main' into exp-shepherd/label-font
# 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/doxa-maps-spa/manifest.json
#	public/doxa-maps/doxa-research-map/doxa-research-map.js
#	public/doxa-maps/doxa-research-map/research-map/index.html
#	public/doxa-maps/doxa-research-map/research-map/maplibre/index.html
#	public/doxa-maps/index.html
#	public/doxa-maps/key-cities/key-cities.js
#	public/doxa-maps/manifest.json
#	public/doxa-maps/prayer-walk-planner/prayer-walk-planner.js
#	public/doxa-maps/prayer-walk-planner/prayer-walk-planner/index.html
#	public/doxa-maps/upg-neighbor/upg-neighbor.js
#	public/doxa-maps/upg-neighbor/upg-neighbor/bloom-cockpit/index.html
#	public/doxa-maps/upg-neighbor/upg-neighbor/flower-mesh/index.html
#	public/doxa-maps/upg-neighbor/upg-neighbor/index.html
#	public/doxa-maps/upg-neighbor/upg-neighbor/lang-cluster/index.html
#	public/doxa-maps/upg-neighbor/upg-neighbor/send-map/index.html
#	public/doxa-maps/upg-neighbor/upg-neighbor/upg-neighbors/index.html
2026-09-25 13:52:21 +00:00
doxa-maps-88655c70
682ef72ae4 Rebuild the label-font consumers on the merged main 2026-09-25 13:52:15 +00:00
doxa-maps-88655c70
a6c15c96c4 Integrate main into bundler-2/work
# Conflicts:
#	public/doxa-maps/dashboard-plus-grid/dashboard-plus-grid.js
#	public/doxa-maps/dashboard-plus-grid/dashboard-plus-grid/default/index.html
#	public/doxa-maps/dashboard-plus-grid/dashboard-plus-grid/index.html
#	public/doxa-maps/doxa-maps-spa/manifest.json
#	public/doxa-maps/index.html
#	public/doxa-maps/manifest.json
#	public/doxa-maps/research-dashboard/research-dashboard.js
#	public/doxa-maps/research-dashboard/research-dashboard/index.html
2026-09-25 13:52:06 +00:00
doxa-maps-88655c70
c7c2050dd3 Installing i18n also provides it, so a host can set the locale
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
A host embeds these elements and sets profile-config.locale.
ProfileLoader applies it by driving the app's i18n directly, but reaches
it with inject('appI18n', null) — and the default is NULL. So a bundle
whose entry does app.use(createAppI18n()) without a matching
app.provide('appI18n', i18n) turned that watchEffect into a SILENT
no-op: no error, no warning, no translation. A partner embedding the
element simply could not choose its language.

FAILS-FIRST, measured with the fix stashed so this is a true before:
  my-upg-100-list/upg-100-mfe   profile-config.locale='ar'    0 Arabic chars
  regions-dashboard/regions-list (provides appI18n, CONTROL) 178 Arabic chars
Same harness, same locale present in both configs. The control is what
makes it evidence rather than a broken probe: 178 vs 0 under one harness
rules out "the config never arrived".

★ WHY HERE AND NOT IN EIGHTEEN index.js FILES. The fix is one line per
bundle, which is exactly why it decays: one fact stored in eighteen
places, and the nineteenth bundle written next month re-introduces it
with nothing to catch it. A vue-i18n instance IS a Vue plugin, so
wrapping its own install() makes the provide a CONSEQUENCE of
installing rather than a step beside it. A bundle cannot forget.
Bundles that already provide the same instance are unaffected —
providing an identical value twice is a no-op, and the control's reading
is unchanged at exactly 178 after.

MEASURED with studio's check:host-locale-contract, which compares each
leaf's host-channel reading against THAT LEAF'S own ?lang=ar reference,
so a leaf with sparse Arabic chrome is not judged against a constant:

  parent   18 FAIL
  here      7 FAIL

  my-upg-100-list/upg-100-mfe      0 -> 3418  (ref 3418, exact parity)
  my-upg-100-list/poster-wall      0 -> 2685  (ref 2685)
  my-upg-100-list/upg-100-list     0 -> 688   (ref 688)
  country-posters/country-poster-wall  0 -> 11025
  plus all five gospel-resources leaves and cities-internet

⚠ BIG PICTURE IS NOT FIXED BY THIS, and should not be marked so. It
reads 0 against ref 273 with the wrapper active and locale 'ar' present
in its config, so its failure is a different mechanism — nested children
following the parent's locale — not the missing provide. Six other
composites remain FAIL for the same reason.

The poster-wall gate grew hi and ru (five scripts: Latin, RTL, CJK,
Devanagari, Cyrillic) plus a guard for a locale that cannot detect the
defect at all: if a catalogue ever translates a key to exactly its
humanized fallback, that locale renders identically whether i18n worked
or threw and would pass forever. It now exits 2 NOT MEASURED naming the
key. Its PASS line also states its scope, because a green line pasted
into a scoreboard is where "translations render" silently becomes
"localisation works".
2026-09-25 13:49:58 +00:00
doxa-maps-88655c70
43e28c03e5 Integrate main into bundler-2/work
# Conflicts:
#	public/doxa-maps/index.html
2026-09-25 13:49:07 +00:00