A new "Experimental shelf" section pictures the two newest shelf plugins, each
shot from its own page after it finished loading: the Wall of People Groups
(all 2,107 groups as photo cards on one zoomable prayer wall, 7fec7426) and the
Prayer Walk Planner (full screen from any embed and a sharp HD print,
30e6e569). The Research Gallery tree now lists all seven shelf plugins
(adds pplr-data-maps and wall-of-people-groups). Shots staled by 92a81ed2 and
later rebuilds are retaken.
BUG-BROWSER-GATES-CAN-MEASURE-ANOTHER-WORKTREES-TREE, the remainder. A
static scan of every internal/ script that drives a browser at a local
target found eight with no identity guard (a9550858 and 59f497eb
guarded the rest):
built tree check-spa-tab-order -> requireThisTree
dev server long-frames-probe, mobile-sheet-probe, theme-flash-probe,
plugin-gallery-smoke, share-roundtrip-smoke (--plugin-gallery),
smoke-studio, legend-columns-probe (only when aimed at
/plugin-designs/; its default target is the DX-life site
it compares against on purpose)
A dev server has no built manifest, so the new
requireDevServesThisCheckout checks the source it serves. It fetches
three library files with ?raw and compares them byte for byte with
this checkout. Another tree's dev server answers differently or 403s,
and the gate exits 75 (NOT MEASURED). Vite answers a plain fetch with
the file's text and an import-style request with `export default "…"`;
both shapes are accepted, since the first version parsed only the
second and refused this checkout's own server.
Proven: against a dev server running from 319064a1's parent and a
built tree whose manifest differs by one byte, all eight exit 75 and
name the differing file (useMapInstance.js). Against this checkout,
check-spa-tab-order and smoke-studio PASS, and plugin-gallery-smoke
gets past the guard. A re-scan finds no unguarded script left.
The Driver asked for full-screen, and for "a print that stays sharp when
zoomed".
Full screen. The planner mounts the shared FullscreenButton (the library's
Fullscreen-API control, with its CSS pseudo-fullscreen fallback for iOS
Safari) as a map overlay. It full-screens the whole planner, toolbar and
map, not just the canvas. Measured from a 520px embed in the Research
Gallery on a foreign page: the planner took 1280x800 and the map grew 459
→ 739px. The label is translated (Arabic "تبديل ملء الشاشة"), because the
planner now keeps the shared vue-i18n instance on its own locale. Before,
Share and Full screen took the PAGE language even when the gallery sent
another.
Print resolution, measured, not asserted. The printed map lays out at
7.53 x 8.4 in on the letter sheet (printable width = 8.5in minus the two
12mm margins; emulateMedia alone does not apply @page margins, which
misread it once as 8.48in). Its image is 2400x2688 px, so 319 x 320 DPI.
A 1:1 crop of the zoomed print was read: small street names (Sancaktar
Yokuşu Sokağı, Baki Dede Sokağı), individual building outlines and
footpaths are crisp, because the map is rendered from vector tiles at
pixelRatio 2, not upscaled from the screen. One PDF page.
check:prayer-walk-planner gains both: full screen from a 520px embed must
take the viewport, and the printed map must measure >= 300 DPI. 10/10
PASS.
UPG-100 carried a ❌ for a PosterWall i18n leak: useI18n() said to throw
where the wall mounts, leaving the print buttons English in every
locale on upg-100-mfe, poster-wall and country-poster-wall.
IT DOES NOT REPRODUCE. Measured on the current build, three leaves x
ro/ar/zh, tree identity checked against the served manifest first: zero
English leaks, zero page errors. ro "Listă de rugăciune / Harta afișului
/ Pachet de cartonașe", ar "قائمة الصلاة / خريطة الملصق", zh "禱告名單 /
海报地图", legend "Opțiuni de tipărire" / "خيارات الطباعة" / "列印選項".
The diagnosis does not fit either: this bundle installs i18n per element
via configureApp, and createI18n is legacy:false, so useI18n() resolves
for every descendant including the wall.
THE REAL DEFECT WAS THE MISSING ASSERTION, and it is worse than the bug
would have been: nothing in the repo checked the shared wall's RENDERED
locale, so a ❌ could sit on the scoreboard with nothing able to confirm
or refute it.
★ AND IT CANNOT BE CHECKED IN ENGLISH. The wall's guard HUMANIZES the
key when useI18n() throws - buttons.prayerList becomes "Prayer List" -
which is deliberate, so a bare host shows readable English rather than a
raw key. It also means "the button says Prayer List" is true both when
i18n worked and when it threw. An en-only check passes forever while
every other locale leaks. This gate therefore runs ONLY in non-English
locales, where the translated string differs from the humanized key and
seeing the key IS the defect.
It covers FIVE consumer leaves, not the three the report named:
key-cities and regions-dashboard mount the same shared wall and were
never checked. A gate that copies a report's sampling inherits its
blind spot.
TWO WAYS IT COULD HAVE PASSED WITHOUT CHECKING ANYTHING, both real on
the first run and both now hard skips rather than green ticks: a wall
with no cards renders no chrome, and regions-list rendered 7 cards with
ZERO wall chrome because that profile mounts the wall without the print
row. Skips are named in the output with their reason - a summary that
counts only the green ones reads as full coverage.
MUTATION TESTED by reproducing the reported condition exactly - forcing
the useI18n() guard to throw - rather than corrupting a string: the gate
goes red with 28-33 leaked strings on every measured combination, which
proves both that it detects the defect and that the defect is genuinely
absent from the unmutated build. Restored, 9 combinations PASS, 6
reported as not covered.
No source change: there was nothing to fix.
BUILD-LOCALIZATION-SURVIVES-THE-PROVIDER-SWAP asked for a check; the
behaviour landed in 319064a1 (library/maps/localize-labels.js) without
one. This reads the labels the map actually DRAWS, at ?lang=en and
?lang=ru on the same view, under both providers:
Mapbox the Standard basemap's 'place-labels' featureset (a plain
queryRenderedFeatures cannot see the imported basemap):
Germany→Германия, Ukraine→Украина, Egypt→Египет… must flip.
MapLibre each country symbol's evaluated text-field: under ru it must
be the feature's name:ru, under en not. English there
restores OSM's own label, so no English word is expected.
It serves this checkout on an OS-assigned port. --dir points it at
another tree for the negative control.
Negative-tested: doxa-simple-map built from 319064a1's parent (no
localize-labels) and served with --dir FAILS on both engines (Mapbox:
68 country labels, none flip; MapLibre: 0/49 in Russian). This tree
PASSES on both (5 known countries flip on Mapbox; 49/49 on MapLibre,
e.g. United Kingdom → Великобритания). A first version counted the
pre-fix Mapbox run as NOT MEASURED instead of FAIL, since it saw no
English names. "Measured" now means enough country labels were drawn.
The Instagram-style prayer wall (2,107 DX people groups as photo cards on a d3
canvas, with a pray dialog and comments) existed only as a no-build SPA whose
components were template strings compiled in the browser. It is now a bundle:
the components are SFCs in src/components, and the root is the app's own
container without the router. The API developer panel does not ship. Pinia comes
from the package. The canvas is found by ref, not document id, which cannot see
into a shadow root. Font Awesome is registered on the document too, because
@font-face does not load from a shadow root.
Data is the live pray.doxa.life list. The uupg.doxa.life endpoint no longer
answers, so that source and every photo URL built on it are gone. The card and
both dialogs show the API's image_url instead; before this, every dialog read
"No image". The 2.6 MB CSV fallback duplicated the live API and does not ship.
Missing photos draw inline, not from via.placeholder.com. Prayers and comments
stay in localStorage under a wall-pg: prefix, and the "Print (Coming Soon)"
button, which only alerted, is gone.
Measured on a served built tree: the leaf page, a script embed on another
origin, the Research Gallery tab and a 390 px phone each load all 2,107 groups
with photos and Font Awesome, 0 page errors, 0 failed requests. Zooming in and
tapping a card's heart opens the prayer dialog with the group, its status and
its photo.
check:composite (content-based since da8bc790) found three composites whose
committed bundle was not what their source builds today. Rebuilt from
their owners' current source; check:composite now reports every committed
composite is exactly what its source builds. The planner is rebuilt against
the merged library.
Merging main brought its build of doxa-research-map, dashboard-plus-grid and the
SPA together with this branch's; a build artifact cannot be merged line by line,
so all three were rebuilt from the merged sources rather than hand-resolved.
SRI re-verified: every leaf's baked integrity matches the bundle it loads.
A reader who picks a religion family on the map now sees the grid narrow to the
same peoples, and a language selection travels the same way. Region already
linked; these two did not, because the two halves do not name a religion the
same way and nothing yet proved what the honest translation was.
The map filters religion through its legend's flat tab, keyed by the FIRST
LETTER of the religion code ('B'); the grid filters through a derived axis that
runs library/colors/religion.js forward per row ('BLA' -> 'Buddhism'). One
shared table, read in opposite directions, copied by neither half — so the wire
carries the family NAME and each side resolves it against its own data.
Two measurements changed the design on the way:
- mapStore.filters.religions, which the map's filter panel writes, is read by
NOTHING: the mapLayers.applyFilters() it was meant to reach is called and
never implemented anywhere in the tree. A link routed through it would have
set state, passed a smoke test and dimmed no pin.
- the map's legend selections are mutually exclusive by design, so it can
light exactly one of region/religion/language. The composite mirrors that
rather than letting the grid hold three facets the map cannot show.
Religion links at the FAMILY and not at the code, deliberately: a map cannot
distinguish 'BLA' from 'BMA' — both dim as Buddhism — so linking at the code
would promise a precision one half cannot keep.
check:linked-vocabulary proves it by measurement rather than by reading: it
compares the total reached over the composite's wire against the total the grid
gives for the same facet on its own (83 of 2,107 both ways), and runs the MUTANT
first — the legend key 'B' instead of 'Buddhism' — failing as NO-TEETH if the
wrong mapping scores the same as the right one. Measured: mutant 0, real 83.
The CLI doc's apk:debug quote was realigned in passing; package.json had gained
app:start-page and the doc still described the older chain.
dx-shepherd's verifier traced it. Merge 52ab6089 from this worktree committed
public/doxa-maps/gospel-resources/gospel-resources.js with 21 conflict markers —
both sides concatenated, 1.13 MB — and that is what threw "Identifier Ge has
already been declared" and killed every gospel leaf.
My diagnosis last commit was wrong. I blamed a module imported twice and
re-routed the import; that change was harmless but it was not the cause. The
REBUILD that came with it is what repaired the file. A plausible cause that a fix
appears to confirm is the easiest kind of wrong answer to believe.
THE ACTUAL CAUSE IS A LANDING SCRIPT I WROTE. On a conflict it resolves the one
file it expects — public/doxa-maps/index.html — and then runs "git add -A
public", which stages every OTHER file under public/ including the ones still
holding markers. One line of convenience turning a conflict git correctly refused
to guess at into a committed artefact.
So: a check that names the real condition. It scans every tracked file for an
unresolved marker, is built from character codes so it can never trip itself, and
carries a poison arm. It runs FIRST in check:gospel-all, because there is no
point measuring anything else about a tree that is mid-merge.
Also verifies the 8 links the GRN recheck recovered, which check:gospel-all
caught as shipped-but-unverified on its very first run — psi, alk, nos, jle, lma
and three more, all now confirmed landing on their own 5fish language pages.
generate-tree decided a single leaf's map-ness as "its own .vue
references an engine OR anything under the BUNDLE's src/ does", so a
map-less profile inherited it from a sibling. regions-dashboard/
regions-list shows a list and no map, but src/RegionCountryMap.vue made
it isMap=true. Its leaf loaded the Mapbox engine for nothing, and since
4a6886d2 its embed snippet also preloaded it.
A leaf's map-ness now follows the RELATIVE imports reachable from its
own .vue, bounded to its bundle, so a map several imports deep
(research-dashboard's DashboardMapPanel) still counts. An explicit
boolean isMap in <profile>.meta.json wins. Composites keep their
separate rule, since they do mount their siblings.
Measured: across all 34 profiles exactly one changed (regions-list,
true -> false). 18 map profiles keep isMap and their preload; 0 non-map
snippets carry one. On a served built tree regions-list makes 0 engine
requests and 0 api.mapbox.com requests and renders its list (673
elements, no errors), where before it downloaded the 1.75 MB engine.
regions-mfe still loads the engine and draws its map. The regenerated
files are only the regions-list leaf, the two manifest copies (still
byte-identical), the staging page and the gallery; check:sri passes.
On a 390px phone the toolbar wrapped to three rows, 139px of an 844px
screen above the map a walker is trying to read. Below 640px it is now one
row that scrolls sideways: no label is truncated, and the two actions a
walk needs, Draw and Print, lead the row so they are always on screen.
The gate compared a child's newest source mtime with the parent
bundle's mtime. A git merge or checkout rewrites sources with
checkout-time mtimes, so rega's publish of beb13da3 stopped on
"doxa-maps-spa / plugin-studio / stack-demo STALE", although a rebuild
reproduced the committed bytes exactly. Two of those parents mount
their children by tag and compile none of them in. It was also
gameable the other way: checking out the parents makes them pass.
It now rebuilds each composite into a temp dir and compares sha256
with the committed bundle. Builds are deterministic (two runs give
identical bytes), so a difference is a real source change the
committed parent predates. The new vite.config.js DOXA_OUT_ROOT
redirects that build away from public/; unset, nothing changes. A bare
sibling-profile entry in composes (gospel-resources' resources-map,
data-tables) now resolves instead of being reported unresolved.
Proven: an edit to people-groups-grid.vue with no rebuild fails
big-picture, which compiles the grid, and leaves plugin-studio and
research-map-plus-grid fresh. Touching every child and library source
with no content change still passes.
On today's main: doxa-maps-spa, plugin-studio and stack-demo (the
false alarm) are fresh. country-posters, dashboard-plus-grid and
regions-dashboard are really stale and need a rebuild. About 2 minutes
for all 10 composites; use --only to narrow.
Every conflict was generated output. The incoming source touches library/i18n/
index.js and four gospel-resources views, which reach effectively every bundle,
so all 23 are rebuilt before the tree and SRI are regenerated.
simpleresearch fixed the phone toast (2221d11b): at 390x844 the "Tap a
coloured dot" prompt now sits above the Progress sheet and the legend reads in
full (checked by eye). The phone row now shows seven maps in two rows of four;
Big Picture keeps its note (desktop by design; on a phone the Research Gallery
opens on Simple Research). Gospel Resources shots retaken after its bundle was
repaired on main (57fecfd0 replaced a bundle committed with merge-conflict
markers in 52ab6089). Other stale shots refreshed from the latest builds.
a9550858 guarded twenty gates; these six were still unguarded. Each
now exits 75 (NOT MEASURED) rather than grading another tree:
- check-legend-export-reachable, check-vision-card-numbers --url:
requireThisTree on the URL they are given.
- check-embed-basemap-paints: it picks a RANDOM port, not an
OS-assigned one, so if another agent holds it, its own server fails
to start and it would measure theirs. It now checks the leaf server's
identity, stops its own servers and exits 75. Its "a server did not
come up" path exited 1 and now exits 75.
- check-tabs-localized, check-poster-prints read library/ from
DOXA_REPO. Pointing that at another checkout now needs
DOXA_REPO_MEASURE_OTHER=1 (new requireThisRepo helper).
- check-packet-render refuses a packet file inside a DIFFERENT git
checkout (new requirePathInThisCheckout helper); a file outside any
checkout is allowed with a note.
Proven by pointing each at a wrong build: a copy of this public/ whose
manifest differs by one byte, served on its own port, and another
worktree for the file and DOXA_REPO gates. All 8 (these 6, plus
shell-scroll and upg-map-data) exit 75. On this tree they measure:
legend-export, vision-card, upg-map-data, tabs-localized and
poster-prints PASS. packet-render gets past the guard and fails on a
non-packet file, as intended. embed-basemap measures and FAILS: "the
page does NOT paint a basemap on its own baked token". That is a
product finding, not the guard, and is reported to its owner.
Step 2 asserted that a wheel OVER the map must scroll the page. That
predates the Driver's 2026-09-23 ruling, recorded in
DashboardMapPanel.vue: "wheel OVER the map zooms; wheel BELOW it
scrolls". It kept the gate red on correct behaviour (dx-shepherd ruled
it stale). It now asserts the ruling: a scroll-down wheel over the map
moves the page 0 px, a zoom-in wheel over it raises the zoom, and a
wheel below the map scrolls the page until the grid is on screen. It
locates the map canvas and wheels at its centre and below it, not at
fixed coordinates.
Proven both ways on a served built tree: PASS (over the map 0 px and
zoom 0.50 -> 1.30; below it 2502 px). With SHELL_SCROLL_NEGATIVE=1,
which disables scrollZoom as before the ruling, it FAILS: the page
moved 300 px. It also gains the tree-identity guard (exit 75 on another
build).
Two defects, both reproduced before the fix and re-measured after.
1. It assigned BASE for its lock path. A variable the caller exported
stays exported in bash, so every gate run through the slot saw
BASE=/tmp/doxa-maps-browser-gate.lock. Seven gates read
process.env.BASE; check:upg-map-data-painted printed "SKIP — nothing
served at /tmp/doxa-maps-browser-gate.lock/…" and exited 0. Renamed
to SLOT_BASE. Proof: with BASE=/x set, a child printed the lock path
before and /x after.
2. With every slot busy, waiters queued on slot 1 alone, so a slot that
freed was never taken and two slots behaved as one queue. It now
polls every slot with flock -n every 2 s until the deadline and
still exits 75 on timeout. Proof, in a private slot dir with slot 1
held 12 s and slot 2 freed at ~3 s: a caller waited 11.5 s before and
4.3 s after. A free slot is still taken at once (66 ms).
check-upg-map-data-painted: its unreachable-server SKIP exited 0 and
its wrong-tree refusal exited 2. Both now exit 75, the repo's "not
measured" code. Its existing tree-identity check is what makes the
:4173 default safe, since another agent's tree there is refused.
Under provider=maplibre (the keyless gallery) four of the seven map views never
finished loading. They built `new mapboxgl.NavigationControl` in the load
handler, and that global does not exist under MapLibre, so they sat on
"Loading…". Popups and the pin marker had the same fault. Every such call now
goes through glFor(map), the namespace of the engine that built the map. The
views' styles and the Dark/Satellite/Light switch now use registry names (ink,
hybrid, paper), which are the same Mapbox styles under Mapbox and OpenFreeMap
under MapLibre.
The search box loads its geocoder by engine, as GeocoderComponent does. Under
MapLibre it is MaplibreGeocoder over this map's own local index, so the keyless
gallery makes no request to api.mapbox.com at all.
The legend's row caret, clear chip and collapse/reopen buttons carry accessible
names and aria-expanded from legend.a11y.* and aria.* (optional i18n, English
fallback), the keys the library legend uses. check:shared-reached now counts
pplr-data-maps as carrying legend-a11y-keys.
window.__pplrMap() reports what the active view's map did (engine, load fired,
style layers, this bundle's data sources), so "loaded" is asserted, not inferred.
Measured on a served built tree, host page on another origin: all ten views
loaded under MapLibre and under Mapbox (load fired, style layers and data
sources present, canvas not blank, 0 errors), with 0 api.mapbox.com requests
under MapLibre. The same check on the parent build fails four map views under
MapLibre. A control with no provider makes 13 Mapbox requests.
Driver: the batch tool is "super buggy", and letting you change the
colour of regions is NOT what is wanted - "the batch selection tool
should let you create your own poster or prayer packet based on your own
selection."
So the selection stops being a thing you PAINT and becomes an INPUT to
the two artefacts this app already makes. Gone: the eight colour
swatches, the custom hex input, and "Copy selection", which produced a
clipboard string and a recolour and nothing a reader could pray from.
In their place the HUD offers Prayer List, Poster Map and Clear, in all
eleven locales - the keys already existed, so this adds no translation
debt.
NO THIRD PIPELINE, which the brief rules out. Both actions end in
printPacket / printPoster, the same calls the legend and the poster wall
make. The selection is handed over as a SYNTHETIC list whose selector is
a pg_slugs union, because 12 of the 27 lists already use pg_slugs and
the evaluator THROWS on that type: resolving the union to concrete slugs
once, against a single fetch of the people-groups catalogue, gives every
existing consumer the shape it already handles. Resolving per zone would
refetch the whole catalogue per zone and still leave the caller to merge.
The packet's map page is per-zone artwork, so a multi-zone selection
prints a cover WITHOUT the map rather than letting one zone's map stand
in for all of them. A poster has no artwork for an arbitrary union at
all, so with several territories selected it says so plainly instead of
silently printing the first zone's poster as though it covered the
selection - generating one would be the new pipeline the brief forbids.
MEASURED at the rendered layer, driving the documented __UPG_SELECT__
gate API rather than guessing at the interaction:
selection 3 territories, exactly Middle East Core / East Africa /
Sudan, batch mode on
recolour UI swatches gone, hex gone, copy gone
buttons en "Prayer List / Poster Map / Clear selection",
ar "قائمة الصلاة / خريطة الملصق / إلغاء التحديد"
page errors 0
Wrote this after shipping a page that returned 200 for every asset, reported
zero console errors, and was completely dead: a throw during setup dies inside
the custom-element boundary. Zero errors is not the same as working, so this
does not trust their absence — it looks for PRESENCE, on all five leaves, in
five steps that each name their own failure: the tag is defined, the host is in
the document, a shadowRoot is attached, it has children, it renders text.
Its first real run went red on all five, and it was right. Every gospel leaf was
dead with "Identifier 'Ge' has already been declared" — while the SRI hash
still matched and every request still returned 200. My own doing: four profiles
imported en/gospel.json directly for an English fallback, and library/i18n
already pulls that exact file in through an eager glob, so the IIFE carried two
instances of one module.
The fix is one module: library/i18n now exports EN_MESSAGES and the profiles
read it from there. The duplicate is gone.
Measured after: all five leaves mount with 3,228 to 233,307 characters of
rendered text, verify-gospel-map passes with 2,107 pins and the gap cutting to
370, and --poison confirms the check goes red on a page with no element at all.
The lesson is the gate's whole reason to exist. I had rich assertions about pin
counts and filters and none of them ran on data-tables, charts or the list, so a
bundle-level failure could take out every leaf while the one gate I did run was
pointed elsewhere. Cheap and universal beats rich and narrow for THIS question.
Every map translated its own chrome but left the basemap's country, city
and street names in the style default (English on Mapbox, Latin + native
on OpenFreeMap) in all eleven locales. A reader in Arabic saw an Arabic
legend over an English map.
library/maps/localize-labels.js uses each engine's supported mechanism:
- Mapbox: map.setLanguage(lang). It also localizes the Standard style's
IMPORTED basemap, which a layer rewrite cannot reach.
- MapLibre: basemap symbol layers point at
['coalesce', name:<lang>, name:latin, name]. English restores each
layer's original expression, re-captured on every style load so a theme
switch never restores the previous style's labels.
Only layers whose text-field reads an OSM name field are touched. A
plugin's own label layers (zone titles, group names) are never rewritten.
useMapInstance applies it on every style.load and when the reader's
language changes (ProfileLoader's 'lang', else profile-config locale, else
?lang / <html lang>). The Prayer Walk Planner drops its private copy for
the shared one.
Measured on the Prayer Progress map, ar/zh/ru/hi:
- Mapbox getLanguage() == locale in all four.
- MapLibre: 13/14 symbol layers localized, and the plugin's own label
layer untouched.
Read in the Arabic screenshots: القاهرة, الإسكندرية, السويس, الأردن on
both engines. check:gallery-embed (+ :maplibre) and
check:prayer-walk-planner pass. The shared change reaches all 11 bundles
that compile initializeMap, and all are rebuilt here. check:shared-reached
marker basemap-labels-localized: 10/10 consumers.
The table offered 107 groups a next step the map did not show. The candidate is
now computed once, in the pin build beside the curated bridge, and both surfaces
read it — the same rule that put the tier in one place. 1,167 candidates across
the research index; 107 of them reach Doxa groups the curated bridge misses.
The map's sheet renders it as its own weaker panel, and the words carry the
limit: "Possibly a way in — worth checking locally", with the language, how
many groups there speak it and what share of the country they are, and "not
ranked and not recommended". No strength, because none can honestly be
computed. Never for a sign language.
A REGRESSION I INTRODUCED AND ALMOST SHIPPED. The edit that wired this used
pinCandidates without declaring it — a string replace that matched nothing and
said nothing. The ReferenceError died inside the custom-element boundary, so the
map never mounted AND the page reported zero errors. My probe then failed with a
30-second timeout that I had caused separately, by passing playwright's timeout
as the waitForFunction ARG rather than its options, which had been silently
using the default in eleven scripts. Two faults stacked: a broken page that
looked error-free, and a probe too impatient to say so.
Zero page errors is not the same as working, and that is the only reason this
was caught: __grState never appeared, which no assertion about pins could have
explained away. Verified after the fix: 2,107 pins, gap cuts to 370, the
candidate panel renders for Abai Sungai naming Malay, and 25 Deaf sheets still
show no bridge at all.
E RESIDUALS, from rega's in-gallery re-sweep. All three lived in SHARED
components, which is why a grep of my own plugin found nothing:
SearchFilterHeader its label map was a literal object of English defaults —
the fallbacks every host gets, since no host passes
`labels`. Filters, Selected, World and the chip remove
button now come from the catalogue; a host's own labels
still win, which is what the prop is for.
ResearchMapFilterPanel the two "Any" options and the "{n} active" pill.
DashboardFacetRail the Expand/Collapse aria template, which repeats per row:
one untranslated template is dozens of English strings in
a screen reader.
dashboard.config.js the facet SECTION titles (People, Place, …). The earlier
pass converted `label:` and missed `title:`.
★ AND A LOOKUP BUG THAT LOOKED EXACTLY LIKE A MISSING TRANSLATION. 'Any' and
'0 active' stayed English after being catalogued, because the panel asks for
`controls.<key>` while I had authored them under `dashboard.<key>`. The
catalogue had the words; the component was looking one namespace over. Keys now
live where the panel reads, and its lookup falls back across both, so a string
authored in either place renders.
MEASURED after, ru, on the built tree: 0 English chrome strings on Simple
Research and 0 on Big Picture.
One probe correction worth keeping: a substring match on 'People'/'World'
flagged 'South Asian Peoples' and 'Arab World' inside aria labels whose frame
was already translated — data, not chrome. Whole-string matching separates them.
TOUCH TARGET (regions' phone sweep): .rd-proj "🌐 Globe" measured 74x23 at
390x844, one pixel under WCAG 2.5.8 AA. min-height:24px under
@media (pointer:coarse) — stating the 24 rather than growing padding, because
the height is padding plus a line box the renderer derives from the font and the
arithmetic lands at 23.7. Mouse sizing untouched. MEASURED after: 73.5x34.
Every bundle embedding the changed shared components is rebuilt in this commit —
a shared fix is not landed until its consumers are.
composition/_switcher.scss has three rules and the clone carried two. The third —
`.switcher > :nth-last-child(n + 6), .switcher > :nth-last-child(n + 6) ~ *
{ flex-basis: 100% }` — gives every child a full row once there are six or more,
instead of letting them share. It is now ported onto .rd-switcher.
It changes nothing today and that is measured, not assumed: the rendered page has
3 direct children at widths 372/372/372, the same three goal cards their
RegionDetail.vue lays out, so a threshold of 6 is out of reach. It is carried
because the brief is an exact clone, and because a fifth or sixth goal card would
otherwise diverge silently from doxa.life — a one-line rule now, or a puzzle later
about why six cards behave differently here.
The other two rules in that file are deliberately NOT ported, which is a
measurement rather than an oversight: .switcher-item{display:flex} never applies
because their switcher's children are `.stack | card`, not `.switcher-item`, and
.three-part-switcher belongs to a component the regions page does not use.
The probe proves both halves, because a rule that cannot be shown to fire is
decoration. It reads the real switcher, asserts no child has flex-basis:100% at
the live count, then clones cards INTO it until there are six and reads the basis
back as 100% before restoring it. Two of my own errors are recorded in it: a first
run drove the page with ?region=africa, got the chooser, and reported "no
.rd-switcher" — a reading of the wrong page state, since the selection comes from
the hash; and a second hardcoded "+2 clones = 6" from a static tag-count that said
4, so it only reached 5 and called the rule dead when its own arithmetic was wrong.
It now derives the clones to add from the count it just measured.
A row of phone screenshots (390x844, taken after each map finished loading)
under the production gallery: Regions, UPG-100 List, Prayer Progress, Cities
with Internet and Gospel Resources. Big Picture is desktop-only by ruling, and
Simple Research is left out until its phone layout is finished: its "Tap a
coloured dot" prompt still covers the Progress legend at 390px. The README
says both plainly.