Commit graph

643 commits

Author SHA1 Message Date
Doxa Maps
b0cd3140bd 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-26 00:19:52 +00:00
Doxa Maps
0f839f1071 MapLibre: reading the projection threw, so a region dashboard never framed
Driver, on MapLibre: opening a region dashboard does not zoom to the region the
way Mapbox does. This is why.

fitTheirWay read `m.getProjection().name` OUTSIDE its try. The two engines
disagree about both halves of that API:
  Mapbox    getProjection() -> { name: 'globe' }    setProjection('mercator')
  MapLibre  getProjection() -> { type: 'globe' } | undefined
                                                  setProjection({ type: 'mercator' })
MEASURED in maplibre-gl@5's own source, which we load from jsDelivr:
Map.getProjection() returns `this.stylesheet?.projection`, and the OpenFreeMap
style we hand MapLibre declares no projection key at all — its top level is
version, sources, sprite, glyphs, layers. So getProjection() is UNDEFINED there
and `.name` threw a TypeError.

Because the read sat outside the try, the throw escaped fitTheirWay AND escaped
applySubject(), of which it is the last statement. The camera was never framed:
the map stayed wherever it was instead of flying to the region.

The read is now defensive and accepts both shapes, and the WRITE is
engine-correct: Mapbox takes the bare string, MapLibre reads e?.type, so handing
it a string would set undefined rather than fail loudly.

It was wrong in a second way worth recording: on a MapLibre style that DOES set
globe, the old read returned null and silently skipped the mercator correction
that exists to stop the fit pulling back. So it both crashed on one shape and
under-corrected on another.

Proven headless per the Playwright policy, with the old expression run against
the same stubs as the control: it THROWS TypeError on the real MapLibre shape and
returns null where it should see globe, while the new one returns 'globe',
'mercator', 'globe', null, null across the five engine shapes. A check that
cannot be shown to fail proves nothing, so the check fails if the old expression
ever stops throwing.

Needs eyes: MapLibre, region dashboard — does it now fly to the region.
2026-09-26 00:19:26 +00:00
Doxa Maps
0de6a7c2f8 PPLR data maps: keep the loading globe up until the pins are drawn, not merely added
Six views set dataReady (which removes the LoadingGlobe) straight after addSource(). The engine
then still tiles tens of thousands of points in its worker, so the globe vanished and the map sat
empty with a full legend. Measured on the DX-life gallery: 0 features for 20 s, then 19,376.
whenGeojsonDrawn(map) resolves once every GeoJSON source is loaded AND a frame has rendered
(not 'idle', which an animating map may never reach), with a 45 s cap so it can never hang.
Religion, pins, coverage, translations, language families and Rosetta cards await it before
dataReady. Plugin-local: pplrData.js has no consumer outside pplr-data-maps.
Headless proof: a fake-map unit test (already loaded / last source late / never loads -> false at
the cap / style mid-swap / no map), 9/9 pass; BUNDLE=pplr-data-maps build + tree + check:sri PASS.
2026-09-26 00:14:16 +00:00
Doxa Maps
8dcd407e29 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-26 00:10:55 +00:00
Doxa Maps
7f707807d6 Merge remote-tracking branch 'origin/main' into rega/research-gallery-embed
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-26 00:05:06 +00:00
Doxa Maps
87ebbace9b Leaf pages load only the engine their provider asks for: no Mapbox requests under MapLibre
A generated map leaf chose its engine at BUILD time and hard-linked it in <head>: mapbox-gl.css,
the geocoder css, mapbox-gl.js and the geocoder js from api.mapbox.com. The provider is chosen at
RUNTIME (profile-config.provider → ?provider= → window.MAP_PROVIDER), so a partner embedding a leaf
with ?provider=maplibre still had every visitor's browser make 4 requests to Mapbox.

