Commit graph

986 commits

Author SHA1 Message Date
doxa-maps-88655c70
2f96f04b49 packets: fixtures at the PAGE BOUNDARY, which nothing had ever rendered
Some checks are pending
build-gate / build (push) Waiting to run
check:packets-all reported "every generated packet renders, in every locale
present" and passed on 11 packets. All 11 were the SAME ZONE (laos) in 11
locales. It proved one zone's layout eleven times.

The answer is not more zones -- 28 zones x 11 locales is a network fetch per
zone and ~100MB of artefacts. It is more SHAPES, chosen where the layout can
actually break, built from committed records with no network.

★ THE SHAPE THAT MATTERS IS THE PAGE BOUNDARY. packet-render.js:76 sets
PER_PAGE = 20 and paginates `for (start = 0; start < groups.length; start +=
PER_PAGE)`. laos has 95 groups and tibet 43 -- both mid-range remainder cases,
so the boundary ITSELF had never been rendered. Three fixtures, each a slice of
the committed tibet records, so they are byte-deterministic and need no API:
  exactly 20 -- one full page; an off-by-one yields a second, empty one
  21         -- two pages, the second holding a single card
  1          -- the minimum a zone can have

MUTATION-TESTED, and this is the argument for the whole change. Flipping the
page loop to `start <= groups.length` -- an off-by-one that only shows when the
count is a multiple of PER_PAGE:

  fixture-edge-exactly-20   FAIL   <- the only one of 15
  fixture-edge-21-groups    pass
  fixture-edge-one-group    pass
  fixture-tibet             pass
  laos x 11 locales         pass

A real pagination bug would have shipped completely green under the old suite.
The failure is specific too: "20 people cards should lay out as 4 sheets but the
packet has 5" and "list pages disagree on the zone total: 20 vs 21".

⚠ EACH BUILD OVERWROTE THE LAST, which is why this is a script and not a line of
shell. packet.mjs names its output for the ZONE, not the fixture, so four
fixture builds against one zone leave ONE file -- and check:packets-all would go
on seeing a single shape while reporting success. Each output is renamed to its
fixture's own name so all of them coexist and every one is evaluated.

packet:ci now builds all four. check:packets-all goes from 11 packets in 1 zone
to 15 across 5 shapes; it still reports "5 of 28" and names what it does not
cover.
2026-09-24 23:53:07 +00:00
doxa-maps-88655c70
a3729129b1 poster cards: the fits gate sweeps LOCALES, not just widths
Some checks are pending
build-gate / build (push) Waiting to run
The Driver's requirement was "full words visible AND support localization".
This gate only ever ran in English, which is the shortest of the eleven -- so
the half of the requirement about translation had never been measured.

MEASURED at 1440px, the three print labels:
  en  Prayer List 76px · Card Deck 72px · Poster Map 79px
  ro  Listă de rugăciune 122px · Pachet de cartonașe 133px · Harta afișului 92px
  zh  禱告名單 56px · 禱告卡組 56px · 海报地图 56px

★ THE WORST CASE IS RENDERED WIDTH, NOT STRING LENGTH, and picking by the wrong
one would have picked the wrong locale. Chinese has the FEWEST characters of the
three and is nowhere near the widest, because each glyph is near double-width;
Romanian is +75% on English. The gate asserts scrollWidth > clientWidth, which
is pixels, so the sweep is pixels too. en is the control, ro and zh the measured
extremes; CARD_FIT_LANG overrides.

MUTATION-TESTED, and the result is the whole argument for this change.
Restoring the ellipsis the Driver rejected, at 1440px:
  en   0 clipped   <- passes
  ro  54 clipped   <- fails
  zh   0 clipped   <- passes
A regression that clips half the controls for Romanian readers would have
shipped completely green under the English-only gate. Restore the wrap and all
15 combinations pass.

Failures now name the locale ("ro 768px: 54 of 108 clipped"), because "768px"
alone is a report nobody can reproduce.

The PASS line reports widths x locales rather than their product -- it used to
say "at 15 width(s)" for 5 widths in 3 languages, and a summary that overstates
its own coverage is how a green line stops meaning anything.

Also verified, separately from the sweep: a PARTIAL translation degrades per key.
German renders translated (Gebetsliste, Kartenset, Posterkarte); an unknown
locale falls back to English; both with 0 blank labels, 0 raw i18n keys visible
and 0 clipped.
2026-09-24 23:41:09 +00:00
doxa-maps-88655c70
6dc979c3ea Rebuild both consumers after the shared PosterWall change merged
Some checks are pending
build-gate / build (push) Waiting to run
The merge brought library/components/poster/PosterWall.vue, which both owned
bundles compile their own copy of, so regenerating the tree alone would have
shipped a stale .js for each. Rebuilt regions-dashboard and country-posters
before fix:sri; check:sri passes across 56 script references and 57 leaves, and
the staleness check finds every sampled source literal present in both bundles.
public/doxa-maps/index.html conflicted and is generated, so it is resolved by
regeneration rather than by hand.

check:shared-reached confirms the poster hover outline reached all four of its
built consumers, this bundle included.
2026-09-24 23:40:37 +00:00
doxa-maps-88655c70
dac7b1ab57 Merge commit 'b6254567e1b32df2c0c5ceb0ee5d0576c47cb159' into bundler-1/work
# Conflicts:
#	public/doxa-maps/index.html
2026-09-24 23:38:21 +00:00
doxa-maps-88655c70
78a7baaa96 Fill the chooser's frame: solve the globe's zoom instead of reusing a mercator formula
The chooser's zoom came from log2(width / 512), and the comment above it said why:
"Mercator's world is 512*2^z wide". But the chooser renders under the globe
projection, so that is the same mistake as the region pages framing too wide --
a mercator answer applied to a sphere -- only here it made the frame too SMALL.
Found by looking at the screenshot, not by any assertion: a sphere adrift in
white space.

Measured at 1200x780: the mercator answer of 1.229 put the sphere at 390.61px,
50.1% of the frame's short side, so half the frame was dead space. And the
number was inert -- zoom 0.5, 1.0 and 1.229 all render an IDENTICAL 390.61px
sphere, because Mapbox clamps the globe's apparent size at low zoom, so the
formula's output sat inside the clamp and could have been 0.5 with no visible
difference.

There is no closed form to swap in. Diameter against a clean doubling is 3.6 to
10.5% off above the clamp, because the globe-to-mercator transition is nonlinear
there. What the measurement does establish is monotonicity, which is the only
property a solver needs, so the zoom is now SOLVED against the engine: bisect
the zoom whose limb projection fills 92% of the frame's short side. project() is
pure transform math, every setZoom lands in one synchronous block before a frame
renders, the original zoom is restored in a finally, and the old mercator
formula remains the fallback because a small globe beats a broken chooser.

