Commit graph

893 commits

Author SHA1 Message Date
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
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
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
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
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
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
doxa-maps-88655c70
3fd1fbcaea Move the private field-access dataset out of the repo
data-private/ was a TRACKED folder, so field-access-visas.json would ship
with the repo the moment it goes public. A tracked folder is not private.

The table generator no longer writes inside the checkout: private tables go
to DOXA_PRIVATE_DIR (a folder outside the repo) or are skipped. data-private/
is gitignored so it cannot be re-created by accident.

The file is removed from HEAD only; it is still in history (5 commits).
Publish from fresh history or scrub it before the repo goes public.
2026-09-24 18:34:01 +00:00
doxa-maps-88655c70
e7f84bb84d country-posters: a phone can export too — and the gate was shutting the panel
Some checks are pending
build-gate / build (push) Waiting to run
Same gap as my-upg-100-list. countries-map.vue provided onSemanticTreeSelect to
the mobile sheet but not onLegendExport, so its embedded SemanticTreeLegend
rendered with exportEnabled defaulting to false. Fixed the same way, gated on
the profile's own EXPORT_FEATURE_ENABLED so the phone never gets an affordance
the desktop deliberately hides.

Mutation-tested, which is what shows the provide is load-bearing: without it
mobile reports 3 .stl-export elements with 0 having a real box, and fails. With
it, 6 present and 3 boxed, and passes.

I deferred this last tick as "another agent's profile". That was my assumption,
not a recorded rule. The swarm lane assignment maps FILES, and
countries-map.vue appears in no lane — while SemanticTreeLegend.vue, which I
have been editing throughout, is in one. Treating profile directories as
ownership boundaries is a reasonable default, but it is not what the swarm
recorded, and it cost a known two-line fix a full tick.

A second defect turned up in my own gate. check:legend-export-reachable clicked
the legend toggle unconditionally. That is right for my-upg-100-list, whose
sheet starts closed, and exactly wrong for country-posters, whose sheet opens
expanded: 8 rows visible before the click, 0 after. The gate was closing the
panel it came to inspect and then reporting "no selectable row" — which reads as
a product defect and was purely the probe. It now toggles only when nothing is
selectable yet, and both profiles pass.

Also re-verified two cards already marked done, after the previous tick found
part (a) of the legend card shipped-but-broken. Legend part (b), "remove or
demote Zone", holds: zero occurrences of "zone" in the rendered legend. The
per-map share policy holds at the rendered layer — upg-100 has no share button,
prayer-progress has exactly one — with smoke:share-all green across all twelve
published bundles and its coverage set checked against the policy's own list.
2026-09-24 18:21:35 +00:00
doxa-maps-88655c70
795382fabc Merge main and regenerate the served tree
Some checks are pending
build-gate / build (push) Waiting to run
public/doxa-maps/index.html conflicted; generated, so resolved by regenerating. fix:sri reports all 113 hashes already in agreement.
2026-09-24 18:12:53 +00:00
doxa-maps-88655c70
3e0a8b49a3 Print the country posters in place, instead of saving them to the device
The regions poster wall was downloading a .webp. UPG-100 opens a print dialog
over the page, and the Driver asked for the same UX with no difference.

This is one prop, not a second implementation: PosterWall's posterAction
defaults to 'download', UPG-100 passes "print", and regions passed nothing.
deliverPoster() branches on it and routes both the shipped-.webp and the
browser-rasterised path through the shared openPrintFrame, so the two walls
now print through exactly the same code.

The probe that covered this had been asserting the wrong thing. Its own
header said the Driver asked for print, but the assertion accepted "delivers
a file or says why" and then checked the file was a .webp — which a download
satisfies. It now asserts nothing is downloaded and that the shared print
frame is created.