- generate-tree: no static engine tags on map leaves; the element's own runtime loaders
  (ensureEngineScript / ensureEngineCSS, the geocoder's on-demand loader) load exactly the engine
  asked for, the path composite leaves already ran on. The forge page drops its api.mapbox.com
  preconnect. A leaf opened with provider=maplibre (same ladder as resolveMapProvider) is token-free,
  so the public copy no longer asks a MapLibre visitor to paste a Mapbox token.
- check:leaf-engine-per-provider (new, browser): every leaf at ?provider=maplibre makes 0 requests
  to *.mapbox.com and still draws; at its default every map leaf draws with the engine it drew with
  before (--baseline <tree> compares against another tree).
- check:dashboard-geocoder-provider is wired into gates:post alongside it; it had been held out
  because this generator's tags made it red.
- verify-map-paint: optional GATE_VIEWPORT for a phone pass.

Measured before this landing (browser runs made before the headless rule): leaf-engine vs main's
tree PASS, 56 leaves, 0 Mapbox requests under MapLibre, same default engine. Pins, region popup,
PPLR search and key-cities PASS on both engines with 0 Mapbox requests under MapLibre. RTL shaping
PASS on both engines wherever RTL labels rendered. Paint under MapLibre: the same 4 leaves fail as
on main (country-posters-mfe, upg-100-mfe, regions-mfe, flower-mesh). Pre-existing, not caused here.
Poster "Render" is missing on country-posters and doxa-research-map under BOTH engines. Pre-existing
on main, read from source: countries-map.vue renders <PosterDialog v-if="showPoster"> without
v-model or getMap, and PosterDialog shows only when modelValue is true.
Headless, on this commit's regenerated tree: 57 leaves, 0 carry api.mapbox.com engine tags (main's
doxa-simple-map leaf carried 5), 56 carry the runtime provider check, check:sri PASS.
2026-09-25 23:44:04 +00:00
Doxa Maps
a8eb431c2f Merge remote-tracking branch 'origin/main' into rega/research-gallery-embed 2026-09-25 23:41:19 +00:00
Doxa Maps
f6571ed4ea Research Gallery: PPLR Data Maps and Wall of People Groups are named in all 11 languages
The two discovered Experimental tabs fell back to their English bundle titles in every
locale (Arabic rail: 'Pplr Data Maps', 'Wall Of People Groups'). Only doxa-maps-spa's own
bundle is rebuilt; its SRI refreshed in both manifests.
2026-09-25 23:41:17 +00:00
Doxa Maps
d8a9811a4e Rebuild regions-dashboard after resolving artifact conflicts 2026-09-25 23:39:33 +00:00
Doxa Maps
6f66625e88 Map controls were unclickable: the scroll strips sat on top of them
Driver, on Mapbox: the region dashboard zooms and draws correctly, but zoom in,
zoom out and fullscreen cannot be pressed at all.

CAUSE. .map-scroll-edge is a 40px full-height strip down each side with
pointer-events:auto and z-index:2 — the affordance that lets a vertical drag
scroll the PAGE instead of panning the map. Both engines place their control
groups in the very corners those strips cover, and at equal z-index the element
later in the DOM wins. The strips are rendered after the map container, so they
covered every control.

The strips must keep their pointer events, or the scroll affordance dies with
them, so the controls are lifted above the strips instead of the strips being
made inert.

ALL FOUR CORNERS AND BOTH ENGINE PREFIXES, deliberately: the left strip covers
the two left corners exactly as the right covers the right, a control can sit in
any of them, and naming mapboxgl- and maplibregl- together is what makes this
survive a provider swap — which is the whole point of the provider prop.

Proven headless, per the new Playwright policy: the shipped bundle carries the
rule for both prefixes and the strips still carry
touch-action:pan-y;pointer-events:auto. Whether the buttons are now clickable is
a drawn behaviour and goes out as needs-eyes.

NOT LANDED YET — main is held for studio's freshness rebuild.
2026-09-25 23:37:52 +00:00
Doxa Maps
58326f2d82 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:
#	internal/check-gospel-rtl-shaping.mjs
2026-09-25 22:55:49 +00:00
Doxa Maps
5238c36573 Gospel map: the country gap list says where to start
The rows under "No resource found" carried a name, a language and a population
and nothing else, so the question a mobiliser opens a country to answer — what do
I do about these? — cost one click per group. 369 of the 370 groups with nothing
have a way in, and it was reachable only by opening each pin in turn. Mali's two
now read "way in: French"; Benin's one reads "way in: SUN (signed)".

Text rather than a link, and it could not be otherwise: these rows are button
drill-ins and HTML forbids an anchor inside a button — the parser hoists it out
and it stops being a link, which is why the rows that DO have resources are plain
containers instead. The row still opens the group sheet, and the sheet carries the
bridge's real links.

It names the bridge and claims nothing further. "Bambara" here does not say
Bambara reaches this group; that stays the local question the sheet spells out.
It turns a list of dead ends into a list with a first move against each one.
2026-09-25 22:49:05 +00:00
Doxa Maps
85ac672f06 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 22:35:50 +00:00
Doxa Maps
29fc335b00 Prove the grid-under-map link on both engines, not just the one we happened to run
BUILD-INTEROP-CONTRACT-GRID-UNDER-MAP-BOTH-PROVIDERS asks for a provider matrix:
the same assertions, once per engine, with identical counts. Interoperability
that holds on one provider and not the other is not interoperability.

It is not a formality here. The map half of this link resolves a religion family
through its own legend, and the legend is drawn by the map engine — MapLibre
drops an entire layer in silence for one misplaced paint property, which would
surface as a link delivering nothing while the Mapbox run stayed green.

So the gate now runs the whole measurement under ?provider=mapbox and
?provider=maplibre, and then asserts the two agree: the same starting total, the
same rows for the same family, and the same rows over the composite's wire. A
per-engine pass with different numbers would mean the same choice narrows to
different peoples depending on which engine a partner embedded.

MEASURED, identical to the row on both:

    mapbox     2,107 groups · Buddhism 83 direct · 83 linked · mutant 0
    maplibre   2,107 groups · Buddhism 83 direct · 83 linked · mutant 0

The mutant still runs first on each engine, so a green here still means the gate
can tell a right mapping from a wrong one — twice over now.
2026-09-25 22:35:48 +00:00
Doxa Maps
32a3bcf36a 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 22:28:19 +00:00
Doxa Maps
69d787cb15 Gospel map: say how many of the gap groups have somewhere to start
"Nothing found 370" is where the legend stopped, and it reads as a wall. Every
one of those groups but a single uncoded row now has somewhere to start — a
curated bridge, a language their own country actually speaks, or SUN for a Deaf
group — and none of it was visible without opening 370 pins one at a time.