The first version of the solver measured pole-to-pole and called it the
diameter, which holds only when the centre is on the equator. The chooser
centres on the mean of every people group, latitude 28, so that was a CHORD:
717.6px where the sphere was really 792-835px across, understating it by 1.104x.
The solver over-zoomed and clipped the globe top and bottom -- again visible in
the screenshot while every number still looked right. It now samples the limb,
points at 90 degrees angular distance, and takes the MAX across twelve bearings,
because Mapbox's globe is a perspective camera rather than a true orthographic
one and the silhouette is not a perfect screen-space circle: 396px along the
meridian against 417.3px east and west at one zoom.

Result: the widest axis is 750.6px inside a 780px frame, the whole limb sits in
frame, and dead space falls from 49.9% to under 4%. Pins are correspondingly
bigger click targets. On a phone the ported 1/2 aspect ratio means the globe
fills the WIDTH, which is the right trade -- filling the height would crop it
horizontally and lose pins off both edges -- and that leaves half the height
empty by geometry, which is carded for a Driver decision rather than resolved by
deviating from a clone-faithful rule.

The chooser's behaviour is unchanged and re-proven after the camera moved:
_probe-chooser 15/15 and the new _probe-chooser-pin-opens-country 8/8.
2026-09-24 23:37:47 +00:00
doxa-maps-88655c70
b6254567e1 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 23:32:16 +00:00
doxa-maps-88655c70
2f77da9d5b Stop the search box clipping the word "code" on a phone
At 390px the placeholder read "Find a people group, language or ISO
cod" — losing the one word that tells a reader an ISO code is accepted
at all, on the control most likely to be used first. Measured: 40
characters of text in 279px of room.

The short form keeps all three things the box accepts and fits with
room to spare, 185px of 279. The full phrase still shows at desktop
width, and the aria-label stays long either way, because a screen
reader has no width to run out of.

Found by checking phone layout after a flight of additions to this
sheet — five gap tabs, a filter, new chips and the bridge panel all
arrived without anyone looking at 390px. The rest held up: no
horizontal overflow, the chips wrap, the sheet scrolls, and the bridge
panel renders at 358px.
2026-09-24 23:25:28 +00:00
doxa-maps-88655c70
6f8ff0ebb7 gate: a shared fix is only as deployed as the last rebuild of each consumer
Some checks are pending
build-gate / build (push) Waiting to run
library/ is the single source of truth, but EVERY BUNDLE SHIPS ITS OWN COMPILED
COPY of the components it imports. So a one-line fix in a shared component is
live in whichever bundles happened to be rebuilt afterwards and silently absent
from the rest -- and nothing detected it, because every other gate measures the
RENDERED BEHAVIOUR OF ONE LEAF, and a leaf whose bundle is stale renders the old
behaviour perfectly.

Three independent instances in one week, which is what makes it a gate rather
than a note:
  - the PosterWall hover outline (a7f57c1) reached my-upg-100-list and none of
    the other three consumers;
  - the SemanticTreeLegend legend.a11y keys reached four bundles and NOT
    research-dashboard, so a legend there still ships nameless carets while
    check:a11y-button-names passes 791/791 -- that gate only sees the caret on
    leaves where an expandable branch happens to render. Nobody was working on
    it. It had just been sitting there;
  - commit 3cfe93c6, "Rebuild the fleet so the translated hint actually reaches
    it" -- someone already hit this and fixed it by hand. The remedy was known;
    the detection was not.

★ IT ASSERTS A MARKER STRING, NOT A TIMESTAMP OR A HASH. Comparing mtimes says
"older" and goes noisy on every unrelated rebuild. Comparing hashes cannot work
at all: a bundle legitimately differs from the library source it compiled. The
answerable question is narrower -- does the BUILT bundle contain THIS change? --
and a distinctive literal answers exactly that.

Consumers are DERIVED from the imports, never listed. A hardcoded list goes
stale the moment someone adopts a shared component, and a gate that silently
stops covering a new consumer is worse than none.

A marker that vanishes from SOURCE is reported as a stale ENTRY, not as failing
bundles -- otherwise reverting a change would blame every consumer for it.

MUTATION-TESTED IN BOTH DIRECTIONS, because a gate that has only ever failed is
indistinguishable from a broken one: rebuilding key-cities flipped its line from
✗ to ✓ and the count from 1 to 2 of 4, live; a positive control over a
long-standing marker reaches the PASS branch with all four consumers green; and
pointing an entry at a marker absent from source produces the stale-entry notice
rather than four false failures.

key-cities is rebuilt here because it was the mutation subject and the rebuild
is the fix the gate prescribes. The two bundles still red -- country-posters and
regions-dashboard -- belong to regions, and research-dashboard's legend drift is
older than this work; all three are reported, not rebuilt for them.
2026-09-24 23:22:18 +00:00
doxa-maps-88655c70
d55285eafe Name the language spoken around a group that has nothing
For 230 people groups the map said "nothing found" and stopped, when
that was not the whole truth: a language already spoken widely in
their country does have Scripture or the film. Kimr in Sudan has
nothing in Assangori; Sudanese Arabic, spoken by 65 groups around
them, has both. That is the difference between no way in and a way in
that is not their heart language.

WORDED SO IT CANNOT BE SKIMMED AS "THEY HAVE SCRIPTURE". The heading
says "Not their language", the body names whose language it is and how
widely it is spoken, the strength is quoted from the source, and the
last line says a second language is a way in and not a translation.
The chips above are untouched and still read "none found" five times,
and the tier is unchanged: 2,001 groups still have nothing of their
own, exactly as before.

Two exclusions, both measured rather than assumed. Fourteen of these
rows offer a DEAF people group a SPOKEN bridge — Gawri "has a film" is
no use to Deaf Pakistanis, the same error as offering them recorded
audio — so sign languages are out. Sixty-five carry the curated
strength "weak — may not be a real lingua franca"; a bridge the source
itself doubts is not an answer, so those are out too.

Placed below the whole chip row after a first pass split it, leaving
the Zume chip stranded under the panel.
2026-09-24 23:20:30 +00:00
doxa-maps-88655c70
a7f57c117d poster cards: a blue outline on hover, and a gate that can see it
Some checks are pending
build-gate / build (push) Waiting to run
The card asks for the Pinterest/Instagram behaviour: hovering a poster outlines
it in blue so a reader knows which one their click is about to act on.

