Commit graph

588 commits

Author SHA1 Message Date
doxa-maps-88655c70
056942b33f engineFor(map): build markers, popups and poster maps with the map's OWN engine
Every generated leaf <head> preloaded mapbox-gl.js whatever the provider, so code that used the
bare `mapboxgl` global always found one, even on a MapLibre map. Removing that peer (it sends
every visitor's IP to Mapbox under provider=maplibre) turns each such site into an error.
engineFor(map), next to isMapboxMap in useMapProvider.js, asks the map which engine built it:
  - useSelectedPin: the selected-pin Marker
  - useMapEvents: the pin-fallback and region Popups
  - useMapPoster: main already builds the hidden map with the source map's engine (1cbfb3aa);
    its choice now goes through this one helper instead of a second inline sniff
  - pplr-data-maps: glFor is now this helper; the geocoder awaits ensureEngineScript first
  - key-cities: ensureEngineScript('mapbox') before reading the global
Measured with every api.mapbox.com tag stripped from the served leaf (no peer at all for
key-cities), before -> after:
  pin select under MapLibre, on cities-internet, country-posters, doxa-research-map and
  doxa-simple-map: "reading 'Marker'" -> marker drawn; country-posters region popup: none ->
  popup; PPLR search: no input -> suggestions (both engines); key-cities: "mapbox-gl not
  loaded" -> map loaded. Mapbox rows unchanged (they passed before and after).
The poster UI could not be driven: PosterDialog never opens in country-posters or
doxa-research-map (they pass props it does not declare and no modelValue/getMap), reported
separately. useMapPoster itself was rendered from a real map with only its engine on the page:
MapLibre "mapboxgl global not found" on the pre-1cbfb3aa library -> a 400x300 PNG with 88 colours.
2026-09-25 18:38:17 +00:00
doxa-maps-88655c70
966c44fcc0 Plugin Studio: a map-token control (paste a Mapbox public token, or point it at a token service)
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
Driver request, 2026-09-25: "add a spot on the studio for placing a Mapbox token, or setting the URL
for where the Mapbox token API is from, like doxa.life has an API token".

A 🔑 Map token button in the studio header opens a small panel:
- paste a Mapbox PUBLIC token (pk.…) — a secret sk.… token is refused, the page is public;
- or a token-service URL: the studio fetches it (JSON {token|tk|accessToken} or a bare string, the
  leaf pages' contract) and uses the pk.… it returns. The maps never read a token URL themselves, so
  the studio resolves it and hands them the token;
- Clear returns to the token built into the page. A dot and a status line say which is in use.
Precedence: your token > a token from your URL > the host's tk > the page's built-in token. Kept in
this browser's localStorage only; applying re-mounts the open map and publishes window.MAP_TOKEN for
maps nested inside composites. Labels in all 11 locales (i18n parity PASS).

Tested in a browser on the built studio: shows the built-in source; sk.… refused and not stored; a
pasted pk.… reaches the pane's profile-config and window.MAP_TOKEN and survives a reload; Clear
restores the built-in token; a token URL is fetched and applied. https://doxa.life/api/maps/token is
refused cross-origin from another site — the panel says so and keeps the current token.
2026-09-25 18:37:39 +00:00
doxa-maps-88655c70
fc86f2ed57 Merge branch 'main' of https://git.nuxtbase.app/doxa-life/doxa-maps into bundler-1/work 2026-09-25 18:24:09 +00:00
doxa-maps-88655c70
ce1097da3f Delete the glFor copy: the instance sniff already lives in library, once
I landed glFor as a local copy of pplr-data-maps' helper and flagged the
duplication as a decision on the record rather than drift. It did not need to be
a decision at all: library/composables/useMapProvider.js already exports
isMapboxMap(map) — the same per-instance test, documented for the same reason
("works even on dual-engine pages where both globals are loaded").

So the copy is gone and the part that could drift is delegated. What remains in
the plugin is the namespace choice, one expression with nowhere to drift to.
When exp-shepherd's engineFor(map) lands, that line goes too.

WHAT IS PROVEN, and what is not, because the browser slots have been saturated
for fifteen minutes and RULE 3 says gates green before committing. This is a pure
expression substitution, so its logic is provable without a GPU:
_chk-glfor-equivalence.mjs stubs the globals, builds BOTH implementations, and
compares their decisions across every engine combination — mapbox map with both
globals, maplibre map 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.
That establishes the refactor cannot have changed the decision, so the pin-click
integration result already measured still applies. It does NOT re-run that click;
the browser re-proof is queued and will run when a slot frees.

check:sri PASS, composite freshness PASS.
2026-09-25 18:23:44 +00:00
doxa-maps-88655c70
42f48b7cf5 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
2026-09-25 17:58:31 +00:00
doxa-maps-88655c70
38b1963d4e Merge branch 'main' of https://git.nuxtbase.app/doxa-life/doxa-maps into bundler-3/work 2026-09-25 17:47:32 +00:00
doxa-maps-88655c70
cac041da42 Gospel access: a bridge for the Deaf, and screen the links nobody was screening
Of the 369 Doxa groups with nothing in their language, 94 had no bridge either,
and 93 of those were Deaf — 17.5 million people, the largest untouched number on
the map. That was structural, not bad luck: every bridge here is a spoken
language widely used in the same country, and a spoken bridge is worthless to a
Deaf community, so the build correctly refused to offer one and then had nothing
else to say.

SUN is a signed language built to be learnt across sign languages, with the Old
Testament, New Testament and Bible stories in it. It is carried the way a spoken
bridge is — something to learn, no substitute for Scripture in a group's own
sign language — in its own columns, never folded into the access rung, which
stays the honest "nothing found in their language". 28 countries where it is
already being taught come with the named local Deaf church or organisation
doing the teaching, which is the difference between a resource and a lead.

Only URLs verified by their content ship. sun.bible answers 200 for
/sun/<anything> and /resource/<anything>, so a per-language URL built by pattern
would look valid and could be empty; the fetcher re-measures that catch-all on
every run rather than trusting a note. Hosts left out are listed with the reason,
including three that are reachable by a person but not by a script.

Doctrine was checked harder here, not less. The Deaf are the most heavily
targeted group in this dataset by publishers this build must never carry, and
the thinner the pickings the stronger the pull. SUN is Wycliffe Associates'.

Screening the result exposed the bigger hole. The doctrine scan read the pin
index only, and within it skipped the Words of Life and Scripture Earth fields,
so the access table — the one the page renders and the one exported to CSV for
posters and QR codes — was never screened at all. The least supervised links in
the build were the ones most likely to be printed and handed to a stranger.
Scanning both, and both fully, takes the screened count from 6,497 to 16,960.

The gate also now proves the server holds these tables. It adopted a server from
another worktree on the default port and reported PASS on it: all three
manifests are byte-identical, because a manifest describes bundles and the
change was to a data asset it never mentions. A planted excluded-publisher link
went undetected, which is the worst possible failure for this particular check.
2026-09-25 17:47:00 +00:00
doxa-maps-88655c70
12f6567634 Merge remote-tracking branch 'origin/main' into rega/research-gallery-embed
# 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/template-bundle/template-bundle.js
#	public/doxa-maps/wall-of-people-groups/wall-of-people-groups.js
#	public/doxa-maps/wall-of-people-groups/wall-of-people-groups/index.html
2026-09-25 17:46:35 +00:00
doxa-maps-88655c70
357b050d6b Rebuild all bundles on the merged source 2026-09-25 17:27:50 +00:00
doxa-maps-88655c70
8a9130294a Rebuild all bundles on the merged source 2026-09-25 17:18:38 +00:00
doxa-maps-88655c70
b9dd5545c8 Merge branch 'main' of https://git.nuxtbase.app/doxa-life/doxa-maps into bundler-3/work
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
# Conflicts:
#	public/doxa-maps/doxa-maps-spa/manifest.json
#	public/doxa-maps/gospel-resources/charts/index.html
#	public/doxa-maps/gospel-resources/data-tables/index.html
#	public/doxa-maps/gospel-resources/gospel-dashboard/index.html
#	public/doxa-maps/gospel-resources/gospel-resources.js
#	public/doxa-maps/gospel-resources/resources-list/index.html
#	public/doxa-maps/gospel-resources/resources-map/index.html
#	public/doxa-maps/index.html
#	public/doxa-maps/manifest.json
2026-09-25 17:12:17 +00:00
doxa-maps-88655c70
d633f496d7 Gospel map: shape Arabic, Hebrew and Persian place names
The map is built raw, for the provider switch, so it never installed the RTL
text plugin — the same opt-out that cost it the engine stylesheet. Without the
plugin those labels draw as disconnected glyphs in the wrong order, which is
unreadable rather than merely ugly.

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

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

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

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

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

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

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

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

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

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

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

glFor is copied, not imported: the same function is in another plugin's folder
(pplr-data-maps/src/utils/pplrData.js:64), and importing across plugin folders
would couple two plugins through a path neither owns. library/ is the right home
the moment a third consumer appears, and both copies should die in that commit.
2026-09-25 16:59:57 +00:00
doxa-maps-88655c70
2f204c8e30 Rebuild all bundles on the merged source; freshness and SRI pass 2026-09-25 16:43:15 +00:00
doxa-maps-88655c70
1870efa5c9 Merge branch 'main' of https://git.nuxtbase.app/doxa-life/doxa-maps into bundler-3/work
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
# Conflicts:
#	public/doxa-maps/gospel-resources/charts/index.html
#	public/doxa-maps/gospel-resources/data-tables/index.html
#	public/doxa-maps/gospel-resources/gospel-dashboard/index.html
#	public/doxa-maps/gospel-resources/gospel-resources.js
#	public/doxa-maps/gospel-resources/resources-list/index.html
#	public/doxa-maps/gospel-resources/resources-map/index.html
2026-09-25 16:39:46 +00:00
doxa-maps-88655c70
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
87cc608b65 Merge remote-tracking branch 'origin/main' into rega/research-gallery-embed
# 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 16:32:46 +00:00
doxa-maps-88655c70
f9762aec60 Keyless posters: MapLibre gets projection as an object; the gate tells a slow render from a dead button 2026-09-25 16:32:37 +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
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
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
9792bc9162 Gallery: its dev manifest fallback names the tree from the build config, not a literal
The one tree path compiled into any bundle. Built today it is byte-identical (sha 3d5467fd…);
built with the config renamed it reads /public/<new name>/manifest.json.
2026-09-25 15:52:20 +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
52fb65e324 Merge remote-tracking branch 'origin/main' into rega/research-gallery-embed
# Conflicts:
#	public/doxa-maps/big-picture/big-picture.js
#	public/doxa-maps/big-picture/big-picture/big-picture/index.html
#	public/doxa-maps/big-picture/big-picture/frontier/index.html
#	public/doxa-maps/big-picture/big-picture/index.html
#	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/people-groups-grid/people-groups-grid.js
#	public/doxa-maps/people-groups-grid/people-groups-grid/driven/index.html
#	public/doxa-maps/people-groups-grid/people-groups-grid/index.html
#	public/doxa-maps/people-groups-grid/people-groups-grid/people-groups-grid/index.html
2026-09-25 15:27:08 +00:00
doxa-maps-88655c70
560e73abb9 Export poster: titled by the view, real legend rows, RTL text, the reader's place names, honest attribution; MapLibre style wait names its cause 2026-09-25 15:26:54 +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
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
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
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
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
e920378689 Merge remote-tracking branch 'origin/main' into rega/research-gallery-embed
# Conflicts:
#	package.json
2026-09-25 13:37:06 +00:00
doxa-maps-88655c70
e50fc858ba WIP: poster capture no longer dies on a late idle; gate waits 120s 2026-09-25 13:36:32 +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
doxa-maps-88655c70
08b6f1a39e 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 13:10:15 +00:00
doxa-maps-88655c70
12b66a9852 Big Picture: act on the locale the host asks for, not just carry it
A host embedding this dashboard with profile-config {"locale":"ar"} got an
entirely English page. The config was threaded correctly and reached the
children; nothing ever turned it into an i18n locale.

WHY THIS COMPOSITE AND NOT ITS SIBLING, which passes the same gate untouched.
Simple Research mounts its children BY TAG, so every child is its own custom
element with its own ProfileLoader — and ProfileLoader is the only thing in the
tree that watches `config.locale || config.lang` and drives the i18n locale from
it. Big Picture BAKES its children in (it imports the .vue files), so there is
no loader anywhere beneath it and nobody was doing that job. The entry does
provide appI18n; it was simply never told which language to use.

So this composite now does what ProfileLoader does for the other one: inject the
i18n instance and drive its locale from the host's lang/locale.

MEASURED with check:host-locale-contract, before and after:
    dashboard-plus-grid   host 0 of 2121  ->  host 32 of 2121

32, not 2121, and the remaining gap has a single known cause: 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 fix lives in
library/i18n/index.js, is measured, and is parked behind another agent's landing
in the same file — the measurement also showed it must be backed by a
window-level store rather than module state, because a composite loads several
bundles and each carries its own copy of that module.
2026-09-25 13:09:57 +00:00
doxa-maps-88655c70
b0c7b52243 Take main
# Conflicts:
#	package.json
#	public/doxa-maps/index.html
2026-09-25 12:38:20 +00:00
doxa-maps-88655c70
d26e416d03 Translate the list and the table headers; correct a claim I had not checked
resources-list's chrome is translated in all eleven — the three filter tabs, the
generic resource names, the people-group count — and the table's column headers
are localised at render in data-tables. resources-map, data-tables and charts now
report zero leaks by the corrected gate; what remains is in resources-list's
data-driven rows.

A CLAIM I MADE AND HAD NOT MEASURED. The previous commit said the CSV export
"keeps the data file's English field names". It does not: Tabulator exports the
column titles it is given, so a Russian reader's CSV heads "Народ","Страна",
"Регион". I checked only after asserting the opposite. Left translated on
purpose — this is a gift for people to use and someone who exports what is on
their screen should get what was on their screen — with the stable machine
contract being data/tables/ itself, which is untouched and English.

THE GATE NEEDED A THIRD CORRECTION, and this one mattered most. Substring
matching over the rendered text reported keys that were never rendered at all:
"Language" matched inside the DATA — the people-group language "Sign Language" —
and "Region" inside a region name. No length filter separates a label from a
word sitting inside one. The unit is now an element's OWN label, and a leak is a
label that EQUALS the English value while a translation exists. A language name
can no longer impersonate a UI string.

Scripts are alphabetised per the fleet convention, so parallel landings stop
colliding on the last line of package.json.
2026-09-25 12:36:41 +00:00