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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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
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.
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.
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.
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.
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.
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.
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().
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.
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.