Commit graph

479 commits

Author SHA1 Message Date
doxa-maps-88655c70
5516c157bb Merge main into bundler-2/work
# Conflicts:
#	public/doxa-maps/country-posters/countries-map/index.html
#	public/doxa-maps/country-posters/country-poster-wall/index.html
#	public/doxa-maps/country-posters/country-posters-mfe/index.html
#	public/doxa-maps/country-posters/country-posters.js
#	public/doxa-maps/index.html
#	public/doxa-maps/manifest.json
2026-09-25 07:14:11 +00:00
doxa-maps-88655c70
b197055bf0 Print a UPG-100 list straight from the map legend
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
The Driver, as a first-time user: "I select one and it gives me print
options at the top right of the legend ... that's the only way that map
would be usable."

The affordance already existed - SemanticTreeLegend renders an export
menu gated on a selection, the plugin passes :export-enabled, and
@export was wired. What the handler did with it was the problem: the
packet branch delegated to the shared composable, and the poster branch
hand-rolled an <a download> of posters/<id>.webp. That is wrong twice.
It downloads a file silently, which was ruled out on 2026-09-23 ("no
silent file download as the default action"), and it has no fallback, so
for the lists whose .webp is not shipped it saved a 404.

The wall had a correct poster print the whole time, private to
PosterWall.vue, so this surface could not reach it. It moves into
usePosterPrints.printPoster and both surfaces now call the one copy -
what the card asked for: "reuse the existing three print actions rather
than growing a fourth implementation." Two guards moved with it, each
learned the hard way: a THROWN fetch is a miss rather than an error (or
the rasterise fallback is skipped entirely), and a blank raster fails
loudly (an SVG in an <img> is an isolated document, so a missing
stylesheet paints black with no error).

Also in this commit, both from measurement rather than review:

THE STICKER OVERLAP IS NOW A CONSTANT, NOT HALF A VARIABLE. Centring the
print row on the banner edge put half its height on the banner - and
that height is content-dependent: 68px in en, 54px in zh, taller when ro
wraps at 390px. Measured ro/390px: 27 of 27 stickers on the map by 4px
while en and zh were clean at the same padding. No fixed padding can
clear half of a variable. The row's top is now pinned --tape-overlap
above the edge and extra height grows downward into the row gap, so the
amount on the banner is identical in all 11 locales at every width.
15 width x locale combinations now pass with map-covered 0.

THE PER-ROW PREFERENCE IS CLAMPED TO WHAT THE VIEWPORT HOLDS. It renders
as an inline grid-template-columns, and inline beats the stylesheet, so
the Driver's default of 2 overrode minmax(210px,1fr) at every width -
including 390px, where two columns give a ~162px card. Reported from a
390x844 phone shot. The preference is for desktop density, not an
instruction to overflow a phone; it survives, clamped by the same 210px
minimum the stylesheet already uses.

THE ROW GAP WAS CLEARING A DISTANCE NOTHING OCCUPIES. 88px was tuned
when the print row hung fully below its card. It now straddles the
banner edge, so the same shot showed a ~180px empty band between rows.
56px still clears a two-line row in the long-label locales.

And the composite shell re-dispatches doxa:select-zone, so a list chosen
in the poster wall is ISOLATED on the map rather than merely scrolled
to. The map half was already built and listening on its own element; the
shell owed it one re-dispatch and nothing here re-implements selection.
2026-09-25 07:10:23 +00:00
doxa-maps-88655c70
89bd9c5d79 Poster wall: the banner gets stickers, top and bottom
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
The card is a BANNER that displays a map, the way a label displays a
design on a bottle. A banner taped up gets a sticker at each end, and
each sticker straddles the edge it tapes: part on the banner's own white
space, part overhanging. Neither one touches the map.

MEASURED at 1440px before this change:
  banner 240-426, map 241-425  ->  1px of white space at each end
  header sticker  35px above the banner's top edge, -1px below
  footer sticker   1px above the bottom edge, 67px below

So the map filled the banner edge to edge and there was no banner
surface for a sticker to sit on. The footer row could only hang off into
space, which is what shipped, and every assertion in the gate was green
through all of it.

The banner now reserves white space at both ends (--banner-pad-top /
--banner-pad-bottom on .main), and each sticker centres on its edge:

  header sticker  17px on the banner / 17px above it
  footer sticker  35px on the banner / 33px overhanging
  map covered by either: 0px

The print row also moved INSIDE .main. It was a sibling, so top:100%
resolved against the CARD, 27px past the banner - the re-parenting and
07d3341c's centring transform are one fix, and neither works alone.
That commit's per-row localStorage preference is preserved.

Card Deck is no longer a peer button with a 162px <select> beside it.
The stock picker is a disclosure: press Card Deck, choose a size, and
that choice prints. A native dropdown in the row collides with the host
page's own dropdowns, which is why it is buttons rather than a <select>.

Print-button labels WRAP instead of clipping. With the per-row default
now 2, a 390px viewport halves the card to ~170px, where Romanian's
"Pachet de cartonașe" cannot share a line: measured 54 of 81 controls
clipped at ro/390px, en and zh clean. The ruling on this wall is that a
long label wraps; nowrap was forcing a clip.

Also fixes the transparent strip above a pinned section header. Sticky
pins to the scroll container's PADDING box, so with .wall's 22px
padding-top the header rested 22px down and posters scrolled visibly
through the gap. A ::before extends the header's opaque fill upward by
exactly that padding, read from the same var so the two cannot drift.
Measured by sampling the strip's pixels: 1 colour, 100% header
background, where before it was 1338-2831 colours of poster artwork.

The MFE shell's own chrome is translated, in all 11 locales. Measured
with ?lang=ar: "Select View", "Views", "Prayer Resources" and "Map"
rendered English while the poster wall's buttons inside the panes
translated correctly, because each pane installs i18n and the wrapper
around them never did - the chrome a reader meets FIRST was the
untranslated part. The wall's own heading, tag and intro go through t()
for the same reason, with the <strong> inside the translated string so
each language emphasises its own words.

The gate grew four assertions, each mutation-tested to fail on the
defect it was written for: off-frame (row floated off the banner),
head-off (header sticker not straddling), map-covered (a sticker on the
poster), and selects (a native dropdown back in the row).

Includes a rebuild of the four PosterWall consumers, which also picks up
studio's shared useMapInstance unmount guard and exp-shepherd's
translated LegendMobile labels.
2026-09-25 06:41:56 +00:00
doxa-maps-88655c70
cd24bda42a Merge commit '8caf2dde90f44b26dea67df511b343b582ae52bc' into bundler-1/work
# Conflicts:
#	public/doxa-maps/index.html
2026-09-25 06:31:59 +00:00
doxa-maps-88655c70
d61819b553 Forward urlSync to the nested panes: the guard was unreachable without it
rega measured that a Regions nested in the Research Gallery STILL rewrote the
host's #anchor and tab title after my urlSync guard landed, and named the cause
exactly: regions-mfe.vue INHERITED_PAGE_KEYS lacked `urlSync`.

They were right, and the lesson is mine. Guarding writeUrl, syncTitle and
popstate in region-dashboards.vue was necessary and NOT sufficient: a nested
pane has no leaf page of its own, so it only ever sees what this composite
forwards. Without the key the panes read cfg.urlSync as undefined and the guard
never fired. A guard you cannot reach is not a guard, and I had verified only
the half I could see from my own file.

It is the same lesson this file already records for cap-4 asset paths: page
scoped config must be forwarded explicitly. The existing filter keeps `false`,
which is the value that matters here -- it rejects undefined, null and '' only.

VERIFIED ON THE WHOLE PATH, composite -> pane -> guard, on a foreign origin
(tree on 127.0.0.1, host page on localhost) with urlSync:false and a host
#anchor: 868 nodes and 2 nested pane roots rendered, and the host's
#host-anchor-must-survive and its tab title both came through untouched.

The probe asserts the embed RENDERED before it believes the URL result, and
that guard earned itself twice: a first run used <regions-mfe> as a tag, which
is a profile of <regions-dashboard> and not an element, so nothing mounted --
7 inert nodes, with the host URL "untouched" only because no code had run.
2026-09-25 06:31:36 +00:00
doxa-maps-88655c70
93dc5d14a2 Merge remote-tracking branch 'origin/main' into exp-shepherd/cities-dots
# Conflicts:
#	public/doxa-maps/index.html
2026-09-25 06:13:08 +00:00
doxa-maps-88655c70
9a18dee1c2 Merge main into bundler-2/work
# Conflicts:
#	package.json
#	public/doxa-maps/country-posters/countries-map/index.html
#	public/doxa-maps/country-posters/country-poster-wall/index.html
#	public/doxa-maps/country-posters/country-posters-mfe/index.html
#	public/doxa-maps/country-posters/country-posters.js
#	public/doxa-maps/index.html
#	public/doxa-maps/manifest.json
2026-09-25 06:12:10 +00:00
doxa-maps-88655c70
03b838e16d Simple Research: one filter state, so the two halves move together
The composite showed a map and a grid that ignored each other: filtering the grid
to Africa moved its own count 2,107 → 315 while the map beside it kept every pin.
The composite now owns ONE filter state; the children report theirs upward as DOM
events and accept ours downward as attributes, so each stays independently
embeddable — a child mounted alone hears nothing and is handed nothing.

MEASURED on a served built tree, region facet:
  grid → map   2,107 → 315, and the map's own state follows (externalRegion and
               selectedRegion both 'africa'); clearing returns both
  map → grid   the map's map-filtered contract → the grid's list 2,107 → 315;
               clearing returns it
  standalone   grid alone 24 rows / Total 2,107 / 0 errors; map alone canvas 1 /
               0 errors — unchanged
  MUTATION     disabling the composite's push leaves the grid filtering itself
               (315) while the map stays empty, and kills the reverse direction:
               the check fails when the link is broken