It says "a possible way in", not "reached". A bridge has to be learnt, it is not
Scripture in their language, and the red dot means exactly what it meant before.
Counted in the same pass as the tier numbers, from the same rows, so the two can
never drift apart.

Also drops a link-pruning pass added earlier in this change and then removed. It
was written on the belief that 74 links recorded broken were still being served;
they are not — the builder has pruned them from the index for some time, and it
reports the count on every run. The measurement that looked alarming was a
verification record listing URLs that had already left the index. Nothing was
wrong, so nothing needed fixing, and a second implementation of a working rule is
worse than none.
2026-09-25 22:21:03 +00:00
Doxa Maps
216fa292d4 Pin popup: render the description their site renders, not a field that is 99% empty
Their CountryMap binds `description: p.imb_people_description ?? ''` and asks for
that field in PIN_FIELDS (master, :89 and :114). The doxa-maps-build branch this
repo's DX-life checkout is parked on still says location_description, and that is
what we cloned — I even kept it deliberately last commit, reasoning that "the
popup needs it", which was true of the branch and false of their site.

MEASURED against the prayer API over 2,107 people groups:
  non-empty location_description      35   (1%)   <- what our popup rendered
  non-empty imb_people_description  2107   (100%) <- what theirs renders
So this was not the wrong paragraph, it was NO paragraph: roughly 2,072 groups
opened a popup with a blank description where doxa.life shows prose.

Nothing caught it because the popup did open, with a name, population and
religion — my own pin probe asserts that a popup APPEARS, never that it says
anything. An element can be present and empty, and "it opened" is not "it is
right".

The verification queued behind this: _probe-popup-description-field.mjs clicks a
pin, reads the group's NAME out of the popup, asks the API for BOTH candidate
fields for that exact group, and asserts the popup contains
imb_people_description and NOT location_description — the second half is the
control, since "the popup has a description" passes with either field. It reports
UNPROVEN when the two fields happen to be equal for that group. Browser slots
have been saturated for fifteen minutes, so what is proven here is the data path:
the shipped bundle requests imb_people_description, carries no
location_description at all, and the two fields demonstrably differ in the API.

Built only regions-dashboard.
2026-09-25 22:15:36 +00:00
doxa-maps-88655c70
60477a29b1 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 21:56:21 +00:00
doxa-maps-88655c70
2b3d0a6c35 Rebuild regions-dashboard after resolving artifact conflicts
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 21:48:25 +00:00
doxa-maps-88655c70
c14967e890 Merge main into bundler-2/work 2026-09-25 21:47:37 +00:00
doxa-maps-88655c70
dcdb23d773 Stop cloning a hidden element DX-life's master has deleted
The shared root-dx-life checkout is parked on doxa-maps-build (9305706, Sept 17),
not DX-life's master (fc4d622, today) — so every file I have read for a RULE 1
port came from a divergent branch, not only the translated strings.

I checked the nine sources I had ported from. Six are identical on master, and
the three that differ are identical in the exact rules I relied on: the uupg
country-and-bloc markup line I matched this morning, the whole .country-map
block, and the .uupg__card 576px reflow. So the clone conclusions hold.

One thing did not. That branch renders <p class="uupg__content"> with
location_description and then hides it with display:none; master has removed the
markup, the rule and the field. We had cloned the hidden version faithfully,
which meant asking the API for location_description on every list request purely
to fill an element nobody can see. All three are gone here too.

The pin POPUP still needs that field and still asks for it — that is
region-dashboards.vue's own FIELDS (:176), a different request, and their
CountryMap really does render it. It is not a search key either
(SEARCH_KEYS is their list verbatim), so nothing else was reading it.

Nothing visible changed, and that is the point: the census still measures 42
strings against their 42 on the country page, 0 unaccounted, 0 omissions.
check:sri PASS. Built only regions-dashboard.

_qa-cycle/02-builder-cycle/dxlife.sh now reads DX-life from github/master via
git show, never from the shared working tree, and exits 75 NOT MEASURED if it
cannot resolve master rather than silently falling back to the stale one.
2026-09-25 21:44:20 +00:00
doxa-maps-88655c70
d38488d799 Merge branch 'main' of https://git.nuxtbase.app/doxa-life/doxa-maps into bundler-3/work
# Conflicts:
#	internal/check-gospel-rtl-shaping.mjs
#	public/dx-maps/assets/tables/country-languages.json
#	public/dx-maps/doxa-maps-spa/manifest.json
#	public/dx-maps/gospel-resources/charts/index.html
#	public/dx-maps/gospel-resources/data-tables/index.html
#	public/dx-maps/gospel-resources/gospel-dashboard/index.html
#	public/dx-maps/gospel-resources/gospel-resources.js
#	public/dx-maps/gospel-resources/resources-list/index.html
#	public/dx-maps/gospel-resources/resources-map/index.html
#	public/dx-maps/manifest.json
2026-09-25 21:38:20 +00:00
doxa-maps-88655c70
652fceac01 Merge main into bundler-2/work 2026-09-25 21:35:58 +00:00
doxa-maps-88655c70
6f2291b467 Point my two map gates at the tree that exists, and stop them reading the old picture
THE RENAME. The built tree is published as dx-maps now, and both gates had
public/doxa-maps hardcoded. A wrong tree name does not error, it 404s — and a
gate that 404s reports a clean page, which is the most expensive kind of green.
They read TREE_NAME from the one helper instead, so the next rename moves them.

