Commit graph

531 commits

Author SHA1 Message Date
doxa-maps-88655c70
6356cc3876 Merge remote-tracking branch 'origin/main' into rega/research-gallery-embed
# Conflicts:
#	public/doxa-maps/index.html
2026-09-25 10:51:41 +00:00
doxa-maps-88655c70
30e6e5699b Prayer Walk Planner: full screen from any embed, and a print proven sharp by resolution
The Driver asked for full-screen, and for "a print that stays sharp when
zoomed".

Full screen. The planner mounts the shared FullscreenButton (the library's
Fullscreen-API control, with its CSS pseudo-fullscreen fallback for iOS
Safari) as a map overlay. It full-screens the whole planner, toolbar and
map, not just the canvas. Measured from a 520px embed in the Research
Gallery on a foreign page: the planner took 1280x800 and the map grew 459
→ 739px. The label is translated (Arabic "تبديل ملء الشاشة"), because the
planner now keeps the shared vue-i18n instance on its own locale. Before,
Share and Full screen took the PAGE language even when the gallery sent
another.

Print resolution, measured, not asserted. The printed map lays out at
7.53 x 8.4 in on the letter sheet (printable width = 8.5in minus the two
12mm margins; emulateMedia alone does not apply @page margins, which
misread it once as 8.48in). Its image is 2400x2688 px, so 319 x 320 DPI.
A 1:1 crop of the zoomed print was read: small street names (Sancaktar
Yokuşu Sokağı, Baki Dede Sokağı), individual building outlines and
footpaths are crisp, because the map is rendered from vector tiles at
pixelRatio 2, not upscaled from the screen. One PDF page.

check:prayer-walk-planner gains both: full screen from a 520px embed must
take the viewport, and the printed map must measure >= 300 DPI. 10/10
PASS.
2026-09-25 10:51:21 +00:00
doxa-maps-88655c70
3df2478a1b Merge remote-tracking branch 'origin/main' into exp-shepherd/wall
# Conflicts:
#	public/doxa-maps/index.html
2026-09-25 10:34:43 +00:00
doxa-maps-88655c70
7fec74260f Wall of People Groups: every Doxa people group on one zoomable prayer wall joins Experimental
The Instagram-style prayer wall (2,107 DX people groups as photo cards on a d3
canvas, with a pray dialog and comments) existed only as a no-build SPA whose
components were template strings compiled in the browser. It is now a bundle:
the components are SFCs in src/components, and the root is the app's own
container without the router. The API developer panel does not ship. Pinia comes
from the package. The canvas is found by ref, not document id, which cannot see
into a shadow root. Font Awesome is registered on the document too, because
@font-face does not load from a shadow root.

Data is the live pray.doxa.life list. The uupg.doxa.life endpoint no longer
answers, so that source and every photo URL built on it are gone. The card and
both dialogs show the API's image_url instead; before this, every dialog read
"No image". The 2.6 MB CSV fallback duplicated the live API and does not ship.
Missing photos draw inline, not from via.placeholder.com. Prayers and comments
stay in localStorage under a wall-pg: prefix, and the "Print (Coming Soon)"
button, which only alerted, is gone.

Measured on a served built tree: the leaf page, a script embed on another
origin, the Research Gallery tab and a 390 px phone each load all 2,107 groups
with photos and Font Awesome, 0 page errors, 0 failed requests. Zooming in and
tapping a card's heart opens the prayer dialog with the group, its status and
its photo.
2026-09-25 10:34:39 +00:00
doxa-maps-88655c70
1ba62ef792 Merge remote-tracking branch 'origin/main' into rega/research-gallery-embed
# Conflicts:
#	public/doxa-maps/dashboard-plus-grid/dashboard-plus-grid.js
#	public/doxa-maps/dashboard-plus-grid/dashboard-plus-grid/default/index.html
#	public/doxa-maps/dashboard-plus-grid/dashboard-plus-grid/index.html
#	public/doxa-maps/doxa-maps-spa/manifest.json
#	public/doxa-maps/index.html
#	public/doxa-maps/manifest.json
#	public/doxa-maps/prayer-walk-planner/prayer-walk-planner.js
#	public/doxa-maps/prayer-walk-planner/prayer-walk-planner/index.html
2026-09-25 10:25:01 +00:00
doxa-maps-88655c70
78efae4eae Integrate main into bundler-2/work
# Conflicts:
#	public/doxa-maps/dashboard-plus-grid/dashboard-plus-grid.js
#	public/doxa-maps/dashboard-plus-grid/dashboard-plus-grid/default/index.html
#	public/doxa-maps/dashboard-plus-grid/dashboard-plus-grid/index.html
#	public/doxa-maps/doxa-maps-spa/manifest.json
#	public/doxa-maps/doxa-research-map/doxa-research-map.js
#	public/doxa-maps/doxa-research-map/research-map/index.html
#	public/doxa-maps/doxa-research-map/research-map/maplibre/index.html
#	public/doxa-maps/index.html
#	public/doxa-maps/manifest.json
2026-09-25 10:20:57 +00:00
doxa-maps-88655c70
16629f4906 Link religion and language between the map and the grid, by the one table they already share
A reader who picks a religion family on the map now sees the grid narrow to the
same peoples, and a language selection travels the same way. Region already
linked; these two did not, because the two halves do not name a religion the
same way and nothing yet proved what the honest translation was.

The map filters religion through its legend's flat tab, keyed by the FIRST
LETTER of the religion code ('B'); the grid filters through a derived axis that
runs library/colors/religion.js forward per row ('BLA' -> 'Buddhism'). One
shared table, read in opposite directions, copied by neither half — so the wire
carries the family NAME and each side resolves it against its own data.

