Commit graph

923 commits

Author SHA1 Message Date
doxa-maps-88655c70
53308c546c Delegation diagram as rings of circles; stop showing Simple Research as the mobile app
Some checks are pending
build-gate / build (push) Waiting to run
- The diagram is redrawn as concentric rings of uniform circles, not filled
  bands: the Plugin Studio ring labels itself, the seven plugins sit on the
  plugin ring, one agent per plugin sits outside it joined by a line and an
  arrow ("each plugin has one agent responsible for building it"), and one
  shepherd sits outside the agent ring. The race framing is gone. SVG source
  updated with the PNG.
- Deployment method 2: Simple Research was never built for phones, so it is
  no longer the mobile example. UPG-100 is; its screenshot lands when its
  phone layout is finished. The shot is removed from readme-shots, with the
  reason and the planned replacement in a comment.
- Retook nine screenshots that the latest bundle rebuilds had made stale.
2026-09-24 21:08:52 +00:00
doxa-maps-88655c70
bb589d584e Vendor Oswald, because Bebas Neue cannot render Russian at all
Some checks are pending
build-gate / build (push) Waiting to run
Their heading face has no Cyrillic glyphs. That is why base/_globals.scss:35-44
switches every h1/h2/h3 to --font-family-cyrillic under [lang="ru-RU"], with
letter-spacing -0.02em — without it a Russian heading leaves the family
entirely and lands on whatever condensed system face the browser picks. We ship
a `ru` locale and had neither the rule nor the font, so that is exactly what
every Russian heading in this clone was doing.

Four subsets vendored, Google's own unicode-range values carried verbatim so
the browser fetches only what a heading needs: cyrillic 6700B and cyrillic-ext
7688B are the point, latin 12248B and latin-ext 11624B come too because a
Russian heading routinely contains Latin characters — a country name, a figure,
an ampersand — and a Cyrillic-only face drops those back to the fallback
mid-word, which looks worse than not switching. Each file verified to begin
with the wOF2 magic after download, because a fetch that returns an HTML error
page also "succeeds" and a font that is really a 404 fails at render time with
no network error left to find.

Two things the rule needed beyond the font. [lang] cannot cross a shadow
boundary, so the attribute has to be INSIDE the root for a selector in the
root's own stylesheet to see it — our roots now carry :lang for the same reason
they already carried :dir, and it is correct HTML besides. And the selector
accepts both `ru` and `ru-RU`: their site tags the document ru-RU while our
i18n locale is `ru`, and a host page embedding this element may set either, so
a selector matching only one would work on their site and fail on ours.

Only the headings, as theirs. Their .region-stat__value is a <p>, so their
ru-RU rule does not touch it and it keeps the heading family even in Russian —
switching everything would be wrong in a way no Russian reader would report and
no English reader could see.

_probe-cyrillic-headings.mjs checks the mechanism and the result separately, so
a rule that is present but inert cannot pass: the attribute is on the root, the
Oswald subset returned 200, the face is usable once document.fonts.ready
resolves, the h1 and h2 switched, the stat value did not, and English is
untouched in both family and letter-spacing. It awaits fonts.ready before
asking about a font — not doing so is what made a sibling probe report a
cross-origin font failure that did not exist.
2026-09-24 21:05:12 +00:00
doxa-maps-88655c70
510b6edbf8 Merge branch 'main' of https://git.nuxtbase.app/doxa-life/doxa-maps into bundler-1/work
Some checks are pending
build-gate / build (push) Waiting to run
# Conflicts:
#	public/doxa-maps/index.html
2026-09-24 20:55:42 +00:00
doxa-maps-88655c70
5869fed6a5 Hold the total row's height, and translate the three strings my own scan's bound hid
Two fixes and a correction, all three found by turning other agents' lessons on
my own already-green work.

THE TOTAL ROW DISAPPEARED WHILE LOADING. Theirs never removes it: it swaps the
count for a visibility:hidden placeholder so the row holds its line and the
results below do not jump down when the number arrives. Ours v-if-ed the whole
row away, so the layout shifted on every load. It surfaced only after the
class-coverage sweep's extractor was widened to read the whole file instead of
just the template — the class is in markup my template scan reached, but the
sweep had never seen the NAME, so nobody had asked what it was for. Widening it
took that sweep from 22 to 58 classes seen on their UupgsList and from 103 to
134 checked overall, inside a run I had already reported as complete. Of the
ten new names, eight are their useSelectCard variant this clone does not render
and each is recorded with that reason and its line number; the ninth,
line-height-tight, was already correct because we ported the resolved value
rather than the class.