★ AND THE RE-VALIDATION THE REPAIR DEMANDED CAUGHT A SECOND BUG — IN THE GATE.
Re-running the clustering gate against its own baseline, Network came back at
1,700 lines: exactly MST's number, where the same build had given 23,896. Not a
regression. The gate settled on "two consecutive reads agree", and the previous
mode's lines stay on the map for as long as the new mode takes to compute — so a
stable read can be the OLD picture. It now waits for the drawing to CHANGE from
what was on screen when the button was clicked, and only then waits for it to
stop moving; if it never changes, that is recorded and the assertions say so.

Re-validated after the fix, matching the baseline exactly:

    off 0/0/0 · native 37/37/0 · mst 0/0/1700 · network 0/0/23896

A repair nobody re-measured is worth nothing, and this one was wrong twice
before it was right.
2026-09-25 21:35:57 +00:00
doxa-maps-88655c70
56dffd1af8 Gospel map: ask what the country speaks, not what the unreached speak
The bridge candidate — the "possibly a way in" offered to a group with nothing —
was chosen from Doxa's own pins: the biggest language among the UNREACHED groups
in that country that has Scripture and the film. That is not the language a
community shares with its neighbours. For Syenara Senufo in Mali it proposed
Minianka, one people group. Mali's vehicular language is Bambara.

The number beside it was worse than the pick. "8.8% of this country's mapped
population" is a share of Doxa's unreached pins, and a reader takes a percentage
for a share of the country. A percentage measured against a denominator nobody
can see is not a caveat, it is a wrong number.

CLDR publishes what each territory speaks and by what share, joined to ISO 639-3
through SIL's own Part1 column, and pinned rather than tracked from a moving
branch. 1,161 of 1,175 candidates now come from it; the remaining 14 keep the old
proxy and say on the row that their count is of this map's groups and not of the
country. Mali now reads French at 46% and official.

Where two languages are within five points the map names BOTH and says it will
not choose. Mali is exactly that: French 46% and official, Bambara 46% and not —
one is the administrative language and the other is what people share in a
market, and which bridges better depends on whether you are handing over a
booklet or starting a conversation. A tie-break would have quietly answered a
question nobody asked it. Both carry their own links, for the same reason the
first one does.

Two field lists were a field short, the same shape twice. The candidate filter
and the link resolver both read c.s for "has a readable Scripture" and forgot
that Faith Comes By Hearing carries text on the audio record — which excluded
Bambara from Mali's candidates entirely, and then, once it was named, offered it
the film while a full 66-book text sat one field away. There is now one
predicate.

The RTL gate read a hardcoded tree path. It reads the build config instead, and
was re-run against its baseline before the result was trusted.
2026-09-25 21:34:38 +00:00
doxa-maps-88655c70
5ebb8f1cd9 Merge remote-tracking branch 'origin/main' into rega/research-gallery-embed 2026-09-25 21:34:35 +00:00
doxa-maps-88655c70
b862c7d61a Export gate reads the tree name from the build config
After the dx-maps rename, its public/doxa-maps literal made it SKIP (exit 2) without measuring.
Validated against its baseline on the renamed tree: 4/4 one-click exports, 1700x2200 PNGs,
0 errors, as before the rename. Also: usage examples and present-tense comments in the
gallery and planner name /dx-maps/ or the configured tree; both bundles rebuild byte-identical.
2026-09-25 21:32:35 +00:00
doxa-maps-88655c70
7fd75a0965 Merge branch 'main' of https://git.nuxtbase.app/doxa-life/doxa-maps into bundler-1/work 2026-09-25 21:23:22 +00:00
doxa-maps-88655c70
bd75f609da Arabic: their people-group vocabulary, and the list leaf reads ?lang= too
Two defects, both invisible to an English diff because both sides were English.

1. THE LIST LEAF NEVER GOT THE ?lang= FIX. I repaired languageCode in
region-dashboards.vue and stopped; regions-list.vue has the same computed and the
same comment block describing the same defect, and it still read only props and
profile-config. So a shared /regions-list/?lang=ar link rendered "المناطق" as its
heading and then listed all 41 countries as "Angola", "Benin", "Cameroon".

2. OUR ARABIC WAS NOT THEIRS. Ten strings used vocabulary doxa.life does not use:
"مجموعة بشرية" where they say "مجموعة شعبية" for a people group, "مُخْدُومَة"
where they vowelise it "مَخْدُومَة", and for unengaged an invented
"غير مبشرة" / "الهدف هو القضاء على…" where they simply say "غير مَخْدُومَة".
Each replacement is a string read off their live /ar/ pages in this session, with
our placeholders re-inserted where their rendered page carried the values —
copied, not translated, which is what the brief asks for and what I can defend.