Two measurements changed the design on the way:

  - mapStore.filters.religions, which the map's filter panel writes, is read by
    NOTHING: the mapLayers.applyFilters() it was meant to reach is called and
    never implemented anywhere in the tree. A link routed through it would have
    set state, passed a smoke test and dimmed no pin.
  - the map's legend selections are mutually exclusive by design, so it can
    light exactly one of region/religion/language. The composite mirrors that
    rather than letting the grid hold three facets the map cannot show.

Religion links at the FAMILY and not at the code, deliberately: a map cannot
distinguish 'BLA' from 'BMA' — both dim as Buddhism — so linking at the code
would promise a precision one half cannot keep.

check:linked-vocabulary proves it by measurement rather than by reading: it
compares the total reached over the composite's wire against the total the grid
gives for the same facet on its own (83 of 2,107 both ways), and runs the MUTANT
first — the legend key 'B' instead of 'Buddhism' — failing as NO-TEETH if the
wrong mapping scores the same as the right one. Measured: mutant 0, real 83.

The CLI doc's apk:debug quote was realigned in passing; package.json had gained
app:start-page and the doc still described the older chain.
2026-09-25 10:20:27 +00:00
doxa-maps-88655c70
01cc2a6a11 Merge remote-tracking branch 'origin/main' into rega/research-gallery-embed
# Conflicts:
#	public/doxa-maps/index.html
2026-09-25 10:11:23 +00:00
doxa-maps-88655c70
bad1ae9d7e Prayer Walk Planner: one scrolling toolbar row on a phone, Draw and Print first
On a 390px phone the toolbar wrapped to three rows, 139px of an 844px
screen above the map a walker is trying to read. Below 640px it is now one
row that scrolls sideways: no label is truncated, and the two actions a
walk needs, Draw and Print, lead the row so they are always on screen.
2026-09-25 10:11:19 +00:00
doxa-maps-88655c70
a15e6a7166 Merge remote-tracking branch 'origin/main' into exp-shepherd/pplr
# Conflicts:
#	public/doxa-maps/index.html
#	public/doxa-maps/manifest.json
#	public/doxa-maps/pplr-data-maps/pplr-data-maps.js
#	public/doxa-maps/pplr-data-maps/pplr-data-maps/index.html
2026-09-25 09:57:22 +00:00
doxa-maps-88655c70
17931ed031 PPLR data maps: every view loads under MapLibre, and the legend has accessible names
Under provider=maplibre (the keyless gallery) four of the seven map views never
finished loading. They built `new mapboxgl.NavigationControl` in the load
handler, and that global does not exist under MapLibre, so they sat on
"Loading…". Popups and the pin marker had the same fault. Every such call now
goes through glFor(map), the namespace of the engine that built the map. The
views' styles and the Dark/Satellite/Light switch now use registry names (ink,
hybrid, paper), which are the same Mapbox styles under Mapbox and OpenFreeMap
under MapLibre.

The search box loads its geocoder by engine, as GeocoderComponent does. Under
MapLibre it is MaplibreGeocoder over this map's own local index, so the keyless
gallery makes no request to api.mapbox.com at all.

The legend's row caret, clear chip and collapse/reopen buttons carry accessible
names and aria-expanded from legend.a11y.* and aria.* (optional i18n, English
fallback), the keys the library legend uses. check:shared-reached now counts
pplr-data-maps as carrying legend-a11y-keys.

window.__pplrMap() reports what the active view's map did (engine, load fired,
style layers, this bundle's data sources), so "loaded" is asserted, not inferred.

Measured on a served built tree, host page on another origin: all ten views
loaded under MapLibre and under Mapbox (load fired, style layers and data
sources present, canvas not blank, 0 errors), with 0 api.mapbox.com requests
under MapLibre. The same check on the parent build fails four map views under
MapLibre. A control with no provider makes 13 Mapbox requests.
2026-09-25 09:57:17 +00:00
doxa-maps-88655c70
fc93847bb6 Batch selection builds a packet, instead of recolouring regions
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
Driver: the batch tool is "super buggy", and letting you change the
colour of regions is NOT what is wanted - "the batch selection tool
should let you create your own poster or prayer packet based on your own
selection."

So the selection stops being a thing you PAINT and becomes an INPUT to
the two artefacts this app already makes. Gone: the eight colour
swatches, the custom hex input, and "Copy selection", which produced a
clipboard string and a recolour and nothing a reader could pray from.
In their place the HUD offers Prayer List, Poster Map and Clear, in all
eleven locales - the keys already existed, so this adds no translation
debt.

NO THIRD PIPELINE, which the brief rules out. Both actions end in
printPacket / printPoster, the same calls the legend and the poster wall
make. The selection is handed over as a SYNTHETIC list whose selector is
a pg_slugs union, because 12 of the 27 lists already use pg_slugs and
the evaluator THROWS on that type: resolving the union to concrete slugs
once, against a single fetch of the people-groups catalogue, gives every
existing consumer the shape it already handles. Resolving per zone would
refetch the whole catalogue per zone and still leave the caller to merge.

The packet's map page is per-zone artwork, so a multi-zone selection
prints a cover WITHOUT the map rather than letting one zone's map stand
in for all of them. A poster has no artwork for an arbitrary union at
all, so with several territories selected it says so plainly instead of
silently printing the first zone's poster as though it covered the
selection - generating one would be the new pipeline the brief forbids.

MEASURED at the rendered layer, driving the documented __UPG_SELECT__
gate API rather than guessing at the interaction:
  selection      3 territories, exactly Middle East Core / East Africa /
                 Sudan, batch mode on
  recolour UI    swatches gone, hex gone, copy gone
  buttons        en "Prayer List / Poster Map / Clear selection",
                 ar "قائمة الصلاة / خريطة الملصق / إلغاء التحديد"
  page errors    0
