Commit graph

1588 commits

Author SHA1 Message Date
Doxa Maps
9fa3b1e484 Composites must mount and their links must exist on both ends, in every landing
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.
2026-09-26 04:21:49 +00:00
Doxa Maps
ad61981395 check:composite-interop: a composite's links must exist on both ends, in source and in the built bundles
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.
2026-09-26 04:21:49 +00:00
Doxa Maps
3735f27296 check:composites-mount: a composite whose setup throws can no longer look green
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.
2026-09-26 04:21:49 +00:00
Doxa Maps
f1f93d78b0 Rebuild after merge
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
2026-09-26 03:57:21 +00:00
Doxa Maps
ba78a389e6 Gospel map: give the zero-access view an address, and say the way in on every row
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.
2026-09-26 03:56:24 +00:00
Doxa Maps
c8fee416b7 Rebuild the consumers main left stale (build owner, README rule 5)
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
check:composite-freshness found stale on main: all-people-groups
big-picture
cities-internet
country-posters
dashboard-plus-grid
demo-map
doxa-maps-spa
doxa-research-map
doxa-simple-map
gospel-resources
key-cities
my-upg-100-list
people-groups-grid
plugin-studio
pplr-data-maps
prayer-walk-planner
regions-dashboard
research-dashboard
research-map-plus-grid
stack-demo
template-bundle
upg-neighbor
wall-of-people-groups. Rebuilt, tree + SRI
regenerated; the landing precondition (fresh, one folder, SRI) passed in main-capture scan.
2026-09-26 03:48:58 +00:00
Doxa Maps
c36ea54904 Gospel leaves: restore the two number call sites the revert had left routed
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.
2026-09-26 03:41:40 +00:00
Doxa Maps
2b49056f17 Gospel map: credit the basemap that is actually drawn
Under MapLibre the attribution read "geoBoundaries · Mapbox · © OpenStreetMap"
while the tiles came from OpenFreeMap. Crediting the wrong supplier is worse than
crediting none: it is a false statement about whose work is on screen, and
OpenFreeMap's terms ask for their credit.

addMapAttribution hardcodes the Mapbox link whenever basemap is true and takes no
engine, so the shared bar now credits geoBoundaries alone under MapLibre and the
engine's own AttributionControl supplies the basemap line. Fixing the helper to
take an engine belongs in library/ and goes to the build owner; this is the
plugin-only half. Mapbox behaviour is untouched.

The wording is read from the style, not restated here. OpenFreeMap's TileJSON
carries the exact text — OpenFreeMap, © OpenMapTiles, data from OpenStreetMap —
so a hand-typed copy would be one more thing to go stale the day they change it.

