Their 24px floor sat only behind @media (pointer:coarse), so a desktop mouse still got the small target
(regions' finding; the phone probe runs with touch, so it measured the passing branch). 2.5.8 is a target-
size criterion, not a touch one. .mobile-collapse-caret (22x20 in a 20+4px slot), .stl-hdr-num (a 9px line
box), .rm-link-btn (11px, padding 0) and .pin-hint-x (16px) each get a 24x24 absolutely-positioned ::after
hit area at every width (the f29b8151 pattern). It adds a target without a box, so no row or slot
geometry moves; the coarse query still sizes the box on touch.
PosterWall's .pbtn, .pstock and .tape-h are HELD: their spacing is the Driver's sign-off.
The desktop verdict (2.5.8's spacing exception, an ancestor with overflow:hidden) needs a rendered layout,
so this is a needs-eyes item for rega.
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.
Their tape system supersedes mine and is the better invariant, so it wins whole.
I had made both stickers self-centre on the card's edge; they pin both by
--card-pad-top and --tape-overlap, which makes the OVERLAP onto the banner the
constant instead of the centre. That is what the Driver's rule actually asks for
-- extra height then grows into empty space rather than walking over the artwork
-- and one variable now governs both stickers so neither can drift from the other.
Kept only the orthogonal part of mine: the (pointer:coarse) block that lifts
.pbtn, .pstock and .tape-h to the 24px WCAG 2.5.8 minimum. Dropped my padding
override on .tape-h -- their rule sizes and places that sticker, and re-stating
its padding here would fight a geometry the Driver has already approved. Also
took their new .pstock.is-on state.
Fixed a comment of mine that the merge made false: it claimed both tapes
self-centre, which stopped being true in this very commit.
Rebuilt all 23 bundles, not just the ones I touched. The incoming diff brought
library/i18n/index.js and three shared library components, which reach nearly
every bundle -- and regenerating the tree without rebuilding has shipped a stale
.js here once before. tree + fix:sri after; check:sri PASS across 54 script
references on 55 leaf pages.
Measured all seven production maps at 390x844 with touch emulation. 195 controls
sat below WCAG 2.5.8's AA minimum of 24x24 CSS px, spread across five maps -- but
they were not five owners' bugs. Grouped by class, 195 failures were ten classes,
and 119 of them lived in library/: the same PosterWall, PinHint, LegendMobile,
SemanticTreeLegend and ResearchMapFilterPanel controls repeated down a list and
across four maps. Routing the per-map counts would have filed hundreds of defects
against owners who do not own the geometry.
Raised the shared controls, scoped to (pointer:coarse). 2.5.8 is a touch
ergonomics rule, and these components sit on maps this change does not own: a
finger gets the bigger target, the mouse keeps the density those owners designed.
The poster row is the reason this matters -- its height was tuned by eye against
the card's blue outline, and growing it for a pointer that needs no help would
have spent the artwork clearance to fix nothing.
min-height states the 24 instead of deriving it. Growing the padding first
MEASURED 23.7px and 23.0px -- under by a third of a pixel and by one, because the
height is padding plus a line box the renderer computes from a 10.5px font. The
declared minimum also survives a font swap or a locale with taller glyphs.
The header tape now self-centres like the footer. It hung on top:-11px, half of
its then-fixed 22px height, which silently becomes wrong the moment the height
changes -- as it just did. top:0 with translate(-50%,-50%) sits on the border at
any height, the mechanism the footer already used, and one less number to keep in
sync.
Two exceptions in 2.5.8 are now encoded in the probe rather than remembered.
Inline: 48 .uupg__country-link "failures" are an <a> inside a paragraph, computed
display:inline, sized by the line-height of the text around it -- the spec exempts
them and the probe now skips display:inline, counting them separately. Equivalent:
a .lpb-seg progress segment is 10px tall inside a track that clips overflow, so no
hit-area extender can reach 24px; it resolves to a tree node and calls the same
selectNode a full-width legend row calls.
195 failures on five maps -> 4 on two. UPG-100 alone went 137 -> 0. The poster
tape gate and per-row persistence still pass.
Measured under ?lang=ru on both composites: every Latin-script string left on the
page was ours. The map above was fully translated; underneath it the grid, the
map's own side menu and filter panel, and the whole research dashboard were
English in Arabic, Chinese, Hindi, Russian and everything else.
Three surfaces, three reasons, none of them a missing translation:
the grid read labels from a host-supplied `t` object that nothing ever
filled, with an English literal at each call site as fallback
side menu +
filter panel plain string literals
dashboard labels declared as literals in dashboard.config.js — facet
rail, chart titles, KPI strip, table headers, segmented
control, search placeholder
New catalogues for all 11 locales (grid, controls, dashboard), REUSING the
catalogue's existing wording wherever the word already existed — Регион, Блок
родства, Население, Вовлечён, Графики — so two panels never name one thing two
ways, and nothing is coined that the project already has a word for.
Read through a new plugin-free tStatic() rather than useI18n, because these
components are also mounted by entries that install no i18n plugin. An unguarded
useI18n() throws and a thrown setup() removes the subtree with no build error —
the defect that had just blanked people-groups-grid everywhere (6603dc17).
★ THE CONFIG LABELS ARE GETTERS, AND A SPREAD WOULD HAVE LOOKED IDENTICAL.
dashboard.config.js is imported once at load; the locale is only known later. My
first version spread a getter object, and a spread COPIES THE RESOLVED VALUE, not
the accessor — every label would have frozen at whichever locale loaded first,
and it would have looked perfect in English. `get label() {…}` resolves on each
read.
MEASURED after, served built tree, identity checked first: Big Picture ru/ar and
Simple Research ru/zh have ZERO Latin-script chrome strings. What remains is
Mapbox's licence attribution (never translate), country and people-group names
(data), and 'UUPGs' — which every locale file deliberately keeps as the acronym.
Three gates went red on the way, two of them mine and worth recording:
check:locale-reachable slices this i18n module between two markers and EVALS
the slice; I had put tStatic between them, so the gate
hit an `export`. Moved to the end of the file.
check:library-index my import+helper above a component's docblock became
that component's DERIVED description. Moved below it.
check:kpi-unit its parser assumed single-brace entries, so getters made
it find ZERO pct KPIs — and it failed rather than
printing a green PASS about nothing, which is the gate
working. Parse now understands getters AND checks the
translated labels, so a '%' reintroduced in any of the
11 catalogues fails by name. Both controls fired.
Standalone repo that builds embeddable JAMstack map bundles (web components),
composes them into a lazy-loaded SPA via layouts/, and optionally wraps a
Capacitor Android/iOS app — one build, choose your output/CDN. Diátaxis docs.
History reset to a single clean commit; no PII, IPs, or internal terms.