2026-09-25 09:56:14 +00:00
doxa-maps-88655c70
6c1c543a92 Take main
# Conflicts:
#	public/doxa-maps/index.html
2026-09-25 09:52:42 +00:00
doxa-maps-88655c70
57fecfd0da A gate that asks whether anything is there — and it caught a dead bundle
Wrote this after shipping a page that returned 200 for every asset, reported
zero console errors, and was completely dead: a throw during setup dies inside
the custom-element boundary. Zero errors is not the same as working, so this
does not trust their absence — it looks for PRESENCE, on all five leaves, in
five steps that each name their own failure: the tag is defined, the host is in
the document, a shadowRoot is attached, it has children, it renders text.

Its first real run went red on all five, and it was right. Every gospel leaf was
dead with "Identifier 'Ge' has already been declared" — while the SRI hash
still matched and every request still returned 200. My own doing: four profiles
imported en/gospel.json directly for an English fallback, and library/i18n
already pulls that exact file in through an eager glob, so the IIFE carried two
instances of one module.

The fix is one module: library/i18n now exports EN_MESSAGES and the profiles
read it from there. The duplicate is gone.

Measured after: all five leaves mount with 3,228 to 233,307 characters of
rendered text, verify-gospel-map passes with 2,107 pins and the gap cutting to
370, and --poison confirms the check goes red on a page with no element at all.

The lesson is the gate's whole reason to exist. I had rich assertions about pin
counts and filters and none of them ran on data-tables, charts or the list, so a
bundle-level failure could take out every leaf while the one gate I did run was
pointed elsewhere. Cheap and universal beats rich and narrow for THIS question.
2026-09-25 09:52:25 +00:00
doxa-maps-88655c70
631f0f027b Merge remote-tracking branch 'origin/main' into rega/research-gallery-embed
# Conflicts:
#	public/doxa-maps/index.html
2026-09-25 09:46:44 +00:00
doxa-maps-88655c70
319064a11b Map place names in the reader's language, on both engines
Every map translated its own chrome but left the basemap's country, city
and street names in the style default (English on Mapbox, Latin + native
on OpenFreeMap) in all eleven locales. A reader in Arabic saw an Arabic
legend over an English map.

library/maps/localize-labels.js uses each engine's supported mechanism:
- Mapbox: map.setLanguage(lang). It also localizes the Standard style's
  IMPORTED basemap, which a layer rewrite cannot reach.
- MapLibre: basemap symbol layers point at
  ['coalesce', name:<lang>, name:latin, name]. English restores each
  layer's original expression, re-captured on every style load so a theme
  switch never restores the previous style's labels.
Only layers whose text-field reads an OSM name field are touched. A
plugin's own label layers (zone titles, group names) are never rewritten.

useMapInstance applies it on every style.load and when the reader's
language changes (ProfileLoader's 'lang', else profile-config locale, else
?lang / <html lang>). The Prayer Walk Planner drops its private copy for
the shared one.

Measured on the Prayer Progress map, ar/zh/ru/hi:
- Mapbox getLanguage() == locale in all four.
- MapLibre: 13/14 symbol layers localized, and the plugin's own label
  layer untouched.
Read in the Arabic screenshots: القاهرة, الإسكندرية, السويس, الأردن on
both engines. check:gallery-embed (+ :maplibre) and
check:prayer-walk-planner pass. The shared change reaches all 11 bundles
that compile initializeMap, and all are rebuilt here. check:shared-reached
marker basemap-labels-localized: 10/10 consumers.
2026-09-25 09:46:21 +00:00
doxa-maps-88655c70
52ab608925 Take main
# 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-25 09:30:10 +00:00
doxa-maps-88655c70
df3e383c42 One source for the bridge candidate, and the map shows it too
The table offered 107 groups a next step the map did not show. The candidate is
now computed once, in the pin build beside the curated bridge, and both surfaces
read it — the same rule that put the tier in one place. 1,167 candidates across
the research index; 107 of them reach Doxa groups the curated bridge misses.

The map's sheet renders it as its own weaker panel, and the words carry the
limit: "Possibly a way in — worth checking locally", with the language, how
many groups there speak it and what share of the country they are, and "not
ranked and not recommended". No strength, because none can honestly be
computed. Never for a sign language.

A REGRESSION I INTRODUCED AND ALMOST SHIPPED. The edit that wired this used
pinCandidates without declaring it — a string replace that matched nothing and
said nothing. The ReferenceError died inside the custom-element boundary, so the
map never mounted AND the page reported zero errors. My probe then failed with a
30-second timeout that I had caused separately, by passing playwright's timeout
as the waitForFunction ARG rather than its options, which had been silently
using the default in eleven scripts. Two faults stacked: a broken page that
looked error-free, and a probe too impatient to say so.