Also corrects the comment on the load guard added last tick. It was written for a
"MapLibre draws only the basemap" report that has since been retracted as a
measurement artifact: polled properly, both engines draw 170 fills and 2,107
pins. The guard stays, because everything this map draws hangs off one callback
and that race is real, but it repaired nothing and the code should not claim it
did.
2026-09-26 03:38:49 +00:00
Doxa Maps
be0a9efecf Token gates follow the auto engine: no token ships, and an explicit engine beats an available token
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.
2026-09-26 03:37:53 +00:00
Doxa Maps
0b9b068b44 Gates: pin-dim-slider runs again, heartbeat follows the translated panel, and the new engine checks are registered
- 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.
2026-09-26 03:35:18 +00:00
Doxa Maps
8991181091 Translate the eight strings that had no catalogue entry, keeping their English exactly
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)
2026-09-26 03:34:42 +00:00
Doxa Maps
9844f72eaf Wall of People Groups: nothing leaks onto the host page (no Font Awesome, no window.d3)
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)
2026-09-26 03:34:31 +00:00
Doxa Maps
05ab8b9da1 Place search makes no Mapbox call without a real token, and says so
(cherry picked from commit cd03a2c6d0)
2026-09-26 03:34:31 +00:00
Doxa Maps
6e54c394b8 Dev server: listens on this machine only, never serves .git or .env, and shows data/ edits at once
(cherry picked from commit d01e19c90c)
2026-09-26 03:34:31 +00:00
Doxa Maps
5285574259 Key Cities (gated): the map, list, tables and posters speak the reader's language
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)
2026-09-26 03:34:30 +00:00
Doxa Maps
7b1e64eaba Wall of People Groups: level of detail like a map, and an HD poster print path
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)
2026-09-26 03:34:30 +00:00
Doxa Maps
d2afa3f153 The dev server says which commit it serves (/__dev-sha), and a helper finds the shared one
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)
2026-09-26 03:34:30 +00:00
Doxa Maps
395a66c9ae My UPG Neighbors: the lenses' chrome speaks the reader's language
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)
2026-09-26 03:34:29 +00:00
Doxa Maps
85019a1543 Wall of People Groups: the wall and its dialogs speak the reader's language
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)
2026-09-26 03:34:29 +00:00
Doxa Maps
cc3651919d PPLR data maps: tab labels and status lines speak the reader's language
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)
2026-09-26 03:34:28 +00:00
Doxa Maps
5ded530b43 Prayer Progress: no hardcoded English left in the map's own chrome
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)
2026-09-26 03:34:16 +00:00
Doxa Maps
730df6a69a The country outline never drew on MapLibre: the worldview clause emptied its layer
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)
2026-09-26 03:34:16 +00:00
Doxa Maps
6e639923af The landing precondition moves to .merge-queue/
(cherry picked from commit 7c131f3aba)
2026-09-26 03:34:15 +00:00
Doxa Maps
0c7f4af47b Engine and token props: a token-service URL is honoured, Clear clears, and a pending token is awaited
(cherry picked from commit 6e4ddceced)
2026-09-26 03:34:15 +00:00
Doxa Maps
05165d9b26 Plugin Studio: the token button says "Mapbox token", the prop it sets
(cherry picked from commit 4cdfa95f46)
2026-09-26 03:34:14 +00:00
Doxa Maps
3711c590c9 One browser slot on this machine: a second browser run queues instead of racing
(cherry picked from commit 928d38d445)
2026-09-26 03:34:13 +00:00
Doxa Maps
ab17c9b6aa The default engine is auto: Mapbox when a token is available, MapLibre when none is
(cherry picked from commit 3cb89eae52)
2026-09-26 03:34:12 +00:00
Doxa Maps
521f8b18d6 Rebuild dashboard-plus-grid research-map-plus-grid, and regenerate the tree
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
2026-09-26 03:04:01 +00:00
Doxa Maps
57009b7e28 Big Picture and Simple Research mount again: import the three locale names they use
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.
2026-09-26 03:02:46 +00:00
Doxa Maps
75c4b54e7f Rebuild the consumers main left stale (build owner, README rule 5)
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
check:composite-freshness found stale on main: all-people-groups
big-picture
cities-internet
country-posters
dashboard-plus-grid
demo-map
doxa-research-map
doxa-simple-map
gospel-resources
key-cities
my-upg-100-list
people-groups-grid
pplr-data-maps
prayer-walk-planner
regions-dashboard
research-dashboard
research-map-plus-grid
template-bundle
upg-neighbor
wall-of-people-groups. Rebuilt, tree + SRI
regenerated; the landing precondition (fresh, one folder, SRI) passed in main-capture scan.
2026-09-26 02:46:20 +00:00
Doxa Maps
9e52b82bd7 Cities list + tables: keep numbers in their previous format while numerals are held
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.
2026-09-26 02:38:06 +00:00
Doxa Maps
701bb266f6 Country posters: the composite's pane tabs speak the reader's language
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).
2026-09-26 02:38:06 +00:00
Doxa Maps
58f2c3bdf6 Cities with Internet: the list and data-tables views speak the reader's language
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.
2026-09-26 02:38:06 +00:00
Doxa Maps
6989e30f44 check:universal-props (draft): every plugin reads engine, token and locale through the library
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.
2026-09-26 02:38:06 +00:00
Doxa Maps
a2f0d90424 Reference: the universal-props contract (engine, locale + dir, Mapbox token, token-service URL)
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.
2026-09-26 02:38:06 +00:00
Doxa Maps
5f1db2350f Landing precondition: one freshness pass per tree, so the merge-queue lock is held for minutes
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.
2026-09-26 02:38:05 +00:00
Doxa Maps
57bf76422f Only a page we own gets its direction set
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.
2026-09-26 02:38:05 +00:00
Doxa Maps
e1e6c287f3 Gospel map: hold number format for the Driver, and guard a load that already fired
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.
2026-09-26 02:30:33 +00:00
Doxa Maps
d2a643d0aa gates:pre green again: the tree generator's quote-escaping regex no longer desyncs the template-literal check
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
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&quot;b),
and the tree regenerates byte-identical.
2026-09-26 02:24:56 +00:00
Doxa Maps
2f4bdf561d Rebuild pplr-data-maps, and regenerate the tree
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
2026-09-26 02:20:09 +00:00
Doxa Maps
4c45d76c68 PPLR data maps: d3 is pinned, integrity-checked and kept off the host page's window
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.
2026-09-26 02:19:25 +00:00
Doxa Maps
4220a25eea Gospel access: export the bridge's training, and stop offering Deaf groups a spoken one
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.
2026-09-26 02:13:26 +00:00
Doxa Maps
e1b9db2265 Merge branch 'main' of https://git.nuxtbase.app/doxa-life/doxa-maps into bundler-1/work
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
2026-09-26 02:10:35 +00:00
Doxa Maps
fcb1c8285e Port their box-sizing reset into the shadow root, and delete the seven patches it replaces
app/assets/styles/base/_reset.scss:3-8 applies border-box to every element on
DX-life. Our leaf document carries the identical rule already -- but these
components render inside a SHADOW ROOT, and a document-level `*` selector does not
cross that boundary. So every element in here was content-box while every element
of theirs was border-box, and no scoped component style could reveal it. That is
how it survived seven global-rule fixes, and it is why this codebase had accrued
seven separate per-element box-sizing:border-box patches: each was a local
workaround for the reset that never arrived.

