check:composite-freshness found stale on main: none. Rebuilt, tree + SRI
regenerated; the landing precondition (fresh, one folder, SRI) passed in main-capture scan.
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.
check:composite-freshness found stale on main: all-people-groups
big-picture
cities-internet
country-posters
dashboard-plus-grid
demo-map
doxa-research-map
doxa-simple-map
key-cities
my-upg-100-list
people-groups-grid
pplr-data-maps
prayer-walk-planner
regions-dashboard
research-dashboard
research-map-plus-grid
template-bundle
upg-neighbor
wall-of-people-groups. Rebuilt, tree + SRI
regenerated; the landing precondition (fresh, one folder, SRI) passed in main-capture scan.
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.
check:composite-freshness found stale on main: all-people-groups
cities-internet
demo-map
doxa-research-map
doxa-simple-map
key-cities
people-groups-grid
pplr-data-maps
research-dashboard
template-bundle
upg-neighbor
wall-of-people-groups. Rebuilt, tree + SRI
regenerated; the landing precondition (fresh, one folder, SRI) passed in main-capture scan.
"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.
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.
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.
A library change merged into main reached the planner without a rebuild (main's own source
builds 0042dda2..., main committed 0b695f70...). Only the planner is rebuilt (its owner);
its SRI is refreshed in the manifest AND the gallery's cross-origin manifest copy, which
fix:sri alone leaves behind.
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.
check:composite-freshness found stale on main: big-picture
country-posters
dashboard-plus-grid
gospel-resources
my-upg-100-list
research-map-plus-grid; also rebuilt: regions-dashboard. Rebuilt, tree + SRI
regenerated; the landing precondition (fresh, one folder, SRI) passed in main-capture scan.
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.
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.
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.
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.
check:composite-freshness found stale on main: dashboard-plus-grid; also rebuilt: plugin-studio. Rebuilt, tree + SRI
regenerated; the landing precondition (fresh, one folder, SRI) passed in main-capture scan.
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.
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.
check:composite-freshness failed on main for big-picture, country-posters, dashboard-plus-grid,
gospel-resources, my-upg-100-list, regions-dashboard and research-map-plus-grid: their committed bundles
were not what their source builds. Cause (dx-shepherd): d49b6c80 rebuilt all 25 bundles in a worktree
that had not merged the dx-maps rename, so it wrote the OLD folder and left dx-maps/ stale (the orphans
90910f32 removed). All 25 rebuilt here on current main with the dx-maps config, tree + SRI regenerated.
check:composite-freshness, check:sri, check:drop, check:public-hygiene PASS.
d49b6c80 rebuilt four bundles under the OLD tree name after the rename to dx-maps (09014f8a), putting
public/doxa-maps/{big-picture,doxa-research-map,research-map-plus-grid,wall-of-people-groups}.js back
beside public/dx-maps/. Nothing references them (the live copies are under dx-maps/). Removed.
check:public-hygiene only WARNED about a root entry outside the drop-in contract unless run --strict, so
the build stayed green. public/ holding only the output folder + README.md is a Driver decision
(BUILD-OUTPUT-IS-ONE-FOLDER), so it is now always a finding. Proven both ways: with the orphans the gate
FAILS ('doxa-maps/ — outside the drop-in contract (dx-maps, README.md only)'); without them it passes.
The tree is now published as dx-maps rather than doxa-maps, and the merge left
this composite's committed bundle behind its source under the new name.
Freshness and SRI are clean.
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.
I have been resolving artifact conflicts with `git checkout --theirs -- public/`
and then rebuilding only the bundles whose SOURCE arrived in the incoming diff.
That is wrong, and it put seven stale composites on main under 6cb61caa:
--theirs replaces my freshly built artifacts with whatever main happened to hold,
including bundles that nothing in the incoming diff mentions. big-picture,
country-posters, dashboard-plus-grid, research-map-plus-grid and my own
regions-dashboard were all committed as bytes their source no longer builds.
Worse, the gate TOLD me. My landing command printed
"research-map-plus-grid is STALE" and pushed anyway, because I ran freshness
next to the push instead of gating the push on it. A check whose output nobody
is obliged to act on is decoration.
Two changes, both in how I land rather than in the product:
- after taking --theirs for public/, rebuild EVERY bundle, not the incoming
subset: --theirs can import staleness into any of them, so the incoming diff
is the wrong trigger here for the same reason it was the wrong trigger when
regions-dashboard went stale this morning;
- the push is now conditional on composite-freshness PASS, so this cannot be
printed and ignored again.
All 25 rebuilt, check:sri PASS across every leaf, composite-freshness PASS.
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.
Driver decision: no embeddable URL carries "doxa" in its folder. The tree's name is one line in
vite.doxa-maps-build-config.json ("name": "dx-maps"); everything else follows it.
- __DOXA_TREE_NAME__ (the vite define rega landed, from MFE_NAME = internal/_tree-name.mjs) bakes the name into the
bundles for the runtime code that must recognise the tree in a URL. StackLayout.vue's
resolveBundleSrc anchored on a literal /doxa-maps/ and would have broken every composite's nested
bundles on a renamed tree; it now anchors on the define. The Research Gallery's dev fallback uses
it (one reader: rega's define, not a second one).
- The native shells follow via internal/sync-app-shells.mjs: capacitor webDir public/dx-maps, Tauri
frontendDist ../../public/dx-maps (its window url is tree-relative since the one-folder landing).
- The WordPress contract test reads the name from config instead of carrying three literals.
- check:locale-reachable extracted detectLocale alone and died with "localeFromUrl is not defined"
after 09d03c60 factored a helper out; it now follows the calls it makes (already red on main).
- generate-tree removes its temp asset pool even when it crashes (a failed build leaked ~50 MB into
/tmp each time; measured on a full disk).
- Capability exemplars regenerated for the new paths.
Proof: rm -rf public && bun run build -> public/ = README.md + dx-maps/; the 25 built bundles carry
zero 'doxa-maps/' paths (scanned); check:sri, check:drop, check:parse, check:public-hygiene PASS.
Tag names (doxa-* -> dx-*) are the next, separate landing.
check:composite-freshness named seven by hash after integrating main. A build
artifact cannot be merged line by line, so they were rebuilt from the merged
sources; freshness and SRI are clean.
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.
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.