Zero page errors is not the same as working, and that is the only reason this
was caught: __grState never appeared, which no assertion about pins could have
explained away. Verified after the fix: 2,107 pins, gap cuts to 370, the
candidate panel renders for Abai Sungai naming Malay, and 25 Deaf sheets still
show no bridge at all.
2026-09-25 09:29:48 +00:00
doxa-maps-88655c70
2221d11bda Merge main into bundler-2/work
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
# Conflicts:
#	public/doxa-maps/index.html
2026-09-25 09:19:04 +00:00
doxa-maps-88655c70
d400861d79 Merge main into bundler-2/work
# Conflicts:
#	public/doxa-maps/big-picture/big-picture.js
#	public/doxa-maps/big-picture/big-picture/big-picture/index.html
#	public/doxa-maps/big-picture/big-picture/frontier/index.html
#	public/doxa-maps/big-picture/big-picture/index.html
#	public/doxa-maps/country-posters/countries-map/index.html
#	public/doxa-maps/country-posters/country-poster-wall/index.html
#	public/doxa-maps/country-posters/country-posters-mfe/index.html
#	public/doxa-maps/country-posters/country-posters.js
#	public/doxa-maps/doxa-research-map/doxa-research-map.js
#	public/doxa-maps/doxa-research-map/research-map/index.html
#	public/doxa-maps/doxa-research-map/research-map/maplibre/index.html
#	public/doxa-maps/index.html
#	public/doxa-maps/manifest.json
#	public/doxa-maps/people-groups-grid/people-groups-grid.js
#	public/doxa-maps/people-groups-grid/people-groups-grid/driven/index.html
#	public/doxa-maps/people-groups-grid/people-groups-grid/index.html
#	public/doxa-maps/people-groups-grid/people-groups-grid/people-groups-grid/index.html
#	public/doxa-maps/research-dashboard/research-dashboard.js
#	public/doxa-maps/research-dashboard/research-dashboard/index.html
2026-09-25 09:18:16 +00:00
doxa-maps-88655c70
4c30190f3b Merge main: rebuild doxa-maps-spa for the incoming source, regenerate tree and SRI
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
2026-09-25 09:15:41 +00:00
doxa-maps-88655c70
53e2c6623e The last English in the shared header, panel and facet rail — and a 24px touch target
E RESIDUALS, from rega's in-gallery re-sweep. All three lived in SHARED
components, which is why a grep of my own plugin found nothing:

  SearchFilterHeader  its label map was a literal object of English defaults —
                      the fallbacks every host gets, since no host passes
                      `labels`. Filters, Selected, World and the chip remove
                      button now come from the catalogue; a host's own labels
                      still win, which is what the prop is for.
  ResearchMapFilterPanel  the two "Any" options and the "{n} active" pill.
  DashboardFacetRail  the Expand/Collapse aria template, which repeats per row:
                      one untranslated template is dozens of English strings in
                      a screen reader.
  dashboard.config.js the facet SECTION titles (People, Place, …). The earlier
                      pass converted `label:` and missed `title:`.

★ AND A LOOKUP BUG THAT LOOKED EXACTLY LIKE A MISSING TRANSLATION. 'Any' and
'0 active' stayed English after being catalogued, because the panel asks for
`controls.<key>` while I had authored them under `dashboard.<key>`. The
catalogue had the words; the component was looking one namespace over. Keys now
live where the panel reads, and its lookup falls back across both, so a string
authored in either place renders.

MEASURED after, ru, on the built tree: 0 English chrome strings on Simple
Research and 0 on Big Picture.
One probe correction worth keeping: a substring match on 'People'/'World'
flagged 'South Asian Peoples' and 'Arab World' inside aria labels whose frame
was already translated — data, not chrome. Whole-string matching separates them.

