Commit graph

712 commits

Author SHA1 Message Date
Doxa Maps
c9dfb668b3 Plugin Studio: collapse the header to give the plugin the whole page, and bring it back in one click
A builder previewing a plugin full screen can hide the studio header (the props panel: language, map
engine, Mapbox token). The header's new "Hide header" button collapses it, as the Research Gallery runs
headerless. A "Show header" button then stays pinned in the top corner, above every plugin, so the
props are always one click away. The choice is remembered in this browser (plugin-studio:header-hidden).
Strings in all 11 studio catalogues. BUNDLE=plugin-studio builds.
2026-09-26 04:39:00 +00:00
Doxa Maps
65017fefa4 The prayer packet shows how its list was narrowed out of all the earth
Page 2 of every packet is now the funnel: 12,379 people groups on earth, 7,341
unreached, 4,754 frontier, 2,107 on this prayer network's list, and N of those
in the reader's hand. A partner holding one list can see where it sits in the
whole, which is the argument the packet is making.

★ IT RENDERS NUMBERS IT DID NOT COMPUTE. Every count is read from
data/people-groups/vision-funnel.json, which build-vision-funnel.mjs generates
from the IMB CSV and the live network list and which already drives the Vision
Card. Re-deriving them in the packet would give one fact two sources; they would
drift, and two internally-consistent surfaces disagreeing is worse than one
wrong number because a reader cannot tell which to trust.

K AND THE SHARE ARE MEASURED AT RENDER TIME, NEVER TYPED, and this mattered:
the brief said "~30 lists of 50-100" and the catalogue says K=27 with counts
from 8 to 118 (west-africa 118, asia-india-rajasthan 116, uncovered-scatter 8).
Only about half sit in that band, so no copy claims it. The page states what the
data can defend -- "1 of 27 lists" and "N of the 2,107" -- and a footnote states
plainly that 21 of the 2,107 are in no list, because a partner adding up rows
would find the gap anyway. Reported to the Driver; K=27 pending his ruling on
whether the two residual buckets count.

A SHARE MUST NEVER ROUND TO ZERO. One decimal gave the single-group fixture
"0.0%", which reads as "this packet covers none of them". Verified exhaustively
over all 2,108 possible list sizes against 2,107: no size prints 0 and none is
malformed. 1 group -> 0.05%, 20 -> 0.95%, 21 -> 1%, 43 -> 2%, 118 -> 5.6%.

NUMERALS STAY WESTERN, as required. num() is toLocaleString(lang), and CLDR's
default numbering system for the bare ar/hi tags is latn -- verified 12,379 and
2,107 in both. Reusing num() rather than writing a second formatter is also what
keeps the funnel's digits identical to every other page of the packet.

PAGE COUNT NOW COUNTS THE ARTIFACT. packet.mjs computed
`1 + ceil(n/PER_PAGE) + 2` under a comment claiming the number was "asked of the
renderer, not recomputed here". It WAS recomputed, and the funnel page silently
made it wrong by one -- exactly the drift the comment warned of. It now counts
the emitted sheets.

THE GATE IS TAUGHT, NOT LOOSENED. check-packet-render went red on every fixture
("43 cards should lay out as 6 sheets, but the packet has 7") -- correct
behaviour on a structure it had not been told about. The wrong fix is to accept
N or N+1: that assertion exists to catch silently dropped people groups, and a
±1 tolerance is the slack a lost list page hides in. So the funnel is DETECTED
via .funnel-title and added as a known term, and a funnel page that renders its
title but no share figure is now its own failure -- a number printed but not
asserted on is decoration.

The page is OPTIONAL: buildPacket omits it when no funnel data was supplied, so
every existing caller keeps working and a host without the data still gets a
correct packet. Better a 4-section packet than one asserting figures it could
not read. A multi-list batch selection keeps the steps but drops "1 of 27",
which is false for a custom selection, while "N of the 2,107" stays exactly true.

Verified: check:packet-strings-parity PASS (31 string keys x 11 locales, my 10
new ones present and translated in all 10 non-English blocks, 0 declared-absent
additions). A partial locale with 3 of 10 funnel keys translated renders those 3
translated and the other 7 in English, nothing blank. Arabic packet generated:
dir="rtl", funnel is sheet 2 of 7, all four steps carry Western digits. All 16
generated packets satisfy the corrected sheet arithmetic, including the 11 older
laos packets that have no funnel page. Mutation-tested: dropping a list sheet is
caught by the independent page-header witness (20 DROPPED), a funnel page with no
share figure is caught by the new assertion, and removing the funnel page passes.
BUNDLE=my-upg-100-list vite build 319 modules clean.