★ THIS IS NOT A RESTORED BORDER. Several reports asked to bring back "the blue
outline around the poster", and MEASURED 2026-09-24 there has never been one:
all 136 elements painting a frame on this wall compute outline:none, and every
border is grey (.card #e3e6ea, .main #dfe3e7). Nothing regressed -- the
affordance was never built. The reports kept recurring because a hover state is
invisible to a screenshot, so each check confirmed "no outline" and concluded
the sighting was mistaken.

outline, NOT border, and that is load-bearing. An outline paints OUTSIDE the
box and takes no space, so it cannot move anything. The rest of this wall's gate
asserts where the print row sits at five widths; a hover that grew a border
would re-lay-out the card and push that row, so a cosmetic state could break a
gate about print controls.

:focus-within carries it to the keyboard, and @media (hover:none) drops the
whole state on touch -- with no pointer it would either never appear or stick
after a tap, leaving a card looking permanently selected.

THE GATE NOW MOVES THE POINTER. Everything it measured before was of a loaded,
untouched page, which is precisely why this affordance could go unbuilt while
being repeatedly requested.

⚠ AND ONE OF MY OWN ASSERTIONS WAS DECORATION UNTIL I MUTATION-TESTED IT. I
first asserted the card's OUTER width is unchanged by hover. It can't change:
the card is a grid item, so the track fixes its width and a border grown on
hover eats inward. MEASURED with border-width:4px on hover -- outer 224->224px,
unfalsifiable, while the CONTENT box went 222->216px. It now asserts clientWidth,
which is what actually repositions the print row.

MUTATION-TESTED: remove the outline -> FAIL at all four pointer widths (exit 1);
grow a border alongside it -> FAIL naming the content-box shift (exit 1);
restore -> PASS (exit 0), reporting "hover 2px rgb(47, 111, 219)" at 768-1920
and "no outline (touch, correct)" at 390.

The print/open controls the card also asks for are already there and were
verified clickable in the same pass: Prayer List, Card Deck, the stock select
and Poster Map, all four with full labels and real handlers, 0 clipped.
2026-09-24 23:17:42 +00:00
doxa-maps-88655c70
1ca75ba838 Sweep GRN batch eight: tier 0 falls below 2,000
170 more codes, 74 with gospel audio. 657 codes carry a Words of Life
link and 872 people groups whose ONLY resource in any pool is that
recording — 78.7 million people — can hear the gospel in their own
language.

Tier 0 is 2,001, from 3,457 when this started: 1,456 groups have been
moved out of "nothing found" by real, verified links. 1,367 of the
gap's 2,456 codes are decided, 56% of the sweep, and the hit rate is
steady at 43% over eight batches.

Provenance exact — 1,246 name GRN as checked, 1,246 decided, none
decided goes unnamed — and all 74 new URLs verified before shipping.
2026-09-24 23:14:37 +00:00
doxa-maps-88655c70
31b934bdf5 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
2026-09-24 23:01:56 +00:00
doxa-maps-88655c70
26892e7274 Merge commit 'f2b667b41cb9e8d9cc5c60724f458f25bbbc7a59' into bundler-1/work
Some checks are pending
build-gate / build (push) Waiting to run
2026-09-24 22:58:44 +00:00
doxa-maps-88655c70
f7e39d5d5c Clone their map legend exactly, reset and all
Looking at the side-by-side Africa crops after the framing fix showed the
legend rows sitting a few px off and the panel reading differently. Diffing
our block against CountryMap.vue's found six differences that no computed-CSS
probe was covering: font-size 12.5px against their 12px (a rounded-token
artifact of mine, not their scale), colour #2a332a against their #1f1f1f, and
four properties simply missing -- the panel's box-shadow, pointer-events:none,
and the swatch's display, border and ring. The swatch border is the one with
layout consequences: a 10px dot inside a 1px border is 12px tall, so dropping
it shortened every row and moved the whole stack, which is the drift the crop
showed. An old comment here called this legend "one of the sanctioned
additions"; that was wrong and is why it drifted -- their CountryMap.vue
carries this legend with these same four entries, so it is a clone and every
property is theirs to dictate. The one deviation stays and stays documented:
inset-inline-start instead of their physical left, so the legend sits in the
reading-start corner in Arabic.

That left the panel 2px wider than theirs on the right edge alone. The text was
ruled out first: the same string measures 129px on both sides, in the same
Poppins at the same weight. The cause is their GLOBAL reset, _reset.scss lines
3-8, which sets box-sizing:border-box on every element and every ::before and
::after. No scoped style could ever have shown it. Our leaf document already
carries the identical rule, but this component renders in a shadow root and a
document-level universal selector does not cross that boundary, so our swatch
was content-box at 10px+2px while theirs was border-box at 10px. Two swatch
borders, exactly 2px.

Porting the reset into .rcm closes it: the panel now measures 165px against
their 165px, the panel bounding box lands on the same pixel on all four edges,
and the legend corner diffs to a mean 1.14/255 per channel, which is
antialiasing. The map frame is unchanged at 1200x780.

This is the seventh global rule of theirs found missing, and it explains the
seven per-element box-sizing patches scattered through this bundle -- each one a
local workaround for the reset that never arrived. Porting it bundle-wide and
deleting those seven is carded rather than swept in here, because border-box
changes geometry in components whose match against live has to be re-measured.
2026-09-24 22:58:16 +00:00
doxa-maps-88655c70
f2b667b41c gate: what readers are SERVED must be what we BUILT
Some checks are pending
build-gate / build (push) Waiting to run
Every gate in this repo measures the LOCAL build. Nothing measured the
PUBLISHED one. So the whole board stays green while readers are served a
months-old bundle, and the only detector is a human noticing something looks
wrong.

Running it today: 17 of 22 published bundles are NOT what we built, 3 more
return 404 and are not published at all, and 2 match. my-upg-100-list: readers
get 474KB, we built 663KB.

★ THE EXPENSIVE PART IS NOT THE STALENESS, IT IS THE PHANTOM BUGS. A stale
publish manufactures reports that cannot be reproduced. Three this week --
print buttons truncated to "Pr...", the legend reading "Generation 1/2/3"
instead of the real tab names, the pin hint saying "red dot" instead of
"coloured dot" -- were ALL fixed in main and ALL still visible live. Each one
read as a regression against a card marked done. Two cost most of a tick to
diagnose, and the diagnosis was the same sentence every time.

It compares the sha384 the manifest already carries, not the byte size: two
different builds can coincide in length, and a size check would call that a
match. Size is reported only to make a mismatch legible.

One finding the hash surfaced that a size check would have blurred:
gospel-resources is published 331KB LARGER than we build it. That is not
"behind", it is a different lineage -- published from a tree that has since
dropped code. And doxa-vision-card and golden-path-check DO match, so this is
not "the host is stale", it is a partial publish, which is a more specific
thing to go fix.

MUTATION-TESTED THREE WAYS: a matching bundle passes (exit 0); appending five
bytes to a matching local file fails it (exit 1); an unreachable host SKIPs
rather than failing, because a network gate that goes red on flaky wifi is a
gate people learn to ignore -- and the SKIP says it is not a pass.
2026-09-24 22:56:56 +00:00
doxa-maps-88655c70
054ff6059d Make a gate's dev server die with the gate
Twelve gates here spawn serve-strict and eleven reap it only on the
happy path — srv.kill() at the end of the script. That holds until the
end is not reached. When the box ran low on memory it killed gate
processes mid-run, and every server those gates had spawned was
orphaned: ten in one worktree, four in another. Each orphan holds
memory, which makes the next kill likelier, which orphans more. The
leak feeds the condition that causes it.

Both halves measured rather than argued. A script using the old spawn
was sent SIGTERM and its server was still answering afterwards —
orphan reproduced on demand. The same script using this helper left
nothing behind.

The helper registers the child against SIGTERM, SIGINT, SIGHUP and
normal exit, and unregisters when stopped deliberately, so it is safe
in a finally block. Nothing survives SIGKILL and nothing can, which is
why the port is returned too — a sweep needs something to look for.

This gate adopts it; it still passes, and its negative control still
discriminates. The other ten are other agents' files, so they get the
one-line change offered rather than applied.
2026-09-24 22:54:11 +00:00
doxa-maps-88655c70
96bd068bfc packets: the all-packets gate says how many ZONES it covered
Some checks are pending
build-gate / build (push) Waiting to run
"PACKETS-ALL: PASS — every generated packet renders, in every locale present"
is true and reads like "every packet is fine". It counts FILES. Only the zones
someone generated are ever in that directory.

MEASURED 2026-09-24: 11 packets, all of them the SAME zone (laos) in 11
locales, against 28 poster zones in out/posters. The suite proved one zone's
layout eleven times and had never rendered the other 27 -- any of which can
carry a different data shape: fewer groups, a missing photo, a map that does
not resolve. The packet gate itself is strong; the claim about its reach was
not.

It now prints "zones covered : N of M" with the zone names, and names the
shortfall. Running it today reports 2 of 28.

NOT a failure, deliberately. Generating 28 zones x 11 locales means a network
fetch per zone and ~100MB of artefacts; checking only what exists is a real
trade. The problem was never the cap, it was that the cap was silent -- a
summary line that overclaims is how a green board stops meaning anything. The
PASS text now ends with what it does NOT cover, and points at packet:ci, which
builds a second zone from a committed fixture with no network.
2026-09-24 22:49:23 +00:00
doxa-maps-88655c70
064ee63a6f Stop the tables saying GRN is missing and miscounting their own rows
Two claims in shipped metadata had gone false, and both were the kind
a reader trusts precisely because it looks specific.

people-group-resources listed Global Recordings Network under "not
included: requires an API key". Wrong on both halves: GRN is included,
583 codes of it, and it needed no key — 5fish resolves by ISO 639-3
and asks only for a crawl delay. A caveat naming a pool as missing
misleads exactly as much as a stale number when the pool is there. The
refresh now moves it out of that list and records how it was reached,
so the list cannot drift again.

The links table said "the 506 zero-reach rows" and "51 of the 168
codes". The true figures are 1,038 and 492: every source added rows
for languages no pin on this map speaks, and a sentence that counts
something has to be recounted when the something changes. Both are
computed now rather than typed.

Found by sweeping every shipped table's metadata for baked numbers
instead of waiting to trip over one. The surfaces came back clean —
their prose is already computed — so the stale claims were all in the
table meta, which is the part nobody looks at and a CSV carries away.
2026-09-24 22:46:16 +00:00
doxa-maps-88655c70
539c458629 Regenerate the tree and re-hash SRI after merging main
Some checks are pending
build-gate / build (push) Waiting to run
The merge auto-merged library/composables/useMapInstance.js, which is bundle
source, so both owned bundles were rebuilt before fix:sri rather than letting
the regenerated tree ship a stale .js. public/doxa-maps/index.html conflicted
and is generated, so it is resolved by regeneration rather than by hand.
2026-09-24 22:46:01 +00:00
doxa-maps-88655c70
4d9b24a82d Merge commit '840830ebc47250b642d6d58ba1e584a30e74d47d' into bundler-1/work
# Conflicts:
#	public/doxa-maps/index.html
2026-09-24 22:45:25 +00:00
doxa-maps-88655c70
840830ebc4 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 22:41:49 +00:00
doxa-maps-88655c70
08294cf232 Sweep GRN batch seven: half the gap's codes now decided
170 more codes probed, 72 with gospel audio. 583 codes carry a Words
of Life link and 785 people groups whose ONLY resource in any pool is
that recording — 78.3 million people — can hear the gospel in their
own language.

Tier 0 falls 2,172 to 2,088, from 3,457 when this began: the gap has
shrunk by 40%. 1,197 of the gap's 2,456 codes are decided, so the
sweep is now about half done, and the hit rate has settled around 42%
across seven batches.

Provenance exact through the change — 1,078 codes name GRN as checked,
1,078 decided, none decided goes unnamed — and all 72 new URLs
verified before shipping.
2026-09-24 22:41:29 +00:00
doxa-maps-88655c70
0ae551fdf0 gate: assert the UPG-100 map carries our polygons, zones and pins
Some checks are pending
build-gate / build (push) Waiting to run
The Driver reported "the polygons for the map and map colors and pins are not
showing up" and no gate could answer it. verify-map-paint asserts a live GL
context, zero console errors and that Mapbox emitted attribution -- every one
of which is true of a perfectly-painted basemap with none of our data on it.
"The engine booted and tiles are loading" and "my data is on it" are different
properties, and only the first was measured.

The profile already counts this, at the moment it adds the layers, and prints
it: upg-100-list.vue:1849 writes "N polys · R/T zones · P pins" into
.upg-status. Three numbers printed by the product and asserted on by nothing.
This gate reads them.

★ THE TWO OBVIOUS APPROACHES ARE BOTH WRONG, and I shipped one before measuring.
Counting DOM .mapboxgl-marker elements returns 0 on a HEALTHY map -- mapbox
draws these pins as GL geometry inside the canvas, so there is no element to
count; I carried "markers: 0" across three probes as proof of breakage, and it
was proof of a wrong selector. Sampling the canvas returns an empty buffer,
because a WebGL drawing buffer is cleared after compositing: 0 of 13362 sampled
pixels had any alpha, on a map visibly full of colour. The product's own count
is authoritative in a way a proxy is not.

It asserts the zone RATIO, not just presence. "27/27 zones" and "1/27 zones"
both contain polygons and only one of them is the product, so the threshold is
what separates "some data arrived" from "the data arrived". Floors rather than
exact counts, well below the real values, so a data refresh does not fail the
gate and teach people to edit the expectation instead of reading it.

MUTATION-TESTED BOTH WAYS: raise the three thresholds above the real values and
each assertion fires independently with its own message (exit 1); restore them
and it passes (exit 0). Blocking the boundary geojson also fails it, via the
unparsed-status branch.

MEASURED, local AND live, both leaves: 195 polys · 27/27 zones · 2107 pins.
The Driver's symptom does not reproduce in that form on either tree.
2026-09-24 22:39:12 +00:00
doxa-maps-88655c70
770d29fd25 Frame the region map the way their constructor does, not the way fitBounds does
The Africa crops showed theirs tighter than ours, and 107 identical CSS
properties could never have said why. Everything I could think to compare
matched: mapbox-gl 3.24.0 on both sides (their bun.lock, our CDN pin), the
style, the 1200x780 container, the padding of 40, the maxZoom of 6, the box
itself (their viewFor() pin extent is byte-identical to our rollup), and
NEITHER page sets a projection.

The asymmetry was when the fit runs. They frame the box in the constructor
(CountryMap.vue:179); Mapbox v3 resolves a constructor `bounds` while
getProjection() still reports 'mercator', giving zoom 3.210. We had recreated
that as a post-style-load fitBounds, resolved with the globe already active,
giving 2.777 -- a sphere shows more planet at the same zoom, so it pulls back.
0.433 zoom is 1.35x linear scale, which is exactly the tightness in the crops.
Copy don't recreate: I recreated their fit instead of copying their construction.

useMapInstance gains a bounds/fitBoundsOptions pass-through, inert unless a
caller passes it, for the deep-link case -- the only case Mapbox can honour,
since the dashboard mounts its one map before the fetch resolves. The normal
case goes through fitTheirWay(), which asks the engine the same question their
constructor asks: setProjection('mercator'), cameraForBounds, restore the globe
in a `finally` so a throw cannot leave the map flat, then easeTo. Asking the
engine beats reimplementing a Web Mercator fit that would drift from Mapbox's
own padding and worldSize semantics at the next upgrade.

Proof, three independent oracles: the camera moved 2.777 -> 3.210 with the
projection still 'globe' afterwards; the like-for-like tile oracle now reads
ours z3 / 8 tiles / [[-45,-66.51],[90,40.98]] against theirs z3 / 8 tiles /
[[-45,-66.51],[90,40.98]], the same tiles at 1.00x; and the regenerated crops,
looked at, put the same labels and pins on the same pixels.

Two oracles of mine were wrong and are fixed here too. The tile-extent probe
compared a z2-quantized extent against a z3 one and reported a "1.33x framing
gap" twice -- 180/135 is four tiles at z2 against three at z3 of the same box,
and our camera's own visible box was 135.44 deg against their 135 deg of tiles.
It now refuses to compare extents across levels and reports the level instead,
which is the real signal. The framing probe asserted our zoom equalled the
GLOBE's cameraForBounds answer, so it would have failed the correct clone; it
now compares against the mercator-resolved camera and checks that asking the
mercator question left the map on the globe.
2026-09-24 22:31:20 +00:00
doxa-maps-88655c70
4b4cb30eb8 Rank the translation priority by the place a team is sent
"Can hear it, no text" is a list of 2,556 groups. A team is not sent to
a group, it is sent to a country, and nothing aggregated that set —
the existing country table counts the groups with NOTHING, which is a
different population and does not contain one of these rows, because
none of them is tier 0.

154 countries, with two columns that disagree on purpose. Nigeria and
the DRC lead on LANGUAGES, 221 and 103, which is the translation
workload. China and Pakistan lead on PEOPLE, 218 and 195 million,
across far fewer languages. Which number matters depends on whether
you are funding translators or broadcasters, so both are shown and
neither is ranked for the reader.

The aggregate reconciles with its source exactly — 2,556 groups and
1,329,044,699 people both ways — and the basis travels as a column, so
a CSV that outlives this page still says "no text found in the pools
checked" rather than "no text exists".

Built without a browser: the box was low on memory this tick and a
gate that needs chromium was the wrong thing to add to it.
2026-09-24 22:29:38 +00:00
doxa-maps-88655c70
d5fff78ccd upg-100: ask the registry for the stylesheet URL, and let the gate hear mapbox
Some checks are pending
build-gate / build (push) Waiting to run
A map inside a SHADOW ROOT has to inject its own copy of mapbox-gl.css, because
a document stylesheet does not cross a shadow boundary. This profile did that
by writing the URL out a second time, and the second copy drifted: it pinned
v3.3.0 while every production loader -- generate-tree's baked <script>,
useMapInstance, generate-gallery -- had moved to v3.24.0. So the map ran new JS
against old CSS.

MEASURED on the leaf: mapboxgl.version 3.24.0, the only mapbox stylesheet
reaching the shadow root v3.3.0, and mapbox-gl emitting its own "missing CSS
declarations" warning. The file's own header already says the engine loader is
imported rather than re-declared "because a second copy of one fact drifts".
The stylesheet URL was that second copy. It now comes from the same registry
via engineStylesheetUrl().

★ THE GATE COULD NOT SEE ANY OF IT, which is the more useful half of this
commit. verify-map-paint asserts a live GL context, zero console ERRORS, and
that mapbox emitted attribution -- all true of a map whose stylesheet never
applied, since the basemap paints fine while the chrome around it does not.
Mapbox itself says so, but as a WARNING, and the listener filtered to
type()==='error'. It now listens for _detectMissingCSS's verdict and fails on
it.

MUTATION-TESTED BOTH WAYS on the same leaf: restore v3.3.0 -> FAIL, exit 1;
restore the registry lookup -> PASS, exit 0.

Two things I had wrong on the way, recorded so the next reader does not repeat
them. I first asserted position:absolute on .mapboxgl-canvas-container -- but
v3.24.0 declares only cursor and touch-action there, so static is its CORRECT
value and that assertion would have failed every healthy map. I also read
"0 .mapboxgl-marker elements" as missing pins; mapbox draws pins as GL geometry
in the canvas, so zero is expected. A screenshot showed the polygons, the
region colours and hundreds of pins all rendering. Both were properties I
assumed rather than verified, and the canary's background-color is the one
mapbox actually defines -- it is the test _detectMissingCSS uses.

The canary alone would still have been a false green here: that rule is
identical in v3.3.0 and v3.24.0, so it is salmon under both. It catches an
ABSENT stylesheet; the warning catches a MISMATCHED one. The gate asserts both.
2026-09-24 22:27:52 +00:00
doxa-maps-88655c70
cc3ab8b822 Check the shipped answer, not the code that wrote it
The same drift has now appeared six times, each spelled differently:
negated fields on a line naming "none", a positive hasAny over a
mapped row, a predicate inside a build script, and — found this tick —
a pair of columns no pass had ever touched. A source-level pattern has
to guess how the next one will be written.

Clause N does not guess. It compares the shipped ANSWER against the
pin tier, so it catches the seventh however it is phrased. Measured
when written: people-group-resources.any_resource_found disagreed with
the tier on 194 rows, and its no_resource_found pair contradicted
ITSELF on 7 more, because neither column was in the refresh at all.
Both now move together, 195 and 202 corrected, and the table agrees
with the tier on all 2,103 judgeable rows.

A code with no pin is SKIPPED, not guessed. The tier lives on pins, so
an unpinned code cannot be judged this way — judging it anyway
reported ajp as a defect on the first run, when the row was right and
the check was wrong. That is the failure this repo keeps meeting, so
it is written into the clause rather than left as a comment.

Also corrected there: the table's own note still told readers GRN
answers 403 to every request and had never been searched.
2026-09-24 22:24:24 +00:00
doxa-maps-88655c70
337a1a863b Tell the curated tables that GRN and Scripture Earth exist
The legacy reconciler reported "nothing to change" and was confidently
wrong: its hasAny predicate never learned the two newest pools, so 122
curated rows — 8.9 million people — still carried heart_has_any=false
for groups the pin index can now reach. A reconciler that cannot see a
source is not reconciling, it is confirming.

Corrected exactly the 122 predicted, and the derived columns moved
with them: status 122, opportunity 93, lbc_carries_resource 69,
aligned 59, gap 29. Gap moves least because it means "nothing in the
heart language AND no in-country bridge either" — most of these have a
bridge, so they were already opportunities rather than gaps, and that
documented meaning is intact.

This was the fifth occurrence of one bug, and the gate written for it
had never looked at scripts — only .vue. Extending it to the build
scripts immediately found a sixth, in the Deaf gap table, which is
latent today because no sign language carries either field: GRN is
deliberately skipped for them and Scripture Earth has none. Fixed
while it is still cheap.

The rule now differs by file because the truth does: a surface must
READ the tier, while a build script COMPUTES it and cannot, so a
script must instead name every pool. Both halves verified against the
real pre-fix lines rather than synthetic ones.
2026-09-24 22:15:53 +00:00
doxa-maps-88655c70
90f0c37ddf 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 22:12:10 +00:00
doxa-maps-88655c70
de3b34beb5 Stop the country sheet calling 729 reached groups unreached
The country sheet split a country's groups into "with resources" and
"no resource found" using its own hand-listed field set, which never
learned about Scripture Earth or GRN. Measured: 729 people groups —
77.9 million people — were filed under "no resource found" while this
same map's pin sheet offered them a working link. Chad alone had 107
groups with something and showed fewer.

It now reads the tier the pin index computed, carried onto the mapped
row. Chad reports 150 groups, 107 with a resource, 43 without, which
is what the tier says.

This is the FOURTH time this bundle has re-derived that decision
somewhere, and the gate written for it missed this one: the gate
looked for negated c.* fields on a line naming "none", and this was a
positive predicate named hasAny over the mapped row.

Widening it took three attempts and the first two were worse than the
bug. "Three OR-ed resource fields" flags canHear(), which ORs media
precisely because it must exclude text. "Spans text and media" flags
`!!c.s || !!(c.au && /text/…)`, because c.au is text or audio
depending on a qualifier and a `|| ''` default inflates the operator
count. Both were confident false findings — the failure this repo has
now met in a forced token, a swallowed flag and a class extractor, and
a gate that cries wolf gets switched off.

So it asks one question it can actually answer: a predicate NAMED for
"has anything" is deciding the tier by definition and must read it.
Verified against the real pre-fix line, not a synthetic one.
2026-09-24 22:10:03 +00:00
doxa-maps-88655c70
77989089e0 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 22:05:31 +00:00
doxa-maps-88655c70
0d394e9924 Sweep GRN batch six: 701 groups reachable by recording alone
170 more gap codes probed, 84 with gospel audio. 511 codes now carry a
Words of Life link, and 701 people groups whose ONLY resource in any
pool is that recording — 77.6 million people — can hear the gospel in
their own language.

Tier 0 falls 2,270 to 2,172. It was 3,457 when this work began, so the
gap is down by more than a third, and the translation-priority table
grows in step: 2,556 groups now, because every code GRN reaches moves
a group out of "nothing found" and into "can hear it, no text".

1,027 of the gap's codes are decided. Provenance stays exact through
the change — 915 codes name GRN as checked, 915 have been decided,
none decided goes unnamed — and all new URLs were verified before
shipping.
2026-09-24 22:04:56 +00:00
doxa-maps-88655c70
5a959d6b94 Two posters per row on the regions wall, with a control to change it
The Driver, verbatim: "i need two posters per row on the regiosn posters and
witha setitn got icnreat eh number of poswers per row".

The wall's own grid is repeat(auto-fill, minmax(210px, 1fr)), which lays five
across a region card. It now takes a `columns` count and an optional
`columnsControl`, both opt-in and both defaulting to the behaviour that shipped
— so country-posters and my-upg-100-list, which did not ask for this, keep the
auto-fill they have always had. Measured after the change: they still lay out
five across and neither grew a control.

The control is a <select>, not a stepper or a pair of chevrons. It is what
every reader already knows for "choose one of a few", it is keyboard- and
screen-reader-operable with no work, and it shows its current value without a
second element. Imitating the solved pattern rather than inventing one. Its
label is catalogued in eleven locales like everything else on this surface.

The probe measures GEOMETRY, not CSS. Asserting grid-template-columns would
pass on a rule that says repeat(2, …) while the cards actually wrapped three
across because one had a min-width the track could not honour — and a computed
style is exactly the kind of proxy that has fooled this suite five times today.
It counts how many cards share a rendered top coordinate, which is what a
reader sees: [2,2,2,2,2].

And it proves the control by USING it — set it to 3 and the rows become
[3,3,3,3,3]. A default that happens to be right would pass a one-shot check
with an inert knob.

One thing worth recording for the next person: the first attempt put the
explanatory comment inside the PosterWall tag's attribute list, and the Vue
compiler rejected it. An HTML comment belongs above the tag. This file had
already learned that once.
2026-09-24 22:04:28 +00:00
doxa-maps-88655c70
66519288dc README: UPG-100 is the mobile app, shot at its owner's "ready at 88a566f"
Some checks are pending
build-gate / build (push) Waiting to run
Deployment method 2 now shows the UPG-100 prayer lists and posters at phone
size: one poster per row, print options wrapping in full. Shot from the
rebuilt bundle at 88a566f, not from source. The capability-4 shots are
retaken from the same build, so they show the print-option fix too.
2026-09-24 22:03:50 +00:00
doxa-maps-88655c70
88a566f49d legend: the height cap now derives from the offset instead of repeating it
Some checks are pending
build-gate / build (push) Waiting to run
THE LEGEND OVERFLOWED THE BOTTOM OF THE MAP because two numbers that had to
agree were written out twice.

.stl-panel is capped at calc(100% - var(--stl-top) - 12px), but this profile
set `top` DIRECTLY with !important instead of setting --stl-top. So the panel
moved down to 58px while the cap kept subtracting 8px, and the two silently
disagreed by exactly the difference.

  MEASURED at 390px in a 420px-tall host: top 58px, max-height calc(100% - 20px),
  panel bottom 458 in a 420px map -- 38px PAST the map. Same 38px at 560px,
  because the error is the mismatch, not the map's size. After: -12px, the
  intended bottom margin, at 390/420, 390/560 and 1440/500 alike.

Two more copies of the same mistake went with it. The mobile rule paired
top:64px with max-height:calc(100vh - 72px) -- a constant unrelated to the
offset, AND the wrong box: 100vh measures the VIEWPORT while the panel is
positioned inside the MAP. Those agree only when the map fills the screen,
which is true of every standalone leaf and false the moment a host puts a
header above it. .fit-content had the same fixed 72px. Both now derive from
--stl-top, so they cannot drift apart again.

★ THIS IS INVISIBLE ON EVERY PUBLISHED LEAF. A standalone leaf gives the map
the whole viewport, and a panel that overflows a full-height map is still on
screen. It only appears when the host makes the map SHORTER than the viewport
-- which is how the element is actually embedded. Verify in a constrained
host, never on the bare leaf.

The a11y half of this work is NOT here. I fixed the same two nameless legend
controls locally while the remote was unreachable, then found on rebase that
they were already fixed on main -- better, with the row's own label
interpolated ("Expand Africa" rather than a generic string), and in one
key namespace instead of the second one I had introduced. Took main's version
whole and dropped my duplicate locale keys; only the CSS above is mine.
2026-09-24 22:00:52 +00:00
doxa-maps-88655c70
608ef3dd56 poster cards: print options wrap to full words instead of an ellipsis
The Driver rejected the ellipsis by name: the print options must LINE WRAP so
every label stays readable, because a button that no longer says what it does
has not been fixed.

Three measurements drove the CSS:

  - a <fieldset> takes min-width:min-content from the UA stylesheet, which
    OVERRIDES width and max-width. .pfs read width:82% while computing 431px
    inside a 212px card. min-width:0 goes on the fieldset ITSELF, not only on
    its children.
  - the row is anchored at its TOP (top:100%;margin-top:-14px), not its bottom.
    At bottom:-14px every extra line grew UPWARD over the preview image, which
    is how the first overflow fix broke the card it was meant to repair.
  - wrapping took the row from 35px to 78px, so the grid's row gap moves 64px
    to 88px. The old gap was tuned when the row was a single line.

No text-overflow anywhere in .pfs. min-width:0 alone had let three 76-79px
labels share 172px, showing 40px each.

The gate gains the assertion that would have caught the ellipsis: a control is
clipped when scrollWidth exceeds clientWidth. Mutation-tested -- restoring
text-overflow:ellipsis reproduces 81 of 108 clipped and exits 1. Without that
assertion the gate passed a wall whose buttons all read "Pra...".

Measured at 390/768/1024/1440/1920px: 0 clipped, 0 overflow, 0 over-preview,
0 neighbour intrusions, 108 controls.
2026-09-24 21:59:34 +00:00
doxa-maps-88655c70
661133dd5c 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 21:57:12 +00:00
doxa-maps-88655c70
6958d0a187 Expose the fitted bounds, so a probe can ask the engine what it would choose
Two lines that assign the box we pass to fitBounds onto window, beside the
map instance that has been exposed there for the same reason since this
element was built. It lets a probe call cameraForBounds on the SAME box and
compare the answer with where the map actually sits — which is what separates
"the fit is wrong" from "something overrode the fit".

It earned itself immediately. Looking at our Africa map beside theirs, the
framing differs: theirs cuts the Sahel off the top and shows city labels, ours
shows the whole continent with margin. The engine's own answer for our box at
our frame is zoom 2.777 and the map sits at 2.777, so fitBounds is correct; the
dynamic world-fill minZoom — the best suspect, whose own comment records it
pinning this dashboard at ~1.45 once — measures 1.229, well below the actual
zoom, so it is not overriding anything either. Both cleared by measurement
rather than by argument, which leaves their bounding box, and their map is a
bundled ESM import with no global to read it from.

Recorded as an open, measured difference. Everything about how the map is drawn
matches — projection, style, highlight, pin colours, legend, controls,
attribution, and Botswana and Lesotho unhighlighted on both sides because
neither is among the 41 countries the Africa rollup returns. What it is pointed
at does not.
2026-09-24 21:56:43 +00:00
doxa-maps-88655c70
bf7fc46dcd check:shots compares bundle content, not dates; retake the stale shots
Some checks are pending
build-gate / build (push) Waiting to run
A retake that came out byte-identical left the PNG unchanged, git dated it by
its old commit, and the gate called a correct, just-taken picture stale. No
clock can see that case. readme-shots now writes shot-bundles.json (shot ->
sha256 of the bundle it was taken from), and check:shots compares that hash
with the bundle built now; dates are only the fallback for unrecorded shots.

Negative-tested: one byte appended to the doxa-simple-map bundle fails exactly
the three cap2 shots; restoring it passes.

Also retook the eight shots a rebuild had made stale since e2beec9.
2026-09-24 21:54:08 +00:00
doxa-maps-88655c70
13a1225182 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
2026-09-24 21:51:30 +00:00
doxa-maps-88655c70
3cb182a783 Unescape the pipe in the prose line
Inside a code span a backslash renders literally, so the sentence read
"| tail" with a stray backslash. The escape inside the table is kept:
there a bare pipe would split the cell.
2026-09-24 21:51:11 +00:00
doxa-maps-88655c70
43e8d0e365 Merge remote-tracking branch 'origin/main' into rega/research-gallery-embed 2026-09-24 21:47:29 +00:00
doxa-maps-88655c70
b4dcf6fa6d Research Gallery: rebuild against f63d75e's VerticalTabsLayout rework
f63d75e (plugin studio's Explorer tree) rewrote library/layouts/
VerticalTabsLayout.vue, which is compiled into doxa-maps-spa.js, and did not
rebuild the gallery. This rebuilds it. check:gallery-embed and the maplibre
variant both pass, js 13 + iframe 13, with the new layout.
2026-09-24 21:47:26 +00:00
doxa-maps-88655c70
e2beec9a11 README: reshoot the Plugin Studio at f63d75e — the Explorer-tree rail
Some checks are pending
build-gate / build (push) Waiting to run
readme-shots now clicks the rail's first plugin row (.st-row[role=treeitem],
not a section heading), keeping .vtl-tab as a fallback. Without that, the walk
fell through to the nested gallery's own tabs.
2026-09-24 21:42:37 +00:00
doxa-maps-88655c70
561d9a42e0 Merge remote-tracking branch 'origin/main' into rega/research-gallery-embed
# Conflicts:
#	public/doxa-maps/index.html
2026-09-24 21:41:30 +00:00
doxa-maps-88655c70
88e4fea550 Research Gallery: rebuild the bundle against the shared layout it compiles in
e3df467 changed library/layouts/VerticalTabsLayout.vue, which is compiled
into doxa-maps-spa.js, but did not rebuild the gallery. So the bundle on main
was stale: new source, old bytes. check:sri cannot see this, because it only
proves the leaf page and the bundle agree with each other.

Measured, not assumed. Rebuilding fd95518's source reproduces the landed
bundle byte-for-byte (sha256 2856a1e2…; the build is deterministic). So the
gallery landings themselves were current. After this rebuild the bundle
contains the new layout code (branchOpen: new 1, landed 0).
check:gallery-embed and its maplibre variant both pass, js 13 + iframe 13.
2026-09-24 21:41:24 +00:00
doxa-maps-88655c70
f63d75ed80 Plugin studio: an Explorer tree for the rail, imitating VS Code
Some checks are pending
build-gate / build (push) Waiting to run
The studio's rail was hand-rolled vertical tabs stretched into a tree.
It is now an Explorer tree modelled on the VS Code Explorer, keyboarded
per the WAI-ARIA tree pattern:

- CAP 5 -> CAP 1 sections with sticky headers; one-line rows with the
  name and a muted element tag; twisty on the left; indent guides.
- Composites fold; their parts hang beneath, one level at a time, each
  part opening the plugin's one canonical entry.
- A filter box on top shows matches together with the path to them.
- One view action, Collapse all.
- Auto-reveal: opening a plugin by click, URL hash or remembered tab
  scrolls its row into view and opens its section.
- Arrow keys walk the tree, Right/Left open, close and move between
  parent and child, Enter opens, a letter jumps to the next name.
- The slim rail becomes an activity bar with one icon per section.
- Fold state is remembered per page.

The tree lives in plugin-designs/plugin-studio/src/StudioTree.vue and
fills VerticalTabsLayout's new #rail slot, so the layout still owns the
collapse toggle, mobile drawer and slim strip. The alias, depth and
branch row fields added to the layout earlier today are removed again:
nothing else used them.

The studio host now has a default height (85vh, min 480px), as
doxa-maps-spa does. Pasting the published embed snippet into a plain
page mounted a complete studio 0px tall. A page's own height or
--plugin-studio-height still wins.

The studio gates that click tabs by text now also match tree rows.
Rebuilt the plugin-studio bundle and re-baked its SRI.
2026-09-24 21:40:48 +00:00
doxa-maps-88655c70
16de564fb4 Record the pools that were checked and rejected
Three audio-Scripture sources were investigated this tick and none is
shippable. Writing down WHY, beside the unchecked-pool list, so the
next agent does not spend a tick rediscovering it — each was reachable
and doctrinally sound, and the reason is always coverage or the join
key, never suspicion.

AudioTreasure publishes 9 languages and every one is major; zero
overlap with the gap. Davar is a ministry site rather than a
catalogue, with no per-language page and so no URL a worker could be
sent to. Talking Bibles is the interesting one: 177 real audio Bibles,
open robots.txt, but the player keys languages by an internal numeric
id and publishes no ISO code, so a join could only be by NAME — which
this map refuses, because names collide across unrelated languages. It
turned out not to matter: of the 81 whose names match a language we
carry, ZERO are in the gap, so even a perfect ISO join would have
added no coverage.

GRN is the contrast worth keeping next to them — equally name-shy at
first glance, but 5fish resolves by ISO 639-3 directly, which is why
it is in and these are not.
2026-09-24 21:40:28 +00:00
doxa-maps-88655c70
a9bad3ccf2 Merge main into bundler-2/work
# 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 21:39:35 +00:00
doxa-maps-88655c70
3cfe93c67e Rebuild the fleet so the translated hint actually reaches it
regions landed the pin hint's i18n — useI18n, messages.pinHint.tapDot in 11
locales, English fallback, pinHintLabel first. Thirteen bundles embed that
component and only ONE of them had been rebuilt, so twelve maps still shipped the
old hardcoded English literal in their served .js.

check:sri passed the whole time, and correctly: every leaf matched the bundle it
loads. A stale bundle and its stale leaf agree perfectly — consistency is not
freshness, and no gate in the chain compares either against source.

MEASURED after rebuilding the eleven that were stale (country-posters and
regions-dashboard were already current):
  es  Toca un punto de color: alguien todavía necesita oración
  fr  Touchez un point coloré — quelqu'un a encore besoin de prière
  ru  Нажмите на цветную точку — кому-то всё ещё нужна молитва
Every one of those was the English sentence before this commit.

The same rebuild carried the legend's a11y fix to the two bundles that had no
owner watching them. All four bundles that embed SemanticTreeLegend now carry
legend.a11y.collapseRow; on doxa-research-map the row carets read "Expand Asia
(Region)", "Expand Afro-Asiatic", "Expand Arab World" — 14, 92 and 34 named
controls across the three tree tabs, with ZERO raw i18n keys, which is the defect
the commit before it fixed. The nine bundles WITHOUT that key were checked
separately and embed no such legend, so their zero means not-applicable rather
than stale.

Verified not-stale the only way that works here: every one of the thirteen now
contains both current source markers (the tapDot key, and the sheet-clearance
logic — 71 hits in the four that also embed the mobile legend). A build that had
failed silently would show tapDot=0, which is exactly how the original
twelve-of-thirteen gap was found.

gates:pre 7/7, gates:post 42/44 with the two known precondition skips.
2026-09-24 21:39:05 +00:00