MEASURED, by the census in Arabic: 190 unaccounted additions and 8 omissions
before, 9 and 8 after the leaf fix, 0 and 0 after the vocabulary. The English
census reported a perfect clone throughout, which is exactly why the locale
dimension was worth running.

The sanctioned list now resolves through our own catalogue, so a permission
granted in English is the same permission in Arabic: "عرض ملصقات الدولة" is
recognised as the Driver's country-poster toggle instead of being reported as an
invention. Hardcoding its Arabic would only have moved the problem to zh.

Also repaired every probe URL for the dx-maps rename — 180 paths across 153 files
were still pointing at /doxa-maps/, which now 404s, so the whole measurement
apparatus was aimed at a tree that no longer exists. Replaced only the
slash-delimited segment, leaving doxa-maps-spa and the repo URL untouched, and
proved the repair by re-running the English census to its known-good baseline
before trusting anything else.

Built ONLY regions-dashboard (rule 5: studio owns the full build). This touches
library/i18n/locales/ar/regions.json, so other bundles need a consumer rebuild —
messaging studio with the sha.
2026-09-25 21:22:44 +00:00
doxa-maps-88655c70
9006a8eabd 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/dx-maps/index.html
2026-09-25 21:14:43 +00:00
doxa-maps-88655c70
d1f9264016 The dashboard's search header follows the engine it was given
It fetched the Mapbox geocoder from api.mapbox.com whatever the provider, and
never passed `provider` to the GeocoderComponent it mounts — so a partner who
chose MapLibre, the keyless engine picked precisely so that no Mapbox key and no
Mapbox traffic is involved, still sent every visitor's IP to Mapbox and still
got the Mapbox geocoder. It now resolves the provider through the usual ladder
and, under MapLibre, preloads nothing: GeocoderComponent already fetches the
MapLibre plugin itself, so the right amount of work here is none.

★ AND THIS DOES NOT FIX THE LEAK THAT WAS REPORTED, WHICH IS WORTH SAYING PLAINLY.
The audit named this file, and this file was wrong. But research-dashboard.vue:23
says in as many words that DashboardSearchHeader "remains available in the kit
but is not mounted here" — so nothing this dashboard renders was ever affected by
it. Measured on the built leaf at ?provider=maplibre, the four requests it makes
to api.mapbox.com are all tags in the GENERATED LEAF PAGE:

    mapbox-gl.css · mapbox-gl.js · mapbox-gl-geocoder.css · mapbox-gl-geocoder.min.js

The leaf ships Mapbox GL and the Mapbox geocoder to every visitor regardless of
the engine asked for. That belongs to the leaf generator, not to this plugin,
and it has gone to studio as a finding.

So what lands here is a component made correct for whoever mounts it, and a gate
that names the real cause. The gate is deliberately NOT in gates:post: it cannot
pass until the leaf stops shipping those four files, and a red gate about another
lane's file teaches everyone to ignore it. Run it by name.
2026-09-25 21:14:19 +00:00
doxa-maps-88655c70
a191bee38a Rebuild regions-dashboard after resolving artifact conflicts 2026-09-25 21:12:18 +00:00
doxa-maps-88655c70
bc4d1f54bd Arabic: their people-group vocabulary, and the list leaf reads ?lang= too
Two defects, both invisible to an English diff because both sides were English.

1. THE LIST LEAF NEVER GOT THE ?lang= FIX. I repaired languageCode in
region-dashboards.vue and stopped; regions-list.vue has the same computed and the
same comment block describing the same defect, and it still read only props and
profile-config. So a shared /regions-list/?lang=ar link rendered "المناطق" as its
heading and then listed all 41 countries as "Angola", "Benin", "Cameroon".

2. OUR ARABIC WAS NOT THEIRS. Ten strings used vocabulary doxa.life does not use:
"مجموعة بشرية" where they say "مجموعة شعبية" for a people group, "مُخْدُومَة"
where they vowelise it "مَخْدُومَة", and for unengaged an invented
"غير مبشرة" / "الهدف هو القضاء على…" where they simply say "غير مَخْدُومَة".
Each replacement is a string read off their live /ar/ pages in this session, with
our placeholders re-inserted where their rendered page carried the values —
copied, not translated, which is what the brief asks for and what I can defend.

MEASURED, by the census in Arabic: 190 unaccounted additions and 8 omissions
before, 9 and 8 after the leaf fix, 0 and 0 after the vocabulary. The English
census reported a perfect clone throughout, which is exactly why the locale
dimension was worth running.

The sanctioned list now resolves through our own catalogue, so a permission
granted in English is the same permission in Arabic: "عرض ملصقات الدولة" is
recognised as the Driver's country-poster toggle instead of being reported as an
invention. Hardcoding its Arabic would only have moved the problem to zh.

Also repaired every probe URL for the dx-maps rename — 180 paths across 153 files
were still pointing at /doxa-maps/, which now 404s, so the whole measurement
apparatus was aimed at a tree that no longer exists. Replaced only the
slash-delimited segment, leaving doxa-maps-spa and the repo URL untouched, and
proved the repair by re-running the English census to its known-good baseline
before trusting anything else.