TOUCH TARGET (regions' phone sweep): .rd-proj "🌐 Globe" measured 74x23 at
390x844, one pixel under WCAG 2.5.8 AA. min-height:24px under
@media (pointer:coarse) — stating the 24 rather than growing padding, because
the height is padding plus a line box the renderer derives from the font and the
arithmetic lands at 23.7. Mouse sizing untouched. MEASURED after: 73.5x34.

Every bundle embedding the changed shared components is rebuilt in this commit —
a shared fix is not landed until its consumers are.
2026-09-25 09:15:30 +00:00
doxa-maps-88655c70
29063551c8 Port the switcher's last rule, so six goal cards would stack as theirs do
composition/_switcher.scss has three rules and the clone carried two. The third —
`.switcher > :nth-last-child(n + 6), .switcher > :nth-last-child(n + 6) ~ *
{ flex-basis: 100% }` — gives every child a full row once there are six or more,
instead of letting them share. It is now ported onto .rd-switcher.

It changes nothing today and that is measured, not assumed: the rendered page has
3 direct children at widths 372/372/372, the same three goal cards their
RegionDetail.vue lays out, so a threshold of 6 is out of reach. It is carried
because the brief is an exact clone, and because a fifth or sixth goal card would
otherwise diverge silently from doxa.life — a one-line rule now, or a puzzle later
about why six cards behave differently here.

The other two rules in that file are deliberately NOT ported, which is a
measurement rather than an oversight: .switcher-item{display:flex} never applies
because their switcher's children are `.stack | card`, not `.switcher-item`, and
.three-part-switcher belongs to a component the regions page does not use.

The probe proves both halves, because a rule that cannot be shown to fire is
decoration. It reads the real switcher, asserts no child has flex-basis:100% at
the live count, then clones cards INTO it until there are six and reads the basis
back as 100% before restoring it. Two of my own errors are recorded in it: a first
run drove the page with ?region=africa, got the chooser, and reported "no
.rd-switcher" — a reading of the wrong page state, since the selection comes from
the hash; and a second hardcoded "+2 clones = 6" from a static tag-count that said
4, so it only reached 5 and called the rule dead when its own arithmetic was wrong.
It now derives the clones to add from the count it just measured.
2026-09-25 09:14:25 +00:00
doxa-maps-88655c70
a7d086619b Merge remote-tracking branch 'origin/main' into rega/research-gallery-embed
# Conflicts:
#	public/doxa-maps/index.html
2026-09-25 08:56:25 +00:00
doxa-maps-88655c70
27151f39a6 Research Gallery: boot on a partner page under the CORS the live server actually sends
The doxa.life script embed was blocked on a server change: the live root
manifest.json answers without Access-Control-Allow-Origin, and the gallery
fetch()es it cross-origin, so no tab could mount. Measured on nuxtbase,
though, the same static layer DOES send ACAO on every path inside a folder
of the tree (doxa-maps-spa/<anything>, even a 404; assets/…).

So generate-tree writes a byte-identical manifest copy at
doxa-maps-spa/manifest.json in the same step as the root one (they cannot
diverge). The gallery reads it FIRST when the tree is on another origin (no
CORS error in the host's console) and the root first when same-origin.
Script URLs still resolve against the tree root, never the copy's folder.

check:gallery-embed:live-cors serves the tree with the live rule
(serve-strict LIVE_CORS=1: no ACAO on the root manifest only).
- The current gallery under it: "did not boot within 60s" (negative
  control, reproducing doxa.life).
- With this change: js 14, iframe 14, neighbour 14, linked 4. The default
  gate also passes all phases.
check:embed-targets, check:drop and check:public-hygiene pass with the copy
present.

The server-side CORS rule (serving/traefik-embed-headers.yaml) is still the
right long-term fix, and it needs server access. This unblocks the embed
without it once the tree is published.
2026-09-25 08:55:52 +00:00
doxa-maps-88655c70
79dd00a12c Merge main: no source conflicts, artifacts rebuilt rather than merged
Every conflict was a build artifact — another agent had pushed a full rebuild of
all 23 bundles. Minified .js and baked-SRI leaf pages are generated files: merging
their text is meaningless and can produce a bundle that matches neither side, so
they are resolved by taking one side and then rebuilding from the merged source.

The incoming diff carries 68 source files, among them library/composables/
useMapInstance.js and the dashboard/gospel catalogues in 10 locales, so the
rebuild is all 23 bundles and not only the ones I touched — regenerating the tree
without rebuilding has shipped a stale .js here before.
2026-09-25 08:42:13 +00:00
doxa-maps-88655c70
0adf6c3021 Take main
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
2026-09-25 08:40:42 +00:00
doxa-maps-88655c70
8313f766b6 Bridge candidates as evidence, and no spoken bridge for a Deaf community
The curated bridge covers 168 of Doxa's 369 with nothing, because it comes from
a curated subset rather than from the data. A candidate IS computable for the
rest: the largest language in their country carrying both Scripture and the film.

WHAT I COULD NOT COMPUTE, AND THEREFORE DO NOT CLAIM. Ranking those candidates
needs a notion of "is this a lingua franca", and neither available proxy
survives contact. By GROUP COUNT, Oman picks Swahili — two groups — for an
Arabic-speaking people. By SHARE OF PINNED POPULATION, a 10% floor REFUSES Urdu
in Pakistan at 7.3%, the national language, because few Pakistani groups are
pinned as Urdu speakers. That is the same bad-denominator mistake this map
already fixed on the choropleth. So no strength is invented: the candidate ships
with its group count and its share, under column names that say "candidate",
and a reader who knows Pakistan can see Urdu at 7.3% and know what to do with it.
A score would have thrown it away on their behalf.

AND THE ERROR CAUGHT BEFORE SHIPPING. The candidate logic was offering 89 Deaf
groups a SPOKEN language — Deaf Afghans to Turkmen, Deaf Arubans to Dutch. That
is not a weak bridge, it is not a bridge, and a mobiliser acting on it would be
sent to do something that cannot reach the people. This build already refuses to
search GRN for sign languages because GRN publishes audio; the candidate logic
had inherited the gap. Now: 92 Deaf groups are offered none, and their gap is
named as the access problem it is.

Of the 369 with nothing: 168 curated bridge, 107 computed candidate, 92 Deaf,
2 with neither.
2026-09-25 08:40:25 +00:00
doxa-maps-88655c70
3d4cd4004e Plugin studio: host attributes stay on the host; map-load measurement probe
inheritAttrs:false on the studio root, so a host's attributes (style,
class, id) stay on the <plugin-studio> element instead of also landing
on the layout inside it: the trap that made the Prayer Walk Planner
76px tall in the gallery. Layout verified unchanged in four hosts on the
built bundle: its own page (shell 1280x900, body 836), a bare partner
snippet (85vh default), a host that sets its own inline height (the
style stays on the host; the shell root has no style attribute), and
the app-shell mounter from BUG-EMBEDDED-PLUGIN-STUDIO-BLACK-SCREEN,
display:block forced on the host: body 656px, rail and tree rendered.
That bug no longer reproduces on the dx-maps side.

internal/measure-map-load.mjs: per production map, the cold, throttled
waterfall (bundle, engine script, style, data, basemap up, visible
tiles loaded) on a served built tree, from the leaf page or, with
--embed, from a partner page that pastes only the embed snippet. It
showed that Vue is already inlined in every bundle and that the engine
waits on the bundle only on partner pages.
2026-09-25 08:34:52 +00:00
doxa-maps-88655c70
afe04bc835 Take main
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
2026-09-25 08:27:09 +00:00
doxa-maps-88655c70
efb19637fd Merge main into bundler-2/work
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
# Conflicts:
#	public/doxa-maps/dashboard-plus-grid/dashboard-plus-grid.js
#	public/doxa-maps/dashboard-plus-grid/dashboard-plus-grid/default/index.html
#	public/doxa-maps/dashboard-plus-grid/dashboard-plus-grid/index.html
#	public/doxa-maps/index.html
#	public/doxa-maps/manifest.json
2026-09-25 08:26:59 +00:00
doxa-maps-88655c70
01d768fdb6 Put the way in next to the gap: bridge languages in the Doxa export
The access table could say "nothing found" and stop. For 168 of Doxa's 369
that is not the whole truth: a language already spoken widely in their country
has Scripture and the film. Afitti in Sudan has nothing of their own; 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, and it is the
next action a mobiliser can take — which is why it belongs in the EXPORT and not
only in the map's sheet, where it has been sitting unexportable.

Four columns: the bridge language, its ISO code, the strength, and how many
groups in that country speak it. Keyed by code|country, because the same
language bridges differently in different places.

IT CLOSES NO GAP AND THE DEFINITION SAYS SO. A bridge is a way in, not a
translation. These 168 are still counted in "nothing found", because they still
have nothing in the language they think in, and the first believers among them
still cannot be trained from a text that does not exist.

The other 201 have nothing AND no bridge. That number is worth having on its own:
it is the isolated set, where neither their own language nor anything spoken
around them carries the gospel.

Verified end to end: the CSV exports 2,107 rows carrying Bridge language, Bridge
ISO, Bridge strength and Groups speaking it, and the Afitti row reads Arabic,
Sudanese.
2026-09-25 08:26:52 +00:00
doxa-maps-88655c70
e67d718603 Merge main into bundler-2/work
# Conflicts:
#	public/doxa-maps/index.html
2026-09-25 08:22:13 +00:00
doxa-maps-88655c70
cc09fd6184 Merge remote-tracking branch 'origin/main' into rega/research-gallery-embed
# Conflicts:
#	public/doxa-maps/index.html
2026-09-25 08:21:21 +00:00
doxa-maps-88655c70
4fb8176b00 Shape Arabic, Hebrew and Persian map labels on MapLibre maps too
library/maps/rtl-text.js installed the RTL text plugin for MAPBOX only
(window.mapboxgl, from api.mapbox.com). So every MapLibre map, i.e. every
keyless embed, drew RTL place names as disconnected, reversed glyphs.
Measured on the Prayer Walk Planner's printed Arabic sheet.

Under it was a second gap. Bundle entries call the installer at LOAD
time, before useMapInstance has lazily injected a MapLibre engine, so even
an engine-aware installer found nothing to install into.

- rtl-text.js is engine-aware. Mapbox is unchanged (same URL, same lazy
  call). MapLibre gets the same plugin package from jsDelivr, keyless and
  not api.mapbox.com, so keyless pages still make zero Mapbox requests.
  New ensureRtlTextPlugin(gl) waits until it is LOADED, for callers that
  capture a single frame (print).
- useMapInstance installs it the moment an engine global exists, the one
  point every map passes through.
- The planner drops its private copy for the shared helper.

Measured: a print map of downtown Cairo (MapLibre, keyless) shows shaped
Arabic street and place names (شارع التحرير, مسجد الطباخ …); plugin
status "loaded", fetched from jsDelivr. check:gallery-embed passes js,
iframe, neighbour and linked (14 tabs); :maplibre passes;
check:prayer-walk-planner passes.

The shared change reaches all 11 bundles that compile initializeMap, and
all are rebuilt here. check:shared-reached gains an rtl-text-maplibre
marker (10/10 consumers carry it; cities-internet, gospel-resources and
my-upg-100-list boot maps another way and are not affected).

Also: the new Experimental bundle pplr-data-maps loads the Mapbox geocoder
under maplibre (pplrData.js:38). It is an owned, printed MAPBOX_KNOWN entry
routed to its owner; delete it in their fix commit.
2026-09-25 08:20:12 +00:00
doxa-maps-88655c70
7f700935c5 Simple Research + Big Picture: the host's style stops at the box, and the last English goes
TWO FIXES THAT BOTH SHOW UP ONLY WHEN THESE PLUGINS ARE SOMEBODY ELSE'S GUEST.

1. ATTRIBUTE FALLTHROUGH. These SFCs mount AS custom elements, so a style the
   host puts on the tag lands on the component's own root element. MEASURED with
   a host style of "display:flex; position:relative; height:100%": that exact
   string appeared verbatim on the inner root div of people-groups-grid,
   stack-demo, research-map-plus-grid and dashboard-plus-grid. The gallery sets
   one on every tab, and the same fallthrough collapsed the Prayer Walk Planner
   to 76px. inheritAttrs:false on each root — a host styling the TAG is styling
   the box, not our internals.
   AFTER: the inner root carries no inline style on any of the four, and each
   still renders full height (2997-4010px in the harness), so the guard did not
   become the collapse it prevents.
   Worth knowing for the next probe: shadowRoot.firstElementChild is a <style>
   node — Vue injects styles first — so a probe that trusts it measures nothing
   and reports "no fallthrough, nothing to fix". Take the first non-style child.

2. THE RESIDUAL CHROME the in-gallery re-measure found: the dashboard title,
   Align Dashboard and its tooltip, World, Vertical Legend, groups in scope, the
   per-row aria templates (Filter by / Remove filter / groups), and the map
   drawer's aria label. Catalogued in all 11 locales and read through tStatic,
   reusing existing wording where the catalogue already had the word.

The aria TEMPLATES matter more than their size suggests: they repeat per row, so
one untranslated template is dozens of English strings in a screen-reader's ear.
2026-09-25 08:19:56 +00:00
doxa-maps-88655c70
e941d642ce Take main
# Conflicts:
#	public/doxa-maps/index.html
2026-09-25 08:18:26 +00:00
doxa-maps-88655c70
77f24ae5ab The last two profiles: chrome translated, prose kept English on purpose
resources-list and charts turned out to be almost entirely explanatory prose.
Their actual chrome is four strings — two page titles, the loading line and
Close — and those are now in all eleven locales. The blanket dir=ltr is off both
roots, so Arabic gets its layout, and each English prose block declares its own
direction where it sits.

That completes option (b) across all five gospel profiles. What stays English is
what carries doctrinal and statistical precision: which pools were searched, what
"nothing found" does and does not claim, why Open Bible Stories is not
Scripture. A machine translation of those would read as authoritative while
being wrong, and one translated line in each locale says so in the reader's own
language.

Verified on a served built tree whose identity I proved first — the Russian title
appears once served and once on disk. en/ar/ru on both leaves: heading
translated, 0 clipped, dir=rtl only for Arabic, no page errors and nothing 4xx.
2026-09-25 08:17:54 +00:00
doxa-maps-88655c70
0fee065212 Composites forward provider, so a MapLibre partner gets MapLibre
A composite shell copies a whitelist of keys from its own profile-config down to
each map it nests. `provider` was not on any of the three lists, so a nested pane
read it as undefined and fell back to Mapbox: a partner who asked for MapLibre,
and who holds no Mapbox token, got Mapbox maps in every inner pane. Same shape as
the missing `urlSync` here before it -- an inner map cannot honour a key the shell
never passed down.

The report named country-posters-mfe. All three composites had it: regions-mfe and
upg-100-mfe were missing `provider` too, so fixing only the one named would have
left two thirds of the defect in place.

Verification is a probe, not an inspection, and it needs a control. "0 requests to
api.mapbox.com" passes trivially on a page where nothing mounted -- the most likely
way this measurement goes wrong -- so each composite is loaded twice: once with
provider=maplibre expecting zero, once with provider omitted expecting more than
zero. The second run is what proves the first measured anything; a run that
renders no canvas is reported UNPROVEN rather than passing.

It also cannot be measured on a leaf page. Every leaf loads its map peer in its
own <head> (regions-mfe/index.html line 13 is a hardcoded api.mapbox.com script),
so on a leaf zero mapbox requests is unreachable whatever provider says. The
question only has meaning on a host that picks the peer itself, which is what a
partner embedding the .js actually does.
2026-09-25 08:12:20 +00:00
doxa-maps-88655c70
edb66d2805 Correct the record: there is no RTL column-clipping bug
The last commit recorded "in RTL the leftmost data column clips against the
edge" as known debt. Measured rather than eyeballed, that is wrong. Counting
cells falling outside the table holder: English and Arabic are IDENTICAL — 13 of
20 columns off-screen in both, scrollWidth 3146 against clientWidth 1400 either
way.

A twenty-column table is wider than 1,400px, and RTL mirrors which edge the
overflow sits on. What I saw in the Arabic screenshot was English's right-edge
overflow appearing on the left, which is what mirroring means. I read a picture
instead of measuring, and filed the result as debt — which would have sent the
next person chasing a defect that does not exist.

Whether twenty columns is right for a 1,400px screen is a real question. It is a
design question, and it is the same question in English.
2026-09-25 07:59:22 +00:00
doxa-maps-88655c70
9eb65faa99 Merge remote-tracking branch 'origin/main' into rega/research-gallery-embed
# Conflicts:
#	public/doxa-maps/index.html
2026-09-25 07:55:26 +00:00
doxa-maps-88655c70
2043384435 Merge remote-tracking branch 'origin/main' into exp-shepherd/pplr
# Conflicts:
#	internal/generate-tree.mjs
2026-09-25 07:40:35 +00:00
doxa-maps-88655c70
ba057df9c9 PPLR maps: country polygons came back after PPLR renamed its fields
PPLR renamed the aCountry fields upstream (GENCalpha3 -> GENCAlpha3, CtryNm -> Name,
ctryNmDisp -> DisplayName, ISOalpha2 -> ISOAlpha2). The query asked for the old
names, ArcGIS answered HTTP 200 with { error }, and the loop read that as "no
features". Every view that draws country polygons silently lost them. The
query now asks for the new names and gives each feature the old ones back, and an
{ error } body throws.

Paging also stopped after one page: f=geojson carries exceededTransferLimit under
data.properties, so only 200 of 251 countries loaded. A full page now means ask
for more.

Coverage: the status no longer claims a 6,000-row sample, and a __pplrCoverage()
handle reports how many polygons matched a coverage row.
2026-09-25 07:40:08 +00:00
doxa-maps-88655c70
b32795aa0e Merge remote-tracking branch 'origin/main' into rega/research-gallery-embed
# Conflicts:
#	package.json
#	public/doxa-maps/index.html
2026-09-25 07:22:18 +00:00
doxa-maps-88655c70
b78e7d1b52 Prayer Walk Planner: fill the page, and print a real walking map
The Driver: "the prayer walk planner is still a very tiny map, not
expanding the full page", and the printed map was a ~10px sliver.

Tiny map. Measured: the leaf filled (1003x739), but inside the Research
Gallery the map was 747x76 on desktop and 390x0 on a phone. The planner
mounts its SFC as the custom-element root (that is what puts its client
API on the element). So Vue's attribute fallthrough copied the gallery's
inline host style onto the root .pwp, and that inline display:block beat
.pwp{display:flex}. inheritAttrs:false keeps host attributes on the host.
The map now fills at 1280x800 and 390x844, in the leaf and in the gallery.
The gallery gets the same guard: its .vtl had survived only through
display:grid !important.

Sliver print. Print used to snapshot the LIVE map, so the page got that
map's on-screen size and shape. Each zone now renders in its own
temporary MapLibre map:
- page-shaped (1200x1344 at pixelRatio 2, a 2400x2688 image, ~320 dpi on
  the 7.5x8.4in frame)
- keyless OpenFreeMap liberty, with streets and building footprints
- fitted to the zone, with the zone outlined on top
- destroyed once its pixels are taken, and the reader's map never moves
One zone = one letter page, with the map filling it.

Localized. The planner read no language at all. It now follows
language → profile-config locale → ?lang → <html lang>, in all 11
locales (private src/strings.js): toolbar, list, status, default zone
names, and the printed sheet (lang/dir set, so Arabic prints RTL).
Street and place labels prefer the OSM name:<lang>. Two Arabic defects
were found by reading the printed sheet, and fixed:
- coordinates were bidi-scrambled → isolated LTR
- map labels were unshaped glyphs → the RTL text plugin is installed
  for MapLibre, from jsDelivr, and awaited before the print frame
  (library/maps/rtl-text.js only does it for Mapbox)

renderZoneMap(id) joins the client API: the print PNG without the dialog.

New gate check:prayer-walk-planner:
- 4 size cells (leaf/gallery × desktop/phone)
- a real zone in Balat, Istanbul renders a >=2000px non-blank print map
- a Spanish toolbar
- 0 page errors
Both gates now prove the served tree is this checkout's (manifest equals
disk) before measuring, and exit 75 NOT MEASURED otherwise.
check:gallery-embed's Big Picture link now asserts that the grid shows
exactly the facet's own count.
2026-09-25 07:21:42 +00:00
doxa-maps-88655c70
0c7d324b5f Take main
# Conflicts:
#	public/doxa-maps/index.html
2026-09-25 07:20:35 +00:00
doxa-maps-88655c70
db317925d7 Translate the tables profile's chrome, and name what is still English
All twelve tab labels, the page title and the filter chip come from the
catalogue in eleven locales. The blanket dir=ltr is gone from this profile's
root, so Arabic gets the RTL layout it should have; the English prose blocks —
the subtitle, the hero sentence, the footnote and the two caveats in the chip —
declare dir=ltr where they sit, the same split the map profile uses.