NOT verified in a browser: check:packet-render launches Chromium, which the
Playwright policy reserves to rega. The simulations above parse the same
attributes from the same generated HTML.
2026-09-26 04:38:59 +00:00
Doxa Maps
22b92ae043 template-bundle: the starter's first screen is now a real map
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).
2026-09-26 04:38:12 +00:00
Doxa Maps
4717193961 Flower Mesh opens on an SVG lens: the leaf-engine gate stops expecting a canvas there
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.
2026-09-26 04:21:49 +00:00
Doxa Maps
2a26aefff1 A gate for the mistake I shipped: a name used but never bound
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.
2026-09-26 04:21:49 +00:00
Doxa Maps
35a4f58fc2 "Show on map" switched the view and the map never acted: retain the request
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.
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
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
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
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
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
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
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
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
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
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
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
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
Doxa Maps
d535a7d97d Merge main into bundler-2/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:48:50 +00:00
Doxa Maps
084e3ec5b7 Revert the popup number format to a fixed en-US: grouping is part of the held decision
NUMERALS ARE HELD covers the whole formatted number, not just the digit system.
-u-nu-latn keeps digits Latin but lets the SEPARATORS follow the locale, so
de reads 1.234.567 and ru 1 234 567 -- and choosing that is the held decision.
I read "numerals" as "digits", shipped it at d988c6f1, and then wrote a check
asserting that grouping SHOULD vary, which passed green while certifying the
breach. A check encoding the wrong intent turns a policy breach into evidence of
compliance, which is worse than having no check. It now asserts one format across
all eleven locales, with a negative control that the reverted form produced five.

Reverted on the shepherd's instruction; gospel shipped the same change and was
asked to revert its number part too, so no single map settles this.

The try/catch goes with it rather than being kept "just in case": it existed
because a locale-derived tag can throw RangeError, and a string literal cannot.

EVIDENCE FOR THE RULING, read from their master, because it cuts both ways:
  RegionDetail.vue:16   n.toLocaleString(`${locale.value}-u-nu-latn`)
                        their own comment: "Locale grouping separators with Latin
                        digits, matching the rest of the site."
  CountryMap.vue:128    toLocaleString(languageCode)   <- the popup, no subtag
So per-locale grouping is THEIR deliberate site-wide convention, and this
element's two other formatters clone that line verbatim. Those are left alone and
flagged, not reverted: changing them would override DX-life's documented choice
rather than stop a map from freelancing, and that is a bigger call than mine.
The popup is the one place theirs follows the locale for digits as well, so it is
also the one place the hold's own rationale actually bites.

And the rationale bites via the REGION, not the language: ar resolves
numberingSystem=latn (1,234,567) while ar-EG resolves arab. A host setting
profile-config.locale to 'ar-EG' is the real exposure, and a fixed en-US is immune.
2026-09-26 01:47:39 +00:00
Doxa Maps
1016bb4740 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:40:23 +00:00
Doxa Maps
9eb2557b35 Gospel research: Bible for Children checked, measured, and left out
A pool nobody here had tried, and the reason it stays out is worth writing down
so the next tick does not spend itself on the same site.

It is reachable and it 404s properly, so unlike the shells that defeated deaf
sources, its answers can be trusted. Its index publishes 118 languages with no
pagination and no stated total.

Out on coverage first: of the 294 languages in the Doxa gap, exactly ONE name
matches a folder there. 82 of the 118 match a language this index carries
somewhere, so the catalogue is real and does overlap us — just not the part with
nothing, which is the only part a new pool is worth adding for.

Out on the join key second: no ISO code is published anywhere, in the index or on
a story page. The folder IS the language name, so joining would mean matching by
name, the guess this build removed when Doxa's own API made it unnecessary, and
the guess that would have handed Akha in Laos one of three sub-varieties. The
single gap match above is a name match and worth nothing on its own.

Out on doctrine third, and that word is chosen carefully: no statement of faith
is served at any of the usual paths. This is unverified, not suspected. The brief
says leave a source out when its doctrine is unclear and say why, so that is what
this is, and it is not a finding against them.

No resource, link or count changes; the index differs only by its generated date.
2026-09-26 01:39:51 +00:00
Doxa Maps
d988c6f1ad 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:37:56 +00:00
Doxa Maps
1e9064e4c6 Merge main into bundler-2/work 2026-09-26 01:37:54 +00:00
Doxa Maps
17d50c8275 The map popup's population could print Arabic-Indic digits, against the numerals hold
Numerals are HELD pending the Driver's call: text translates, digits stay Latin.
Two of this element's three number formatters already carried -u-nu-latn; the map
popup's population was the odd one out, and it is the one a reader of an Arabic map
would actually have seen.

THE REASON IS NARROWER THAN I FIRST WROTE, and I measured it after asserting it.
Bare 'ar' was never the problem: on full-ICU Node it resolves numberingSystem=latn
and prints 1,234,567. It is the REGIONED tag that switches systems --

    ar     -> latn -> 1,234,567
    ar-EG  -> arab -> ١٬٢٣٤٬٥٦٧

so the codes this element ships (SUPPORTED_LOCALES, and localeFromUrl() which strips
the region at .split('-')[0]) were already compliant. The exposure is a tag arriving
from OUTSIDE: a host setting profile-config.locale to 'ar-EG' reaches that line with
its region intact. The subtag makes the hold hold whatever arrives, rather than
depending on every future caller to strip a region first.

