Commit graph

1361 commits

Author SHA1 Message Date
doxa-maps-88655c70
8a9130294a Rebuild all bundles on the merged source 2026-09-25 17:18:38 +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
doxa-maps-88655c70
f3a71d3ff8 Hand the research dashboard its i18n instance, so a host's locale can reach it
Embedded with profile-config {"locale":"ar"}, this dashboard rendered entirely
in English. Not a missing catalogue and not a missing i18n install — the install
was there, one line short of being usable.

ProfileLoader is the root of this custom element, and vue-i18n's useI18n()
cannot resolve its injection at a custom-element root. So ProfileLoader takes
the i18n INSTANCE by reference through inject('appI18n') and drives its global
locale from the host's config. This entry called createAppI18n() and installed
it, but never provided it under that key, so the inject returned null and the
watchEffect that applies the locale quietly did nothing.

The sibling map entry (doxa-research-map/index.js:66) has always provided it,
and this file's own header calls itself a mirror of that one. That is how the
divergence survived: the two read as identical plumbing until you diff them.

MEASURED with check:host-locale-contract, before and after:
    research-dashboard/research-dashboard   host 0 of 1891  ->  host 75 of 1891

75, not 1891, and the remainder has the one known cause already recorded against
Big Picture: most of this dashboard's chrome is read through tStatic, which
resolves the locale from the URL and the document and cannot see a locale set at
runtime. That change is parked behind another agent's landing in the same file.
2026-09-25 13:49:00 +00:00
doxa-maps-88655c70
d2b13adaec 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/manifest.json
2026-09-25 13:48:31 +00:00
doxa-maps-88655c70
e94cb56b79 Map labels ask for a font the engine's glyph server has: one shared helper
library/maps/label-font.js picks a symbol layer's text-font by engine: Noto Sans
(Regular, or Bold for medium/bold) under MapLibre, which is what OpenFreeMap
serves, and exactly the caller's fonts under Mapbox. Mapbox maps render as
before.

useMapClustering hard-coded 'DIN Offc Pro Medium, Arial Unicode MS Bold', which
OpenFreeMap does not have. Under MapLibre every native cluster count requested
it and got a 404; MapLibre then drew the counts in its fallback weight instead
of the intended bold. It now uses the helper, as do upg-neighbor (replacing its
own copy), prayer-walk-planner, and key-cities' city-name labels, which named no
font and so asked for MapLibre's default (also a 404 on OpenFreeMap).

Measured by importing the real composable in a browser, 3,000 points in native
mode:
- main under MapLibre: 1 glyph 404 (DIN Offc Pro Medium,Arial Unicode MS Bold),
  and the counts drawn in the fallback weight;
- this change under MapLibre: 0 glyph 404s, text-font Noto Sans Bold, 22
  clusters with counts in bold;
- this change under Mapbox: text-font still exactly DIN Offc Pro Medium /
  Arial Unicode MS Bold.
No production map reaches native cluster counts today (Simple Research and
Country Posters force the 'mst' line mode), so this was latent in shared code.

Correction to b1e0c7fa: it said those labels "drew nothing". The screenshots here
show MapLibre falls back and draws them. The defect was the failed glyph request
on every load and the wrong weight, not missing text.

Rebuilt: country-posters, doxa-research-map, upg-neighbor, prayer-walk-planner,
key-cities.
2026-09-25 13:48:10 +00:00
doxa-maps-88655c70
544819624f browser-slot: a slot is held for at most BROWSER_MAX_HOLD seconds, and only by the wrapper
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
A probe that awaited map.once('load') with no timeout held slot 1 for 89 minutes on 3 s of CPU,
and every other browser gate on the box queued behind it and timed out as NOT MEASURED.

- The command now runs as a child under `timeout --kill-after` (default 900 s, BROWSER_MAX_HOLD);
  timeout signals the whole process group, so the chrome a probe spawned dies with it. 124/137 are
  reported as 75 NOT MEASURED, naming the command.
- fd 9 (the slot lock) is closed for the child. With exec, the command and every grandchild
  inherited it, so an orphaned process kept the slot locked after its parent exited — measured
  with the previous wrapper: the next caller got 75 after its wait.
- Proven on a private slot dir (1 slot, 5 s limit): sleep 99999 freed at 5.07 s with 75 and the next
  waiter ran at 5.58 s; a TERM-ignoring child killed at 7.1 s; after a command that leaves an orphan
  the slot is free in 261 ms; exit codes and stderr pass through unchanged.
2026-09-25 13:46:33 +00:00
doxa-maps-88655c70
2983d1c020 Merge main: regenerate the gallery index; regions-dashboard verified fresh by hash
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:32:48 +00:00
doxa-maps-88655c70
925351a758 Rebuild regions-dashboard: main was carrying a bundle its source no longer builds
check:composite-freshness named it by hash — committed 8420509dde0e, rebuilt
36560d8557d6 — so main shipped a .js that did not match the source beside it.
This is the failure the build rule exists to prevent, and it reached main anyway:
recent merges brought only other plugins' source, so deriving "what to rebuild"
from the incoming diff skipped this bundle, while something in the shared tree it
compiles against had moved underneath it.

The lesson is that the incoming diff is not a sufficient trigger. The freshness
gate compares hashes and is; it now runs before I land, not after someone else
finds it. Rebuilt on current main, tree and SRI regenerated, check:sri PASS across
every leaf, and the gate now reports regions-dashboard fresh.
2026-09-25 13:28:11 +00:00
doxa-maps-88655c70
2c83cce407 Studio and stack-demo hand the host's language down to what they mount
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 that embeds either composite with profile-config.locale was ignored (check:host-locale-contract:
0 Arabic characters with locale "ar", against 2121 / 2249 at ?lang=ar).

- stack-demo mounts its children by tag and already threaded tk and provider; locale now rides the
  same way to the research dashboard, and the grid (which reads cfg.locale itself) gets it only when
  the host gave one. 0 -> 358: the grid half. The dashboard half waits on its own bundle providing
  appI18n.
- plugin-studio seeded its language from localStorage / the browser and handed that to every pane.
  The ladder is now host profile-config locale/lang -> ?lang= / ?locale= -> saved choice -> browser;
  a host- or URL-given language is not persisted as the reader's. 0 -> 893 (its own chrome); the rest
  is the Research Gallery pane it opens on, which does not pass the locale on yet.
- plugin-studio's language switch rebuilt a pane's profile-config by hand without provider, tokenUrl
  and treeBase, so switching language turned a maplibre pane back into a mapbox one. Mount and
  switch now share one paneConfig().
2026-09-25 13:26:01 +00:00