Built ONLY regions-dashboard (rule 5: studio owns the full build). This touches
library/i18n/locales/ar/regions.json, so other bundles need a consumer rebuild —
messaging studio with the sha.
2026-09-25 21:11:38 +00:00
doxa-maps-88655c70
ca548b37ba Gospel map: the Deaf groups get their way in on the map, not only in the table
SUN reached the access table and the CSV first, and the map — the surface a
mobiliser actually opens — still showed Deaf groups a panel that explained their
situation honestly and then left them with nothing to do. That is the largest
population on this map with nothing: 93 groups, 17.5 million people, and
structurally so, because a spoken bridge cannot reach a Deaf community and there
was no other kind.

Clicking Deaf Beninese now offers the SUN New Testament, the SUN Bible stories
and the language itself, and says that SUN is already being taught in Benin by
Mission Évangélique des Sourds — a named local Deaf church rather than a
resource with nobody attached to it. It is offered only where the group has
nothing, because a group with Scripture in its own sign language does not need
to learn a second one, and it says on its face that it is a bridge which has to
be learnt and closes no gap in their own language.

Carried in the pin index beside the spoken bridges rather than fetched by the
leaf, so the sheet and the access table cannot disagree about what exists. It is
one small object, not a per-language join, because SUN is one signed language
for all of them.

The doctrine scan reads it too. Those URLs live in a new top-level block that the
per-language loop cannot see, so they were added to the scan in the same change
rather than found missing later, the way the access table was.
2026-09-25 21:00:44 +00:00
doxa-maps-88655c70
134b6e484e Plugin Studio: the header's menus stay on top of every plugin
Driver, 2026-09-25: "the Mapbox token menu is not z-index on top … it is underlapping the plugins". The
header row and the pane area were siblings with no stacking context of their own, so a menu at z-index 30
lost to plugin chrome (a filter bar at 55, a drawer at 1000). The header now stacks above the content and
the pane area is isolated (isolation:isolate), so no plugin's z-index can rise over the studio's header.
Measured in a browser with a plugin open: the Settings panel and the engine menu are the topmost element at
3 of 3 sampled points.
2026-09-25 20:44:24 +00:00
doxa-maps-88655c70
acb837ff02 Gospel charts: the titles and the counts inside them are translatable
The three chart titles, subtitles and axis units were the last inline English in
this leaf. The subtitles carry a count, so they are held whole in the catalogue
with a placeholder and the number arrives as a parameter — a sentence built by
gluing a number between two fragments cannot be translated, because word order
differs and several of these locales put the number where English does not.

The fallback interpolates too. Without that, a host that never installed i18n
would render the placeholder literally on screen, which is worse than the English
sentence the fallback exists to protect.

The leak gate could not have caught any of this. Whole-label equality is the
right unit for a fixed label and blind to a sentence carrying a number: the
catalogue holds "Across all {n} languages in the corpus." and the screen shows
"Across all 5,067 languages in the corpus." Those are never equal, so three
subtitles could have shipped untranslated in eleven locales with the gate
reporting PASS. A placeholder is now a wildcard and those keys are compared by
pattern; everything else stays equality, which is what keeps a word inside a
sentence from impersonating a label. Comparison went from 2,100 pairs to 2,244.
2026-09-25 20:39:19 +00:00
doxa-maps-88655c70
c53367f920 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 20:31:17 +00:00
doxa-maps-88655c70
1ffbd3b584 Use library engineFor(map); the last local engine-resolution line is gone
exp-shepherd landed engineFor(map) in useMapProvider, so the expression I was
carrying can go. Theirs is stricter than mine: it instance-tests BOTH engines
rather than inferring the second from the first.

This closes a three-step path, and each step deleted something. It began as
glFor.js copied from pplr-data-maps; became one expression delegating to
useMapProvider's isMapboxMap once I found that already existed; and is now a
plain import. Nothing engine-related is left in this plugin.

Three implementations were compared before swapping, because a substitution I
have not measured is one I am guessing at: _chk-glfor-equivalence.mjs builds the
deleted copy, my interim expression and library engineFor, and runs all three
across every engine combination — mapbox map with both globals, maplibre with
both, either alone, and the case the whole fix exists for, a MapLibre map with no
Mapbox global at all. Identical in all of them.

Built on the POST-RENAME tree: 68 files under public/dx-maps/, none under the old
public/doxa-maps/. My previous all-bundle rebuild predated that rename in this
worktree and wrote orphans; confirming the tree name before building is now part
of the sequence. composite-freshness PASS on all 10, check:sri PASS.
2026-09-25 20:10:13 +00:00
doxa-maps-88655c70
01096d67d3 Integrate main into bundler-2/work 2026-09-25 19:50:05 +00:00
doxa-maps-88655c70
c727c87411 Plugin Studio header: language, map engine and Settings set the props every plugin gets
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
Driver, 2026-09-25: "the studio at the top needs a localization dropdown, a map dropdown to swap
between Mapbox and MapLibre, and a settings button to add a Mapbox token or the URL it comes from…
the header needs to let you swap between props… so the studio becomes more functionally useful."

