The engine default is now auto (c8fee416): no token means MapLibre, which is what every keyless
partner embed shows, and the Driver wants the README pictures to show that. The script no longer
reads a baked token or fetches doxa.life's, and appends no ?tk=. SHOTS_WITH_TOKEN=1 restores the
old token ladder for Mapbox shots.
Checked headless: vite.doxa-maps-build-config.json tokenUrl is empty and the in-process server has
no /api/maps/token, so a leaf's token ladder ends empty and generate-tree's auto branch mounts
MapLibre with no warning banner (TOKEN_FREE, no 'Token fetch failed' text). node --check passes.
A new writer's first copy used to draw a coloured box. template-map-a.vue now
draws the points in data/places.geojson on a basemap with one translated title,
through the library's own pieces (useMapInstance, resolveMapProvider, labelFont,
emissivePaint). It follows the page's engine, locale (right-to-left included) and
token, and with no token it runs on keyless MapLibre, so it works with no account.
template-map-b.vue stays as the second-screen example, so the README, the tutorial
and the plugin-designer skill (rename a, delete b) stay true. The README gains the
map's pieces table and the dx-maps paths.
Library change: library/i18n/locales/<11>/template.json (key template.title) is
eagerly globbed into every bundle, so consumers need rebuilding (rule 5B).
check:composite-freshness found stale on main: country-posters
my-upg-100-list. Rebuilt, tree + SRI
regenerated; the landing precondition (fresh, one folder, SRI) passed in main-capture scan.
upg-neighbor/flower-mesh is a preset of the upg-neighbor element that opens on
its Flower Mesh lens, which is SVG and has no GL map. The leaf is still a map
element: the view-switcher reaches Send Map and the other GL lenses, so it must
keep isMap (engine peer, token ladder). Marking it isMap:false would break those
lenses. check:leaf-engine-per-provider judged it as a drawing map and failed it
on both engines ("drew: none"), before and after the auto engine alike.
- upg-neighbor.instances.json: the flower-mesh preset says "opensOnMap": false.
- generate-tree.mjs: an instance's manifest entry carries opensOnMap:false when
it is set (absent otherwise, so every other entry is byte-identical).
- check-leaf-engine-per-provider.mjs: such a leaf is not judged as a drawing
map. Its Mapbox-request count under ?provider=maplibre is still measured.
Headless: after bun run tree, the manifest marks only
upg-neighbor/upg-neighbor/flower-mesh with opensOnMap:false. The gate's leaf
selection, replayed over it, judges 28 of 56 leaves as drawing maps, with
flower-mesh excluded and its five siblings unchanged. No browser run.
I broke both composites on main. Editing them with a pattern-match script, I
replaced an import line by matching text that had already changed, so the
replacement silently matched nothing and three names — RTL_LOCALES,
getRuntimeLocale, detectLocale — were used and never imported. Setup threw a
ReferenceError and both elements rendered nothing until a hotfix landed.
★ IT PASSED EVERY GATE, AND THAT IS THE INTERESTING PART. It compiled, because a
free identifier is valid JavaScript until it RUNS. It passed my grep of the
built bundle for the new code, because the code was genuinely there. It passed
template-literal, sfc-parse, composite-freshness and SRI, because not one of
them evaluates a setup function. The evidence I had could not distinguish a
working composite from a dead one, and I called it "verified headless".
So this asks the one question those gates cannot: is every name a component uses
actually bound — imported, declared, a parameter, or a known global? It reads
the same source the compiler reads, tracks real scopes (module, function, block,
catch, class), and runs in milliseconds with no browser. Fleet-wide, over every
.vue in plugin-designs/ and library/, because the mistake was a text-editing
habit rather than anything about one plugin.
WRITTEN BY RUNNING IT, which is the only way an allowlist gets honest. The first
run reported 4,259 findings — every one a false positive from a real bug in my
own walker: a helper that takes a node TYPE was being handed the NODE, so the
function branch never fired and every parameter read as unbound. The second run
found the globals this tree genuinely uses (d3, customElements, ShadowRoot, CSS,
Function, self, the typed arrays) and vite's build-time defines. The third found
a real gap in the gate: an SFC may have BOTH a plain <script> and a <script
setup>, and they share a scope — LegendTools exports a function from one and
uses it in the other, which I was reporting as free. Reading only one block
would have taught the fleet to ignore this gate on its first run.
NEGATIVE CONTROL, because a check that has never been seen to fail is not
evidence: restoring the broken import makes it FAIL naming all three names and
their lines; restored, it passes across 127 components.
It is the cheap half of a pair — it proves a name is bound, not that the thing
runs. The expensive half is a check that actually mounts a composite.
rega measured two broken interop links and read them as one timing bug. They are
two different bugs, and neither is the one hypothesised (that the event fires
before the map pane has mounted -- both shells create and append their pane
elements in their own onMounted, so the element exists at click time).
country-posters: THE FORWARDING WAS NEVER WRITTEN. onShowOnMap() was a guard,
go('map'), return. No dispatch, no attribute, no property -- nothing carried the
selection to the map at all, which is why the camera stayed at world view. Not a
race, and no amount of waiting would have fixed it. The shell also created its
pane elements and dropped them on the same line, so there was no handle to address
the map pane with even if it had wanted to.
upg-100: forwarding existed but was fire-and-forget, losable two ways. The listener
is attached at the END of an async onMounted, after 5 awaits and only `if (host)`,
and an element dispatch has no replay. And once attached, the zone id was resolved
against legendNodes and applied by `if (node) onLegendSelect(node)` with NO else --
so an id arriving before the data was discarded in silence. rega's second-click
measurement settled which one fires: the first request dies before readiness.
ONE PATTERN, and it is deliberately not "attach the listener sooner" -- that would
fix one of the two losses and leave the other. The request is RECORDED as an
attribute on the pane element and only then announced by event. An attribute is
state: it survives custom-element upgrade, and it is still readable when the pane
arrives, and when the pane's data arrives later still. The event remains the live
path for a map that is already up. Both triggers end in one apply function, so
there is one apply path with two triggers rather than two ways to select.
Unreadiness is retained; an UNRESOLVABLE id is not. Retaining a typo'd id would
spin on every readiness change forever, so it is dropped.
The drain runs on nextTick, and that is not caution. upg100-plugin flagged from
experience that watch([batchMode, isDark]) marks legend rows on nextTick, so a
pre-flush drain would apply the selection against rows about to be replaced by the
re-render, and the highlight could be stomped. That failure is invisible in the
worst possible way: "the selection happened but you cannot see it" is
indistinguishable at the rendered layer from "the selection never happened" --
the exact bug being fixed. The fix would have impersonated the defect.
SOURCE ONLY, deliberately. Both bundles were built to prove the source compiles,
then the built artifacts were reverted: these are not my bundles, and rule 5 keeps
rebuilds with the build owner. country-posters and my-upg-100-list both need one.
Verified by _chk-showonmap-selection-is-not-retained.mjs, 30 assertions. It asserts
the ONE APPLY PATH property rather than just the functions' presence, on
upg100-plugin's suggestion -- a later third trigger that bypassed the apply path
would otherwise fail nothing and reproduce the invisible-selection symptom.
check:composites-mount and check:composite-interop join gates:post, and composites-mount is also a landing
precondition (3b, ~20-45 s): 08f89f55 blanked Big Picture and Simple Research while every existing check
passed, so a landing that blanks a composite is now refused before it reaches main.
A composite is a composite because its children talk, and every link is a name
written in two files that nothing ties together. Rename it on one side and the
build stays green while the link silently disappears. The interop matrix also
found the other silent way a link dies: research-map.vue looked up its host with
getCurrentInstance() inside a watcher callback, where Vue returns null, so
map-filtered was never sent, and the only proof had used a synthetic event.
Headless, in about a second:
- A: each link's send pattern is in the sender and its listen pattern in the
receiver (17 links across 7 composites).
- B: the literal is in every built bundle that ships an end of the link.
- C: every capability-4 composite is mapped, or states why it has no links
(stack-demo).
- D: no getCurrentInstance() inside a plain function in plugin-designs/ or
library/. The three routed sites are printed as KNOWN, and an entry left
behind after its fix fails.
Negative-tested on 521f8b18, and each of these fails: a receiver renamed in
source, a built bundle out of step, an unmapped composite, a new late
getCurrentInstance (one-line function, arrow, multi-line), and a stale KNOWN
entry. check:no-orphan-gates, check:script-flags and check:quoted-scripts pass.
It is not wired into gates:post; that is the build owner's call.
08f89f55 shipped Big Picture and Simple Research calling getRuntimeLocale,
detectLocale and RTL_LOCALES with no import. The build was green and every
structural gate passed, but setup threw a ReferenceError in every locale and
both composites rendered nothing.
This gate runs the part that failed, with no browser. Vite loads each
capability-4/5 composite the way its bundle entry defines it (configureApp,
plus ProfileLoader's provides where the entry mounts ProfileLoader), and
@vue/runtime-core mounts it on a no-op renderer in en and ar. Any error from
setup, a watcher's first run or a mounted hook fails it, naming the composite.
Measured on 75c4b54e: it fails exactly the two broken composites in both
locales. On 521f8b18 all 20 mounts pass. Throws poisoned into a directly
defined element and into a ProfileLoader profile both fail. It takes about
20 s. check:no-orphan-gates, check:script-flags and check:quoted-scripts pass.
P2. The list of groups with nothing already existed and could only be reached by
knowing which control to press. It is the question this map is for, so it gets an
address: ?view=zero opens it with the map already filtered to those pins. That
address is also what a partner embeds and what a QR code points at.
Each row now carries its way in. This was the only surface showing groups with
nothing that did not say what to do about them — the pin sheet and the country
panel both do — and it is the one a mobiliser reads top-down.
Building it exposed the same flag under-reporting for the third time. `sg` comes
from the sign-language catalogues and asf (Auslan) carries none, so reading it
alone this leaf missed Cocos Deaf, Deaf Christmas Islanders and Deaf Norfolk
Islanders: no SUN panel on their pin sheet, and no way in on the list, while the
access table gave all three SUN. The pins builder and the access builder were
each corrected the same way earlier; this puts one Deaf test in the leaf so the
four surfaces agree. Measured after: 369 of 369 zero-access rows carry a way in,
none blank.
The revert left fmtShort and the table's row count going through fmt() instead of
calling toLocaleString() directly. Behaviour was identical, because fmt is that
bare call again — but the acceptance measure for holding number format is a
literal count of the pre-change state, and 6 is not 8. An "identical in effect"
argument is the wrong currency when the point is that this map must be byte-for
-byte what it was until the Driver rules.
Measured against the build at 2eac0fb3^: numberingSystem latn calls 0, bare
toLocaleString() 8, both exact. The number handling in all four leaves now
differs from that build only in comments.
The build config no longer bakes a Mapbox token, and the default engine is 'auto' (Mapbox only when a
token is available). Two gates still assumed the old contract.
- check:nested-token: with the build config's token empty (the shipped default), no leaf and no bundle
may carry a pk. token, and every leaf must carry the runtime engine ladder (EXPLICIT / HAS_TOKEN),
so with no token it draws MapLibre, keyless. A builder who bakes their own token gets the original
check. Fails on the pre-rebuild tree, which still has baked tokens.
- check:provider-switch: the Mapbox baseline is now ?provider=mapbox with a fake host token (its
requests are aborted, never sent); the switch run adds ?provider=maplibre to the same token (an
explicit engine must win); a third run requires the no-token default to make no Mapbox requests.
- check:pin-dim-slider: its description sat in "scripts" under the same key as
its command, so the prose won and bash failed on the apostrophe. It moves to
"//scripts".
- check💓 the panel's bin labels now call T(key, en), defined above the
slice the gate evaluates, so the gate threw "T is not defined". It now passes
the same shape, answering in English.
- check:map-props-unit (Node, 26 cases) and check:auto-engine (browser) had no
script, so check:no-orphan-gates failed. Both are registered; map-props-unit
joins gates:post. auto-engine stays a browser run, outside gates:post.
The shepherd's call: translating wording that already exists is not a product
decision. So every string keeps the English it reads today and gains a key, in
all eleven locales.
Big Picture's four first, since it is the localization priority: Export CSV, the
detail drawer's title and its empty state, and the legend's "heartbeat zone"
bin. Then Top countries, Select all, the table dock's Expand/Collapse titles,
and the singular "group" whose plural was already translated — a half-translated
plural is its own small defect.
Full screen / Exit full screen now read buttons.toggleFullscreen, which already
shipped in every locale, so that one needed no new wording at all. The arrow
glyph still carries the state.
Three more went in that were not on the list, because leaving them would have
been worse than the defect being fixed: "Deselect all" is the other half of a
toggle whose first half we just translated, and the two row-selection aria
labels are the same control speaking to a screen reader. A control that changes
language halfway through is harder to use than one that never changed at all.
★ ONE STRING I AM NOT CONFIDENT IN, AND IT SHOULD BE CHECKED BY A SPEAKER.
"heartbeat zone" is the Driver's own coinage, not standard UI vocabulary, and a
metaphor is exactly where a translation I cannot read can go quietly wrong. I
translated it literally in all ten languages. The rest are ordinary interface
terms; that one is a judgement, and I would rather flag it than have it
discovered on the gift.
VERIFIED HEADLESS: i18n-parity passes with zero debt; three bundles build; the
keys AND their Arabic values are present in the built output (تصدير CSV,
منطقة نبض القلب, اختر شعبًا لعرض التفاصيل, and the header's placeholder).
Numerals are untouched, per the hold — this is text only.
(cherry picked from commit 67568c0509)
The critic fact-check (CRITIC-9) found the Wall injecting Font Awesome 6.5.1 into the HOST
page's <head> (global .fa-* rules that can restyle a partner's own icons) and loading d3@7 (a
floating range, no SRI) as window.d3, overwriting a host's own d3.
- The card's heart / comment / share icons are now plain SVG paths in the card itself, not
Font Awesome <i> tags in three foreignObject/xhtml divs per card (~6,300 on the wall: lighter
too). The dialog hearts are inline SVG; the source-picker info icons are a text glyph. No
Font Awesome stylesheet is injected anywhere.
- src/wallD3.js loads d3@7.9.0 exactly, with integrity + crossorigin, keeps it in the module
(wallD3()) and puts the host's window.d3 back (or leaves none).
- The heart used `self.$store`; `self` is the browser's window, so a prayer made in this
session never lit it. It now reads this.$store.
Headless proof: a stubbed-DOM test of the d3 loader, 6/6 (the host's d3 restored, none leaked,
exact version + integrity + crossorigin, a failure rejects). The bundle builds and contains 0 Font
Awesome references.
needs eyes: the Wall: hearts, comments and share icons render on the cards; a card whose group has
prayers shows a red heart; a host page with its own d3 or Font Awesome is unaffected.
(cherry picked from commit 28575d6013)
The last bundle of the L1 pass: key-cities had no i18n at all though its entry installs it. A new
keyCities area (38 keys, all 11 locales) carries the map title, band legend, caveats, the city
sheet's labels, the list's intro, filters and section lines, the data tables' title, intro, tabs and
loading line, and the poster wall's heading, tag, nouns and "coming soon" message. Unnamed places
keep showing coordinates, now inside a translated phrase. Counts use a "label: n" form outside English
and numbers keep their existing format (numerals held). cities-map's script message goes through a
guarded useI18n with the English catalog as fallback.
check:i18n-parity PASS; placeholders match English in every locale; the bundle builds.
A library catalog change: studio lands it (rule 5B).
needs eyes: key-cities (gated) with ?lang=ar: the map legend, a city sheet, and the list.
(cherry picked from commit 7d4da13721)
The wall gave all ~2,100 card photos their full href at once, so every photo was fetched and
decoded even with the whole 4000x3000 wall on screen and each photo a few pixels wide. Now:
- photo URLs are parked in data-hd; zoomed out (a card under 48 px on screen) the wall shows
only the light image slots, with no photos loaded;
- zoomed in, updateLod() (rAF-throttled on every zoom/pan) gives HD photos only to the cards in
view plus one card of margin, and REMOVES them from cards that leave the view; a 300-image
cap keeps any view light;
- "Print HD poster" loads every photo, waits for them, prints, then returns to level of detail;
it replaces the old "coming later" alert (new key wall.printPoster, all 11 locales).
The geometry is a pure module (src/wallLod.js), so it is tested without a browser.
MEASURED, headless: the real 2,107-card layout at 1280x800. Sampled photos are ~200x250 (23 real
image URLs from the live API): ~0.22 MB each decoded, ~45 KB compressed. Loading all of them, as
before, is ~466 MB decoded + ~94 MB downloaded. With LOD: zoom 1-4x = 0 photos; 6x = 150 (~33 MB);
8x = 80 (~18 MB); 12x = 63 (~14 MB). lodPlan matches a brute-force visibility oracle at every zoom.
Photos are the part of the 1.4 GB that can be measured without a browser; the rest (DOM,
rasterisation) needs a browser heap snapshot: needs eyes.
needs eyes: Wall of People Groups: DevTools memory before/after; zoomed out = no photos, zoom in =
photos appear only where you look; "Print HD poster" prints every photo.
(cherry picked from commit ef9646f8f5)
A shared dev server follows main, so what it serves changes under whoever checks against it; a check
that cannot name the commit it measured is not evidence. /__dev-sha answers "<sha>" (or "<sha>+dirty"),
read at request time. internal/dev-server.mjs: url(), devSha(), waitForSha(sha) (resolves once the
served commit contains it) and a CLI, for headless checks against the shared server on 127.0.0.1:5190
(DOXA_DEV_URL overrides). Its header says when NOT to use it: an unlanded change, and release/embed
checks, which stay on the built tree.
(cherry picked from commit e43310e479)
The universal-props matrix flagged upg-neighbor as a locale BYPASS: no t() in any of its ten
components. By the agreed scope (dx-shepherd, 2026-09-26), this localizes the CHROME in all 11 locales
through a new neighbor area (103 keys): the brand, view tabs and hints, lens titles, search
placeholders, select/aria labels, loading and error lines, legends, stat labels, tooltips, chart-strip
controls, list actions and copy feedback. Research terms, provenance footers and proper nouns stay in
English as named sources: SEND, doxa-2000, Bounded set, JP, PPLR, IN/CN, the 4-D join notes.
Templates use the globally injected $t; script strings use useNeighborT() (guarded, English fallback).
Stat lines use a "label: n" layout in every locale so no language needs plural rules. Country names
in the lookup tables are data and unchanged.
check:i18n-parity PASS; every locale's placeholders match English; the bundle builds.
A library catalog change: studio lands it (rule 5B).
needs eyes: My UPG Neighbors with ?lang=ar: the view tabs, the Send Map legend and the Overview chart strip.
(cherry picked from commit 090f64fbe3)
The universal-props matrix flagged the Wall as a locale BYPASS: no t() anywhere. A new wall area
(library/i18n/locales/<lc>/wall.json, all 11 locales, 39 keys) now carries the header, orientation
and zoom controls, the loading and offline notice, each card's prayer count and "View prayers", and
both dialogs (Prayer Tools, People Group Chats), with aria-labels on the close buttons, which had none.
The components use the globally injected $t (createAppI18n, globalInjection). The prayer count uses
vue-i18n's plural choice in English and a neutral "label: n" form elsewhere, so no language needs
plural rules. Status values from the API are data and stay as they come.
Headless proof: vue-i18n against the real catalogs, 8/8 (en 0/1/4 plural, ar neutral form, ar title,
de label form); check:i18n-parity PASS; the bundle builds. Library catalog change: studio lands it.
needs eyes: Wall of People Groups with ?lang=ar: the header, a card's prayer line, and both dialogs.
(cherry picked from commit 7c1c60975e)
The universal-props matrix flagged pplr-data-maps as a locale BYPASS: every tab label and every
loading or ready line was an English literal. A new pplr area (library/i18n/locales/<lc>/pplr.json,
all 11 locales) carries the 10 tab labels and 20 status lines; usePplrT() reads them through a
guarded useI18n with the English catalog as fallback. Counts go in as parameters, and their number
formatting is unchanged pending the Driver's numerals decision. The views' own legend and panel text
is the next pass.
check:i18n-parity PASS (all 11 complete); the bundle builds. A library catalog change (the i18n index
globs every area into every i18n bundle), so studio lands it (rule 5B).
needs eyes: PPLR Data Maps with ?lang=ar: the tab bar and the loading and ready lines.
(cherry picked from commit 4a16f0538b)
The Driver's localization list, item 4. doxa-simple-map already took its language from profile-config
locale/lang and ?lang= (ProfileLoader) and set dir=rtl for Arabic, but its own strings were English
literals: "Loading map…", "The map could not be loaded." plus a technical init message, the legend
title 'Legend', the default "Prayer" tab label, and the whole "About This Map" help modal. They now
read the catalogs: legend.titleBar.legend, tabs.<id>, allPeopleGroups.loadError, buttons.aboutThisMap,
buttons.close (the close button had no accessible name), and a new simpleMap area in all 11 locales.
A tab label a HOST passes in profileConfig.tabs is still shown as given. The map formats no numbers
itself; the legend, popups and detail panel are library components already translated.
check:i18n-parity PASS; the bundle builds. A library catalog change, so studio lands it (rule 5B).
needs eyes: Prayer Progress with ?lang=ar: the loading line, the tab, the legend title and RTL layout.
(cherry picked from commit c35bc84794)
Second site of the defect rega found on regions-mfe, and this one is shared by three
plugins — country-posters, doxa-simple-map and doxa-research-map.
The source asymmetry here was already handled carefully: MapLibre gets a geojson
source instead of the mapbox:// tileset, the loader bridges iso3 -> iso_3166_1_alpha_3,
and the layer drops 'source-layer' because MapLibre rejects it on geojson. What was
missed is one field deeper. countryFilter() also ANDs a worldview clause, which exists
because the tileset repeats a country once per worldview and the outline would otherwise
draw two or three times over. world-adm0.geojson has no such duplication and carries no
worldview property, so ['get','worldview'] is null, both sides of the `any` are false on
every feature, and the `all` admits nothing. The outline is absent with no error, which
looks exactly like boundary data that failed to load.
Unlike the ISO field this cannot be bridged in the loader: there is no worldview to
invent. It is a fact about the tileset's shape, so only the Mapbox path asks for it.
The engine sniff already in the file moves UP, above the filter. It sat inside the
try block, below the line that built the filter, so the filter could not have consulted
it; the fast path at the top (setFilter on an existing layer) reuses the same filter and
is now covered by the same branch.
MEASURED, fail-first, against the real 162 features with MapLibre's own style-spec parser:
the worldview clause admits 0 / 162
the MapLibre branch admits 1 / 1 (== one country, this composable's shape)
the Mapbox path still collapses a duplicate worldview copy to 1
The check also REGISTERS both worldview sites in the tree and fails on a new unlisted
one, so the class cannot come back quietly in a component nobody re-shoots.
Not landed here: three consumer bundles compile this, so it needs their rebuild.
(cherry picked from commit b483606054)
08f89f55 gave both composites a text-direction computed that calls
getRuntimeLocale(), detectLocale() and RTL_LOCALES, but imported none of them.
The build does not check free names, so the bundles on main reference three
undefined globals. watch(textDir, reflectDir) evaluates the computed during
setup, so setup throws a ReferenceError in every locale, and Vue renders
nothing for the whole composite: the map, the dashboard and the grid.
Reproduced headless with the same computed + watch shape under
@vue/runtime-core: lang '' fails with "getRuntimeLocale is not defined", and
lang 'ar' with "RTL_LOCALES is not defined"; neither renders.
After this import, both bundles build and neither contains a free reference
to the three names.
922a8b66 formatted counts with toLocaleString(<reader's locale>), which switches Arabic to
Arabic-Indic digits. Numerals are held for the Driver (dx-shepherd, 2026-09-26), so num() goes back
to the browser-default toLocaleString() these views used before. Only the text is translated.
The bundle builds.
country-posters-mfe labelled its panes with English literals ('Country Posters', 'Map') while the
panes inside translated: the chrome a reader sees first (universal-props matrix, cell L1). They now
read tabs.countryPosters (new, in all 11 locales, worded as the Research Gallery's own rail) and
the existing tabs.map, through a guarded useI18n with the English catalog as the fallback.
check:i18n-parity PASS; the bundle builds. Library catalog change: studio lands it (rule 5B).
Both views were English-only with no t() (the universal-props matrix, cell L1): titles, intro,
count chips, section and row lines, band pills, the source note, the five table tabs and the
loading line. They now read connectivity.list.* and connectivity.tables.* in all 11 locales, with
the English catalog as the fallback where no i18n is installed (the connectivity-map pattern), and
format numbers in the reader's locale instead of the browser's. Count chips use a "label: N" form
outside English so no language needs plural rules. Region and country names are data, unchanged.
check:i18n-parity PASS; every new key's placeholders match English in every locale; the bundle builds.
Touches library/i18n/locales (eagerly globbed into every i18n bundle), so studio lands it with the
consumer rebuild (rule 5B).
needs eyes: Cities with Internet > list and Cities Data tabs with ?lang=ar and ?lang=zh.
Headless static gate for the contract in documentation/reference-universal-props.md: scans each
production and experimental bundle's source (and the shipped bundle for strings the minifier keeps)
for a bypass. The bypasses: a mapbox:// style in setStyle or a raw new gl.Map (a style handed to
useMapInstance is translated there and passes), a bare mapboxgl constructor, a literal provider pinned
into useMapInstance, a window.DOXA_TOKEN read, a pk. token literal, and toLocaleString('en-US')
(warn-only until the Driver sets a numerals policy). Documented design exceptions are ALLOW-listed
with their reason. A self-test plants each bypass and one clean case; a blind or over-eager rule
exits 3. Today: FAIL, 7 bypasses in 4 bundles (routed as defects), 12 locale warnings.
One page that says, for each prop the Plugin Studio header sets for every plugin, which channels
can carry it (header, profile-config, URL, window, attribute), which one wins, and the default;
how the studio hands it to a pane; and the rule a plugin follows to honour it. Ends with the open
points found while writing it down (tokenUrl has no reader, the token-clear bridge, raw maps on
window.DOXA_TOKEN, ladders that disagree), each routed as a defect. Scope is the four props the
header sets today; anything wider is DECIDE-UNIVERSAL-PROPS-SCOPE.
The freshness precondition rebuilds every bundle in its scope (~10 min for a library change) and ran
inside the lock, so a build-owner landing held main for 30+ minutes while other landings timed out
behind it. Its verdict depends only on the committed tree and the scope, so a PASS is now recorded
against both (in the repo's git dir). The landing runs the precondition before taking the lock; the
scan inside the lock finds the record and skips the rebuild. A different tree or a wider scope has no
record and is checked in full, as before.
An RTL locale reflected onto document.documentElement whenever it
resolved — so a reader following a ?lang=ar link to a PARTNER'S page
flipped the partner's whole <html>: their headings, their nav, their
footer. The existing comment already worried about the host owning that
decision, but it only guarded against CLEARING dir, never against
setting it.
A generated leaf is the only thing that knows a document belongs to the
plugin, so it says so: window.MAP_OWNS_PAGE, written into all three leaf
templates. library/i18n reads it before touching the document. Nothing
else sets it, so the DEFAULT IS "not ours" — a partner page that
declares nothing is never touched. Absence has to mean restraint for a
flag like this.
The element's own direction was never this code's business: ProfileLoader
binds dir on .profile-loader from effectiveLocale (0f6d5049), so the
plugin mirrors correctly whether or not it owns the page.
★ THE OLD GATE COULD NOT HAVE SEEN THIS, which is why it needed a new
case rather than a re-run. check-embedded-rtl drove the locale through
profile-config — the channel a HOST uses. This defect fires on the URL
channel, which is what a READER uses, and detectLocale() reads the URL
FIRST. Added a ?lang=ar case on the partner page:
before profile-config cases partner html unset / body ltr pass
?lang=ar partner html RTL / body RTL FAIL
after ?lang=ar partner html unset / body ltr pass
plugin root stays rtl throughout — it still mirrors
Standalone leaves are unchanged and verified: ownsPage true, html
dir="rtl", plugin root rtl. The page we own still mirrors completely.
NUMBER FORMAT REVERTED, on jurisdiction rather than on merit. Formatting with the
locale i18n resolved is almost certainly right, but it decides number format for
this map alone and that decision belongs to every map at once. The finding stays
in the source so nobody re-derives it: toLocaleString() with no argument formats
for the JS runtime's locale, so the same page shows different numbers to readers
on different browsers; and pinning latn would change digits only for region tags,
since the eleven tags this bundle ships already resolve to latn.
MAPLIBRE. Reported against the built tree: under ?provider=maplibre this map drew
the basemap and nothing else — no fills, no pins, queryRenderedFeatures returning
0 while the source held all 2,107 features — while ?provider=mapbox drew 170
fills and 2,107 pins, with no page errors either way. Everything this map draws is
added inside one map.once('load') callback, so a load that has already fired
leaves exactly the basemap, which is the reported symptom; the two engines
resolve their styles on different schedules, so the race lands differently on
each. Draw immediately when the style is already loaded, keep once('load')
otherwise.
This is the cause the evidence indicts, not a verified repair. Browsers here
belong to one agent, so it needs re-running before anyone believes it.
template-literal-check reads source with a simple state machine that does not know regex literals.
The .replace(/"/g, …) in esc() looked to it like an open string, and it stayed "in a string" until a
later quote. Its state happened to re-sync on a `//` inside the forge page template; when 87ebbace
removed that line (the static api.mapbox.com preconnect), the check lost track and flagged a
backtick in an ordinary JSDoc comment (generate-tree.mjs:1084) as a stray backtick in a template.
gates:pre failed for everyone.
The regex is now /\u0022/g: the same character, so escaping is unchanged (verified: 'a"b' → a"b),
and the tree regenerates byte-identical.
The critic fact-check (CRITIC-9 / CRITIC-5): the ROP Sunburst loaded d3@7 (a floating range, no
SRI) as window.d3, overwriting a host's own d3, or running on whatever d3 the host already had.
ensureD3() now loads d3@7.9.0 exactly with integrity + crossorigin, keeps it in the module
(pplrD3()) and puts the host's window.d3 back (or leaves none). SemanticSunburst reads pplrD3(),
and if a caller forgot to await ensureD3 it loads it and builds instead of asking for a refresh.
Headless proof: the stubbed-DOM loader test, 6/6 (host d3 restored, none leaked, exact version +
integrity + crossorigin, a failure rejects). BUNDLE=pplr-data-maps builds. Plugin-only.
needs eyes: PPLR > ROP Sunburst draws; on a host page with its own window.d3 it is untouched.
Two things, and the second was found by the first disagreeing with itself.
The CSV is what a team prints onto a poster or turns into a QR code, and it was
the only surface that did not say when the bridge language carries the Zume
course. 179 rows now do. Same URL the sheet already shows and the verification
already proved; nothing new is fetched.
"Candidate groups" is removed rather than renamed. It counted people groups
speaking the candidate language, which only the old pins-derived proxy could
produce; since candidates came from CLDR it has shipped as 0 on all 108 candidate
rows, and an empty column in an exported CSV reads as missing data rather than as
one that stopped applying. CLDR does give whether the language is official there,
which is a real fact about how likely it already is to be taught, so that takes
its place.
THE BUG. 182 rows had a bridge carrying Zume and only 179 could export one. The
three were Deaf groups speaking Auslan, and codes.asf has no sg flag — it is set
from the sign-language catalogues and asf is in none of them. So the pin index
built a SPOKEN bridge for them and the map offered it, because the map's is_sign
test reads that same flag. The access table refused it, because it also matches
the group name. A spoken bridge is not a weak bridge for a Deaf community, it is
not a bridge; this is the residue of the finding that already removed 89.
Suppressed on either signal now, so the two builders agree and the map cannot
offer what the table refuses.
Still 369 with nothing and still none of them without a way in.