Four things this cost, each of which first looked like it worked:

1. StackLayout applies `attrs` ONCE at create — its documented contract. The
   first version recomputed `sections`; the composite adopted the filter and the
   map's attribute in the DOM never changed. A computed nobody re-reads is not a
   channel, so the state is now PUSHED onto the mounted children.
2. The echo cancelled itself. Composite pushes → the grid re-filters → it emits
   groups-filtered with activeFilters still EMPTY → the composite adopts that as
   "the reader cleared it" and wipes what it just applied. Both handlers were
   correct; the round trip undid itself in under a second. A push is not a user
   action, so the push window ignores children.
3. The grid ignored host filters BY DESIGN when its own dropdowns are visible:
   activeFilters picks local OR props, never both. Right for the desktop
   composite, which hides them; silently wrong here. A host facet now seeds the
   LOCAL state, so the map's selection appears as a chip in the grid's own bar
   and the reader can clear it there.
4. That seeding called lbl(), which is local to another computed — "lbl is not
   defined", watcher dead, attribute set, list unchanged. Visible only after the
   probe was widened to console.error. A silent watcher failure looks exactly
   like a feature that was never wired.

SCOPE, DELIBERATE: region only. The map filters religion and language by the
canonical strings its own data carries; the grid filters them by slugs from a
different table. Linking those means inventing a mapping, and a wrong link is
worse than an honest gap.