Catching that took two attempts, because the frame is transient by design:
print() returns immediately in headless, afterprint fires, and the frame
removes itself within milliseconds. Polling the DOM afterwards found nothing
and looked like a broken print path. Subscribing to frameattached before the
click observes it without racing the cleanup.
2026-09-24 18:11:23 +00:00
doxa-maps-88655c70
6e958bc22f legend: open on the UPG-100 list, which is what the Driver asked for first
Some checks are pending
build-gate / build (push) Waiting to run
Measured at the rendered layer: the legend opened on REGION at desktop and
mobile. That is the Driver's first complaint about this legend word for word —
"it shouldn't have me sitting there on Regions, I don't know what to do, it's
confusing" — and the card recorded that part as shipped.

Every source signal said it worked. LEGEND_DEFAULT_TAB = 1 existed, defaultTab
was a declared prop, it seeded a local ref, and it was named in a watch fallback.

The root cause is one layer down. SemanticTreeLegend resolves its tab as
`instance ? instance.activeTab.value : localActiveTab.value`, and
usePplrInstance initialises activeTab to ref(0). Zero is indistinguishable from
"the user chose tab 0", so for every profile sharing an instance store the
defaultTab prop was dead — read on every render, never able to win. An
activeTabTouched sentinel fixes it: the store only wins once something actually
chose a tab, meaning a tab click or a node selection that jumps to that node's
generation. A sentinel rather than a new default, so no other profile's landing
tab moves as a side effect.

And a regression of my own, from the previous commit. There was only one
:default-tab left in the profile and it was on <LegendMobile>, where defaultTab
is not a prop. Last tick I "dropped the inert attribute" with a string replace
that matched the DESKTOP block first, deleting the working one and leaving the
inert copy. A dropped attribute is silent, so the build stayed green. Restored
to SemanticTreeLegend, and LegendMobile now has a real defaultTab prop so the
phone honours it too.

Both widths open on "UPG-100 Lists", and on ?lang=es both open on "Listas
UPG-100" — locale-independent. A user's own choice still sticks: clicking Region
holds at 0, and selecting a row does not snap back, so the sentinel is not a
trap.

check:legend-export-reachable still passes and now selects a real zone rather
than a region, because the legend opens on the Lists tab. The two fixes compound:
a phone user lands on the lists and can export the one they pick.
2026-09-24 17:59:57 +00:00
doxa-maps-88655c70
f344b74334 Merge main into bundler-2/work
Some checks are pending
build-gate / build (push) Waiting to run
2026-09-24 17:49:22 +00:00
doxa-maps-88655c70
4d79c08fd4 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 17:48:14 +00:00
doxa-maps-88655c70
cc6a5ea901 legend: a phone can now export the packet — it was a prop, not a design call
Some checks are pending
build-gate / build (push) Waiting to run
Last tick I found the export unreachable on mobile and recorded it as
Driver-gated: "WHERE the control goes on a phone is a design call, the sheet has
no titlebar". Both halves of that were wrong, and checking them was this tick.

LegendMobile RENDERS SemanticTreeLegend itself — titlebar and all — and the 0x0
elements my measurement found belonged to the OTHER, hidden desktop instance
mounted below the breakpoint. That geometry made it look as though the sheet had
nowhere to put the control. A Driver decision was never needed.

Two gaps, not one.

The sheet passed neither :export-enabled nor @export, and exportEnabled defaults
to false, so the control's v-if was never satisfied. Fixed via provide/inject,
matching onSemanticTreeSelect immediately above it, because the sheet renders
several levels down where an emit would not reach. Opt-in by construction:
enabled only when a profile provides a handler. The other three LegendMobile
consumers provide none and are verified untouched.

Even then the export did nothing, and that second gap is the one that mattered.
The profile passed :nodes but no :tabs, so hideTabs was true and the sheet showed
a single flat Region tree with no tab strip — no UPG-100 Lists tab, no selectable
zone, and onLegendExport bails on `if (!zoneId) return`. Passing :tabs fixed it.
A :default-tab attribute was added and then removed: defaultTab is not a prop on
LegendMobile, so it would have been inert.

Verified end to end at 390x844: the tab strip appears, a real zone selects,
Export opens, Packet clicks, and one print frame is produced. A packet on a
phone. The gate written last tick — which failed on mobile by design — now
passes at both widths. The newly visible mobile tabs are fully localized in
en/es/zh, so catalog work from earlier ticks now reaches a surface that never
showed tabs at all.