The header now reads: Language ▾ · 🗺 Map engine ▾ · ⚙ Settings.
- Map engine: Mapbox (needs a token) / MapLibre (no token needed) / each map's own default. The choice
  is handed to every pane as its profile-config provider and published as window.MAP_PROVIDER for
  maps nested inside composites; changing it re-mounts the maps.
- ⚙ Settings replaces the separate 🔑 button: the Mapbox public token (sk.… refused) or a token-service
  URL, with a status line that says which is in use — and that MapLibre needs none.
- Kept in this browser (localStorage); one menu open at a time. New labels in all 11 locales.

Browser-tested on the built dx-maps tree: the header shows the three controls; MapLibre sets the pane's
provider and window.MAP_PROVIDER and survives a reload; Settings then says no token is needed; Mapbox
plus a pasted token puts both on the pane; "each map's own" clears the provider.
2026-09-25 19:41:20 +00:00
doxa-maps-88655c70
b122009b96 Merge main into bundler-2/work 2026-09-25 19:27:30 +00:00
doxa-maps-88655c70
588a2a5d3c Translate the map legend's title, without inventing a word
Big Picture in Arabic still showed "UPGs / country" over its choropleth legend.
It is now composed from two catalogue entries that already ship in all eleven
locales — legend.columns.upgs and dashboard.country — so nothing new had to be
written in ten languages to fix it. The English shifts to "UUPGs / Country",
which is how this tree spells both words everywhere else.

It was the only one of the six remaining English strings on that view that
could be done this way. "Export CSV", "Full screen", the detail drawer's title
and empty state, and the legend's "heartbeat zone" bin all need wording that
does not exist yet, so they are on DECIDE-SR-CHROME-WITH-NO-CATALOGUE-WORDING
with one useful note: "Full screen" needs NO new wording either, because
buttons.toggleFullscreen already ships in every locale — only a decision to use
one toggle label instead of a two-state pair.

★ TWO OF THE REPORTED DEFECTS DO NOT REPRODUCE, and the measurement says why.
The table's last column header is neither English nor clipped on this tree: all
nine headers render Arabic and every one of them has scrollWidth equal to
clientWidth at 1440×900. The screenshot that reported "ent" predates the commit
that gave that column a catalogue getter.
2026-09-25 19:27:29 +00:00
doxa-maps-88655c70
ebf43e6001 Gospel map: give the bridge language its own links
Both bridge panels named a language and stopped there. For a group with nothing,
the only link on the sheet was "Every resource in their own language" — which is,
by definition, the empty table. Clicking a gap pin showed it plainly: Syenara
Senufo offered five "none found" chips, named Minianka as the way in, and the one
thing to click led to nothing. The sheet told a mobiliser where to go and then
made them go and find it themselves.

Nothing new had to be fetched. Both bridge records already carried the bridge's
ISO 639-3 code and the pin index already held that language's verified links, on
the same screened publishers and through the same doctrine scan as every other
link on the sheet. Minianka is ebible.org/myk/ and jesusfilm.org/watch/jesus/
minianka, and both answer 200 while a made-up sibling path 404s. Joined on the
code, never the name.

All 1,298 bridge records resolve to a language that has at least one link, so no
named bridge becomes a dead chip; the markup renders only what the index holds,
so that stays true if a pool ever loses one.

Also records the ruling on publisher names as settled rather than as this gate's
own judgement: organisation and product names are proper nouns and stay English,
scoped to the Source column and product names. The structural cell rule is what
lets the same words be a name in a Source cell and a translated label in the
chrome at the same time.
2026-09-25 19:27:23 +00:00
doxa-maps-88655c70
e31738762c Wall of People Groups: say so when the live API cannot be reached, and stop a render error
With pray.doxa.life unreachable (offline, or a local dev server whose 127.0.0.1 origin the API
refuses by CORS) the wall sat at "0 Groups Loaded" with nothing explaining why, and logged
"Cannot read properties of undefined (reading 'length')". That error was not the fetch at all:
SourceSelector's template read `sources`, which does not exist (its computed is
availableSources), so it threw on every render, online as well.
  - SourceSelector: availableSources.
  - PeopleGroupsWall: when nothing loaded and the store holds an error, an in-wall notice says
    what failed and offers Try again (inside the wall, never position:fixed over a host page).
  - The store logs the network failure as a warning: it is an expected state now shown on screen.
Measured with every pray.doxa.life request aborted: the TypeError is gone, the notice renders,
and the only console line left is the browser's own "Failed to load resource". Unblocking and
pressing Try again loads 2107 groups. Live (unblocked): 2107 groups, 0 console errors.
2026-09-25 19:20:48 +00:00
doxa-maps-88655c70
57065889f6 Integrate main into bundler-2/work 2026-09-25 19:07:04 +00:00
doxa-maps-88655c70
82d7f21e85 The raw map shapes Arabic, Persian and Hebrew place names
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
upg-100-list builds its map RAW (`new gl.Map`) rather than through
useMapInstance, so it inherits none of the bundle entry's side effects —
including the RTL text plugin. It appeared to work only because the leaf
head PRELOADED the Mapbox peer, which let an entry-time registration
land in time. studio is removing that preload.