The map also gains a verification handle (window.__researchMapFilterState), the
same contract as __rdashMap and __grMap: a linked filter is a STATE claim, and
every DOM proxy for it was either tab-specific or a selector guess.
2026-09-25 06:10:43 +00:00
doxa-maps-88655c70
b4a0387495 Translate the chrome that was English in all eleven locales
Measured under ?lang=ru on both composites: every Latin-script string left on the
page was ours. The map above was fully translated; underneath it the grid, the
map's own side menu and filter panel, and the whole research dashboard were
English in Arabic, Chinese, Hindi, Russian and everything else.

Three surfaces, three reasons, none of them a missing translation:

  the grid          read labels from a host-supplied `t` object that nothing ever
                    filled, with an English literal at each call site as fallback
  side menu +
  filter panel      plain string literals
  dashboard         labels declared as literals in dashboard.config.js — facet
                    rail, chart titles, KPI strip, table headers, segmented
                    control, search placeholder

New catalogues for all 11 locales (grid, controls, dashboard), REUSING the
catalogue's existing wording wherever the word already existed — Регион, Блок
родства, Население, Вовлечён, Графики — so two panels never name one thing two
ways, and nothing is coined that the project already has a word for.

Read through a new plugin-free tStatic() rather than useI18n, because these
components are also mounted by entries that install no i18n plugin. An unguarded
useI18n() throws and a thrown setup() removes the subtree with no build error —
the defect that had just blanked people-groups-grid everywhere (6603dc17).

★ THE CONFIG LABELS ARE GETTERS, AND A SPREAD WOULD HAVE LOOKED IDENTICAL.
dashboard.config.js is imported once at load; the locale is only known later. My
first version spread a getter object, and a spread COPIES THE RESOLVED VALUE, not
the accessor — every label would have frozen at whichever locale loaded first,
and it would have looked perfect in English. `get label() {…}` resolves on each
read.

MEASURED after, served built tree, identity checked first: Big Picture ru/ar and
Simple Research ru/zh have ZERO Latin-script chrome strings. What remains is
Mapbox's licence attribution (never translate), country and people-group names
(data), and 'UUPGs' — which every locale file deliberately keeps as the acronym.

Three gates went red on the way, two of them mine and worth recording:
  check:locale-reachable  slices this i18n module between two markers and EVALS
                          the slice; I had put tStatic between them, so the gate
                          hit an `export`. Moved to the end of the file.
  check:library-index     my import+helper above a component's docblock became
                          that component's DERIVED description. Moved below it.
  check:kpi-unit          its parser assumed single-brace entries, so getters made
                          it find ZERO pct KPIs — and it failed rather than
                          printing a green PASS about nothing, which is the gate
                          working. Parse now understands getters AND checks the
                          translated labels, so a '%' reintroduced in any of the
                          11 catalogues fails by name. Both controls fired.
2026-09-25 06:10:27 +00:00
doxa-maps-88655c70
502a4186dc Cities with Internet and the phone legend speak the page's language
Cities with Internet was English end to end: the legend title, the four bands,
the World Bank caveat, the new people-group row and the whole country sheet. Its
chrome now comes from a connectivity catalog area in all eleven locales, following
gospel-resources: i18n is optional, English from the shipped catalog is the
fallback, and carrier and platform names stay as data. The legend and the sheets
are anchored with logical insets, so in Arabic the legend sits opposite the control
rail instead of under the zoom buttons.

LegendMobile hard-coded its column labels ("Prayer Status", "Engagement Status",
…) and titles ("Legend", "Languages", …) in English. They now read
legend.colLabel.* and legend.titleBar.*, which every catalog already translates.
English is byte-identical, so other consumers are unchanged until they rebuild.

Measured in the Research Gallery tab on a served built tree, at 1280 and 390, in
en/ar/zh/ru/hi: Cities shows no English UI string (only the Mapbox and
geoBoundaries attribution); ar is right-to-left with 0 clipped and the legend
clear of the control rail.
2026-09-25 06:05:46 +00:00
doxa-maps-88655c70
405db377d8 Take main
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
2026-09-25 05:51:48 +00:00
doxa-maps-88655c70
c31df9f349 IBT is the best lead on this map, and recording why it is not a pool
36 Doxa groups carry an IMB "audio Scripture exists" flag while every pool here
says nothing. Asking why rather than shrugging: most are Dagestani and Caucasus
languages — Akhvakh, Andi, Bagvalal, Botlikh, Chamalal — which the
English-centred catalogues simply do not cover. The Institute for Bible
Translation in Moscow does, and their /languages page lists 54+ projects
including every one of those, with an ordinary robots.txt that allows it.

It still cannot be a pool. The page gives a native-script name, a region and a
language family and nothing else: no ISO code, no per-language page, no
publication URL. Joining it means matching "Akhvakh (ашвалӏи мицӏцІи)" to a
code by NAME, which is the guess this build removed two commits ago when Doxa's
own API made it unnecessary — the same guess that would have handed Akha in Laos
one of three sub-varieties.