Four of my own probe errors were caught by measuring rather than concluding.
Clicking a REGION row left selectedKey empty, so the handler correctly bailed
and it read as "export does nothing" — the same zero on DESKTOP is what proved
it was the probe and not a regression.

country-posters shows the same mobile gap on its own surface. Not taken; its
owner's call.
2026-09-24 17:47:26 +00:00
doxa-maps-88655c70
fdaeb7b44b README: explain how many builders share one main
The repo is built by several people at once against a single branch,
and nothing in the README said so. The new section carries the
concentric-rings diagram and states the rule that makes it work: a
builder captures main before pushing, resolves every conflict while
holding it, then releases.

Placed before "Repository layout" — it answers the question a reader
has after seeing the studio and before reading a folder list.
2026-09-24 17:46:42 +00:00
doxa-maps-88655c70
bea4a4ff2d Rename the app-shell to Research Gallery
"DX-Maps SPA" named the technology; "Research Gallery" names what the
thing is to someone opening it — the Big Picture plus every research
map behind one vertical tab rail.

Display labels only: the profile folder, bundle name and custom-element
tag are untouched, since changing those breaks every existing embed.
The in-shell header moves with the other two — it is the most visible
label of the three, and leaving it reading "DX-Maps" would have made
the rename look half-finished to the only people who see it.
2026-09-24 17:46:35 +00:00
doxa-maps-88655c70
e1df0d5a7c Add Scripture Earth, and lift 28 groups off "nothing found"
SIL and its translation partners publish Scripture for small languages
the big catalogues skip, which is exactly the population left in the
gap: 2,901 groups had nothing in eBible, Arclight, Door43 or FCBH.
921 codes now carry a Scripture Earth link and 28 groups that had
nothing at all — 339,550 people — have Scripture in their own language
for the first time on this map. Tier 0 falls 2,901 to 2,873.

Most of this source is not what it appears to be, and the fetcher
throws away both false positives rather than shipping them. Their open
index lists 3,477 language directories, but 764 of the ones we can join
are empty stubs: trusting the listing alone would have handed 716
people groups a link to an empty folder. And a "video" directory holds
JESUSFilm-<iso>.txt metadata, not playable film — 1,588 codes have
nothing else, so video alone never qualifies a code. Only PDF, audio,
Scripture App Builder, study notes and SFM text count.

All 921 URLs verified HTTP 200 before shipping, and the artefacts were
opened by hand first: Baka's is 14.4 MB of application/pdf, Foodo's
40.7 MB, and a nonexistent sibling 404s, so a 200 here means a file.

The coverage gate, the verifier and the links table each had to be
taught the new field. Leaving any one of them ignorant would have been
worse than not adding the source: the gate caught all 28 tier changes
as inconsistent, and the links table filed 921 rows under the "other"
publisher — the fallback that exists to make an unscreened domain
impossible to hide. The sheet's "checked" line now names the pool too,
since it is searched.
2026-09-24 17:46:27 +00:00
doxa-maps-88655c70
1fba4ff86f Merge main into bundler-2/work 2026-09-24 17:43:45 +00:00
doxa-maps-88655c70
9acd4f8865 Queue the browser gates, and let them serve their own tree
Two problems, one change.

LOAD. Seven agents each running a wave put 7+ chrome-headless renders on one box;
measured today it reached load 47 with 8GB of swap, and a browser gate that is
swapping measures the swap, not the product. The wave runner now holds browser
steps while 1-min load is at or above LOAD_MAX (12) and takes a coarse flock
before each one, so they queue instead of dogpiling. Thresholds, cadence and the
20-minute cap are bundler-3's wait-for-quiet.sh verbatim, and BROWSER_GATE_LOCK
selects the rendezvous, so every agent lands on one lock whichever entry point
they use. Static gates are untouched: in gates:post that is 38 free, 4 queued.

