Commit graph

908 commits

Author SHA1 Message Date
doxa-maps-88655c70
460ccb564b Merge main into bundler-2/work
Some checks are pending
build-gate / build (push) Waiting to run
# Conflicts:
#	public/doxa-maps/index.html
#	public/doxa-maps/manifest.json
#	public/doxa-maps/regions-dashboard/region-dashboards/index.html
#	public/doxa-maps/regions-dashboard/regions-dashboard.js
#	public/doxa-maps/regions-dashboard/regions-list/index.html
#	public/doxa-maps/regions-dashboard/regions-mfe/index.html
2026-09-24 20:26:59 +00:00
doxa-maps-88655c70
c276a2158e Gate the embed contract: a composite must pass tk and provider down
Four elements shipped with this broken and nothing could see it, because every
existing gate loads a composite on its OWN generated leaf — and that leaf bakes a
token and assigns window.MAP_TOKEN, supplying the very thing under test. The
failure exists only where there is no leaf: the partner page the published embed
snippet is written for.

The gate serves the built tree on one free port and a partner page on another, so
CORS is exercised rather than assumed, and asserts per composite:

  tk        given only {"tk": <the token this tree bakes>}, a canvas appears and
            api.mapbox.com actually serves it
  provider  given only {"provider":"maplibre"} and NO token, a canvas appears and
            there are ZERO requests to api.mapbox.com

Zero is the assertion, not "it rendered": a build that quietly fell back to
mapbox with a baked key renders perfectly and is exactly the regression this
exists to catch.

★ IT FAILS ONLY WHERE THE EVIDENCE POINTS AT THE KNOB. The first draft failed
NINE composites for "no map canvas under a minimal config" — an accusation it had
not measured, since several of those elements simply need a `profile` key to
choose which profile to show. It now distinguishes: a token WAS supplied and the
element still says there is none, or maplibre was asked for and mapbox tiles
still loaded — those are findings. No canvas and no complaint is UNMEASURED, and
says so with the reason. A gate that red-lights other people's plugins over a
claim it cannot support is worse than no gate.

COST, because a slow gate stops being run: a flat 14s-per-case wait became a poll
for the canvas, and the provider case short-circuits when that element's tk case
drew no map — there is nothing to compare against, so a second page load was pure
waste. 5m38 -> 3m15.

GREEN on the landed tree: dashboard-plus-grid, research-map-plus-grid, stack-demo
and plugin-studio all pass both knobs, every provider case at ZERO api.mapbox.com
requests. plugin-studio only reached zero once the cap-5 shell started setting
window.MAP_PROVIDER — profile-config alone reaches the child a composite writes
config for, while the global rung reaches a map nested at any depth, which the
studio-inside-SPA-inside-composite chain needed.

gates:post is 44 steps; the quoted CLI doc is updated to match.
2026-09-24 20:26:33 +00:00
doxa-maps-88655c70
728d3cbaef Merge branch 'main' of https://git.nuxtbase.app/doxa-life/doxa-maps into bundler-1/work
Some checks are pending
build-gate / build (push) Waiting to run
# Conflicts:
#	public/doxa-maps/index.html
2026-09-24 20:12:15 +00:00
doxa-maps-88655c70
29eaf3a62a Port their pin popup, and close the last English out of the regions clone
THE POPUP WAS A STUB. Click a pin on their region page and you get a card: the
people group's photo, the name, "country · primary ethnolinguistic group", a
definition list of Population / Religion / Intercessors-out-of-100, up to three
clamped lines of location description, and two buttons — Pray, which is the
point of the entire site, and Full Profile. Click one on ours and you got four
lines of hardcoded English with en-US number grouping in all eleven locales.

No diff here could ever have found it. Every vs-live probe reads the page at
rest, and a popup only exists after a click. So there is a new one, and the
popup now matches theirs on 37 of 37 measured properties with the same stat
labels, the same two actions, and their ?source=doxalife attribution on the
Pray link — which is invisible on screen and exactly what a re-implementation
drops silently.

Noticing it was not enough to fix it: the DATA was not on the page. Their
PIN_FIELDS asks for rop1, religion, location_description, image_url and
has_photo; ours asked for none of them, so a faithful popup would have rendered
empty rows. Five more columns on the same single request. Their row mapping
comes with it, including the has_photo gate on the image — a row can carry a
placeholder image_url and they only show it when the flag says there is a real
photo — as does their fuller escape, which also escapes both quote characters,
and that matters because these strings are interpolated into href and alt.

AND THAT FILE HAD NO t() AT ALL. Its prayer legend rendered four hardcoded
English strings on a page whose every other word is translated into eleven
languages — and three of those four were already in the catalogue, translated
for the Prayer Coverage card and hardcoded here anyway.

LOCALISATION, re-measured honestly. The old 97.4% was counting strings
identical to English as untranslated, which is wrong: es/fr/pt/ro share
"Total", and French shares "Population" and "Religion". Those are cognates. The
real question is coverage of their surfaces, so: every t() argument in
RegionDetail.vue, pages/regions/index.vue, CountryDetail.vue and CountryMap.vue
— 35 strings — checked against our catalogue. Two were missing, both
CountryMap's: "Outside this region" and "Pray". Added in all eleven, the eight
DX-life translates copied from their own common.json and it/ro/zh composed from
words already attested in those locales' own files rather than invented. Then
the reverse direction: a scan for capitalised text nodes outside {{ }} found
two of our own strings left in English — the dashboard's loading state and the
list's empty state, neither of which DX-life has an equivalent for because
their page is server-rendered. Both translated. The scan now returns zero in
all three components, and coverage of their surfaces is 35/35.

Verified alongside: all seven regions at 107 properties identical to live each
(not just Africa), mobile parity still 44/0 at 390 and 768 after the CSS
surgery, and first paint of the list measured at 3766ms with 1863KB of it a map
engine that leaf never calls — carded to the build machine, not worked around.
2026-09-24 20:11:44 +00:00
doxa-maps-88655c70
6fc39f525a Merge main into bundler-2/work
# Conflicts:
#	public/doxa-maps/index.html
2026-09-24 20:10:09 +00:00
doxa-maps-88655c70
15b9c94399 Phone: the pin hint stops sitting on top of the legend sheet
MEASURED at 390x844 on doxa-research-map: the hint pill sat at y=700 and the open
legend sheet begins at y=603, so "Tap a coloured dot — someone still needs
prayer" printed straight across the Progress rows. 1,418 Needs Prayer and the
Full Prayer Coverage line were unreadable underneath it, in the phone screenshot
the README showcases.