What it is worth is the other thing the Driver asked for: the people who do the
translation work, so a group with nothing can be handed to a team that already
has the linguistic work and the relationships. For the Caucasus cluster, IBT is
that team, by name. That belongs in a leads table with a human confirming each
code, never auto-joined into the index.
2026-09-25 05:51:26 +00:00
doxa-maps-88655c70
6d59645b37 Merge remote-tracking branch 'origin/main' into rega/research-gallery-embed
# Conflicts:
#	public/doxa-maps/index.html
2026-09-25 05:48:29 +00:00
doxa-maps-88655c70
3b8e8e21e7 Research Gallery: name the Experimental maps, in every locale
The Experimental folder's rail rows were the one part of the gallery's own
chrome still in English in every locale. They were also machine-titleized
manifest ids, so even English read "Upg Neighbor".

The six Experimental bundles known today now have proper rail names in all
11 locales (Prayer Walk Planner, UPG Neighbor, Key Cities, All People
Groups, Country Posters, Doxa Vision Card). A future bundle that isn't
listed still appears, under its manifest title. Outside English its
English manifest description is replaced by the translated generic
subtitle.

Measured on the served built tree with the folder opened (7 → 12 rows):
translated in it/de/hi/ru/ar, en "UPG Neighbor", and 0 clipped. The rail
clip sweep with the folder open (11 locales × 1400/390) is 22/22 clean.
check:spa-tab-order passes on my own served tree. check:gallery-embed:
js 13, iframe 13, neighbour 13, linked 4.
2026-09-25 05:48:21 +00:00
doxa-maps-88655c70
c980d4e576 The Deaf gap is larger than this map was treating it as, and the route is a person
Cross-checking Doxa's roster against IMB's own access flags: 122 of their 2,107
people groups are DEAF — 17.5 million people — and 92 have nothing in this index.
For 33 of those 92 IMB records that material EXISTS, much of it dated 2021 at
"Selections" or "Stories": Armenian, Bulgarian, Myanmar, Cambodian and Cuban
sign languages among them. That is a coordinated body of work this build cannot
see.

Re-probed all three hosts so nobody spends another tick on them:

  doorinternational.org   the REAL DOOR International. Cloudflare 403 "Just a
                          moment..." on every path INCLUDING robots.txt. A bot
                          shield, and this repo does not circumvent shields — it
                          reached GRN through their own public site rather than
                          around it. Out on access, never on doctrine.
  deafbible.com           still the same 3,894-byte app shell for every URL,
                          /sitemap.xml included. robots allows everything; there
                          is simply nothing served to a fetch. api.deafbible.com
                          answers 525, so their own app's API is down too.
  door.international      NOT theirs. Parked for sale on GoDaddy, and it now
                          publishes an llms.txt saying exactly that. It looked
                          like a lead precisely because it advertises an LLM
                          policy. Their real domain is the first entry above.

This is the largest identified-but-unreachable set on the map. The route is
someone asking them for access, not another agent probing the same three hosts.
2026-09-25 05:46:52 +00:00
doxa-maps-88655c70
8423411757 The GRN sweep was defaulting to "none", not measuring it
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
Cross-checking IMB's own access flags against this index turned up 112 Doxa
groups where they say a resource exists and we found nothing. Nineteen of those
were gospel recordings — GRN, a pool this build had already swept — so one of the
two was wrong. Asking 5fish directly about six of them, with a real code and an
impossible one as controls: FOUR have recordings. alk Alak, nos Nisu Eastern,
jle Ngile, lma Limba East, each landing on its own numbered language page.

The fault is here. classify() read the page TITLE and its last rule was a
catch-all: any title matching neither known pattern became "none". That is a
default wearing the clothes of a finding, and it was silently losing every page
whose title was phrased in a way nobody had anticipated.

The landing decides now and the title only names. A published code lands on
/<id>?language=<name>; one they do not carry is swept to /regions — the same
discriminator already proved in band in the verifier and the rot sampler. A
request that errors leaves the code UNDECIDED rather than "none", because
recording a failed request as an absence is how this started.

GRN_RECHECK=1 re-asks the codes the old rule defaulted, and a hit can now take a
code back OUT of the none list — a recheck that could only add would be unable to
correct the thing it exists for. Proved on the six: 4 recovered, 2 confirmed
empty, controls passing.
2026-09-25 05:42:16 +00:00
doxa-maps-88655c70
60347ec0b7 Take main
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
2026-09-25 05:35:33 +00:00
doxa-maps-88655c70
26cd737587 All 2,107 of Doxa's groups, placed and searchable, from Doxa's own record
The map got coordinates and a language code by matching each group's NAME
against the pin dataset. That left 285 unplaceable and, worse, unsearchable: no
ISO code is no key to look them up by in any pool, so the access table could only
report them as "not searched" — nobody had been able to ask.

Doxa's own per-group endpoint carries both, and their robots.txt disallows
nothing. /api/people-groups/detail/<slug> gives primary_language and latitude and
longitude for every one: Akha to ahk, Akeu to byo, Alxa Mongols to xal, Ajja to
krs — four the name join had failed. So the name matching is not improved, it is
REMOVED. It was always a guess wearing a join's clothes: Akha in Laos sat beside
Akha Jipea, Akha Jaweuy and Akha Piso in the pins, and any pick among them would
have invented a language for a people. The publisher saying which language their
group speaks ends the guessing.

2,107 of 2,107 placed. Zero without a language code. The "not searched" rung is
gone from the access ladder because nothing is unsearched any more.

The gap grows 281 to 381, and that is the point: those 100 groups were always
without the gospel in their language, and the map could not say so because it did
not know what their language was.