The translator is named tr(), not t(), and that is load-bearing: the tab loop
binds v-for="t in TABS", so a t() here would be shadowed inside the one block
that needs it most and every label would render as whatever a tab object
stringifies to.

Verified on a served built tree whose identity I proved first — the Russian
string I had just added appears once in the SERVED bundle and once on disk, and
the port owner's cwd is this worktree. en/ar/zh/ru/hi: heading and tabs
translated, 50 rows rendering, 0 clipped chrome, dir=rtl only for Arabic.

STILL ENGLISH, and not claimed otherwise. The table's own column headers and
title come from the DATA FILE's columns[].title and meta.title, not from this
component, so translating them means translating the table schemas — a separate
job. And in RTL the leftmost data column clips against the edge; my clip check
only measured the chrome, so it did not catch it. Both are named here rather
than left for someone to discover.
2026-09-25 07:20:16 +00:00
doxa-maps-88655c70
33bd7ca387 All People Groups loads fast on a light map; the Vision Card becomes a true funnel from data
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
ALL PEOPLE GROUPS. The Driver: "not even loading, takes a very long
time", and its DOXA pins were dark on a dark map.
- It downloaded the 7.5 MB people-groups-slim.json (22 columns) to draw
  ten fields. It now fetches people-groups-points.json: the same rows in
  column order, repeated strings in dictionaries, coordinates at 3
  decimals. 0.74 MB, generated deterministically by
  library/scripts/people-groups/build-points.mjs and staged into the tree
  by generate-tree. It falls back to the old file on an older tree.
