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.
With pray.doxa.life unreachable (offline, or a local dev server whose 127.0.0.1 origin the API
refuses by CORS) the wall sat at "0 Groups Loaded" with nothing explaining why, and logged
"Cannot read properties of undefined (reading 'length')". That error was not the fetch at all:
SourceSelector's template read `sources`, which does not exist (its computed is
availableSources), so it threw on every render, online as well.
- SourceSelector: availableSources.
- PeopleGroupsWall: when nothing loaded and the store holds an error, an in-wall notice says
what failed and offers Try again (inside the wall, never position:fixed over a host page).
- The store logs the network failure as a warning: it is an expected state now shown on screen.
Measured with every pray.doxa.life request aborted: the TypeError is gone, the notice renders,
and the only console line left is the browser's own "Failed to load resource". Unblocking and
pressing Try again loads 2107 groups. Live (unblocked): 2107 groups, 0 console errors.
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.
repo's Arabic screenshot of the list was clean except for one string:
the search box still read "Search people group, zone, or territory…" in
every locale. It was a hardcoded literal, the last English on an
otherwise localized screen.
NO NEW TERMINOLOGY — composed from wording this product already ships
and has already approved: search.kinds.peopleGroup for the group,
tabs.upg100Zone for what this plugin calls a "zone" (its own legend
names them "UPG-100 Lists" / "قوائم UPG-100" / "UPG-100 名单"), and
search.kinds.region for the territory. That is precisely why this one
could be translated while the UUPGs column header could not: there, no
source established any candidate acronym, and a wrong term looks
authoritative to a reader who cannot check it. Here the words were
already on the page.
⚠ A computed, and declared beside LEGEND_TITLE rather than at the
literal's old position, because t() is not initialised until later in
setup — a const at the original line would have captured the fallback
and never re-evaluated when the locale changed.
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.
These two maps build `new gl.Map` directly, so they never got useMapInstance's RTL install, and
their bundle entries registered nothing either. The basemap draws each place in its OWN script,
so over the Middle East most labels are Arabic, Persian or Hebrew for EVERY viewer: they were
drawn as disconnected glyphs in the wrong order. installRtlTextPlugin(gl) now runs after the
engine global resolves and before new gl.Map (an entry-time call cannot work once the engine is
injected lazily).
Gate: check:rtl-raw (+ :poison), gospel's check-gospel-rtl-shaping method (pan to Arabia first;
no RTL label on screen is NOT MEASURED; one defineProperty per global for hook + poison) over
cities-internet on both engines and key-cities (Mapbox-only, gated, no leaf: mounted on a
same-origin host page). The shared gate's --leaf argument can replace the cities rows once it
lands; the key-cities host page still needs this one.
Measured: before, cities-internet mapbox 58 RTL labels with the shaper 'unavailable' and maplibre
62 stuck at 'requested', both unshaped; after, shaper 'loaded' on both, and key-cities 56 RTL
labels shaped. Poison: 3/3 rows caught.
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.
The gate could only ever read Arabic. Its rule — English prose declares itself
with dir="ltr" — is right about the prose and wrong about the root: the embedded
RTL fix sets direction on the plugin's own root element, so under ar the root was
rtl and the walk ran, while under zh, ru and hi the root matched the prose test
and the entire leaf was skipped. Measured on identical DOM in both locales: 1,359
elements walked and 589 carrying text either way, of which 1 sat in a prose
subtree under ar and all 1,359 did under zh.
It then reported that silence as "rendered no labels at all", which reads as a
product failure and is not one. The Chinese leaf renders correctly, title and
all. Silence is now NOT MEASURED, reported separately, and never counted as a
pass. dir is honoured below the root and ignored on it; the comparison went from
488 key/locale pairs to 1,876.
What that exposed was a translation that gave up halfway. Type is an enumerated
label whose eight values the catalogue already translates, and the heading above
it was translated while the column beneath stayed English. The map is derived
from the catalogue rather than listed in the component, so a ninth resource type
is covered the moment it is added; a hand-written list would have been one entry
short and nothing would have failed. Both code paths localise, including the
filtered view a pin or country click produces.
Two values had no key because the catalogue spelled them differently from the
data — Audio Scripture and Bible Stories, now translated in all eleven locales.
Open Bible Stories stays English: it is a product name, as are the organisations
in the Source column beside it.
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.
Dragging the side menu's pin-dim slider changed nothing: it wrote a store field
that did not exist, the same defect as the clustering picker beside it and found
while fixing that one.
The obvious fix would have created a worse bug. The pin layer's circle-opacity
already has an owner — legend dimming writes a ['case', …, 1, 0] expression — so
a flat slider value from a second writer would silently erase dimming, or be
erased by it, depending on which control the reader touched last. Nothing would
have looked broken; the map would just have stopped answering one of them.
So there is now exactly ONE writer and the value is the PRODUCT:
opacity = slider × (1 for a matched pin, 0 for a dimmed one)
The slider scales the whole layer, dimming still dims relative to it, neither
wins. Seven call sites that each wrote opacity by hand now go through it.
MEASURED, reading the live paint property rather than the store:
clustering off 1
clustering on, 0.5 0.5
+ a dimming selection ['case', <religion is B>, 0.5, 0]
slider back to 1.0 ['case', <religion is B>, 1, 0]
One thing the gate had to learn, and it is written down in it: a REGION
selection does not dim at all — it sets a layer filter, so non-matching pins are
removed rather than faded. Testing that path would have reported "the slider
erased the dimming" about code that never writes opacity.
★ ALSO: the Globe/Flat control's 24px touch target now applies at EVERY width.
The first fix scoped it to `@media (pointer: coarse)`, reasoning that a mouse
needs no touch floor. Wrong twice: WCAG 2.5.8 is a TARGET SIZE criterion, not a
touch one, and the query meant phones passed at 34px while desktop stayed at
73.5×23 — the same one-pixel failure, now invisible to a phone sweep. Measured
after: 73.5×34 at 390, 430 and 1440.
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.
Driver decisions (cards BUILD-OUTPUT-IS-ONE-FOLDER, BUILD-PLUGIN-STUDIO-IS-THE-OUTPUT-FRONT-PAGE).
- public/ holds only <tree>/ and a generated README.md. The build no longer writes public/index.html,
public/assets/ or public/__gallery/; `rm -rf public && bun run build` recreates exactly these two.
- The Plugin Studio is <tree>/index.html (tree-base "."), replacing the staging-forge chooser; nothing
is written above the tree. check:staging-lang, which tested that chooser, is retired with it.
- Assets are gathered in a temp directory and the fetched allowlist staged into <tree>/assets/ as
before. fonts/, images/, brand/ and legacy/ existed ONLY in public/assets: they are sources and move
to data/ (git mv). Poster previews keep their tracked sources in plugin-designs/*/assets/previews
(all 378 verified present).
- __gallery/ is archived out of public/ (a copy kept in the umbrella's _qa-cycle/archive) and out of
the build; generate-gallery renders only on request, into a directory you name, never public/.
- Native shells load the output folder: capacitor webDir public/<tree> (cap copy needs an index.html
there), Tauri frontendDist + window url relative to it. internal/sync-app-shells.mjs keeps both in
step with the tree's name on every build, so the coming rename stays one config line; the Capacitor
start page resolves its leaf tree-relative.
- dropin.include now lists only extras (README.md): the tree is always shipped and never named twice;
excludes are tree-relative ({tree}/...). clean.mjs removes the old root entries from existing
checkouts; the build lock and public/.gitignore's rules move out of public/.
- check:wp-embed read the BUILT manifest from gates:pre, before the build, so a from-scratch build
could never pass; it runs in gates:post and reads the tree name from config.
- Stale bundles refreshed by the clean build (key-cities, template-bundle, wall-of-people-groups);
the CLI doc's quoted scripts re-synced; capability exemplars regenerated; the '#' note leaves
check:stale-sweep-claim's command (it was already in //scripts).
Proof: rm -rf public && bun run build -> public/ = README.md + doxa-maps/; check:sri, check:drop,
check:parse, check:public-hygiene, check:quoted-scripts, check:capability-exemplars PASS; all 57 pages
(the front page + 56 leaves) mount under /doxa-maps/ on this tree. check:facet-cascade is red as on main.
Cell E verification for Big Picture and Simple Research needed the
gospel leak gate's method on two other bundles. That gate hardcodes its
four leaves, its element tag and its single catalogue file, so pointed
elsewhere it finds no shadow root and silently compares nothing. This
one takes the leaf, the tag and the locales as arguments and compares
against EVERY catalogue area — a COMPOSITE has no t() calls of its own,
so its chrome comes from the children it nests.
SIMPLE RESEARCH CELL E IS CLEAN, independently measured:
research-map-plus-grid in ar/zh/ru/hi, tree identity checked first, no
leaks, 0 page errors, and 86/34/27/43 comparable keys — so the
comparison was non-vacuous in every locale rather than "0 leaks over
nothing". A second instrument agreeing with the owner's reading is what
the verifier role is for.
★ TWO FALSE POSITIVES, BOTH OF WHICH LOOKED LIKE TEXTBOOK DEFECTS. The
first run reported 3 leaks x 4 locales on Big Picture, and all three
keys ARE translated in every catalogue, so the pages really were showing
English where a translation existed.
DATA CELLS. "Unengaged" rendered in .rd-td inside .rd-grid — table
data. people-groups-grid.vue:150 records the field as
{value:'unengaged', label:'Unengaged'}, 100% populated, and
dashboard.config.js:132 counts 2,081 Unengaged / 27 Engaged. It is an
API VALUE that happens to equal a catalogue string, which is the
population rega's method excludes by name. Whole-label matching stops
a substring impersonating a label; it cannot stop a data cell
rendering the same text.
FACET VALUES. "Other" rendered in span.fr-lbl in the facet rail, which
LOOKS like chrome. The template is
`<span class="fr-lbl">{{ r.label }}</span>` with `{{ r.count }}`
beside it, and the component's own comment calls it "the 'Other'
bucket". The string comes from row data, not from t('regions.Other').
Both are excluded BY ROLE rather than by class. The facet exclusion keys
on the row carrying a COUNT, so the rail's own section headings stay in
scope and a real leak there is still caught — excluding .fr-lbl
wholesale would have blinded it.
⚠ NEITHER IS GOSPEL-SPECIFIC. Any bundle with a data grid or a facet
rail produces these, and whether "Unengaged" and "Other" as DATA should
be localised is a genuine product question — but a different one from
cell E, and filing it as a leak would send someone to fix correct code.
⚠ BIG PICTURE CELL E IS NOT CLAIMED CLEAN. The confirming run after the
facet exclusion came back RC=75 (no browser slot within 900s), and the
run before it still reported regions.Other. Whether the exclusion takes
effect is unverified, so the cell stays open rather than being marked
on an unconfirmed reading.
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.
check:element-census-vs-live now has a preset for this lane, so the question
"what is on our page and not on theirs, and the reverse" has an answer that runs
instead of an opinion.
★ THE LEAF IS THE COMPOSITE, NOT THE GRID ALONE. people-groups-grid is a copy of
DX-life's UupgsList.vue, which is the BODY of doxa.life/research — but that page
is a map plus that body. MEASURED: censusing the grid leaf against /research
reports 45 omissions, and every one of them is the research map's chrome
(Clustering, Off/Native/MST/Network, the tab strip, the legend) — nothing to do
with the grid. A list that long and that wrong teaches a reader to ignore the
gate. research-map-plus-grid is map + grid, which is what /research IS, and it
lines up 98 strings against their 95.
The negative control is the pin hint ("someone still needs prayer"), and the run
confirms the census SEES it as ours-only — without that, every count it prints
would be worthless.
The sanctioned list is deliberately EMPTY. The first run's 14 additions and 14
omissions have gone to the shepherd as findings; an allowlist written by the
owner of the code to make its own gate green is not evidence. Most of the
omissions look like wording rather than absence — their "Full Profile" against
our "Full profile", their "Load More" against our "Load more", their legend's
"1+ People Praying" against our "Has Prayer" — but that is exactly the call the
Driver makes and not the one this file should quietly make for them.
The gate defaults to the deployed site, so a failure line reads as "this code
is broken" when it means "this code is not published yet". That misread cost
two wrong diagnoses in one day: the resources map and the connectivity map both
reported the stylesheet absent while the fix for exactly that sat verified on
disk. Served locally, the same gate passes all thirteen leaves.
The identity guard cannot close this. It fingerprints the manifest to catch the
wrong worktree on a localhost port and deliberately passes a remote target,
because lagging behind a checkout is a deployment's normal state. The output
was the thing at fault: the one number that tells the two failures apart was
never on screen.
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.