The fetcher runs its controls in band both ways before trusting a record — a real
slug must return a language and coordinates, an impossible one must not — so a
site answering 200 with an empty body could not fill this table with silence.
IMB's own access and engagement fields come along beside ours, never merged into
them, because where two sources disagree that disagreement is worth seeing.
2026-09-25 05:35:11 +00:00
doxa-maps-88655c70
5bbbcc61f4 Plugin studio: mount the element the bundle defines, not the one a profile's JSDoc names
A profile's `element` in the manifest is copied from its @element JSDoc,
and that can name a tag nothing registers. country-posters-mfe's says
<country-posters-mfe>, but country-posters' index.js defines only
<country-posters-map>, so the built studio showed "never registered"
on a working plugin (gospel-dashboard had the same shape until
5418a79c). The studio now uses a profile's element only when the
bundle's `elements` (what index.js actually registers) includes it, and
otherwise the bundle's defined element.

Across all 33 manifest profiles this is the only mismatch, and its
published embed snippet already uses the right tag. Verified on the
built tree: country-posters-mfe mounts <country-posters-map> with
content (2,025 elements).
2026-09-25 05:34:16 +00:00
doxa-maps-88655c70
ca79ccb91c Merge remote-tracking branch 'origin/main' into exp-shepherd/cities-dots
# Conflicts:
#	public/doxa-maps/index.html
2026-09-25 05:23:30 +00:00
doxa-maps-88655c70
d6496bd87d Rebuild my six bundles to carry the useMapInstance unmount guard (0c1a91cd)
doxa-simple-map, cities-internet, all-people-groups, upg-neighbor,
prayer-walk-planner and demo-map compile useMapInstance in, so 0c1a91cd reaches
them only through a rebuild. check:shared-reached reports all five that use it as
carrying the guard.

cities-internet builds its map directly and never calls ensureEngineCSS, so it gets
its own guard: onMounted stops before new gl.Map if the pane unmounted during its
awaits. Removing the element 150, 400 or 900 ms after mount did not throw on either
build, so this is defensive, not a measured fix.
2026-09-25 05:23:08 +00:00
doxa-maps-88655c70
4cfe8ec5e2 Merge commit 'd989d598f18227e677f762c15c21df27013a57ad' into bundler-1/work
# Conflicts:
#	public/doxa-maps/index.html
2026-09-25 05:16:03 +00:00
doxa-maps-88655c70
4d773d6ee8 Move internal files out of the repo; fix five dead links; a fuller delegation diagram
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
Moved out (kept outside the repo, in the team's internal tracker):
- AGENT-BRIEF.md: operating rules for the team's own agents
- documentation/slide-deck-outline.md, documentation/README-longform.md:
  internal pitch material; the README and why.md cover the same ground
- plugin-designs/gospel-resources/SOURCES-CHECKED-AND-REJECTED.md: research log
- .claude/ (an editor status line), .forgejo/ (a CI workflow that never ran),
  _archive/ (a retired staging page; already gitignored but still tracked)
Seven code comments that named these files now describe the rule instead.

Fixed five dead links left from the docs/ -> documentation/ move, including
the one in template-bundle that every new plugin inherits.

The delegation diagram now shows the whole model: the Plugin Studio with the
shared library and the merge queue into main, seven production plugins with
capability badges and the Experimental shelf, one agent per plugin (a roaming
agent for the shelf), the shepherd's oversight ring, a legend, and the five
ways a build ships.
2026-09-25 05:14:10 +00:00
doxa-maps-88655c70
7eaa4012c1 Plugin studio: right-to-left in Arabic
Some checks failed
build-gate / build (push) Has been cancelled
rega's :dir(rtl) mirror for VerticalTabsLayout (5e7f73d0) compiled into
the studio, but nothing set the direction, so the studio stayed
left-to-right in Arabic. The studio now sets dir from the language
list's own dir flag, as doxa-maps-spa does.

The Explorer tree's own CSS assumed left-to-right: indentation, indent
guides, the active-row bar, the part count and the slim-rail activity
icons used left/right properties. They now use logical properties
(padding-inline, inset-inline-start, border-inline-start,
margin-inline-start), so they mirror, and a closed twisty points toward
the start of the line.

Checked at 1280x900: in Arabic the rail sits on the right, the active
bar on its right edge, the guides on the right, and doxa-simple-map
still renders its map; in English the geometry is unchanged (rail 0..256,
stage from 268).
2026-09-25 05:13:40 +00:00
doxa-maps-88655c70
0d846c3a1e Merge remote-tracking branch 'origin/main' into rega/research-gallery-embed
Some checks are pending
build-gate / build (push) Waiting to run
2026-09-25 05:09:14 +00:00
doxa-maps-88655c70
9e43332aad EMBED.md: lead with the picture and an at-a-glance table
The embed guide opened with prose and a code block. It now leads with the
existing screenshot of the gallery embedded by script on a partner page,
then a two-column table (script vs iframe): what to paste, sizing,
language, deep link, keyless maps, and what the server must send. Every
row is behaviour measured this session. Then it names the seven
production maps in their sections.
2026-09-25 05:09:09 +00:00
doxa-maps-88655c70
66750a59db The table the map is for: what each Doxa group can read, watch or hear
Some checks are pending
build-gate / build (push) Waiting to run
The Driver's question, in their words, is which of THEIR people groups have
gospel resources and which do not. Every tab here answered it for the research
corpus — 12,379 groups across 4,231 languages — which is the right scope for
finding what exists and the wrong one for working a list of 2,107. This one is
one row per group on Doxa's roster, it leads the tabs, and it exports.

The access ladder says what a person can DO, because that is what decides what
you send: read and watch 785 · watch or hear but cannot read 698 · read 53 ·
stories only 5 · nothing found 281. The second rung is the translation brief —
those groups can already receive the gospel and cannot yet study it, so the
first believers among them cannot be trained from a text that does not exist.

NO LANGUAGE CODE IS ITS OWN RUNG. The first run collapsed it into "nothing
found" and reported 566, when 281 had been searched and come back empty and 285
had never been searchable at all — no ISO 639-3, so no pool has a key to look
them up by. That would have told a mobiliser 285 peoples have no gospel in their
language when the truth is nobody has been able to ask. It is an identification
problem before it is a translation one.

CSV carries the filter and the roster: the whole table exports 2,107 rows, China
exports 154, and German exports a header and nothing else because German is not
one of Doxa's groups.
2026-09-25 05:07:53 +00:00
doxa-maps-88655c70
ebaaa9cd75 Merge remote-tracking branch 'origin/main' into exp-shepherd/cities-dots 2026-09-25 05:06:14 +00:00
doxa-maps-88655c70
299eb24a14 Cities with Internet: red people-group dots that open the people-group panel
Driver: "cities with internet map should have red dots on the map to show me
where the people groups are." Every DOXA people group is now a dot over the
connectivity shading, using the pray-tools list that Prayer Progress draws. It
carries the slug the detail panel fetches by. The dots are #e11d48 with a white
ring so they read on the "under 25%" red countries too, and the legend gains a
"people group" row with the count.

Tapping a dot opens the same PeopleGroupDetail panel Prayer Progress uses, through
useSelectedPin and the shared LegendDesktop / LegendMobile wrappers: a card on
desktop, the bottom sheet on a phone. Tapping a country still opens its carriers.

Measured on a served built tree: 2,107 dots for 2,107 groups; a tapped dot opens
the panel with the group's data at 1280 and at 390, in English and in Arabic; the
Research Gallery tab shows the same 2,107.
2026-09-25 05:06:11 +00:00
doxa-maps-88655c70
debb67338a Merge remote-tracking branch 'origin/main' into rega/research-gallery-embed
# Conflicts:
#	public/doxa-maps/index.html
2026-09-25 04:59:26 +00:00
doxa-maps-88655c70
c9d04a99f8 Research Gallery: translate the shared layout's own controls
The rail's visible "Collapse" button and the layout's four aria-labels
(Toggle list, Open the list of views, Expand list, Collapse list) stayed
English in every locale. They are the shared VerticalTabsLayout's defaults.
The gallery now passes them through the layout's `labels` contract (added
by studio in 814124b3), using studio's exact wording from studio.json, so
both shells name the same buttons the same way.