AND A CORRECTION TO MY OWN LOCALISATION CLAIM. I reported zero hardcoded
English in the regions components. That was wrong, and my own instrument is
why: the scan bounded matches at sixty characters, and three failure messages
are longer than that —

    Region data could not be loaded. This is a connection problem, not a filter.
    People groups could not be loaded. This is a connection problem, not a filter.
    The map could not start. This is a map-token or connection problem, not a
    filter — the figures above are unaffected.

Re-scanned with no upper bound and across line breaks, translated into all
eleven locales, wired through t(). A scan with a bound can only find what fits
the bound, and I quoted its zero as though it covered everything.

Verified against live after all of it: the region card diff is now 37 of 37
identical (it grew by the icon's own margin, which nothing had read), the
bullet 11 of 11 exact, the region page 107 of 107, the country page 32 of 32.
2026-09-24 20:55:19 +00:00
doxa-maps-88655c70
795b357ab8 Merge branch 'main' of https://git.nuxtbase.app/doxa-life/doxa-maps into bundler-3/work
Some checks are pending
build-gate / build (push) Waiting to run
# Conflicts:
#	public/doxa-maps/gospel-resources/charts/index.html
#	public/doxa-maps/gospel-resources/data-tables/index.html
#	public/doxa-maps/gospel-resources/gospel-dashboard/index.html
#	public/doxa-maps/gospel-resources/gospel-resources.js
#	public/doxa-maps/gospel-resources/resources-list/index.html
#	public/doxa-maps/gospel-resources/resources-map/index.html
#	public/doxa-maps/index.html
#	public/doxa-maps/manifest.json
2026-09-24 20:52:29 +00:00
doxa-maps-88655c70
938f58c5f7 Rank the translation-priority set, and sweep GRN batch four
The map could select the 2,289 groups that can hear the gospel but
have no text Scripture; it could not say which of them to weigh first,
and 2,289 dots cannot be read top-down. The gap sheet gains a fifth
tab that ranks them by population and, on each row, names what the
group DOES have — "has audio Scripture, the JESUS Film — no text
found" — so a reader can see whether they are being sent to a
recording or a film before they go.

The tab describes a different population from the other four, so it
does not inherit their sentences: the nothing-found opener, the audio
caveat and the macrolanguage note are all scoped away from it. Its own
paragraph states the bound twice, because the top rows are Western
Punjabi at 97 million and Wu Chinese at 92.5 million, and a reader
skimming those numbers must not come away thinking no text exists in
any variety they speak.

GRN batch four: 170 more codes, 90 with gospel audio. 343 codes now
carry a Words of Life link and tier 0 falls 2,488 to 2,373 — from
3,457 when this work began. 689 of the gap's codes are decided, and
provenance stays exact at 588 claiming GRN, 588 decided, none
decided-but-unnamed.
2026-09-24 20:50:27 +00:00
doxa-maps-88655c70
71ff1d3b02 Plugin studio: call the cap-5 shell "Research Gallery"
Some checks are pending
build-gate / build (push) Waiting to run
The studio listed the app shell by its profile id, doxa-maps-spa. It now
shows the Driver's name for it, Research Gallery, in the rail, in every
composition tree and in the title bar. Display only: the row key, the URL
hash, the remembered tab and the <doxa-maps-spa> tag under the name are
unchanged.

Rows now carry the real profile id separately from the label, because the
mount handed the label to the element as its profile; a display name
there would have mounted the shell with a profile it does not have.
2026-09-24 20:49:42 +00:00
doxa-maps-88655c70
8bb7f47e53 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
2026-09-24 20:48:39 +00:00
doxa-maps-88655c70
fac00c00bf Adopt the research map's region colours, and pin the one unit that wasn't px
THE DRIVER RULED ON THE PALETTE. He compared the two screens himself: "when i
look on teh color ffor th regiosn tab of the simpel research map i don tsee the
you using htes colors on the reigosn dashbarod hwen you shoudl". Two surfaces
disagreeing about what Africa's colour means is a defect whichever palette is
prettier, and he chose which one wins. library/colors/wagf-region.js now
carries doxa-research-map/src/colors/doxa-region.js's values.

The blast radius turned out to be nil, and my earlier escalation overstated it:
wagf-region.js is imported by exactly two files, both mine. I had reported it as
needing a cross-agent decision on the grounds that seven surfaces would be
repainted. They would not have been — nobody else imports it.

WHAT THE CHANGE COST, measured with the same validator and surface the previous
palette was stepped against:

    lightness band   FAIL   #1ee05f .793 · #FFEB3B .928 · #00e0bd .810 sit
                            above the .43–.77 band
    chroma floor     PASS   all 7 >= .1
    CVD separation   PASS   worst adjacent #FFEB3B/#1ee05f dE 8.7 protan
    normal vision    PASS   worst adjacent dE 21.0
    contrast         WARN   under 3:1 vs surface: #1ee05f 1.72 · #FFEB3B 1.19
                            · #00e0bd 1.65 · #ff7f11 2.47

The palette this replaced passed all five. The contrast warning is survivable
here for a reason and not by dismissal: it obligates relief through a secondary
encoding, and both surfaces already carry one — the chooser renders a legend of
labelled chips in these colours, the dashboard renders the same seven as a
table. Identity never rests on hue alone. The lightness failure has no such
relief: three of these read faintly as pins on a light basemap. Darkening those
three by about 10% L would enter the band with the hue still recognisable, and
that is the Driver's call, so it is asked rather than done quietly.

_chk-region-palette-agrees.mjs is what stops the two drifting apart again. It
compares them value by value and names both files on a disagreement. Ours is
imported; theirs is parsed, because doxa-region.js imports a Vite alias and node
refuses the module — so the parse asserts its own shape first, requiring the
eight entries that file declares, and fails rather than comparing against an
empty object if anyone refactors it to computed values. Negative-tested: flip
one hex and it names that region and both paths.

AND ONE UNIT IN THIS PORT WAS NOT PX. The chevron's gap was a literal 1rem,
ported from their `.with-icon .icon.right { margin-inline-start: 1rem }` — 10px
at their 10px root. `rem` resolves against the document root wherever the
element lives, which is the whole reason dxlife-tokens.js resolves their scale
to px, so ours rendered 16px on our own leaf and would render 10px by accident
on a host with the common 62.5% reset. Pinned to 10px. No diff caught it: the
list diff's link spec reads colour, decoration, weight, display and alignItems,
and never a margin. It reads the icon's margin now.
2026-09-24 20:48:27 +00:00
doxa-maps-88655c70
e3df4677d3 Plugin studio: fold every composite, so the rail starts as a plain list
Some checks are pending
build-gate / build (push) Waiting to run
With every composition tree open, the rail was too long to find your way
around. Each composite now carries a chevron and starts folded, so the
rail first reads as the 35 plugins by capability, each composite saying
how many parts it has. Open one and you see its direct parts; a part that
is itself a composite folds again, so doxa-maps-spa opens to its tabs and
each tab opens to what it is made of, one level at a time. Choosing a
composite also opens it; only the chevron closes it. The same child folds
independently wherever it appears.

VerticalTabsLayout gains two optional row fields: `branch` makes a row
foldable, `ancestors` hides a row until every branch above it is open.
Rows are wrapped so the chevron can sit beside the tab rather than
inside it. doxa-maps-spa sets neither field and renders unchanged.
2026-09-24 20:45:19 +00:00
doxa-maps-88655c70
edccc8ee05 Filter the map to the groups a translator would go to next
The pin filter could separate nothing / something / both, which are
tiers. It could not ask the question a translation team actually
starts from: who can already HEAR the gospel — a recording, the JESUS
Film, a Bible video, Words of Life — but has no text Scripture in
their language? That set cuts across all three tiers, so no tier
button could select it, and it is the largest actionable group on this
map: 2,289 people groups across 1,147 languages.

The claim is bounded in the button, the caption and the tooltip: no
text Scripture FOUND IN THE POOLS CHECKED, never a claim that none
exists. It matters here more than usual, because the largest entries
are Western Punjabi, Shanghainese and Egyptian Arabic — varieties
where a recording plainly exists and a text in that same variety is
genuinely contested.

The flag is baked into the pin features so the filter stays a GL-side
predicate like the others; recomputing it per toggle would re-upload
12,379 features to answer a yes/no already known at build time. The
button carries no colour swatch, because it names a question rather
than one of the three colours.

Measured in the browser: 12,379 pins rendered, 2,289 after the filter,
matching the offline count exactly.
2026-09-24 20:41:49 +00:00
doxa-maps-88655c70
1257ab575b 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
#	public/doxa-maps/manifest.json
#	public/doxa-maps/regions-dashboard/region-dashboards/index.html
#	public/doxa-maps/regions-dashboard/regions-dashboard.js
#	public/doxa-maps/regions-dashboard/regions-list/index.html
#	public/doxa-maps/regions-dashboard/regions-mfe/index.html
2026-09-24 20:33:58 +00:00
doxa-maps-88655c70
e944132933 Fix an illegible active filter button, and the comment that defended it
Apply a filter on the country page and the button you just pressed went white
text on 5%-alpha blue over a white ground — an invisible label. Theirs is white
on #6d766e. We had copied their DEAD FALLBACK:

    background-color: var(--color-surface-brand-lighter, rgba(0,80,160,0.05));
    color: var(--color-white);

and --color-surface-brand-lighter is defined, at tokens/semantic/_colors.scss
:64, as $color-green-300. The fallback never applies on their site.

What kept it alive was a comment of mine sitting right above the rule. It said
their active state is white-on-white on purpose, that the token "is NOT DEFINED
in their token files", and that the white-on-white had been "MEASURED on their
live page". The first two are contradicted by a file in the read-only clone.
The third could not have been what it claimed: getComputedStyle returns the
specified colour and never the composite, so neither candidate can serialise as
white — the token is #6d766e, the fallback is rgba(0,80,160,0.05). The only
rule on that button that yields white is .input's var(--color-surface-white),
which is the inactive state. The measurement had read a button without
data-active set.

Measured properly, by clicking the Adopted filter on both sides: theirs is
background rgb(109,118,110), colour rgb(255,255,255). Ours now matches, and so
does Clear All — theirs is `.button compact link`, which blocks/_buttons.scss
:64-69 gives --button-text-color: var(--color-brand) = #3b463d, where ours had
used the body ink #2a332a. 30 of 30 properties identical to live, 0 differ.

That second one was found twice over, independently: by this diff, and by a
static sweep of their markup's classes that flagged `.link` as a rule our CSS
had no equivalent for at all. The sweep is worth reusing — list every class and
tag a component's markup uses, resolve each against their scoped block and
their global sheet, then check ours has something for it.

The probe exists because _probe-country-vs-live.mjs printed one line everybody
had read past: "NOT MEASURED (absent on both sides, so neither identical nor
different): chip". A filter chip only exists once a filter is applied. Absent
on both sides is not a pass. It is the second diff here that clicks something
before measuring, after the pin popup, and its own first run was a probe bug
worth recording: it waited thirty seconds for the three filter buttons and then
reported that Adopted could not be found. The panel is collapsed by default on
both sides and the buttons sit behind a v-if, so they did not exist yet — a
probe failure dressed as a product finding.
2026-09-24 20:33:03 +00:00
doxa-maps-88655c70
25e3edec8f upg-100: no mapbox stylesheet under maplibre, and assert the preview exists
Some checks are pending
build-gate / build (push) Waiting to run
Two things, both found by other agents' reports rather than by my own gates.