What it cost, measured rather than described: their legend swatch is width:10px
with a 1px border -- 10px under border-box, 12px under content-box. Two swatch
columns of 1px each are exactly the 2px by which our legend panel measured wider
than theirs (167 vs 165) after the rest of the legend was aligned to the pixel.
The same label measured 129px on both sides in the same Poppins, so the text was
never the cause.

The port is a geometric NO-OP and that is why it is safe to do in one move: the
blast radius was measured FIRST, by injecting this exact rule into every shadow
root and diffing every element's rect. 0 of 431 elements moved on
region-dashboards, 0 of 678 on regions-list, 301 already border-box by their own
declarations. The detector was negative-tested, because a zero from a check that
cannot fail proves nothing. So the whole value of this commit is the DELETION --
six per-element patches plus the interim `.rcm *` subtree rule, which the global
reset strictly contains.

Placement is plugin-local, not library/useShadowStyles.js. Putting it there would
fix all 25 bundles in one line and is tempting for exactly that reason, but the
blast radius above was measured on THESE two pages only; 23 other bundles would
have their geometry changed on no evidence. RULE 2 says default to the plugin
folder until a second consumer is named.

One module rather than three copies: three roots inject into the shared shadow
root and any of them can be the one that mounts, so all three need it.
2026-09-26 02:10:30 +00:00
Doxa Maps
694dad7bab Docs: an iframe updates itself; a script embed's integrity hash pins the bytes, so re-copy it after each release
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
From the critic's SRI check. how-to's embed section had no word on it; the CLI section said the
hash changes on every build, which is only true when the bundle's bytes change.
2026-09-26 02:07:14 +00:00
Doxa Maps
3361cb0ed8 Merge branch 'main' of https://git.nuxtbase.app/doxa-life/doxa-maps into bundler-3/work
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
2026-09-26 02:01:53 +00:00
Doxa Maps
e72afe7985 Gospel map: the country gap list says whether the training is there too
The rows under "No resource found" name the way in. For 182 of the 369 groups
with nothing, that bridge language also carries the Zume discipleship course, and
the row did not say so — which is the difference between "there is a way in" and
"there is a way in and the course to train the first believers already exists in
it". A mobiliser scanning a country is deciding where to start, and that is the
deciding fact.

The bridge's own name stays first, because the language is the decision and the
training is what makes it actionable. It reads "way in: French · Discipleship
training", with the label coming from the catalogue so it follows the reader's
locale like every other label on the row.

Nothing new is fetched or linked: it reads the same Zume URL already verified and
already on the bridge panel, so the doctrine screen is unchanged at 16,963.
2026-09-26 02:00:48 +00:00
Doxa Maps
bc369d9cda Merge branch 'main' of https://git.nuxtbase.app/doxa-life/doxa-maps into bundler-1/work
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
2026-09-26 01:57:38 +00:00
Doxa Maps
d8b3541865 Merge branch 'main' of https://git.nuxtbase.app/doxa-life/doxa-maps into bundler-3/work
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
2026-09-26 01:53:48 +00:00
Doxa Maps
79965b860b Gospel map: offer the bridge language's discipleship training
The point of this map is not that somebody reads a page. It is that the first
believers in a group are trained and become the first messengers to their own
people and their neighbours. Zume is the piece that does that, and it is
published in 52 languages.

Measured across the 369 Doxa groups with nothing: ZERO have Zume in their own
language, and 182 have a bridge language that does. The map was showing Zume only
to groups that already had something — the half that needs it least — while for
half the gap the training already exists in a language their neighbours speak.

Both bridge panels now carry it beside the Scripture and the film. All 19 distinct
URLs behind those 182 groups were fetched and answer 200, with a bogus locale
404ing so the status means something; every one is on zume.training, already a
screened publisher, so the doctrine screen is unchanged at 16,963 links.
2026-09-26 01:49:58 +00:00