Nothing was clipped, which is why the existing below-the-fold clamp had nothing
to say: the pill was fully visible and fully in the way. So the sheet becomes a
FLOOR, exactly as the attribution strip already is — this component has always
treated the credit line as something to clear rather than cover, and the sheet is
the same kind of obstacle. If the pill would cross the sheet's top edge, it lifts
by the overlap; only when the two really intersect horizontally, and only ever
upward, so a surface with no sheet is unchanged.

TWO THINGS THE FIRST ATTEMPT GOT WRONG, both found by re-measuring rather than by
reading the diff:

  · The sheet does not exist when the pill mounts — LegendMobile renders after the
    map's first canvas. A mount-time querySelector found nothing, so no observer
    was bound and the clamp never ran again: the pill moved 0px and the "fix" was
    a no-op that looked plausible. The observer is now bound lazily by the first
    clamp that SEES a sheet, with three settle re-measures after mount to make
    sure one of them does.
  · The sheet also changes height as the reader drags or collapses it, and each
    height is a different floor. The existing host observer watches the MAP, which
    a sheet drag does not resize, so the sheet needed its own.

MEASURED AFTER — 390, 360 and 414px wide: no overlap between the pill and any
painting part of the sheet; screenshot read at 390, where Progress now shows 689
/ 136.7M, 1,418 / 69.6M and Full Prayer Coverage in full. Desktop 1280x900
unchanged: pill still at the map's bottom edge, no lift applied. The two other
hint consumers (country-posters-mfe, regions-mfe) show no overlap either.

One probe note worth keeping: .legend-sheet-root spans the whole map with
pointer-events:none and paints nothing, so an overlap check that counts it reports
a collision no reader can see. The obstacle is the sheet, not the box it lives in.
2026-09-24 20:09:51 +00:00
doxa-maps-88655c70
fd95518152 Merge remote-tracking branch 'origin/main' into rega/research-gallery-embed
Some checks are pending
build-gate / build (push) Waiting to run
# Conflicts:
#	public/doxa-maps/index.html
2026-09-24 20:00:50 +00:00
doxa-maps-88655c70
706ec3fab8 Merge main into bundler-2/work
# Conflicts:
#	public/doxa-maps/index.html
2026-09-24 19:59:57 +00:00
doxa-maps-88655c70
b1106ee191 Merge remote-tracking branch 'origin/main'
Some checks are pending
build-gate / build (push) Waiting to run
2026-09-24 19:58:34 +00:00
dx-shepherd
0eb3390375 Regenerate capability-exemplars table (stale after legacy bundle; unblocks check:capability-exemplars) 2026-09-24 19:58:17 +00:00
doxa-maps-88655c70
8ade2f3062 Merge branch 'main' of https://git.nuxtbase.app/doxa-life/doxa-maps into bundler-1/work
Some checks are pending
build-gate / build (push) Waiting to run
# Conflicts:
#	public/doxa-maps/index.html
2026-09-24 19:58:09 +00:00
doxa-maps-88655c70
dd2443b7de Merge remote-tracking branch 'origin/main' into rega/research-gallery-embed
# Conflicts:
#	public/doxa-maps/index.html
2026-09-24 19:57:53 +00:00
doxa-maps-88655c70
3a4f065ec5 Research Gallery: a host that asks for maplibre gets maplibre in every tab
The shell forwarded tk, tokenUrl, locale and treeBase to each nested map,
but not the engine. So <doxa-maps-spa provider="maplibre"> still loaded
mapbox-gl and Mapbox styles. On a keyless host, that is the embed failing.