- Pins no longer wait for the live prayer API. Every pin draws by GSEC
  from the file, and the DOXA overlay recolours when the API answers. The
  DOXA count reads "…" until then, not 0.
- Named style 'paper' (Mapbox light-v11 / MapLibre positron) instead of
  'ink'. The four categories keep their meaning; yellow and green are a
  step darker, and pins get a thin dark ring, so they read on near-white.
- Its words are in all 11 locales (allPeopleGroups.json), with a guarded
  useI18n and values passed to vue-i18n so {n} is not blanked.
Measured on a served built tree, 3 fresh runs each, median time to first
rendered pins: 8.3 s before, 5.2 s after, on localhost. This box renders
GL in software, so most of the remaining time is map start-up; the 10x
smaller download matters more on a real connection.

DOXA VISION CARD. The Driver: "the data doesn't make sense … super false
information". The card rotated four typed-in figures with no source.
It is now a proportional funnel, every number computed from data by
library/scripts/people-groups/build-vision-funnel.mjs:
  12,379 people groups   (IMB rows: one group in one country)
   7,341 unreached       (IMB LostnessPriority 0-2, < 2% evangelical)
   4,754 frontier        (LostnessPriority 0, < 0.1% evangelical)
   2,107 DOXA groups     (the DOXA prayer list, snapshot saved in data/;
                          1,745 of them frontier)
