rega measured dashboard-plus-grid's own leaf at 390px: the page carried
"mobile": "desktop-only" in its profile-config, and the dashboard, the grid
and a MapLibre canvas mounted anyway. The rule lived in ProfileLoader, and
this composite defines its element without ProfileLoader (it bakes its
children in), so nothing on that page read the flag.
- library/composables/useScreenGate.js holds the rule (narrow screen, not
inside the Gallery, dismissed for the session); ProfileLoader uses it,
unchanged in behaviour.
- dashboard-plus-grid calls it: on a narrow screen the desktop-only message
replaces the element, and the dashboard and grid never mount.
- check:mobile-gating (g): every flagged profile's element must mount
through ProfileLoader (an import in its index.js, not a mention in a
comment) or call useScreenGate itself. The pre-fix composite fails it.
Every map profile's copy-paste snippet began with a preload of
api.mapbox.com's mapbox-gl.js, while its own profile-config said provider
"maplibre", and since 2026-09-28 only an explicit provider "mapbox" selects
Mapbox. So an unedited paste downloaded the Mapbox engine on every view,
never used it, and made a Mapbox request from a keyless page. Its only
guard was a stale comment asking the partner to delete the line.
- embed.script now preloads MapLibre's script and stylesheet, pinned the
way useMapInstance loads them so the preloads are reused, and its note
points a host who wants Mapbox at the variant.
- embed.scriptMapbox is that variant: the Mapbox preloads and the same
element with "provider":"mapbox".
- check:snippet-engine (gates:post, with a self-test) reads the built
manifest: no default snippet names api.mapbox.com, every map snippet
preloads MapLibre, and every Mapbox variant preloads Mapbox and sets its
provider. Planted: the old snippet and a provider-less variant both fail.
i18n-areas kept an area whenever its name showed up as any string literal
in a bundle's source graph, to cover keys assembled at runtime. But
'gospel', 'religion', 'regions', 'detail', 'poster' and others are also
ordinary ids (a colour strategy, a data source, a field, a mode), so every
bundle importing those modules carried catalogues it never shows, in all
11 locales: 80 area inclusions across 26 bundles, and gospel's catalogue
alone pushed country-posters 9.5 KB over the ceiling.
A bare name now counts only where it really builds a key: a namespace
helper (useOptionalT, useOptionalI18n, qualify) or a translator reading a
whole area (tm('regions') in useRegionsI18n is the one in use). Key
prefixes ('area.' / `area.${x}`) count as before. A bundle that assembles a
key with a leading `${…}` keeps the old generous rule, since its area
cannot be read from the source.
Since a9550858e the gate called _fileURLToPath, which it never imported
(only fileURLToPath is), so it died with a ReferenceError at load and
measured nothing — the UPG-100 legend's info popovers in es/ar/zh
included. And with no leaf URL it exited 0 as SKIP, which the wave runner
reads as a pass; it now exits 75 (NOT MEASURED), the convention the file
itself states. Swept every gate for the same undefined path aliases:
none.
A single-file build inlines every import, dynamic ones included, so
packet-strings.json (~115 KB minified: every language's prayer points,
captions and headings) rode in every bundle that can print a packet, list
or card deck. With the wall's print messages translated, country-posters
crossed the 1.5 MB ceiling (1,502,226 bytes).
The table is now served at <assets>/lib/packet-strings.json and fetched at
the first print by library/packet/packetStrings.js, the same move as the
sheet generator and ne-places.json:
- Pinned like them: PACKET_STRINGS_SHA384 in poster-render.integrity.js
(pin-poster-render.mjs); the text is hashed before it is parsed, and an
unmatched table is not used. These strings are laid into the print
document, so an unverified copy never reaches it.
- English is bundled as the fallback (a named import of the JSON, so only
the en block ships): with no asset base, on file://, on a network error or
an integrity mismatch the print comes out in English and the console says
why. A print never fails for want of this file.
- generate-tree copies it into the pool AND the served allowlist; verified
by regenerating the tree (the served copy is byte-identical to source).
- check:poster-render-pinned, two-sided: the pin matches source and served
copy, the genuine copy loads all 11 languages, a tampered copy falls back
to English (mutation-tested: disabling the hash check fails it), and an
unserved one falls back rather than failing.
- sha384 is one shared helper (library/packet/sha384.js).
Sizes: country-posters 1,378,463 (was 1,484,053), my-upg-100-list 1,143,456
(was 1,243,509), regions-dashboard 973,802 (was 1,079,448).
The legend's region sections, its Deaf row and its one-country zones, the
wall's card names, the print progress and error messages, the map's status
badge and the Population column all rendered English in every locale. None
of them was visible to check:hardcoded-english: they were single words, .js
modules, Error() arguments, library components or number formatting.
- Legend: region rows through one REGION_KEY table shared with the wall
(poster.region_*, new region_deaf); zones through zoneTitle (tier A by
country, the Deaf row by its bloc name); node ids stay English-derived.
- Wall: card labels through zoneTitle, so the Show-on-map name, alt text,
tooltip, HUD title and printed title agree with the legend and packet.
- Errors a reader sees are keyedError(key, English, params);
errorText(e, t) shows them translated, and an unkeyed error still shows
its own message. Developer detail for a failed data load goes to the
console; the badge says upgMap.mapDataFailed.
- Population: Intl compact notation in the app's locale, same precision.
- Country Posters: the unsupported card's reason is
messages.countryPosterTooMany, not the data file's English note.
- Strings only the UPG-100 map shows move to a new upgMap area, so other
bundles do not carry them (country-posters 1,490,907 bytes).
- check:hardcoded-english judges keyedError's English as a fallback
(quiet when the key resolves everywhere, a finding when it does not);
check:label-key-coverage follows REGION_KEY to legend-tree.js and covers
the legend's table and its regionFor producer. Both self-tests two-sided.
fix:sri rehashes the leaf pages but does not carry a <profile>.meta.json
change into their BASE_CONFIG, so a pinHint, a label or a mobile flag could
land in the source and stay invisible in the tree until some later landing
happened to regenerate it (PPLR's pinHint:false did exactly that).
The generator is deterministic: a clean tree regenerates to 0 changed
files, in about 3 seconds. So landing-preconditions now runs it (step 2b)
and refuses if anything under public/ changes, naming the files and the
command to run. Checked both ways: a meta.json change without the tree
regenerates one leaf page (refused); after `bun run tree` it is 0 (passes).
openPrintFrame's autoprint prints on the frame's 'load', which waits for every image, but the frame was removed 60 s after it was CREATED, loaded or not. The Wall's poster loads 939 photos (132 MB), so on a connection below about 18 Mbps the frame was gone before 'load': no print dialog, and nothing to say so. Now the frame is reclaimed 60 s after its load, at afterprint (as before), or at a 10-minute ceiling for a load that never ends. printImageBlob revoked its object URL on the same creation clock, which blanks an image that has not loaded; it now follows the frame's clock too.
New gate check:print-frame-reclaim (gates:pre), fake timers and a fake frame, so 61 s and 10 minutes take no time: a load at 61 s still finds the frame (and the object URL), 60 s after load it is reclaimed, a frame that never loads goes at the ceiling and not before, afterprint reclaims at once. Two-sided: the copy before this fix fails 6 of its 12 cases (the load-after-61-s and ceiling arms); the controls pass on both.
gospel found 12 real defects — including a search box's aria-label, which is
screen-reader text — inside 88 findings they had already described as mostly
deliberate. None were visible from the count. They classified every remaining
finding by hand and named the two shapes the gate miscounts.
A string compared with === against table data is a value, not a label.
`scope === 'Full Bible'` tests a cell of the shipped resource table; the
reader never sees that literal, and the row it selects renders through a key.
Translating it makes the comparison miss and the feature stop, while the
count falls and it looks like progress — the same inversion as a lookup-table
key, which is why gospel classed them together.
A separator followed by a brand is not a sentence. `{{ t('…') }} · GRN`
splits into a keyed half and the text node "· GRN", and only the second
reaches this scan.
The names are LISTED, not patterned. "Punctuation then a capitalised word"
would swallow "· Not found in any pool", a real caveat, and that is the class
gospel's 12 defects were hiding in. A name earns its place by being a
publisher or standard that is the same word in every language.
Each quiet case is paired in the self-test with the nearest thing that must
stay loud: a caveat after the same separator, prose that merely begins with a
brand, a real English sentence used as a table key, and an .includes()
argument. Poisoning the name list into a punctuation rule fails.
Poisoning the operator check did NOT fail, which is why there is now a
concatenation case: `greeting + 'Welcome to the prayer map'` is a
BinaryExpression too, so accepting any operator would silence a sentence
assembled for the reader. Only an equality test means "compared against
data".
Also the last finding in this owner's plugins. All seven main tabs resolve
through tabs.<id> in all eleven locales, so their English labels could never
render — a second copy of each name that could only drift. The sub-tab tables
lost theirs the same way.
Fleet 152 -> 143. This owner's plugins: 1 -> 0.
The served generator was run by importing its VERIFIED text from a blob: URL, which fails closed on a
partner page whose Content-Security-Policy forbids blob: scripts: safe, but no generated sheet there
(dx-shepherd's FIX-POSTER-RENDER-LOAD-WITHOUT-BLOB-FOR-STRICT-CSP).
The tree now also serves a CLASSIC-SCRIPT copy, lib/poster-render.global.js: the same file under a
mechanical transform (strict mode kept; the one export becomes window.DoxaPosterRender), written by
generate-tree with the function the pin is computed from (globalVariant, pin-poster-render.mjs) and on
its served allowlist — produced by regenerating the tree, not copied in by hand. posterSheets keeps the
proven path first; only when the blob import itself is refused does it load the classic copy with
<script integrity=sha384-…>, so the browser verifies it natively. An integrity FAILURE never falls back:
it throws before any import is tried.
check:poster-render-pinned adds: the classic copy matches its pin and the served copy, and — run as a
real classic script — draws the module's sheet byte for byte. The <script> path under a strict policy
needs a browser (rega). country-posters 1,473,964 bytes (ceiling 1,500,000); verify-bundles-shipped
PASS; gates:pre 74/74.
verify-bundles-shipped (every shipped bundle under 1,500,000 bytes) ran only
inside `verify`, in gates:post, and nothing in the landing called it. So a
landing that grew a bundle past the ceiling landed and then went red:
694b00991 shipped country-posters at 1,662,962 bytes. It is now step 3c of
the landing precondition, on the rebuilt tree, before the push (~0.2 s).
Its message now reports bytes: it printed "1.6MB (ceiling 1.4MB)" for
1,662,962 against 1,500,000, mixing MiB with the decimal ceiling.
A landing scan sat 10+ minutes in state S before printing step 1, with no
build under it, and the log could not say what it was waiting on. Now:
- the first line is written before any child starts (pid, node version);
- every child gets an empty stdin and a deadline (git 3 minutes, the
freshness rebuild 20), and a timeout REFUSES by name, with the elapsed
time and a pointer to main-capture diag, instead of holding the lock;
- each step prints how long the scan has run.
gates:post runs several checks at once, and a Date.now() budget measured the neighbours too: 723 ms of work failed the 3,000 ms budget once. The budget is now the process's own CPU (process.cpuUsage, user + system); the output says "CPU" and prints the wall time beside it as not budgeted.
The budget moves to 1,500 ms, near the log-midpoint of the two builds measured here: the util takes 0.5 to 0.6 s of CPU, and the pre-924a185aa all-pairs path 4.4 to 6.9 s. Each side keeps about 2.5x, so a CPU 1.5x faster or slower still tells them apart.
Two-sided, on the 8-core box under load: 24 copies at once all PASS (CPU mean 464 ms, max 625 ms, while their wall time reached 4,076 ms); the previous Date.now() check under the same load: 18 of 24 FAIL. Against the pre-924a185aa MSTClusteringUtil it FAILS: 4,418 to 6,921 ms of CPU across four runs.
Following the generator's pin (studio's follow-up): the Natural Earth table the sheets fetch at print
time (boundaries/ne-places.json) is data, not code, but pinned the same way — its sha384 sits beside the
generator's in poster-render.integrity.js (bun run pin:poster-render writes both), and posterSheets hashes
the fetched text before parsing it. A table that is not the pinned one is not used: the sheet draws no
city dots and says so in the console, rather than plotting places it cannot vouch for.
check:poster-render-pinned, two-sided for the table as for the generator: the pin matches the source
and the served copy; a FORGED table (every name replaced) yields exactly the sheet drawn with no places;
the genuine table IS used (its sheet differs) — the probe's pins now span Chad, because a single pin
framed too small an area for any place to fall inside it and the check could see nothing.
check:poster-browser-equiv still byte-identical; gates:pre green.
countries-map.vue and research-map.vue share 1,756 lines across 60
contiguous blocks, and 78% of that is one concern: the wiring between a
semantic-tree legend and the map beside it. The two files already import
the same 41 library modules and the same five legend-data composables, so
the data layer was never the duplication. The glue was, and glue is where
a fix lands in one copy and not the other: the packet kind-split, the
poster attribution reading and the packet page-1 map hand-off each shipped
fixed in one twin and broken in the other.
Only the four functions that are BYTE-IDENTICAL in both files move:
clearCountryHighlight, _flatItemsToStlNodes, onLegendRowClick and
_bridgeSelection. Measured, not assumed — applyDimFilter (185 lines against
170) and switchTab (49 against 50) genuinely differ and stay where they
are. Sharing code that merely looks similar is how a refactor picks one
plugin's behaviour for both.
A composable rather than a component: the legend markup already lives in
SemanticTreeLegend and its two shells, and a component would have forced
both plugins to give up the template where their real differences live.
Nothing is injected or reached for, so a second dashboard can adopt it
without inheriting research-map's provide tree.
Both calls sit after countryOutline and pplrInstance. The declarations they
replace were hoisted and ran above those consts; a composable call is not
hoisted, and placed too early it would read undefined at the first call
rather than at import — the legend would stop highlighting with no error.
The gate asserts both the dependency list and that ordering, for every
caller.
Rebuild-and-compare on both bundles: research-map 1,237,385 -> 1,237,709
bytes, country-posters 1,550,988 -> 1,551,317, and every behaviour-bearing
token identical in both — regions-fill, regions-border, fill-opacity,
fill-outline-color, line-color, line-width, visibility,
doxa:legend-row-click, _flat_filter, iso_3166_1_alpha_3. Counted rather
than set-compared, because minified output renames freely.
104 lines out of the plugins, shared lines 1,756 -> 1,702.
Page 1 keeps the live map rather than a fetched sheet, and it was handed
across as a bare <img> in mapSvg — raw markup the renderer interpolates.
The page-1 redesign no longer draws that full page, so the map arrived
shrunk inside a layout built for a stored sheet. It reads as a styling
quirk rather than a wiring mistake, which is why it survived here after
country-posters was fixed. The URL now goes through mapImage, which places
it as the whole page.
This is the third defect of the same shape between these two files: a fix
lands in one twin and not the other, and nothing reports it. So the gate
asserts the seam for every host that rasterises a live map, rather than for
one file.
Two fixes to the check itself, both found by poisoning it rather than by
reading it. `[^)]*` cannot cross the arrow function's own parens, so
`useMapPoster(() => map.value).render(` never matched and the new section
skipped both files — it passed while testing nothing. And an older
assertion pinned the exact literal `{ mapSvg, variant }`, so moving to the
seam failed a check that was still describing the shape it replaced; it now
asserts that the variant travels, not the argument list it travelled in. A
gate that names one spelling of a call turns every refactor into a false
alarm, and a false alarm is how a real one gets waved through.
Both twins now use the seam. The render is skipped for a card deck, which
has no map page; this map's legend menu only asks for full or lightweight,
so that arm guards a future caller rather than a path taken today.
A locale can arrive two ways. ?lang and <html lang> are read at script
evaluation, and the preload in i18n/index.js covers them. profile-config —
the channel a host page uses — reaches ProfileLoader's watchEffect and
nowhere else, which is the channel's whole purpose; the comment there
already records that an earlier document-level fix never saw it. So with
the build shipping one locale, a host-configured embed set its locale,
mirrored its direction, and then rendered English with no error anywhere.
One line beside the setRuntimeLocale that marks that path.
The RTL hold is the second half. When the catalogue is fetched rather than
inlined, nine locales get a re-render — the words change, nothing moves —
and Arabic gets a layout flip: English left to right, then the whole chrome
mirrors. So the text waits and the map does not.
Not a v-if on the profile: <component :is="ProfileComponent"> is the entire
plugin, map included, and gating it would hold the one thing that must
never wait. A data attribute on the existing root plus a scoped rule over
named chrome classes leaves every component mounted, re-enters nothing, and
lets the map paint on its own schedule. The classes are named rather than
matched by a blanket descendant rule because two of the text-bearing
components also carry map objects. visibility rather than display, so the
chrome keeps its box and nothing reflows when the text appears — a reflow at
swap time is the same defect as the flip, only quieter.
The gate asserts the seam. "ProfileLoader mentions loadLocale" would pass on
an import nothing calls, so the watchEffect's own body is read by brace
depth and the call has to sit inside it. It also refuses a hold that names
anything map-shaped, a hold applied to the whole subtree, and a fetch that
could run without a pin. Poisoned both halves: removing the call and
pointing the hold at a map container each fail with the message that names
the defect.
(cherry picked from commit 5c54750d58)
Every leaf inlines all eleven catalogues, so a reader who opens the map in
one language downloads the other ten. Measured on doxa-simple-map: the ten
unused locales are 113 KB gzipped, 40% of what a reader actually fetches.
With the flag on, the build inlines en — it is the fallbackLocale, so it
has to stay — and emits the other ten as <bundle>/locales/<lc>.json holding
exactly the areas that bundle uses. Same areasForBundle() answer the area
narrowing already uses, so the two dimensions cannot drift apart, and
emitted from the same plugin so composite-freshness, fix:sri and the
.gated/ split cover them with no new step. Off by default: the build is
byte-identical until someone sets it.
Not Vite's dynamic import. That was tried in April and GREW the bundle
~22 KB, because IIFE output needs inlineDynamicImports, which pulls every
chunk back in and adds a wrapper closure each. This is a plain fetch.
The base URL is captured as the script evaluates. Rollup shims
import.meta.url to document.currentScript, which is null once the script
has run, so a base read at mount would resolve against document.baseURI —
right on our own pages, wrong on every host that serves the bundle from
another path. deriveTreeBase() carries that same warning but returns the
tree root; these assets sit one level deeper, in the bundle's own
directory.
Each emitted file's sha384 is baked into the bundle and passed to
fetch({ integrity }), hashed from the same bytes the emit writes, so a
swapped catalogue fails closed to English instead of injecting text.
The fetch starts at module evaluation from the detected locale, so it
overlaps the bundle's own startup, and localeChromeReady() lets an RTL
locale hold its text chrome for up to 1.5s. For ar the flash is a layout
flip, not a text swap, which is worth a short hold; the map never waits,
and the timeout means a failed fetch degrades to English rather than to a
blank panel. loadLocale() runs after the instance exists, never inside
createI18n, because setLocaleMessage touches no effect scope while
createGlobal is the call that throws inside a stopped one. It also installs
into each instance that asks, since vue-i18n copies messages at
construction and a catalogue arriving later would otherwise reach only the
instances built after it.
Measured, doxa-simple-map: 284.6 KB gzip -> 165.2 KB, with ar as a 13 KB
asset. Ten assets emitted, every hash matching, ar no longer inlined, en
inlined.
The treeshake locale arm reads the narrowed build correctly (reports "10
asset") and fails when an asset is deleted. Its length floor is now per
script: a 25-character Chinese string is a whole sentence, so a
Latin-calibrated floor found no zh in a composite carrying short labels and
called it absent.
(cherry picked from commit 41d33d9a40)
A map says how it behaves on a narrow screen once, in its <profile>.meta.json:
"mobile": "desktop-only" (blocked on a phone) or "larger-screens" (opens,
under a dismissible notice). Absent means phone-ready. Until now the rule
lived only in the Research Gallery, as two hard-coded id sets, so a map
opened standalone on its own leaf page on a phone got neither the block nor
the notice.
- profile-meta.mjs knows the vocabulary; generate-tree and the dev manifest
carry the flag into each profile's manifest entry, and generate-tree into
the leaf page's BASE_CONFIG (with the map's name for the desktop-only
sentence).
- ProfileLoader mounts library/components/LargerScreenNotice.vue on a screen
under 640px: for desktop-only the profile is never mounted and a centred
message says why; for larger-screens a slim in-flow bar sits above the map
(it cannot cover the map's controls), dismissible per profile for the
session, in the element's own locale and direction. Inside the Gallery it
stays quiet: every Gallery pane now carries data-doxa-gallery.
- One text: the Gallery's two sentences moved into the library catalogue as
messages.largerScreenNotice / desktopOnlyNotice (+ noticeDismiss), all 11
locales moved as they were, and the Gallery reads them from there.
- The Gallery derives its rules from the manifest's flags. On a phone it
mounts nothing until the manifest has landed: its baked first tab (Big
Picture) would otherwise mount there before the flags say it is
desktop-only.
- dashboard-plus-grid is desktop-only and gospel-internet/board is
larger-screens, the two sets the Gallery held; nothing changes for either.
- check:mobile-gating now asserts the vocabulary, that each flagged leaf page
carries its flag, the Gallery's derivation and its closed-before-manifest
rule, that Big Picture stays desktop-only, the 11 locales, and the
notice's mounting. Its self-test plants one failure per rule.
generate-tree gathers assets in two stages: a pool, then an allowlist of
what the tree serves. ne-places.json and lib/poster-render.js were added to
the pool only, so every `bun run tree` deleted the served copies the two
commits before this one checked in, and a print would 404 the generator.
Both are now in the allowlist; a regenerated tree keeps them byte-identical
to their sources, and check:poster-render-pinned passes on it.
Code loaded at print time executes on partner pages, so it is pinned like every embed. The bundle
carries the generator's sha384 (library/packet/poster-render.integrity.js, written by
`bun run pin:poster-render`); posterSheets fetches <assets>/lib/poster-render.js as TEXT, hashes it
with WebCrypto, and on a match imports the verified text from a blob: URL — never the network URL a
second time. A mismatch refuses to run, with a message naming the URL and both hashes; the print falls
back to what it can do without the generator.
The page base is captured when posterSheets evaluates, and every URL it resolves (the generator, the
boundaries, the places) resolves against it — not the bundle's location, and not a URL a single-page
host changed after load.
check:poster-render-pinned (gates:pre), two-sided: the pin equals the sha384 of the source and of the
served copy (a stale pin names the command — proven by an unpinned edit); a TAMPERED copy is refused
and its planted side effect never runs; the genuine copy loads and draws. check:poster-browser-equiv
still passes byte-identical through the pinned path. country-posters 1,472,734 bytes (ceiling
1,500,000), my-upg-100-list 1,232,508, both carrying the pin; verify-bundles-shipped PASS; gates:pre 74/74.
development was RED on gates:post (verify bundles-shipped): country-posters.js had grown to 1.66 MB, over
the 1.5 MB ceiling, when the in-browser sheet generator landed. Measured by building without it: the
generator alone was 64.9 KB of the bundle (single-file builds inline any import), and Natural Earth's
places another ~170 KB (moved to a fetch in 8167415c5).
library/packet/poster-render.js is a pure module with no imports, so the tree now serves it verbatim
at assets/lib/poster-render.js (one generate-tree line) and posterSheets imports it BY URL at the first
print — resolved against the page, not the bundle, and kept out of the build with @vite-ignore. Every map
that can print a sheet sheds it; it is downloaded once, by a reader who prints, and cached across maps.
Measured on real builds: country-posters 1,662,962 -> 1,472,015 bytes (ceiling 1,500,000);
my-upg-100-list 1,231,789; verify-bundles-shipped passes; the bundle holds the runtime import and no
generator code. check:poster-browser-equiv now also asserts the served copy is byte-identical to its
source, and still passes byte-identical through makeZoneSheet and generateCountrySheet, which import the
served copy. gates:pre 73/73.
Generating map sheets in the browser inlined Natural Earth's capitals and cities (170 KB) into every
bundle that can print one: single-file builds inline lazy imports, so the whole page paid for a table
only a reader who prints needs. The tree now serves it beside the boundaries (boundaries/
ne-places.json, one line in generate-tree's copy list; public domain, no credit to carry) and
posterSheets fetches it at the first print, with the boundaries. No asset base, or a failed fetch,
draws no city dots — the generator's own rule for absent data. The Node generator reads the same file
from its source.
Measured: my-upg-100-list 482 -> 432 KB gzipped, country-posters 547 -> 496 KB. check:poster-browser-
equiv still passes byte-identical through makeZoneSheet and generateCountrySheet, which now fetch the
served table — so the fetch path draws the same dots Node does. gates:pre 73/73.
Ten agents read today's branch (five finders, one skeptic per area who had to confirm each finding
against the current code); 21 distinct defects survived. All are fixed here.
Generator and its proof
- generateCountrySheet fed only ADM0, so a country beside India or China printed without their
provinces (Nepal: 251 KB against Node's 921 KB). It loads the full boundary set, as the driver does.
- check:poster-browser-equiv rebuilt its own feed, so it could not see that. It now calls what the
browser calls (makeZoneSheet, generateCountrySheet — Nepal), deletes the target before the Node run,
runs Node with a clean environment, and FAILS (not "not measured") when the Node generator breaks.
Proven two-sided: it fails with the ADM1 fix reverted.
- The facts table's country list sorted in the reader's browser locale; pinned to en-US (Node's
default, so Node's output still hashes to the baseline).
- zone-sheet-print.js imports the library relatively, so bun, Node and the bundler resolve it alike.
Print seams and wiring
- The wall's Poster Map button used its own stored-file fetch and never the generated sheet, so every
country poster 404'd on the published tree; it now prints the generated sheet first.
- Country Posters wall: card stocks passed (its Card Deck could never print), iso3-names passed (zh/ar
page 1 kept English names), the stored fallback reads previews/ (where the tree serves them).
- Card-stock labels render at render time, so a locale change relabels them.
- The records provider caches by language AND list, and does not keep a failed membership load.
- The UPG-100 legend's Card Deck returns quietly on a parent row instead of an evaluator error; the
selection fetch uses PRINT_FIELDS.
Packet
- The centered-set caption counts what is DRAWN (a ring too full to separate reports its remainder).
- Each ring's number lane is sized to its own radius (ring 1 put a dot under its badge).
- Per-box line keeps a decimal below 10 people ("≈ 0" for small lists); worksheet rows name the country;
the localized poster kicker is fitted against the poster's real 19px, not 22.
Plurals and gates
- regions.countryCount / regionCount were authored in English zero|one|other order in de, es, fr, hi, it
and pt, so CLDR picked "27 Land", "1 países". Rewritten in each locale's own order; check:plural-forms
now fails a string in English order (proven on the old German string).
- check:optional-t-equivalence (gates:post) crashed on the new plural fallback's imports; it injects the
real cldrChoiceIndex and asserts the plural path (ru 21/3/5, en 0/1/7).
- check:packets-all and check-packet-render counted people as cards only, so every lightweight packet
read as "a packet of nobody"; worksheet rows count, and the browser gate checks the grid's top fill
(the renamed .lc-frame) instead of the retired scatter. check:packet-variants now reads the grid itself
— 5,200 boxes, Jesus' 1,716 from the top, the % shading from the top — proven: it fails on a renderer
that shades the last boxes.
gates:pre 68/68, check:optional-t-equivalence PASS, both bundles build.
Run on a real token against seven production leaves, the gate reported "no
sized canvas appeared within 30s" for all six of its targets, and its own
negative arm failed too. Three separate causes, all in the harness.
IT LOADED 404 PAGES. The gate asked for `${base}/${id}/` -- a bare directory
URL -- and internal/serve-strict.mjs answers 404 for those deliberately,
because the live layer does and a gate passing on a URL that 404s in production
is the divergence that server exists to prevent. Its header even records that
only ad-hoc probes and one gate ever used the bare-directory form; this was that
gate. Measured here against a real serve-strict: the directory URL gives 404
with and without the query, index.html gives 200 both ways. Both call sites now
ask for index.html.
IT JUDGED BEFORE THE TILES ARRIVED. waitForFunction resolved as soon as it
could return {engine, features}, and that object is truthy with features:0 --
so it stopped at the first sized canvas and read an empty frame. It now holds
out for a rendered feature and lets the timeout report a genuinely empty map.
IT COULD ONLY SEE TWO OF THE SEVEN MAPS. Features were read from
window.__rcmMap || window.__rdashMap, the two instances this repo happens to
publish, so the other five leaves read -1 and would have FAILED as "0 rendered
features" even having drawn correctly. An init script now wraps
maplibregl.Map/mapboxgl.Map as soon as the CDN script defines it and collects
every instance the page builds, whatever it names them.
The WebGL flags are NOT among the causes, though they were the first
diagnosis. A three-way A/B -- these flags, ANGLE's software backend, none at all
-- gets WebGL and WebGL2 in every case. They stay as they were.
The negative arm is not a bug either; it is why this was caught. It failed
because MapLibre genuinely did not draw, which correctly refused to certify six
"no sized canvas" results as broken production maps when the fault was this
gate's own URL. Its message now says to suspect the harness first when both
engines report nothing.
Still exits 75 here: no token on this box.
library/packet/poster-render.js and posterSheets.js arrive in this landing
carrying the generator's comments as they stood before the sweep: two lines
named the Driver and one said "he changed the SHAPE". They are reworded as
the sweep reworded them in generate.mjs, and two pronouns the sweep left
behind (SearchFilterHeader, check-filter-width-stable) now say what was
asked instead of who asked. The baseline drops the two generate.mjs lines
that moved out.
The Driver's print standard: every export is generated when it is asked for, and only previews are
stored. The zone and country map sheets — the poster, and the map-only sheet on a packet's page 1 —
existed only if library/scripts/posters/generate.mjs (Node) had written them ahead of time; no country
had a print-resolution sheet at all.
- library/packet/poster-render.js is generate.mjs's drawing layer (2,645 lines) as a pure factory,
createPosterGenerator({ env, SERIES_DEF, ISO3_NAME, adm0, adm1Ind, adm1Chn, nePlaces, series }). Moved
VERBATIM (template literals carry their whitespace into the output), `process.env` read through a
local shadow; its inputs are its free variables, found by a scope-aware parse, not a grep (a grep
missed adm0, and the proof caught it). generate.mjs is now its Node driver.
- PROVEN BYTE-IDENTICAL: all 118 sheets of a pinned offline matrix — the 28 zones in 3x4, 3x3, letter
and packet, and a country sample at both sheet sizes — hash the same before and after.
- data/zone-sheet.js is which group pins in which zone (first claim wins), now ONE function both the
Node driver and the browser call, so a group cannot pin in one zone on paper and another on screen.
- library/packet/posterSheets.js feeds the factory in the browser: the served full-resolution
boundaries, the shared series and places tables, a generator per call (caching by format pinned the
first caller's data on every later sheet). generateCountrySheet does a country end to end;
data/zone-sheet-print.js a UPG-100 zone. ISO3_NAME moves to library/data (territory-counts re-exports).
- usePosterPrints gains opts.sheet: the poster print is the generated letter sheet as a VECTOR page,
and the packet's page 1 the generated map-only sheet (item.mapId honoured); stored images remain the
fallback when generation fails. PosterWall forwards it; the Country Posters wall, the UPG-100 wall and
the UPG-100 map pass it. The UPG-100 wall and map drop their own record fetches for the shared
provider, and the map's legend and selection HUD offer the card deck (production).
- check:poster-browser-equiv: the browser feed's sheet is byte-identical to the Node generator's for an
India zone (packet) and a country — proven two-sided (it fails when the feed drops the city dots).
check:selection-kinds reads the handler's branches instead of a fixed poster/packet pair, anchored on
`if (` (proven: it fails with the cards branch deleted). library/packet/README.md: the standard.
gates:pre 68/68; both bundles build.
An English _note key sat in gospel.json in all eleven locales, so every
Arabic reader downloaded an English paragraph about how the file is
organised. It also fooled a gate: because it was text present in ar and
absent from en, it satisfied a probe asking "does this bundle contain
Arabic translations?" for a bundle with every Arabic character stripped
out. A value no reader is meant to see is a value no check should be able
to count.
The reasoning it carried is real and is kept — which pools were searched,
why a mistranslated caveat there would read as authoritative, why Open
Bible Stories is not Scripture — moved to a README beside the catalogues
along with the rule that nothing in those files is a comment.
Adding that README turned check:i18n-parity red, which is the second half
of this commit: it enumerated locales with existsSync, true for a file as
well as a directory, so the README read as a locale missing all 1,685
keys. Documentation belongs next to what it documents; the filter was
wrong. Locales are directories now.
2.7 KB out of every bundle, and gospel.prose.english — the one translated
line that says the prose stays English — is untouched.
The area arm asks whether every area a bundle uses is present. It names
locales nowhere, so it is equally green whether a bundle carries eleven
locales or one. That is fine while the glob inlines them all, and stops
being fine the moment the build narrows by locale: a per-locale file that
failed to emit would fall back to English silently, with no error and no
gate. Written now, while the answer is still "all inlined", so it is
already guarding when the build changes underneath it.
A locale counts as reachable if it is inlined in the IIFE or emitted as
<bundle>/locales/<lc>.json beside it, which is the shape the design
proposes. The list comes from SUPPORTED_LOCALES, not from the folders on
disk: the folders are what happens to exist, while SUPPORTED_LOCALES is
what the runtime will actually try to resolve, and a locale added to the
list without a catalogue is precisely the failure to catch.
Three things it had to stop reporting, each a correct absence:
Bundles that include no areas carry no catalogue at all. Composite hosts
mount leaf bundles at runtime and render no catalogue text of their own —
stack-demo makes zero translator calls in its own source, and failing the
host would send someone looking for a bug in the wrong file. And the probe
was asking bundles for text from areas the build deliberately left out,
which reported five bundles as broken when none of them were.
The probe requires a locale's own script for ar, ru, hi and zh. "Not in
en" is not the same as "is a translation": the ar catalogue holds an
English developer note that en does not, and that one string was enough to
report ar as present in a bundle with every Arabic character stripped —
the check voting on a string no reader will ever see.
Proven both ways on real output. Stripping ar from a built bundle fails
naming ar; restoring passes. Simulating the design's end state — ar not
inlined, ar.json emitted — passes with the asset present and fails when it
is deleted.
gospel's resources-list maps the resource titles that arrive in the data to
the i18n keys that render them:
const RES_KEY = { 'Bible video': 'gospel.res.bibleVideo', ... }
title: RES_KEY[r.title] ? tr(RES_KEY[r.title]) : r.title
The English on the left is matched against a value from the data; what the
reader sees is the key on the right. Wrapping the left side in t() would
make every lookup miss and fall through to the raw data value, so the count
would drop while the display broke — the worst shape a gate finding can
have. Found by gospel, who triaged it rather than acting on it.
Exempt only when the mapped key really resolves in every locale, the same
way a fallback has to earn its exemption. A table pointing at a key nobody
wrote still counts: the lookup succeeds and hands t() a key the catalogue
has never heard of. A value that is a plain category rather than a key
counts too — the gate can only vouch for what it can resolve.
Self-test covers all three arms, using the real RES_KEY case. Poisoning the
exemption to skip the resolution check trips it.
gospel 153 -> 146; fleet 159 -> 152.
poster-wall translates its section headings by looking the key up by the
English: one table maps a zone id to a heading, a second maps that heading
to a catalogue key, and the helper returns the raw English when the second
table has no entry. Two headings reachable from four zone ids had no key, so
they printed in English in all eleven locales. upg100 found and fixed that
one; this is so the next table cannot repeat it.
The hardcoded-English scan cannot see this class. There the English is a
lookup index rather than a rendered label, and the key is computed, so a
scan that reads keys written literally at the call site has nothing to
check. Resolving arbitrary key expressions is a static-analysis problem;
this does the decidable part — two object literals in one file where the
values of the first are the keys of the second, and coverage is a set
difference. The table list is declared, not discovered, because a heuristic
hunting for related-looking objects would be guessing about code it has
never read.
Two things the check has to get right and could quietly get wrong. It
compares the VALUES of the producing table, not its keys — the ids are
never translated, and reading them finds nothing while looking like a
check. And a label passed straight to the helper as a literal counts as
produced: three of the nine headings never appear in the table at all, and
"Other" is exactly the catch-all that gets added last and keyed never.
A renamed table fails loudly rather than comparing two empty sets and
calling it agreement. The self-test covers a covered table, a gap, a gap
reachable only through a literal argument, and the renamed case; poisoning
either parser or the object matcher trips it.
Verified against both real states: development's poster-wall fails naming
Americas and Europe, and upg100's fix passes.
RegionCountryMap builds its map popup by concatenation and escaped every value
in it -- the name, the religion, the description, the image, both button labels
-- except the two URLs:
<a class="cm-popup__btn" href="${profileUrl}">
Both come from joinBase(props.researchUrl, p.slug), and joinBase says plainly
that it only normalises slashes. So two untrusted values reached an HTML
attribute raw: the slug, which is API data, and the base URL, which is an
attribute set by whoever embeds the element. With a slug of
`x" onerror=alert(1) data-x="` the rendered tag is
<a href="https://doxa.life/research/x" onerror=alert(1) data-x="">
out of the attribute and into new ones. esc() was already imported in that file.
The same omission in library/packet/packet-render.js, found by sweeping for the
class rather than assuming it was one site: the people-group card writes
src="${src}" where src is photos.get(slug), and usePosterPrints builds that Map
directly from the API's image_url. esc(g.name) sits two lines below it.
Neither could be caught by review or by template analysis -- both are inside
template literals -- and the popup had never once rendered, because the Regions
map was grey. So the gate: no href= or src= in built HTML takes a raw
interpolation. Data URLs, blob URLs, encodeURIComponent and safeUrl() are
recognised as safe, each verified at its call site rather than assumed from the
name; anything else needs an href-escaped-ok: reason in the four lines above.
Two such reasons are recorded, for the two sites the check cannot see the safety
of: useMapAttribution's hrefs are module constants selected by engine type, and
export-public builds its rel entirely from encodeURIComponent one line earlier.
The sweep also cleared seven sites it first flagged -- three blob: URLs from
createObjectURL, two data: URLs from readAsDataURL, safeUrl(), and the
encodeURIComponent pair -- so the check now recognises those shapes instead of
reporting them.
Mutation-tested: reverting either fix alone turns the gate red. 6 self-tests
carry the founding bug and the safe shapes. gates:pre 65/66, the one red being
check:poster-action-print waiting on gospel's GiTree fix.
This gate reported a 403 as "tileset entitlement, not the embed" and printed
only the first blocked URL. Both halves were wrong.
The claim is disproved. Measured on this gate's own token: /v4/ streets-v8 and
country-boundaries both return 200 -- with no Referer, and from
research.nuxtbase.app, maps-dev.nuxtbase.app, doxa.life, 127.0.0.1 and
example.com. The token is unrestricted and entitled. The old conclusion rested
on three no-Referer probes, which cannot tell a URL-restricted token from a
missing entitlement; it named a cause those probes could not support. Whatever
403s is a different request -- a style, a sprite, glyphs, a composite -- or a
different token than the one measured.
Printing one URL is what hid that. A leaf blocked on forty tiles showed
whichever tile lost the race, so the tileset was never legible and a second
blocked endpoint was invisible behind the first.
NOT MEASURED now names every distinct blocked path, with tile coordinates
folded to {z}/{x}/{y} so one blocked tileset is one fact rather than forty, and
styles, sprites and glyphs stay separate -- which is exactly the class the
evidence now points at. It also names where the token came from (env var or the
leaf's own baked one), never its value: the query string is stripped before any
path is printed, asserted in the exercise below. The real-failure arm gets the
same treatment.
The gate asserts no cause any more. It reports what was blocked and lets the
reader judge.
Exercised on six URLs spanning two tilesets, a sprite and a glyph range: 6 ->
4 distinct facts, and a planted token value does not appear in the output.
The map-ness scan follows a profile's own imports, which was the fix for a
map-less list inheriting map-ness from its bundle. It follows RELATIVE imports
only, so a component that moved into library/ became invisible: the profile
reaches it through @map/..., the alias resolves nowhere in the walk, and the
profile reports no map.
MEASURED on the seven curated tabs, alias imports resolved and re-tested:
exactly one was wrong. regions-dashboard/regions-mfe draws a real map through
@map/components/maps/RegionCountryMap.vue, which does match the engine test --
it imports useMapInstance and useMapProvider -- while region-dashboards.vue
holds no engine token of its own. The other five map tabs carry an engine token
in their own files too, which is why only this one showed the symptom. It is
not a recent break: regions-mfe is isMap=false in all eight manifest revisions
in history.
The consequence was a blind gate. check-mapbox-drew takes its targets from this
flag, so it measured six of the seven tabs and silently omitted the one with an
open map bug. Map leaves go 6 -> 7. country-map also flips to true, correctly:
it renders the same component.
A stricter test applies once the walk crosses into library/. The engine pattern
matches the bare string maplibre-gl, which in a shared library is mostly prose
and CSS class names -- 12+ library files match it while using no engine. The
one that bit: wall-of-people-groups, a D3 wall with no map, imports
src/wallD3.js, which imports library/constants/cdn.js, a pinned URL and SRI
table that only NAMES the engine CDNs. With the loose test that wall flipped to
isMap=true and would have baked ~400 KB it never uses, which is the regression
the per-profile scan exists to prevent. Inside library/ a file now counts only
if it imports the package, calls useMapInstance/useMapProvider, or touches a
map global. Plugin files keep the original test, so no in-bundle result moves.
Probe in _qa-cycle: nine cases including two reverse poisons -- regions-list and
wall-of-people-groups must stay false. It reads the shipping functions out of
this file rather than reimplementing them, and it fails on the code as it stood
before this change.
gates:pre 65/66. The one red is check:poster-action-print, which wants gospel's
GiTree fix that is not on this branch yet.
The legend's card-deck item is opt-in: it sits behind v-if="exportCards",
a prop that defaults to false. countries-map passes it and handles 'cards';
upg-100-list passes nothing, so its legend never shows the item and cannot
emit it. The gate still demanded a 'cards' branch in upg-100-list's handler
and failed gates:pre on a menu item that plugin never renders.
An item guarded by a prop now needs a handler only where upg-100-list passes
that prop. Both sides planted: upg-100-list opting in without a branch
fails, and the legend losing the guard fails.
check:optional-t-equivalence evaluates useOptionalT.js on its own, with its
imports stripped and tStatic and useI18n handed in. The composable now also
imports getRuntimeLocale and cldrChoiceIndex, so every section died with a
ReferenceError. The gate now hands in a bare host's getRuntimeLocale and the
shipped cldrChoiceIndex, lifted from library/i18n/index.js's own text; its
import control accepts tStatic among several names, but still only from
'@map/i18n/index.js' (a catalogue import of the module's own still fails).
check:frozen-labels caught the research filters' new search placeholder:
tStatic inside a computed that reads no locale never re-runs, so the
placeholder stayed in the boot language. It now reads and passes
locale.value, like the search chip in the same file.
Its scope note named the Driver while stating the poster rule;
check:no-new-driver-references fails on it. It now states the rule alone.
(GiTree.vue's line of the same kind was reworded by its owner on
development first.)
A second look: the bounded set on one page and the centered set on another. Each chart
keeps everything the redesign gave it — its heading, the cross at the centre, the nine numbered
target bands with dots spread through them, the full ring legend — as one large square panel (960px)
centred on its page, instead of two 660px panels sharing one. The centered set's page carries the
legend; both carry the pair's heading so they read as one spread.
Also the profile row's photo URL is escaped (esc(src)), the identical hunk regions landed in
1aeafead0, so the two merge cleanly: a quote in an API image_url closed the attribute and injected
markup into the printed document.
check:packet-a1-structure and check:packet-variants assert the two-page order and the derived
lightweight delta (four back pages dropped).
check:poster-action-print asserted that PosterWall defaults to printing and
that any consumer passing posterAction 'download' explains itself. Both held,
and both missed the case that actually shipped: a component that never touches
PosterWall and writes its own <a download> for a poster image. GiTree had a
"Download SVG" button as the sibling of its own printPoster(), same
makePoster() behind each, so the poster could reach disk without the print
dialog ever opening.
Second arm: no shipped file may set a.download to an image or document name
(svg/png/webp/pdf/jpg) unless the line is reached only after a print attempt
failed (if (!opened) / if (!w)), or is the prop-gated opt-in the first arm
already polices, or carries a posterAction-download: reason. Data exports stay
out of scope -- csv, txt and json are not posters, so the five legitimate CSV
and letter exports stay green.
Keying on the filename at the call site rather than on the helper matters:
four of the seven download sites pass a variable, so a check written against
the helper would have passed them without being able to see what they wrote.
Six self-tests carry the shapes: the GiTree line fails, the print-blocked
fallback passes, the gated opt-in passes, the explained exception passes, csv
and txt pass. They are the proof the arm still bites now that no offender is
left in the tree.
An absolute embed address works where it was built and breaks everywhere
else, which is how the CDN route failed and was retired. The build config
keeps embedBaseUrl empty, so the committed tree is relative and a deploy
makes it absolute at build time (EMBED_BASE_URL). But nothing stopped a
tree built with EMBED_BASE_URL from being committed: check:drop looks for
root-relative "/..." references and deliberately skips https:// ones.
With embedBaseUrl empty in the config, the manifest must say embedBase "."
and every scriptUrl, iframeUrl and embed snippet src must be relative. Data
addresses (runtime.apiBaseUrl) are not the tree and are not checked. SKIPs
when the config sets embedBaseUrl. In gates:post, after check:drop.
Today's tree: PASS, embedBase "." and 173 embed addresses, all relative.
Poison-tested, each fails: an absolute snippet src, an absolute embedBase,
a protocol-relative iframeUrl, an empty manifest. A real deploy manifest
built with EMBED_BASE_URL fails with 174 findings, as it should.
gates:post: PASS 82, NOT MEASURED 13 (browser/server), FAIL 1: the known
check:packets-all worktree failure (no generated zone packets here).
Eighteen keys across eleven locales for text that was written in English in
the components: the rail and filter toggles, the selection count, the CSV
export toast, the drawer's prayer and information links, the dashboard
title, the map's failure message, and the four search placeholders that name
the tiers of whichever tab is open.
The export toast is interpolated rather than concatenated. Its counts keep
going through fmt(), which groups digits the way each locale does, and only
the words around them come from the catalogue — the order of "rows",
"fields" and the outcome is not the same in every language, so a sentence
built by joining fragments cannot be translated at all. It uses the shared
fillParams, which leaves a placeholder visible when a value is missing
instead of rendering the word "null".
The map's failure message no longer names the token and the container: that
is for the console line, which stays English for whoever is debugging. What
the reader sees says what to do.
Three more shapes the scan could not see, each of them correct code: tStatic
called directly rather than through a wrapper; `config || t(key) || English`,
where the translator is not the immediate left operand because || is
left-associative; and the licence credits, media queries and product name
that must not be translated at all.
Own leaks 91 -> 1, the last being a tab label behind a key that resolves in
all eleven locales. Fleet 249 -> 159. check:i18n-parity,
never-translated, treeshake and stopped-scope all pass.
The legend emits 'packet:full' or 'packet:lightweight' — it widened its
existing event rather than adding a second one — and this router still
tested the whole string for 'packet'. Neither submenu item matched any
branch, so the Packet export opened nothing, logged nothing, and looked
like a menu item nobody had wired. Found by upg100, who had already hit
the same thing in its own copy.
The kind is split before it is compared, the way upg-100-list does it, and
a bare 'packet' still means the full packet so an older emitter keeps
working. The variant then has to survive the call as well: openPacket
ignored it and printPacket defaults to full, so lightweight would have
printed the full packet even once the menu fired.
The gate asserts the seam rather than one file — it reads the kinds the
legend actually emits and requires every consumer to split before
comparing, because this bug cannot be seen by reading either side alone.
It currently fails on country-posters/countries-map.vue, which has the
identical dead menu and is not mine to change.
Two fixes to the check itself while writing it: a fixed-size window read
only part of a long router and reported correct code as broken, so the
function's extent is found by brace depth; and a trailing \b after a
quoted literal never matches, which made the assertion fire on every
consumer including the working ones.
tStatic is the library's translator for code outside a component's setup, and
it was missing from the set of names the scan knows. Anywhere it was called
directly rather than through a wrapper, correct guarded code read as a leak.
The two-sided rule still applies to it: a tStatic fallback is exempt only
when its key resolves, and the self-test asserts both halves.
Three allow shapes, each for a string that must not be translated:
A matchMedia query. Rendering "(prefers-reduced-motion: reduce)" in another
language matches nothing, so motion safety would switch off for exactly the
readers who asked for it, and nobody would see an error.
Tile licence attribution. The credit a data licence asks for is the one
string that stays as the licensor wrote it in every language. These render
only when the live sources cannot be read, which is the case a licence never
allows to come out empty.
The product name, and bare CSS position keywords.
Each shape is anchored rather than fuzzy: prose that resembles them —
"Start end of the list", "center the map on your region", "Doxa Maps is a
prayer tool for the unreached", a copyright line with a real sentence in it —
is still reported. 190 -> 178.
The country wall passed no records, so each of its 175 cards offered only the poster and said the
prayer list was "coming soon", while the people groups were one request away. library/lists/
peopleGroupRecords.js is the records half of the print standard: one people-groups fetch per language
(normalised the way the prints read rows, list membership applied), then a SELECTOR picks the set — a
country by ISO3, explicit slugs, any field, or a host's own selector language. A selector nobody
understands selects nothing rather than every group on earth, and a failed fetch is not cached.
The country wall uses it by ISO3, so every country now prints the list, the deck (all five stocks) and
the full or lightweight packet. The packet's page 1 is, for now, the country's stored poster when it is
really there; the in-browser map generator will replace that.
check:people-group-records asserts the selection two-sided with an injected fetch; registered in
gates:pre.
library/layouts/app-wrapper-material/index.html carried the one library
reference the sweep's file filter missed (it looked at scripts, components
and docs, not .html): reworded the same way, comment only.
The reference baseline then shrinks from 505 to 147 lines, so nothing
reworded in this landing can come back. What remains is in the owners'
plugins, documentation and skills.
Thirteen lines arriving in this landing named or quoted the Driver: the
packet renderer and its two gates, and the desktop build. check:no-new-driver-references fails on each of
them, so they are reworded here the same way as the sweep: the date and
the requirement stay, the speaker and the quote go.
Every reference to the Driver in internal/ is reworded the same way as in
library/: the reason stays, the speaker and the quote go, an open decision
reads "provisional, pending a product decision", and agent names and decision-
sheet rows are dropped. Where a gate printed the word, its message now names
the reviewer or the ruling instead.
Three spots were code, not comments, and changed with their callers:
- the element census's allowlist field `driver` is now `ruling`, in the gate
and both presets (same validation: match, ruling, date and card required);
- check:i18n-never-translated's DRIVER-GATED marker is DECISION-PENDING, in
the entries and in the regex that counts them (its output is otherwise
byte-identical);
- the package.json description of the census says "ruling".
public-hygiene-check keeps its two patterns for the word: they are what keep
it out of the served folder.
studio measured the trap: t(key) with no params BLANKS every placeholder. On
vue-i18n 9.14.5 "{nNone} of {nLang} have {noScripture}" comes back as " of have "
— so sentenceParts would find nothing to split on and the sentence would lose its
values, silently, in every locale at once. I reproduced it before building on it.
keep(...names) returns { name: '{name}' } to hand t(), making the lookup a no-op
over the placeholders. Preferred over tm(), which returns the raw string only
while messages are compiled at runtime; this works with precompiled messages and
with plurals, which is the point of the pattern.
The previous commit SAID it works with precompiled messages. It was not measured,
and it was wrong in the way that matters: the helper was right and the call
pattern around it was not, so every arm passed while the real thing would have
rendered a blank sentence. Three new arms run an actual createI18n — English
renders, Arabic reorders the clauses, and the negative control asserts that
WITHOUT keep() the placeholders really are blanked, so if vue-i18n ever stops
doing that we find out rather than carrying keep() by habit.