-u-nu-latn is also closer to their output than the hardcoded 'en-US' it replaced,
which is the point worth keeping: it holds the DIGITS while letting the grouping
SEPARATORS follow the locale, so de still reads 1.234.567 and ru 1 234 567. Their
formatPopulation follows the locale outright; when the hold lifts, dropping the
subtag in all three restores exact parity.

Also added the try/catch the two sibling formatters have and this one lacked. A
malformed tag throws RangeError, and this runs while building popup HTML, so an
uncaught throw takes out the whole popup instead of one number.

Verified: 11/11 locales print Latin digits; 5 distinct grouping formats survive, so
it is not flattened to en-US; and the negative control uses ar-EG, an actual
violation, because a control built on bare 'ar' passed for the wrong reason and
proved nothing.
2026-09-26 01:37:49 +00:00
Doxa Maps
003fd1c92b Merge main into bundler-2/work 2026-09-26 01:33:36 +00:00
Doxa Maps
72fb327951 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:30:10 +00:00
Doxa Maps
2eac0fb3c1 Gospel leaves: numbers follow the reader's locale, not the browser's
toLocaleString() with no argument formats for the JS runtime's locale, which has
nothing to do with the locale the plugin was asked to render in. The Arabic build
on an English browser got English grouping and the same page on a French browser
got French grouping: one plugin, one locale, two different numbers. All four
leaves now format with the locale i18n actually resolved.

numberingSystem is pinned to latn, and the obvious justification for that is
wrong, so here is the measured one. Against full ICU, the eleven tags this bundle
ships all resolve to latn already — plain "ar" included — so for them the pin
changes nothing. It earns its place only if a host passes a region tag: ar-EG
resolves to arab and fa to arabext, and those would switch the digits. Numeral
format is held for the Driver, so the pin keeps that his decision rather than a
host's.

What does change is grouping, and it is visible: Hindi groups in lakhs, so
1234567 reads 12,34,567, and French and Russian use a space. Those are correct
for the locale and differ from what shipped before.

The bare calls left in the built bundle are in library components this bundle
does not render — LegendProgressBar and PosterWall — and belong to whoever owns
library, not here.
2026-09-26 01:29:52 +00:00
Doxa Maps
0776443961 PPLR data maps: the Globe/Mercator toggle works on MapLibre too
Three views called map.setProjection(projection.value) with the bare name ('globe'|'mercator'),
unguarded, including inside style.load handlers. Mapbox takes the name; MapLibre 5 takes a
specification object ({ type }), so under provider=maplibre the toggle did nothing or threw
mid-restyle. setProjectionFor(map, name) passes the engine's shape and is guarded; all six call
sites use it. Found by the engine-asymmetric API sweep (universal-props matrix, new column).
Headless: a fake-map unit test (MapLibre gets {type}, Mapbox the string, a throw is swallowed,
null/empty no-op) 5/5; BUNDLE=pplr-data-maps builds. Plugin-local: pplrData.js has no other consumer.
needs eyes: PPLR Religion view with ?provider=maplibre: the 🌐/🗺 button switches globe and mercator.
2026-09-26 01:23:15 +00:00
Doxa Maps
6d7da1c0f7 plugin-designs/README: the tree is dx-maps, and a bundle's tag is its folder name
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
Four path lines said public/doxa-maps/ (the folder was renamed in 09014f8a), and the embed
example used a <doxa-map> tag, but a bundle registers the tag named after its folder
(template-bundle/index.js), so the example is <your-map>.
2026-09-26 01:19:53 +00:00
Doxa Maps
504be20040 Doxa Vision Card: take the page language from profile-config lang as well as locale
Composites forward the page language as `lang` (dashboard-plus-grid, research-map-plus-grid,
the mfes). The card read only `locale`, so nested in one it fell back to English. Now locale,
then lang, then ?lang, then <html lang>, per the universal-props contract. Headless: the bundle
builds. needs eyes: a vision card inside a composite with lang=ar renders in Arabic, right-to-left.
2026-09-26 01:10:51 +00:00
Doxa Maps
2a548d2bae Merge main into bundler-2/work 2026-09-26 01:02:09 +00:00
Doxa Maps
6104b10fc0 Gospel map: read the Mapbox token through the library's channel
resources-map read profileConfig.tk || window.DOXA_TOKEN. DOXA_TOKEN is set by a
few dev pages and nothing else, so in the studio and on every built page this map
ignored the page's token entirely. It now takes profile-config tk, then
ProfileLoader's injected mapboxToken, then window.MAP_TOKEN, as the
universal-props contract says.

The pattern is copied from the same fix on cities-internet and key-cities
(1cf57f7a) rather than re-invented. Three raw maps each deciding for themselves
how to find a token is how all three came to be wrong in the same way.

Headless: the built bundle no longer contains DOXA_TOKEN at all, and carries the
injected token and MAP_TOKEN instead.
2026-09-26 00:58:02 +00:00