rega found upg-100-list.vue:1464 injecting mapbox-gl.css v3.3.0 into the shadow
root unconditionally, so a keyless maplibre embed still pulled a stylesheet from
api.mapbox.com. Token-free and harmless to render, but wrong-engine leakage on a
page whose point is having no Mapbox dependency. Gated on isMaplibre, which
already existed at line 119.

This commit is ATOMIC by design: the fix, the rebuilt bundle, and the deletion
of rega's MAPBOX_KNOWN exemption land together. Either half alone leaves a
window — delete first and main red-gates on its own known bug; fix first and a
stale exemption sits there hiding the next one.

Proven before deleting, because the exemption is keyed on a URL STRING while the
defect is a network REQUEST: the built bundle still contains the v3.3.0 literal
(the constant remains; the guard stops the fetch), so a source-grep gate would
have been misled. rega's gate measures requests, and it reported
"provider=maplibre made 0 request(s) to api.mapbox.com" in BOTH js and iframe
modes with no KNOWN line printed at all — i.e. the exemption was already dead
code. Re-proven after deletion: still 0 requests, still PASS.

The leaf-head preloads are explicitly NOT covered and the reason is recorded in
the gate: mapbox-gl.css v3.24.0 and mapbox-gl-geocoder v5.0.0 are a different
version and a different owner (generate-tree, bundler-1's lane), already tracked
separately.

Second: the Driver's report had two halves — "it is supposed to show the
previews" and the print options overlapping. I fixed and gated the overlap and
left the first unasserted. The over-preview check was written as `if (img)`,
which means a card with NO preview satisfies it perfectly: nothing can overlap
an image that is not there.

The gate now counts previews. Mutation-tested by aborting every previews/**
request: the product degrades well — all 27 cards swap to a "poster not
generated yet" placeholder with no broken-image icons and no <img> at all, which
is precisely why an image-health check cannot see it. So the fallback itself is
asserted, and all-cards-placeholder now fails.

A first version of that assertion was wrong and the reason is recorded in the
code: it called 18 lazy-loaded previews broken. Every img carries loading=lazy,
the first unpainted one sat 3926px down an 844px viewport, and no HTTP response
failed. Deferred is not broken; it now judges only in-viewport images.
2026-09-24 20:28:45 +00:00
doxa-maps-88655c70
460ccb564b Merge main into bundler-2/work
Some checks are pending
build-gate / build (push) Waiting to run
# Conflicts:
#	public/doxa-maps/index.html
#	public/doxa-maps/manifest.json
#	public/doxa-maps/regions-dashboard/region-dashboards/index.html
#	public/doxa-maps/regions-dashboard/regions-dashboard.js
#	public/doxa-maps/regions-dashboard/regions-list/index.html
#	public/doxa-maps/regions-dashboard/regions-mfe/index.html
2026-09-24 20:26:59 +00:00
doxa-maps-88655c70
c276a2158e Gate the embed contract: a composite must pass tk and provider down
Four elements shipped with this broken and nothing could see it, because every
existing gate loads a composite on its OWN generated leaf — and that leaf bakes a
token and assigns window.MAP_TOKEN, supplying the very thing under test. The
failure exists only where there is no leaf: the partner page the published embed
snippet is written for.

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

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

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

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

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

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

gates:post is 44 steps; the quoted CLI doc is updated to match.
2026-09-24 20:26:33 +00:00
doxa-maps-88655c70
14bbc5fb5e Sweep a third batch of GRN: 385 groups now reachable
170 more gap codes probed, 89 with gospel audio — a 52% hit rate,
steady across three batches now. 253 codes carry a Words of Life link
and 385 people groups whose only resource in any pool is that
recording — 71.6 million people — can hear the gospel in their own
language. Tier 0 falls 2,610 to 2,488, from 3,457 when this work
started.

520 of the gap's 2,456 codes are decided. The provenance stays exact
through the change: 434 codes name GRN as checked, 434 have really
been decided, and none that was decided goes unnamed.
2026-09-24 20:17:17 +00:00
doxa-maps-88655c70
728d3cbaef Merge branch 'main' of https://git.nuxtbase.app/doxa-life/doxa-maps into bundler-1/work
Some checks are pending
build-gate / build (push) Waiting to run
# Conflicts:
#	public/doxa-maps/index.html
2026-09-24 20:12:15 +00:00
doxa-maps-88655c70
29eaf3a62a Port their pin popup, and close the last English out of the regions clone
THE POPUP WAS A STUB. Click a pin on their region page and you get a card: the
people group's photo, the name, "country · primary ethnolinguistic group", a
definition list of Population / Religion / Intercessors-out-of-100, up to three
clamped lines of location description, and two buttons — Pray, which is the
point of the entire site, and Full Profile. Click one on ours and you got four
lines of hardcoded English with en-US number grouping in all eleven locales.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Also localises the last nine strings of our own two additions across all
eleven locales, composed from their attested vocabulary rather than invented:
their own translations of Regions, Select, View, Choose, and the dashboard
word from "See %region% dashboard". zh goes 93.9% to 97.4%; what remains is
Mapbox's own canvas chrome and one API code.
2026-09-24 19:06:59 +00:00