Measured on the served built tree: ar طي / zh 折叠 / ru Свернуть /
hi संक्षिप्त करें, with matching aria-labels; en unchanged. All 11 locales
carry the same 34 keys. check:gallery-embed: js 13, iframe 13,
neighbour 13, linked 4.
2026-09-25 04:59:22 +00:00
doxa-maps-88655c70
5db368f94c Place 98 more of Doxa's groups by reading the misses instead of guessing at them
Some checks are pending
build-gate / build (push) Waiting to run
383 of the roster had no coordinates — 18% of Doxa's people invisible on the map
and missing from every count on it. Reading why, rather than reaching for fuzzy
matching, one class turns out to be a spelling convention: the roster puts the
qualifier first and the pin dataset puts it last, around a comma.

  Acre Arara <-> Arara, Acre        Ashkuni Nuristani <-> Nuristani Ashkuni
  Banka Samogho <-> Samogho, Banka  Butuo Nosu <-> Nosu, Butuo

Same words, same country, different order. Matching on the sorted token set
absorbs exactly that: 94 recovered, plus 4 that only needed diacritics folded,
and every one resolved to a SINGLE pin inside its own country. Spot-checked
against the languages they landed on — Ashkuni Nuristani to Ashkun [ask] in
Afghanistan, Banka Samogho to Bankagooma [bxw] in Mali — and all 94 carry a
language code, so the resource join holds.

What it refuses is the point. Doxa lists Akha in Laos; the pins carry Akha
Jipea, Akha Jaweuy and Akha Piso — a parent against three sub-varieties, and
picking one would invent a language for a people. Doxa lists Akeu in China; the
nearest pin is Ake, a different language. Two candidates is a refusal, not a
coin toss. 285 still have no candidate at all and stay unplaced, because they
need a source this build does not have rather than a looser string match.

1,822 placed now. The gap grows 268 to 281, which is the join telling the truth.
2026-09-25 04:58:37 +00:00
doxa-maps-88655c70
c2e8d9afed Take main
# Conflicts:
#	public/doxa-maps/index.html
2026-09-25 04:50:21 +00:00
doxa-maps-88655c70
1e41394a86 Draw Doxa's roster, count instead of dividing, and set the type for reading
The map drew 12,379 people groups. Doxa works 2,107. It now draws theirs — 1,724
placed, 268 of them with nothing found, which is the number this map exists to
answer for. Every other group stays in people-group-pins.json as the research
record and the resource index; nothing was deleted, it stopped being dumped on
the map.