The first three nest; DOXA groups do not all sit inside frontier, so the
card says how many do. The rules are on the card ("How these are
counted") and in HOW-THE-NUMBERS-ARE-COUNTED.md, which also explains why
the total is not the 16-17 thousand of Joshua Project's count. Material
light, all 11 locales, right-to-left in Arabic, sources and dates shown.
The old fake stats are removed from the instances file, the frontier
page, the manifest, the staging page and the gallery.

CHECKS.
- check:vision-card-numbers recomputes the funnel independently (own CSV
  reader, the published rules) and compares with vision-funnel.json and,
  with --url, the rendered bars. Negative-tested: a corrupted count exits
  1. On the built tree: bars 12,379 / 7,341 / 4,754 / 2,107 and "1,745 of
  them are frontier", all matching.
- check:map-embed-cells measures PRODUCTION-MATRIX cells B and C for the
  seven gallery maps on a foreign origin; C proves the host's token is
  used by handing the host a distinct token and checking every Mapbox
  request. 14 of 14 pass.
2026-09-25 07:18:30 +00:00
doxa-maps-88655c70
ad81d16511 Merge main into bundler-2/work
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
# Conflicts:
#	public/doxa-maps/index.html
2026-09-25 07:15:27 +00:00
doxa-maps-88655c70
5516c157bb Merge main into bundler-2/work
# Conflicts:
#	public/doxa-maps/country-posters/countries-map/index.html
#	public/doxa-maps/country-posters/country-poster-wall/index.html
#	public/doxa-maps/country-posters/country-posters-mfe/index.html
#	public/doxa-maps/country-posters/country-posters.js
#	public/doxa-maps/index.html
#	public/doxa-maps/manifest.json
2026-09-25 07:14:11 +00:00