⚠ IT IS NOT A UI-LANGUAGE FEATURE, which is why it matters more than it
looks. A basemap draws each place in its OWN script, so over the Middle
East, Iran and Israel MOST labels are Arabic, Persian or Hebrew for
EVERY viewer in EVERY UI language. Unshaped they render reversed — a
WRONG NAME for exactly the groups this map is about — and it fails
silently: no error, no warning, just mirrored text that reads as a font
problem to anyone who does not know the script.

MEASURED with gospel's gate, which PANS to Arabia before judging because
the shaper registers lazily and reads 'deferred' at a world view:

  before   mapbox    ok    58/61 RTL labels shaped
           maplibre  FAIL  62 RTL labels on screen, shaper 'requested'
  after    mapbox    ok    58/61
           maplibre  ok    63/63 shaped

★ THE MAPBOX ARM PASSING BEFOREHAND IS THE FINDING. It is the preload
rescuing an entry-time call, so mapbox looks fine today and breaks the
moment that preload goes. MAPLIBRE ALREADY FAILED, with no preload to
rescue it — this was live for every keyless viewer, not only a future
regression. Measuring just the mapbox arm would have read "already
fine".

The install goes after the engine global resolves and before
new gl.Map: with the engine injected lazily, an index.js call runs
before `gl` exists and does nothing at all.

THE GATE IS PARAMETERISED, NOT FORKED. dx-shepherd routed it to the
other raw-map owners (cities-internet, key-cities, RegionCountryMap), so
it takes --leaf instead of hardcoding gospel's. Copying it would have
given each owner a drifting copy of the pan-first logic and the poison
arm — the two parts that needed corrections to get right.

Also carries the leak gate's third correction: the survivors of the
data and facet exclusions were in .rd-lt inside .rd-li and .rd-bl inside
.rd-bar — DashboardChartsRail renders `{{ s.key }}` beside `{{ s.value }}`
over chart.geometry, the same shape as a facet row. Chasing one class
name at a time is how that went wrong three times, so the rule is now
general: an element whose own text sits beside a sibling rendering a
NUMBER is enumerating data, not naming the UI. Section headings and
column titles have no numeric sibling and stay in scope.
2026-09-25 19:05:59 +00:00
doxa-maps-88655c70
86442aa4c0 The UPG-100 search placeholder speaks the reader's language
repo's Arabic screenshot of the list was clean except for one string:
the search box still read "Search people group, zone, or territory…" in
every locale. It was a hardcoded literal, the last English on an
otherwise localized screen.

NO NEW TERMINOLOGY — composed from wording this product already ships
and has already approved: search.kinds.peopleGroup for the group,
tabs.upg100Zone for what this plugin calls a "zone" (its own legend
names them "UPG-100 Lists" / "قوائم UPG-100" / "UPG-100 名单"), and
search.kinds.region for the territory. That is precisely why this one
could be translated while the UUPGs column header could not: there, no
source established any candidate acronym, and a wrong term looks
authoritative to a reader who cannot check it. Here the words were
already on the page.

⚠ A computed, and declared beside LEGEND_TITLE rather than at the
literal's old position, because t() is not initialised until later in
setup — a const at the original line would have captured the fallback
and never re-evaluated when the locale changed.
2026-09-25 19:05:59 +00:00
doxa-maps-88655c70
ac6347aa15 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/manifest.json
2026-09-25 19:04:56 +00:00
doxa-maps-88655c70
ac66728a69 Gospel charts: the chart vocabulary comes from the catalogue
Every bar label, tab and legend key in the charts leaf was written inline in
English and never wired to i18n — a surface whose headings were translated while
the words inside the picture were not. Eight of them already had catalogue
entries and were simply not used; the other fourteen are new keys, translated in
all eleven locales from each catalogue's own vocabulary rather than invented
beside it.

The tab strip had to become computed. A plain array is evaluated once at setup,
so it would have kept whichever language the leaf first rendered in and never
followed a locale change; the legend arrays live in the template and already
re-evaluate on render.

Adding a key for "Full Bible" then exposed what the leak gate cannot tell apart.
It read table CELLS as if they were chrome, and a cell whose entire text equals
an English term is indistinguishable from a label by text alone. Both findings it
produced were the language being correct: Source cells carry organisation names —
JESUS Film, Faith Comes By Hearing, Door43 — and Scope cells describe what a
resource contains, one of whose 114 values is the words "Full Bible" while most
of the rest are already in the target language. Translating either would be the
defect. The boundary is structural, so the DOM now draws it.

That leaves the Type column, the one cell column carrying catalogue labels, with
its own arm. Asked the obvious way round it was wrong: requiring every translated
type to be present failed on Gospel audio in all four locales, because 1,027 rows
carry it and none was on the first page of fifty — pagination reported as a
translation defect. It asks instead that no cell equal one of the eight
enumerated type strings, which stays silent for a type that is off-screen and
fails for one that is on-screen and English.
2026-09-25 19:03:44 +00:00
doxa-maps-88655c70
bd180fac82 Merge main into bundler-2/work
# Conflicts:
#	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/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/manifest.json
#	public/doxa-maps/research-dashboard/research-dashboard/index.html
2026-09-25 19:00:09 +00:00