Countries are a COUNT of Doxa's groups with nothing, never a share. 106 of their
175 countries hold one or two groups and the United States holds three, so one
group without a resource read as 33% and turned the whole country amber. That is
what made the map look mainly yellow, and it was arithmetic rather than palette —
a prettier ramp over the same division would have been a prettier lie. A country
Doxa does not work is now left UNCOLOURED, because "no finding" is not "zero".

The ramp is sage through clay to oxblood, on the house light palette. No yellow:
it has the least luminance contrast of any hue against #f3f3f1, which is why the
old map looked jaundiced over a pale basemap.

Pin tiers stopped indexing the band array. A band is now a count of groups in a
country and a tier is what one group HAS — indexing one by the other would have
painted "nothing found" in the colour of "1-4 groups".

Type is up about three points throughout, 10-16px to 13-19px, because this is
meant to be usable by people who are not squinting at it.
2026-09-25 04:49:56 +00:00
doxa-maps-88655c70
72f88910a7 Merge remote-tracking branch 'origin/main' into rega/research-gallery-embed
# Conflicts:
#	public/doxa-maps/index.html
2026-09-25 04:41:21 +00:00
doxa-maps-88655c70
5e7f73d0be VerticalTabsLayout mirrors for right-to-left, and the gallery sets its direction
Arabic did not flip the shell (reported by studio for matrix cell E). The
layout is written in physical terms: rail at left:0, content at left:256px,
translateX(-…) slides. So an Arabic gallery kept its rail on the left with
LTR geometry.

The layout now carries an additive :dir(rtl) mirror block, in BOTH copies
of its CSS (VTL_SHADOW_CSS and the style block). The rail anchors right,
content right:256px, the collapse and drawer slides go positive, the
collapsed paddings, auto-margins and text-align mirror, and the arrow SVGs
flip. translateX has no logical form, so this is a mirror block rather than
logical properties. LTR consumers match none of it.

The gallery sets dir (and lang) on the layout from its own language: rtl
for Arabic, ltr otherwise. So an English gallery on some other RTL page
keeps a Latin rail unmirrored.

Measured on the served built tree:
  1400px ar: rail [944,1200], content [0,944], text right; collapsed =
             a 72px rail on the right
  390px ar:  drawer parked off-canvas right, slides in to [944,1200]
  LTR:       studio, stack-demo and the gallery at 1400px geometry
             identical before and after. The 390px differences seen once
             were transition timing and did not reproduce.

The shared layout is compiled into doxa-maps-spa and plugin-studio, so
both bundles are rebuilt here. stack-demo's bundle came out byte-identical:
it does not compile the layout.
2026-09-25 04:41:01 +00:00
doxa-maps-88655c70
158ef5ff5c Merge remote-tracking branch 'origin/main' into exp-shepherd/prayer-progress
# Conflicts:
#	public/doxa-maps/index.html
2026-09-25 04:34:57 +00:00
doxa-maps-88655c70
ab80c5c21a The roster this map should have been drawing: Doxa's 2,107 people groups
The map was built on the full 12,379-group dataset. Doxa works the groups on
pray.doxa.life, so a map of everyone buries the ones they can reach and paints
countries by a gap that is not theirs.

The roster is now its own table, from their own API: 2,107 groups across 175
countries, 206 million people. 1,724 join a pin by (name, country) and every one
of those carries an ISO 639-3 code, so the resource join is intact. The other 383
are kept with mapped:false rather than dropped — they are Doxa's groups whether
or not this build can place them yet.

The measurement that settles the colour complaint: 106 of those 175 countries
hold one or two Doxa groups, and the United States holds three. The choropleth
paints a country by the SHARE of its groups with nothing found, so one group of
three turns the whole United States amber. That is the mainly-yellow map, and
underneath it is not a palette problem — it is a percentage over a denominator
of three being drawn as though it meant something. A share is only meaningful in
the 35 countries holding eight or more.

Nothing is lost: every index, verified link and sweep result stays where it is.
2026-09-25 04:34:55 +00:00
doxa-maps-88655c70
0cda2907a7 Merge remote-tracking branch 'origin/main' into exp-shepherd/prayer-progress
# Conflicts:
#	public/doxa-maps/index.html
2026-09-25 04:32:24 +00:00
doxa-maps-88655c70
f779336c02 Take main
# Conflicts:
#	public/doxa-maps/index.html
2026-09-25 04:31:20 +00:00
doxa-maps-88655c70
2f4152da82 Translate the group sheet: chips, actions and headings
Clicking a pin is the first thing this map is for, and its sheet was still
English under every locale. Both halves of every resource chip now come from the
catalogue — the resource name and the link state — along with Pray for, Every
resource in, and Also speak. Proper nouns stay Latin with the frame translated:
Open Bible Stories, Scripture Earth, Zume, FCBH and GRN are names, not strings.

The bridge panel was English prose without declaring it, so Arabic threw its
sentence-ending periods to the start of every line — ".Not their language". It
declares dir=ltr like the other prose blocks now.

Verified on a served built tree in en, ar, zh, ru and hi: seven chips each,
nothing clipped, and the first chip reads Scripture · none found, الكتاب المقدس
· لا شيء, 圣经 · 一无所获, Писание · ничего не найдено, पवित्र शास्त्र · कुछ नहीं मिला.
2026-09-25 04:31:03 +00:00
doxa-maps-88655c70
f46c76f97e Prayer Progress: the legend's status column speaks the page's language
The column header ("Prayer Status", "Engagement Status", …) was hard-coded
English, so an Arabic, Chinese, Russian or Hindi embed showed it in English
above translated rows. It now reads legend.colLabel.*, which all 11 catalogs
already translate.