The load wait is taken ONCE per wave, not once per gate. Per-step it multiplied
the same hold by the number of browser gates — an hour of a wave doing nothing,
which is how a load fix becomes a reason to stop running gates. The flock is
what serializes; the wait only decides when to join the queue.

Browser steps are identified from the gate FILE (a playwright import), not a
hand-kept list that goes stale the first time someone adds a gate — the one
missing from such a list is exactly the one that rejoins the dogpile. `verify` is
caught too: it imports no playwright itself but spawns three gates that render,
and it already marks them browser:true, so that declaration is honoured rather
than duplicated.

COVERAGE. Yesterday's guard made check:provider-switch and check:packet-browser
SKIP in every wave, because their default ports (4173, 4898) belong to other
worktrees on this machine. Skipping was honest but a gate that skips every time
is a gate nobody runs, so on a mismatch they now start a short-lived server over
THIS checkout and measure that instead — the point was never the port, it was
being able to prove which build answered.

MEASURED: a served tree whose manifest differs by one key but has all the same
pages -> "serves a DIFFERENT build", then "serving this checkout on :28384
instead", then a real verdict (16 -> 5 mapbox requests). packet-browser likewise,
once it re-navigates to the new origin — a dynamic import across origins is
refused by a plain static server, which cost one failed run to learn.
gates:post 40/42, the only skips being the two that genuinely have no input
(packets-all, zone-census); it was 4 before this.

The lock path default is product-named. The shared file several shells export
carries an internal code name, and check:no-codename refuses to let a repo we
hand over carry one — it caught this commit's first draft.
2026-09-24 17:43:32 +00:00
doxa-maps-88655c70
3db1dc1852 legend: on a phone you can browse the UPG-100 lists and cannot export them
Some checks are pending
build-gate / build (push) Waiting to run
The Export affordance lives in SemanticTreeLegend, which upg-100-list.vue mounts
for desktop only — its own comment says "Hidden below the mobile breakpoint,
where <LegendMobile>...". LegendMobile, the phone bottom-sheet, contains the
string "export" zero times. So the whole export path is desktop-only.

The packet IS the product for this profile. The Driver asked for "print options
at the top right of the legend... that's the only way that map would be usable".
A reader on a phone gets the browsing half and none of the printing half, with
no error and nothing to click — the failure is invisible rather than broken,
which is why it survived.

Measured at 390x844 against 1500x1000. Desktop: selecting a row takes
.stl-export from 0 to 3 elements, all with a real box, labelled "Export".
Mobile: the same 3 elements exist and measure 0x0 at (0,0), with display:block
and visibility:visible, inside an overflow:hidden parent — the desktop panel
rendered without being laid out.

Two earlier readings of mine were wrong, and the gate now encodes against both.
"No export button at either width" — I had not selected a row, and the control
is gated on selectedNode. "No legend rows on mobile" — 20 rows exist with 0
visible, because the sheet starts closed. Both were true statements about the
wrong surface, and both would have been reported as product defects.

The gate asserts a real box rather than a CSS property, for the same reason: the
mobile elements report visibility:visible while measuring nothing.

It FAILS on mobile by design and is deliberately not wave-wired. WHERE the
control goes on a phone is a design call — the sheet has no titlebar to put a
top-right action in, and SemanticTreeLegend is shared by four profiles — so the
gate names the gap instead of assuming a fix. Mutation-tested: flipping
SHOW_EXPORT_BUTTON off drops desktop to zero and the message distinguishes "no
element at all" from "exists but 0x0".
2026-09-24 17:27:28 +00:00
doxa-maps-88655c70
1cfb0578d9 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/gospel-resources/charts/index.html
#	public/doxa-maps/gospel-resources/data-tables/index.html
#	public/doxa-maps/gospel-resources/gospel-dashboard/index.html
#	public/doxa-maps/gospel-resources/gospel-resources.js
#	public/doxa-maps/gospel-resources/resources-list/index.html
#	public/doxa-maps/gospel-resources/resources-map/index.html
#	public/doxa-maps/index.html
#	public/doxa-maps/manifest.json
2026-09-24 17:04:21 +00:00