The shell now forwards `provider` in each child's profile-config, validated
to the two known engines. That fixed the simple map tabs, but not the
composites: they mount their inner maps without passing the provider on.
Measured with one tab per clean page: Big Picture made 19 api.mapbox.com
requests, Regions 17, UPG-100 15, Simple Research 14. So when a host asks
for an engine, the shell also sets window.MAP_PROVIDER (only if the page
hasn't). That is rung 3 of every map's own ladder, so it reaches maps
nested at any depth. After it, all four composites make 0 Mapbox requests.

check:gallery-embed:maplibre runs the cross-origin gate with a keyless
provider and fails on any api.mapbox.com request, attributed to the tab
that was open. Before this change, js mode failed with 20 URLs. It has one
named, printed exemption: upg-100-list.vue pins a token-free mapbox-gl.css.
That belongs to the upg100 plugin and is routed there.
2026-09-24 19:57:23 +00:00
doxa-maps-88655c70
c57b51044f Merge branch 'main' of https://git.nuxtbase.app/doxa-life/doxa-maps into bundler-1/work
# Conflicts:
#	public/doxa-maps/index.html
2026-09-24 19:56:38 +00:00
doxa-maps-88655c70
a5a3c6b9d1 Merge branch 'main' of https://git.nuxtbase.app/doxa-life/doxa-maps into bundler-3/work
Some checks are pending
build-gate / build (push) Waiting to run
# Conflicts:
#	public/doxa-maps/index.html
2026-09-24 19:56:32 +00:00
doxa-maps-88655c70
d1fc658de4 README: screenshots first — one per capability, three deployments shown live, the studio last
Some checks are pending
build-gate / build (push) Waiting to run
- The five capabilities each lead with a current screenshot; the Research
  Gallery's composition tree folds into a <details> block.
- Deployment: (1) the prayer map in a host page's iframe, (2) the Simple
  Research dashboard as the phone app, (3) the whole Research Gallery
  JS-embedded on dx-life's research page. WordPress/Disciple Tools and Tauri
  are stated as proven proofs of concept, not production-ready.
- The Plugin Studio closes the README with its current screenshot, the
  four-command start and the command table.
- 4,151 -> 1,330 words. Every image retaken against the current build.
- readme-shots: deploy2 shoots research-map-plus-grid at phone size; deploy3
  shoots a running dx-life (DXLIFE_URL=...), skipped when it is not set;
  an http(s) path is used as-is. Retired four images nothing shows any more.
2026-09-24 19:55:24 +00:00
doxa-maps-88655c70
65e959a08f Say which pools were really searched for THIS language
The sheet's "checked" line is what tells a reader whether "nothing
found" means anything, and it was wrong in both directions. 5,131
codes omitted Scripture Earth, which had in fact been swept for every
one of them. And 110 codes named GRN as checked when the sweep has
never looked at them — GRN is crawled a batch at a time at the delay
their robots.txt asks for, so 2,100 codes are still unread.

The cause was ordering, the same lesson this file already carries for
the dead-link drop: the pass that normalises provenance ran before the
Scripture Earth and GRN fetchers, so the codes those fetchers create
kept the raw constant. A tidying pass that runs before the thing it
tidies reports a confident answer about a room still filling up. It
now runs after every source attaches, and the count is exact — 282
codes name GRN, 282 have been decided, and none that was decided goes
unnamed.

Scripture Earth is claimed unconditionally because that sweep really
did cover every code this map uses: a code absent from their index is
a checked negative. GRN is claimed per code, and the rest carry
"grn (not yet swept)" in the unchecked list instead.

Clause M of the coverage gate now fails if any code names a pool the
sweep never decided for it. Poison is 13/13.
2026-09-24 19:55:12 +00:00
doxa-maps-88655c70
d5effe7c66 Copy their list bullet instead of my own, and stop shipping rounded tokens
Two findings on the regions LIST tab, both from extending a diff that had been
reporting a clean pass.

THE BULLET WAS MINE, NOT THEIRS. RULE 1 says copy, don't recreate, and I had
recreated their list marker as a 6px border-radius:50% dot. base/_globals.scss
:117-131 paints every list marker on their site with a 16x16 data-URI SVG
circle in #92b195 sized to 1.5em, hung off the text's starting edge by
margin-inline-start:-1.3em (-2em in RTL), top .3em, line-height .9 — and their
.countries-grid carries a comment saying keeping it is deliberate. Against live
mine was wrong in 9 of 11 properties: 6px against 29.2969px, a solid fill
against a transparent box carrying the image, top 11.72px against 5.86px,
positioned by inset-inline-start:-16px instead of their negative margin. Two
matched. Not a near-miss — a different bullet. Theirs is now copied, and
.countries-grid's physical padding-left:35.2px is replaced by their logical
padding-inline-start:1.8em, because the physical one put the indent on the
wrong side in Arabic, where the bullets hang off the right edge.

THE TOKENS WERE ROUNDED, AND EM MULTIPLIES THE ERROR. With their bullet ported
it still measured 29.2812px against their 29.2969px. 0.0157px is nothing to
look at, but it is the rounding of --font-size-md (19.5312 for their 19.53125)
times 1.5, and every em- and percentage-derived length on the page inherits the
same error. dxlife-tokens.js said re-deriving to live's precision would be "a
separate, separately measured step"; this is that step. Both scales are exact
powers of their two ratios on a 10px root — type 1.25, spacing 1.33 — so full
precision costs nothing. The bullet now matches on all 11 properties exactly,
and the region card on all 32.

The diff could not have found either before. getComputedStyle(el) does not see
::before and no spec named one, so a pseudo-element that was wrong in nine ways
sat next to "32 of 32 identical". The probe now reads the bullet as a pair on
both sides — and compares its backgroundImage after normalisation, because
their live CSS serialises the SVG with xmlns first while the checked-out SCSS
writes it last: their deployed stylesheet is not byte-for-byte the source, so a
raw compare would report a permanent difference for an identical bullet while
blanket-accepting it would hide a real change of image. The comparison
percent-decodes, drops the charset Chrome adds and sorts the attribute tokens —
sensitive to the circle, its radius and its colour, blind only to their order.

Also here: the maps render the globe projection, measured rather than argued —
projection=globe on both the chooser and a region page, via a constructor trap
installed before any page script. And the probe's own two defects fixed: it
asserted "our style JSON does not override the projection" and failed, which
was my guess about where the value comes from rather than a requirement
(Mapbox Standard declares globe itself, which is why the default is globe); and
it read the map list from the main frame only. The remaining honest gap is
named in the file and on the card: their CountryMap imports mapbox-gl as a
bundled ESM module, so no global exists to trap and the ours-vs-theirs half is
unproven until a pixel oracle with a forced-mercator negative control exists.
2026-09-24 19:55:03 +00:00
doxa-maps-88655c70
b026c3ef83 poster cards: the print row belongs below the preview, and a fieldset ignores width
Some checks are pending
build-gate / build (push) Waiting to run
The Driver caught my own fix breaking the poster section, in the same session I
landed it. flex-wrap wrapped the print row onto three lines; the row is anchored
at the card BOTTOM, so growing it 35px to 78px pushed its TOP up from 216px to
173px in a 238px card — straight over the preview image and the outline. I
traded a horizontal overflow for a vertical one.

My gate measured only left and right edges, so it reported PASS on a visibly
broken card. Same instruments-lie family the flock banked today: a green that
measures the wrong thing.

The real cause of the ORIGINAL overflow, which both the old CSS and my first fix
missed: a <fieldset> gets min-width:min-content from the UA stylesheet, and that
OVERRIDES width and max-width. The rule said width:82% while the element
computed 431px inside a 212px card. That is why "it has a width, looks fine"
survived two rounds of this bug — the declaration was there and inert.

Fixed with min-width:0 on the fieldset itself, flex-wrap:nowrap, and flex:1 1 0
on the stock <select>, which sizes to its widest option and was taking 188px of
a 198px row.

Measured through all three states: 431x35 at top 216 overflowing, then 78px tall
at top 173 covering the preview, now 172x35 at top 216 — one line, below the
184x184 preview, inside the 212px card. Clean at 390/768/1024/1440/1920px with
all 108 controls full size.

The gate now measures the vertical axis and sweeps five widths. Mutation-tested
against my own regression: restoring flex-wrap fails with "27 print rows COVER
the preview image (37px)" at four of the five widths. 390px stays clean, which
is exactly why single-width testing would have missed it a second time.
2026-09-24 19:54:43 +00:00
doxa-maps-88655c70
e888a2e5ba Show every resource a language has in the list, not two of them
The list built its links from c.s and c.d, then appended film, audio
and Bible video by hand. Scripture Earth, GRN, gospel presentations
and Zume were never added, so 190 languages rendered with no link at
all while the map offered a working one for the same language — Naba
in Chad, 753,000 people, had a Words of Life recording and the list
showed nothing.

Both surfaces now derive their rows from one helper in this plugin's
own folder. It is private on purpose: nothing outside gospel-resources
has a codes[] entry, so publishing it to library/ would advertise a
signature only one bundle can call.

Two things this nearly got wrong. The link kind was hardcoded to
"scripture" for the first rows, which was harmless while the list held
only text Scripture and became a doctrinal error the moment it held
gospel audio — Words of Life is gospel messages with songs, and
styling it as Scripture tells a reader they have a translation they do
not have. The kind now comes from the row's own type. And the eBible
translation count on the page reduced over the same array, so widening
it would have overstated how many translations exist under a sentence
that names eBible.org; it counts text Scripture rows only.

The gap pill is untouched and still reads "no text" for Naba, because
that remains true.
2026-09-24 19:49:00 +00:00
doxa-maps-88655c70
f22609fb3c Clone the six global rules the component's own stylesheet never showed
The Driver looked at the two sides and named the components: "the tbabel
compoent is not the same", and of Prayer Coverage / Adoption Progress /
Engagement Progress, "thes three are not th esame css of the dx lfie site, it
properl spces and lcolros thigns while ours doe snot". He was right about both,
and for one reason: I ported RegionDetail.vue and its <style scoped>, and the
rules that differ are not in either. They are in the global sheet every page on
their site loads.

  base/_globals.scss:99-105  every <table> is a 25px-rounded, #666-ringed WHITE
                             card; :205-207 gives every <td> a 1px #666
                             gridline. Ours was bare and borderless.
  composition/_stack.scss    .stack spaces children with margin-top, not gap —
                             and .region-goal{margin-top:auto} OVERRIDES it.
                             Ported as gap, which no margin can override, so
                             both applied and every goal card carried an extra
                             23.526px. That is his "spacing is not the same".
  base/_globals.scss:19-23   every <section> is padded
                             clamp(--spacing-2xl,6vw,--spacing-4xl) top and
                             --spacing-4xl bottom: 73.614px on each side of the
                             countries table, where we had none.
  base/_globals.scss:132-141 every <a> is --color-brand-light at weight 500
                             with a 2px/4px underline. Their country cell is a
                             NuxtLink; ours is a <button>, which inherits none
                             of it and read as plain body text.
  base/_globals.scss:200-202 <strong> is 500, not the UA's bold. The "Goal:"
                             label wears it three times.

All five ported, and the remaining .stack ports converted from gap to their
margin-top so the next margin-overriding child behaves as it does on their
page. Measured against live doxa.life/regions/africa: 107 properties
identical, 0 CSS differences, 0 structural-text differences.

The measurement gap matters more than the fix. _probe-region-vs-live.mjs
reported 79/79 IDENTICAL through every one of these and could not have failed:
goalCard's spec was padding/radius/background/colour/align with no gap, so the
property that spaces the children was never read; table's was minWidth and
width; td had no border; and the country link had no entry in the selector map
at all. The spec now also reads gap and flexDirection on the card, marginTop on
its children, the td border, the table's radius/background/box-shadow/overflow,
section padding, strong weight, rowGap on all five stacks, and a new tdLink
pair — 107 properties.

Also, the Driver's other report on that screen: "poster not generated yet ...
it is not supposed to say not generated yet it is supposed to show the
previews". Ethiopia's country-eth.webp exists — 174 of the 175 records have
theirs, India being the one that legitimately does not and carrying its own
reason. Measured on the built tree and on the dev server: every preview request
200, zero cards in that branch, and the lazy contract holds (nothing fetched
until the dropdown is opened). So the file is not missing; a host layout is.
PosterWall now tries the self-contained copy generate-tree stages inside the
tree before giving up, and when it does give up it no longer claims the poster
was never generated — it says the preview is unavailable, which is the only
thing a failed image request establishes.
2026-09-24 19:46:36 +00:00
doxa-maps-88655c70
3921c605d6 Merge main into bundler-2/work
# Conflicts:
#	package.json
#	public/doxa-maps/index.html
2026-09-24 19:42:35 +00:00
doxa-maps-88655c70
4727005aba Studio and stack-demo: forward the host's engine and token too
Extending the composite embed fix to the two remaining composites that a partner
can mount, plus two red gates that arrived on main.

plugin-studio already threaded tk, tokenUrl, locale and treeBase into every pane
it mounts — but not `provider`, so a studio embedded with provider=maplibre
showed mapbox panes and, on a keyless host, panes asking for a token. It now
forwards the engine the same way, validated to the two known values, and accepts
it as an attribute as well (a host showing plugins to partners wants to flip both
at once).

stack-demo declared no props at all, the same hole the two shipped composites
had: <stack-demo profile-config='{"tk":"…"}'> had its token read by nobody, and
on a page with no baked token the nested dashboard reported "No Mapbox access
token provided". It now parses the host config, publishes the token page-wide and
threads tk + provider into the child it mounts by tag.

MEASURED on a foreign origin, minimal config: stack-demo [tk] canvas 1 with 13
api.mapbox.com requests; stack-demo [provider] canvas 1 with ZERO. plugin-studio
[tk] canvas 1 with 19.

★ ONE LEVEL DEEPER, NOT MINE, MEASURED AND REPORTED: plugin-studio still shows
mapbox tiles under provider=maplibre, and the pane chain says why — the studio
hands provider=maplibre to <doxa-maps-spa> correctly, and the SPA then mounts
<dashboard-plus-grid> with a config carrying tk and NO provider. The cap-5 shell
drops it. Same three lines fix it there; routed to its owner rather than reached
into.

Also fixed two gates that were red on main and blocking every builder:
  check:script-flags       check:gallery-embed advertised --mode/--only in an
                           inline comment its command ends with, so the flags
                           could never arrive and the gate would run its default
                           and exit 0 looking like success. Gave the modes their
                           own entries and stopped the parent advertising what it
                           cannot receive.
  check:capability-exemplars  the generated table had drifted from the manifest
                           (a legacy bundle arrived); regenerated, 35 rows match.
2026-09-24 19:39:59 +00:00
doxa-maps-88655c70
02b43b76d8 Sweep a second batch of GRN: 263 groups now reachable
170 more gap codes probed at the ten-second delay their robots.txt
asks for. 93 hold gospel audio — a 55% hit rate, higher than the first
batch because the sweep runs largest-population first and those are
the languages GRN has always concentrated on.

164 codes now carry a Words of Life link, and 263 people groups whose
ONLY resource in any pool is that recording — 66.7 million people —
can now hear the gospel in their own language. Tier 0 falls 2,746 to
2,610. Largest reached this round: Min Bei Chinese at 11.3 million,
Swiss German at 5.5 million, Huizhou Chinese at 5.4 million.

All new URLs verified before shipping; 350 of 2,456 gap codes decided
so far, and the fetcher resumes where it stopped.
2026-09-24 19:39:05 +00:00
doxa-maps-88655c70
2693c6ffa3 Plugin studio: one rail, highest capability to lowest, trees under their plugin
Some checks are pending
build-gate / build (push) Waiting to run
The separate "What each composite is made of" part is gone. It started
over at CAP 5 below the CAP 1 singles, so reading down the rail jumped
back up the ladder. Each composite's composition tree now sits directly
under that composite's own row, inside its capability section, so the
whole rail reads CAP 5 -> CAP 1 once: doxa-maps-spa with its six tabs and
what they are made of, then each CAP 4 composite with its parts, then
the multi-view, preset and single elements.

Tree rows are still aliases of each plugin's one canonical row, arrow
keys still visit each plugin once, and section counts count plugins, not
repeats. An unpublished part now sorts after every published child.
2026-09-24 19:31:40 +00:00
doxa-maps-88655c70
edb70fccd9 Merge remote-tracking branch 'origin/main' into rega/research-gallery-embed
Some checks are pending
build-gate / build (push) Waiting to run
2026-09-24 19:27:17 +00:00
doxa-maps-88655c70
6ecd845e05 Plugin studio: give the plugin the whole stage, and stop showing the grid twice
Some checks are pending
build-gate / build (push) Waiting to run
The "Composed of" panel above the preview is gone. It sat between the
title and the plugin and took the plugin's space; composition already
lives in the rail's "Inside <composite>" trees. The toolbar is back to
one line: name, element tag, Copy embed.

big-picture is no longer listed. It is a dark header of search plus four
filters over the people-groups grid, with the grid's own identical
controls switched off: in the studio it read as the grid a second time
under a misleading name. The real Big Picture is dashboard-plus-grid,
and the app shell already hides big-picture for the same reason. The
bundle is still built and embeddable. The studio's own composes list now
names dashboard-plus-grid instead of bp-big-picture.
2026-09-24 19:25:50 +00:00
doxa-maps-88655c70
53498c392b Merge remote-tracking branch 'origin/main' into rega/research-gallery-embed 2026-09-24 19:24:32 +00:00
doxa-maps-88655c70
67de5b11fd check:gallery-embed: let the mode flag reach the gate, and count tabs per mode
The package.json entry ended in an inline # comment that named --mode and
--only. So `bun run check:gallery-embed --mode iframe` put the flag inside
the comment, the shell dropped it, and the gate ran its default. That made
check:script-flags red on main.

The mode now has its own entries, check:gallery-embed:js and
check:gallery-embed:iframe, and the base comment names no flags.

The default run always covered both modes (13 tabs each, recorded in the
original run). Only a caller-supplied flag was lost. So nothing can pass as
"both modes" again without running them, the summary now prints the tab
count per mode, and a requested mode that drove zero tabs is a FAIL.
Negative-tested: a filter that matches no tab exits 1.
2026-09-24 19:24:26 +00:00
doxa-maps-88655c70
7ae1572c94 poster cards: the print-options row stays inside its own card
Some checks are pending
build-gate / build (push) Waiting to run
The repo-hygiene agent found this in a fresh 1440px screenshot, which is the
whole lesson: .pfs already had width:82%, so reading the CSS said it was fine.
Flex items default to min-width:auto and refuse to shrink below their content
width, so four controls — Prayer List, Card Deck, the stock select, Poster Map —
pushed the row open regardless of what the container declared. A constrained box
does not constrain flex children, and only geometry shows that.

Measured at 1440px on cap4-upg100-mfe: 27 of 27 cards overflowing, worst 110px
past a 212px card, 36 intrusions into a neighbouring card's box. After
flex-wrap:wrap plus min-width:0 on the children: 0 and 0, with all 108 print
controls still a usable size.

That second number is asserted deliberately. "Nothing overflows" is trivially
satisfied by a row whose buttons collapsed to zero width, which would be a worse
product than the overflow it replaced. check:poster-card-fits holds both, and is
mutation-tested: reverting the fix reproduces the reporter's exact figures.

Also adds check:backlog-refs — a card may not depend on a card that does not
exist, because a dangling depends_on can never be done, so the card can never
become ready and nothing says so.

Its first run disproved the finding that motivated it. I had censused three
dangling references, including the PRINCIPLE card the loop prompt lists as open
work. The gate came back PASS, and chasing the discrepancy rather than assuming
the gate was broken showed my census was wrong: it read only the builder
backlog, and all three cards exist in 01-ideation-research. Cross-phase
references are legitimate, and a single-backlog census structurally cannot see
them. Reporting three dangling refs would have sent someone hunting for cards
that were never lost.

It fails only for depends_on on an OPEN card; a dangling parent is reported and
passes, because failing on filing debt teaches people to delete the reference
rather than restore the card. Both severities mutation-tested.
2026-09-24 19:19:02 +00:00
doxa-maps-88655c70
f0c46022ee Merge branch 'main' of https://git.nuxtbase.app/doxa-life/doxa-maps into bundler-3/work
Some checks are pending
build-gate / build (push) Waiting to run
# Conflicts:
#	public/doxa-maps/index.html
2026-09-24 19:15:28 +00:00
doxa-maps-88655c70
a5ab04ec75 Measure the basemap, in a real embed, on the token the page ships
"The map renders" had never been measured. Every browser gate here
forces a Mapbox token onto the page from a shared file; that token is
restricted and 403s on the basemap, while the leaf bakes a different
one that works. The gates then counted PINS — which are drawn from our
own GeoJSON and paint with no token at all — so they passed on a
basemap the probe itself had broken.

This gate forces nothing. It serves the leaf on one port and a host
page on another so the iframe is genuinely cross-origin, loads it as a
stranger's browser would, and takes evidence two ways: Mapbox responses
counted by status, and colour variety in a slice of the canvas. The
gospel map passes — 9 tile responses, none denied, and the screenshot
shows coastlines, ocean shading and country labels.

The negative control runs every time, and the check reports PROVED
NOTHING rather than a pass if the control succeeds. That is not
decoration: the first two attempts at the control did nothing at all.
A page-level init script never reaches a cross-origin iframe, and even
at context level it was ignored, because this leaf injects `tk`
straight into the element and only falls back to window.MAP_TOKEN —
so it is immune to the forced-token hazard by construction. The
control had to move to the wire. With the token rewritten there, 6 of
8 tile requests are denied and the basemap vanishes, leaving only the
pins and choropleth: the picture that shows exactly why a pin count
was never evidence of a map.
2026-09-24 19:13:46 +00:00
doxa-maps-88655c70
e565c58ca0 Merge main into bundler-2/work
# Conflicts:
#	public/doxa-maps/index.html
2026-09-24 19:13:31 +00:00
doxa-maps-88655c70
b105fcebcf Merge main and regenerate the served tree
Some checks are pending
build-gate / build (push) Waiting to run
Generated index.html conflicted; resolved by regenerating rather than hand-merging.
2026-09-24 19:07:41 +00:00
doxa-maps-88655c70
f6e5466019 Restore their zoom and fullscreen controls, and stop overriding the projection
Two clone gaps the Driver caught by looking at the pages side by side.

Their CountryMap adds NavigationControl({showCompass:false}) and
FullscreenControl() at top-right. Ours added neither, and no probe could
have noticed: every vs-live diff compares computed CSS and text on selected
elements, so two whole controls were absent while the page still reported
79 of 79 properties identical. Ported with their exact options and corner,
each guarded so a MapLibre provider cannot take the map down with it.

The mercator override is deleted. I had forced it in the chooser because
Mapbox's globe below zoom 6 left a world view as a small sphere in a large
frame. The Driver has ruled the maps are a clone with no change beyond
colour and the scroll strips, the globe included, so the override goes
rather than gets argued — and deleting is the right shape here, since their
page sets no projection at all.

Also localises the last nine strings of our own two additions across all
eleven locales, composed from their attested vocabulary rather than invented:
their own translations of Regions, Select, View, Choose, and the dashboard
word from "See %region% dashboard". zh goes 93.9% to 97.4%; what remains is
Mapbox's own canvas chrome and one API code.
2026-09-24 19:06:59 +00:00
doxa-maps-88655c70
6ab560e42f Merge remote-tracking branch 'origin/main' into rega/research-gallery-embed
Some checks are pending
build-gate / build (push) Waiting to run
# Conflicts:
#	public/doxa-maps/index.html
2026-09-24 19:06:54 +00:00
doxa-maps-88655c70
7609cd36f0 Composites: pass the provider prop down, so keyless embeds are keyless
The token fix was half the contract. resolveMapProvider's ladder starts at
profile-config.provider, and a child reads the config its PARENT writes — so a
partner asking for

    <research-map-plus-grid profile-config='{"provider":"maplibre"}'>

got a child still on mapbox, which then demanded a token. MEASURED on a
third-party origin: "No Mapbox access token provided", 0 tiles, no canvas — for
the exact configuration whose entire purpose is needing no token. Keyless at
volume is why MapLibre support exists; a composite that drops the prop cancels
it, and the failure reads as a token problem rather than a plumbing one.

Both composites now thread `provider` beside `tk`, validated to the two known
values so a typo cannot silently select a third engine.

MEASURED AFTER, partner page, NO token anywhere in the config:
  research-map-plus-grid  canvas 1280x906, ZERO requests to api.mapbox.com,
                          basemap painted, 12,379 pins, legend, grid
  dashboard-plus-grid     canvas present, ZERO requests to api.mapbox.com
Screenshot read: the keyless map also shows the deferred search-bar rule holding
on a partner page — no search bar, and the legend risen into its place with no
gap.
2026-09-24 19:05:19 +00:00
doxa-maps-88655c70
3a2cf0e8f2 Research Gallery: embed on someone else's page, in both modes
The gallery is going onto the DX-life research page, and nothing had tried
it on a page it didn't generate. From a second origin, a bare
<script> + <doxa-maps-spa> drew over the host's header, and 8 of 13 tabs
showed an error card.

Three causes, each fixed in the shell:

- No :host box. The layout root is position:absolute, so it anchored to the
  viewport instead of the element. :host is now a positioned block with a
  default height the host can override.
- A foreign page sets none of the globals a leaf page bakes. Every data call
  hit the host's own origin and got HTML back. The manifest now publishes a
  `runtime` block (apiBaseUrl + the public token every leaf already carries).
  The shell fills window.MAP_APP_API_URL / window.MAP_TOKEN only when the page
  hasn't set them, and forwards tk in profile-config.
- Nested elements resolved assets to <tree>/../assets, a sibling pool the
  publisher doesn't ship (404 live). The shell now forwards an absolute
  <tree>/assets. This also fixes the iframe mode, where Cities with Internet
  and Gospel Resources were failing the same way.

Also adds a `tab` attribute (plus ?tab=) to deep-link into one map, and a
`tab-change` event for the host.

check:gallery-embed drives every tab in both modes from a second origin. It
passes here and fails on the previous tree (7 findings).

EMBED.md documents the contract. The Traefik template now sends CORS for the
whole tree: live manifest.json answers without Access-Control-Allow-Origin.
2026-09-24 19:03:37 +00:00
doxa-maps-88655c70
dc252bb304 Composites: give a partner's embed the token it passes in
A JS embed of either composite mounted a map that could not draw. MEASURED the
way a partner actually embeds — one origin serving the tree with CORS, a
SEPARATE origin serving the page:

  research-map-plus-grid   "No Mapbox access token provided", style 0, tiles 0, no canvas
  dashboard-plus-grid      the same
  (iframe embeds were fine throughout: style 2, tiles 9, 0 failures)

Both composites declared NO PROPS, so the host's profile-config — token included
— was read by nobody. Our own leaves hide this completely: a leaf carries a
BAKED token and assigns window.MAP_TOKEN, which the shared-token bridge hands to
the nested map. The failure therefore exists ONLY where there is no leaf, which
is precisely the partner page the embed snippet is written for. It is also the
snippet we publish: profile-config='{"tk":"YOUR_MAPBOX_TOKEN"}' is the documented
way to supply a key, and it did nothing.

Both now parse their host config, publish the token page-wide through the shared
composable, and hand it to their children. The two differ in how children are
mounted, so the last step differs:

  research-map-plus-grid composes by TAG + BUNDLE URL, so the token is threaded
  into the child element's profile-config, where its own ProfileLoader reads it.

  dashboard-plus-grid bakes its children in and bypasses ProfileLoader entirely
  — and ProfileLoader is the only provider of `mapboxToken` in the repo, so
  DashboardMapPanel's inject fell through to its empty default. It now provides
  `mapboxToken` itself, from the host's tk with the shared page token behind it.

Spread last and only when present, so a host that supplies none leaves the key
absent and the child asks for exactly what its standalone leaf asks for. A
malformed attribute is caught and ignored: the map half is more useful
token-less than the page is with a thrown setup().

This went in the PLUGINS, not the library. A shared change reaches every map —
that is the lesson SHOW_EXPORT_BUTTON taught this week, and nothing here needs
to be shared to work.

MEASURED AFTER, same harness: research-map-plus-grid canvas 1280x906, style 2,
tiles 9, 0 failures; dashboard-plus-grid canvas present, style 2, tiles 10, 0
failures — basemap, pins, legend, table and charts all drawn. Screenshots read.
Both LEAVES re-checked for regression: canvas 1, 0 failed requests, each still
painting its own map.
2026-09-24 19:00:24 +00:00
doxa-maps-88655c70
3a70abf248 Write down the shape of the main lock
Both obvious forms of the landing recipe are wrong, and both were run.
Ending with "; release" frees the lock even when capture FAILED, so a
non-owner drops the real owner's hold — that happened, to an agent who
was mid-landing. Joining it with "&& release" skips the release when
the PUSH fails, leaving the branch protected by a lock nobody will now
drop and everyone else wedged.

Release belongs to your capture, not to your push: capture && { scan &&
push ; release ; }. Capture failed and release never runs; capture
succeeded and release always runs.

Recorded with the two neighbouring rules that cost the same kind of
work to learn — hold the lock through fetch-merge-push rather than
around the push alone, and never rebase a branch carrying a -s ours
merge.
2026-09-24 18:58:12 +00:00
doxa-maps-88655c70
cfe46b31d5 Agent brief: capture --wait, so a held lock is retried instead of failing the landing
Some checks are pending
build-gate / build (push) Waiting to run
2026-09-24 18:56:42 +00:00
doxa-maps-88655c70
b089f9effb Reach Global Recordings Network, and lift 147 more groups
GRN records the gospel as AUDIO for languages with no writing, which is
most of what is left in the gap — and every earlier attempt recorded it
as unreachable. globalrecordings.net sits behind a proof-of-work bot
shield that challenges even robots.txt; that is an access control and
is not worked around. Their own delivery site, 5fish.org, is not
shielded, allows what this reads, and resolves by ISO 639-3 directly.

First 180 gap codes swept: 74 hold gospel audio, a 41% hit rate, and
147 people groups that had nothing now have the gospel in a form they
can receive. Tier 0 falls 2,873 to 2,746.

The host NEVER 404s. A nonsense path returns HTTP 200 and a plausible
geo-located page, so reading 200 as existence would have marked every
code on earth as covered. Existence is read from the page title, and a
negative and a positive control run in band on every run — the fetcher
aborts if a nonsense code ever reads as a real language. Their
robots.txt asks for a ten-second crawl delay, so the sweep is
sequential, capped per run and resumable. Do not add concurrency.

Three sign languages are deliberately left out. GRN's catalogue is
audio; 5fish holding a page titled "Pakistan Sign Language" says they
index the language, not that they have signed video. Attaching it told
20 Deaf groups — 11.5 million people — that gospel audio was available
to them. Unverified, and for that audience useless.

Six places across three surfaces still told the reader GRN answers 403
to everything and had never been searched. That is now false, and a
stale caveat misleads exactly as much as a stale number.

Also here, because adding a source is what exposed it: the charts
profile decided "this group has nothing" a second time, by listing the
resource fields by hand. That list went stale every time a source was
added — 7 languages once, 23 when Scripture Earth landed — so the chart
drew groups as having nothing while the map offered them a working
link. It now reads the tier the pin index already computed, and a new
gate refuses any surface that re-derives it. The gate was checked
against the real pre-fix file, not only a synthetic one.
2026-09-24 18:55:28 +00:00
doxa-maps-88655c70
0cdf079fc4 All people groups: count the DOXA groups again
Some checks are pending
build-gate / build (push) Waiting to run
The legend read "DOXA people groups 0". The prayer API source maps its
ROP3 (imb_reg_of_people_3) to peopleGroupCountry and maps no
peopleGroupId, which is the only field the DOXA set read, so the set was
empty and every group fell through to its GSEC colour. Read
peopleGroupCountry too: the legend now shows 2,622 DOXA rows, the count
the raw API and peoplegroups.org data predict, and the four categories
still sum to all 12,379 rows.
2026-09-24 18:52:33 +00:00
doxa-maps-88655c70
c469edadca Agent brief: release only a lock you took
Some checks are pending
build-gate / build (push) Waiting to run
The landing snippet I wrote ran `capture && scan && push ; release`. release
is not owner-checked, so when capture failed because another agent held the
lock, the ';' still ran release and freed THEIR lock mid-landing. That
happened today. Wrap the landing in `if main-capture capture; then ... fi`:
release still runs after a failed scan or push, but never after a failed
capture. Also warn against piping capture, which makes the test grade the
pipe's filter instead of the capture.
2026-09-24 18:50:25 +00:00
doxa-maps-88655c70
f9146d5105 Plugin studio: every plugin by capability, and a composition tree per composite
Some checks are pending
build-gate / build (push) Waiting to run
The rail now has two labelled parts. "Every plugin, by capability" keeps
one node per plugin in collapsible CAP 5 -> CAP 1 sections. "What each
composite is made of" adds one collapsible section per composite, headed
by the composite, with every element nested into it drawn as an indented
tree at full depth, ordered by capability at each level. A child repeats
in every composite that nests it, as specified.

Repeats are aliases, not new nodes: clicking people-groups-grid under
big-picture selects the same canonical entry as clicking it in CAP 2 and
highlights every copy, so each plugin still has exactly one name. Arrow
keys walk only the canonical rows. A declared child that is published
nowhere (big-picture's bp-research-header) shows as a "part" row, so the
header/grid split inside Big Picture can be named.

The dev server's manifest never carried `composes` edges, so under
`bun x vite` the Composed-of panel was empty for every composite. It now
publishes them from the same meta.json source as the built manifest.

Also: bare sibling profile refs (gospel-resources composes "data-tables")
now resolve, and rail rows use local SVG icons per capability instead of
emoji that rendered as empty boxes without an emoji font.

VerticalTabsLayout gains two optional row fields (`selects`, `depth`) and
shows badge/count on folder headings; existing consumers set neither.
2026-09-24 18:49:20 +00:00
doxa-maps-88655c70
6dbae65c91 Regenerate the served tree for the Research Gallery's new composes list
Some checks are pending
build-gate / build (push) Waiting to run
29dfef1 changed doxa-maps-spa.meta.json but not the manifest built from it.
2026-09-24 18:48:04 +00:00
doxa-maps-88655c70
42c2279ee3 UPG Neighbor: build the five neighbor maps from source, not from a frozen IIFE in an iframe
Send Map, the Bloom cockpit, Flower Mesh, Lang Cluster and Flowers ran in
the Experimental shelf as a 976 KB prebuilt legacy bundle inside a sandboxed
iframe. Their original source survived in the old app-profiles/doxa-upg-neighbor
bundle, and it only imports library pieces that still exist, so it moves here
as-is: the unified hub becomes upg-neighbor.vue and the lenses go in src/.

The hub now picks its opening lens at setup rather than on mount. Switching
on mount tore Send Map down mid-initialisation and threw on every other lens.
The five instance ids stay the same, so existing embed URLs still work, and
the old legacyElement key still maps to a lens.

The legacy sandbox (data/legacy/upg-neighbor and its staging rows) is gone
because nothing loads it any more.
2026-09-24 18:47:14 +00:00
doxa-maps-88655c70
4f689a8fb1 Date screenshots by commit, so a fresh clone can pass check:shots
Some checks are pending
build-gate / build (push) Waiting to run
check:shots compared mtimes, and a checkout writes files in arbitrary order:
every clone failed, pristine main included (11 shots "2s behind"). Committed,
unmodified files are now dated by their last commit; files with local edits,
or no git at all, still use mtime.

With the honest clock the same 11 shots really were stale (3h to 31h behind
their bundles), so they are retaken here with ONLY=. Emoji now render: the
shot machine has an emoji font. Negative-tested: a byte appended to the
doxa-simple-map bundle fails exactly the three cap2 shots.
2026-09-24 18:44:53 +00:00
doxa-maps-88655c70
29dfef1fc4 Agent brief lands through the merge queue; the gallery meta says what ships
- AGENT-BRIEF.md told agents to push with an inline token, to a retired
  branch, and to main without the lock the server now requires. It now
  gives the askpass setup, the capture/scan/push/release order, and says to
  grade the push by exit code.
- README: the full build needs BUILD_ALLOW=1 (it refuses with exit 75
  otherwise); say so, and why, where the build is documented.
- doxa-maps-spa.meta.json described the retired iframe mode and listed six
  old children. `composes` now names the seven tabs the shell mounts, so the
  studio's Composed-of panel and check:composite see the real tree.
2026-09-24 18:44:51 +00:00
doxa-maps-88655c70
c46a9a04bd README: lead with the objectives, showcase the Research Gallery, teach the agent swarm
Some checks are pending
build-gate / build (push) Waiting to run
- Opens with what dx-maps is for: seven objectives, each paired with the gate
  or section that proves it is met.
- New Research Gallery section: the cap-5 app shell and the tree of cap-4..1
  plugins it nests, each still independently embeddable.
- Plugin Studio: clone main (the old branch was 198 commits behind), a command
  table, and a fresh studio screenshot (the old one showed a retired nav and
  tofu icons).
- Agent swarm: renamed to industry terms (trunk-based development, merge queue,
  maintainer), a laptop recipe (worktrees, strict ports, one builder, flock as
  the queue), and a redrawn diagram whose inner ring is an actual queue with
  one gate. SVG source committed next to the PNG.
- MapLibre is now described as it is: a one-prop, token-free engine.
- Removed internal credential/push instructions (AGENT-BRIEF.md covers agents).
- documentation/: fixed 19 dead links left by the docs/ move, removed internal
  references, and listed the working docs in the index.
- readme-shots: ONLY=<name> retakes a single image.
2026-09-24 18:34:02 +00:00