Measured on a served built tree with the host page on another origin: in ar, zh,
ru and hi the only text still identical to English is the Mapbox attribution
and "UUPGs", which every catalog keeps in Latin script.
2026-09-25 04:30:41 +00:00
doxa-maps-88655c70
47ced6d6b2 Merge remote-tracking branch 'origin/main' into rega/research-gallery-embed
# Conflicts:
#	public/doxa-maps/index.html
2026-09-25 04:23:12 +00:00
doxa-maps-88655c70
c8b13286f2 Research Gallery: speak the host page's language, and prove the composites' links
Language. The gallery fell back to the browser's language and forced it
onto every nested map through profile-config.locale, while its own rail
stayed English. Measured: a Spanish page viewed in a German browser showed
German maps under an English rail. It now follows the ladder every nested
map already uses: the `language` attribute, then ?lang=, then <html lang>
(watched live, so a host that switches language switches the gallery), then
English. The browser only counts once a visitor has used the gallery's own
switcher.

Its own words (rail, sections, hints, badges, notice) now ship in all 11
locales, in a private src/strings.js that reuses the library's terms.
Rail sweep, 11 locales at 1400px and 390px: 21 of 22 clean. The one clip,
Italian 'Avanzamento della preghiera' (191px in a 176px slot), now uses the
rail form 'Avanzamento preghiera'. A live <html lang> switch from en to
pt-BR re-renders the rail.

Interop (matrix cell D). check:gallery-embed gains a `linked` phase that
drives each composite's real control INSIDE a gallery tab:
- Big Picture: a facet shows "Showing 1,309" in the grid.
- Simple Research: a grid search narrows 2107 → 3. Its halves are
  independent by design.
- Regions: clicking "Africa" in the list retitles the dashboard "Africa".
- UPG-100: "Show on map" brings the map.
UPG-100's missing zone selection is an owned, printed LINKED_KNOWN entry.
Full gate: js 13, iframe 13, neighbour 13, linked 4.
2026-09-25 04:22:54 +00:00
doxa-maps-88655c70
3ad7bb7438 Merge commit '8836182db264adc34b251ea3cc9298d7d31eed09' into bundler-1/work
# Conflicts:
#	internal/check-research-gallery-embed.mjs
#	public/doxa-maps/index.html
2026-09-25 04:20:26 +00:00
doxa-maps-88655c70
3a748c9ae9 Take main
# Conflicts:
#	public/doxa-maps/index.html
2026-09-25 04:20:24 +00:00
doxa-maps-88655c70
5ede611d81 Translate the map's chrome into all eleven locales
The gospel map rendered identical English under every locale — measured, 3,018
words in en and in ar alike and not one non-Latin character. Its short UI labels
now come from library/i18n/locales/<lc>/gospel.json: the two mode buttons, the
search placeholders, the legend title and its eight colour bands, the four tier
filters, the five gap tabs, the twelve country and group stat rows, the sheet
controls and the dot tally. Key parity across all eleven.

The explanatory prose stays English on purpose and is marked dir=ltr where it
sits. It carries doctrinal and statistical precision — which pools were
searched, what "nothing found" does and does not claim — and a machine
translation there would read as authoritative while being wrong. One translated
line says so in the reader's own language, and it is absent in English, where
saying it would be noise.

Two things Arabic taught. Numeric ranges needed explicit LTR isolates: 30-59%
rendered as 59%-30 inside RTL text. And the dot-tally sentence was pinned LTR
first, which pushed it out of the panel and clipped "with a link" against the
left edge — it is a count, not doctrine, so it went into the catalogue and the
bidi problem stopped existing.
2026-09-25 04:20:07 +00:00
doxa-maps-88655c70
6e85370797 Regions honours urlSync:false, so a nested tab stops rewriting its host's URL
rega's embed sweep found region-dashboards.vue:499 URL_SYNC unconditional.
Nested in the Research Gallery this element rewrote the HOST page's #anchor
through writeUrl() and retitled the host's tab through syncTitle(). Those two
capabilities are ours when we ARE the page and not ours when we are one tab
inside someone else's. The gallery already passes profile-config.urlSync:false
for exactly this.

writeUrl, syncTitle and popstate now check it. The guard is a COMPUTED, not a
second module-level const, because the injected profile-config arrives after
this module evaluates -- a snapshot taken beside URL_SYNC would read undefined
and sync anyway. That is the same late-injection trap that already made every
value read off the string prop fall back to its default in this file.

DELIBERATELY NOT GUARDED: slugFromHash(), the boot-time READ. The complaint is
about writing to a host we do not own, and refusing to read would change which
subject a standalone deep link opens. The residual is narrow and stated in the
comment: on a host whose own #anchor happens to equal one of their slugs, a
nested Regions opens that subject instead of the chooser. Say so rather than
quietly widen the change.

Deletes both routed NEIGHBOUR_KNOWN entries in check:gallery-embed --
regions-dashboard:url and regions-dashboard:title -- in this same commit, so
main never carries a stale exemption for a bug that is fixed.
2026-09-25 04:19:09 +00:00
doxa-maps-88655c70
b5bebbe1e4 Merge remote-tracking branch 'origin/main' into exp-shepherd/apg-assets 2026-09-25 04:18:49 +00:00
doxa-maps-88655c70
34ec9eba78 Merge remote-tracking branch 'origin/main' into exp-shepherd/apg-assets
# Conflicts:
#	public/doxa-maps/index.html
2026-09-25 04:13:21 +00:00