Commit graph

1439 commits

Author SHA1 Message Date
doxa-maps-88655c70
42b3105fed Rebuild the consumers main left stale (build owner, README rule 5)
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
check:composite-freshness found stale on main: dashboard-plus-grid; also rebuilt: plugin-studio. Rebuilt, tree + SRI
regenerated; the landing precondition (fresh, one folder, SRI) passed in main-capture scan.
2026-09-25 20:49:01 +00:00
doxa-maps-88655c70
134b6e484e Plugin Studio: the header's menus stay on top of every plugin
Driver, 2026-09-25: "the Mapbox token menu is not z-index on top … it is underlapping the plugins". The
header row and the pane area were siblings with no stacking context of their own, so a menu at z-index 30
lost to plugin chrome (a filter bar at 55, a drawer at 1000). The header now stacks above the content and
the pane area is isolated (isolation:isolate), so no plugin's z-index can rise over the studio's header.
Measured in a browser with a plugin open: the Settings panel and the engine menu are the topmost element at
3 of 3 sampled points.
2026-09-25 20:44:24 +00:00
doxa-maps-88655c70
db92d79d0a Landing precondition: judge public/ as COMMITTED, and point stale consumers at the build owner
- Check 2 read public/ from the filesystem, so a gitignored, untracked leftover (a pre-rename public/assets/
  in an older worktree) REFUSED repo's clean README-only landing. It now reads the commit being landed
  (git ls-tree HEAD public/). Tested: an ignored leftover passes; a committed extra folder is still refused.
- The stale-consumer refusal now states README rule 5 (dx-shepherd, 2026-09-25): one agent owns the full
  build. A change that touches library/ or anything compiled into other plugins is handed to studio to land
  with the consumer rebuild; a change stale only in the author's own plugin rebuilds just that bundle.
2026-09-25 20:40:31 +00:00
doxa-maps-88655c70
c53367f920 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
2026-09-25 20:31:17 +00:00
doxa-maps-88655c70
6d919169c9 A landing is refused unless the commit is fresh, one-folder and SRI-consistent
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
check:composite-freshness found SEVEN composites stale on main (the committed bundle was not what its
source builds). Cause: d49b6c80 rebuilt all 25 bundles in a worktree that had not merged the dx-maps
rename — it wrote the OLD folder and left dx-maps/ stale — and every landing involved passed its own
checks. A check BESIDE the push can be skipped; this makes it a PRECONDITION of the push.

- internal/landing-preconditions.mjs, run through .main-capture/prepush on the exact commit about to
  land: (1) the working tree IS that commit, (2) public/ = README.md + the configured tree only — a build
  under another tree name writes another folder and is refused, (3) check:sri, (4) every composite
  rebuilds to its committed bytes.
- `main-capture scan` (the Driver's harness) runs a repo's .main-capture/prepush, when present, after its
  merge scan is clean; non-zero refuses the landing. Repos without the file are unaffected.

Proven with main-capture scan: (A) main as it was, 7 stale -> REFUSED naming all seven; (B) the rebuilt
main 7c96759d -> PASS; (C) a people-groups-grid string changed and committed WITHOUT a rebuild ->
REFUSED (big-picture, dashboard-plus-grid stale); (D) a public/doxa-maps/ folder beside dx-maps/ ->
REFUSED ("extra: doxa-maps"). Scratch commits were reset, never pushed.
2026-09-25 20:25:47 +00:00
doxa-maps-88655c70
a9add45656 Rebuild every bundle after resolving artifact conflicts
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 20:21:29 +00:00
doxa-maps-88655c70
ee46ff0610 Merge main into bundler-2/work
# Conflicts:
#	public/dx-maps/dashboard-plus-grid/dashboard-plus-grid.js
#	public/dx-maps/dashboard-plus-grid/dashboard-plus-grid/default/index.html
#	public/dx-maps/dashboard-plus-grid/dashboard-plus-grid/index.html
#	public/dx-maps/doxa-maps-spa/manifest.json
#	public/dx-maps/manifest.json
2026-09-25 20:16:05 +00:00
doxa-maps-88655c70
1ffbd3b584 Use library engineFor(map); the last local engine-resolution line is gone
exp-shepherd landed engineFor(map) in useMapProvider, so the expression I was
carrying can go. Theirs is stricter than mine: it instance-tests BOTH engines
rather than inferring the second from the first.

This closes a three-step path, and each step deleted something. It began as
glFor.js copied from pplr-data-maps; became one expression delegating to
useMapProvider's isMapboxMap once I found that already existed; and is now a
plain import. Nothing engine-related is left in this plugin.

Three implementations were compared before swapping, because a substitution I
have not measured is one I am guessing at: _chk-glfor-equivalence.mjs builds the
deleted copy, my interim expression and library engineFor, and runs all three
across every engine combination — mapbox map with both globals, maplibre with
both, either alone, and the case the whole fix exists for, a MapLibre map with no
Mapbox global at all. Identical in all of them.

Built on the POST-RENAME tree: 68 files under public/dx-maps/, none under the old
public/doxa-maps/. My previous all-bundle rebuild predated that rename in this
worktree and wrote orphans; confirming the tree name before building is now part
of the sequence. composite-freshness PASS on all 10, check:sri PASS.
2026-09-25 20:10:13 +00:00
doxa-maps-88655c70
7c96759d4e Rebuild every bundle on the dx-maps tree: seven composites were stale on main
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
check:composite-freshness failed on main for big-picture, country-posters, dashboard-plus-grid,
gospel-resources, my-upg-100-list, regions-dashboard and research-map-plus-grid: their committed bundles
were not what their source builds. Cause (dx-shepherd): d49b6c80 rebuilt all 25 bundles in a worktree
that had not merged the dx-maps rename, so it wrote the OLD folder and left dx-maps/ stale (the orphans
90910f32 removed). All 25 rebuilt here on current main with the dx-maps config, tree + SRI regenerated.
check:composite-freshness, check:sri, check:drop, check:public-hygiene PASS.
2026-09-25 20:08:30 +00:00
doxa-maps-88655c70
2590fe5eb1 Merge main into bundler-2/work 2026-09-25 20:00:13 +00:00
doxa-maps-88655c70
20e3d5f1e4 Rebuild the bundles the merge left behind in the renamed tree 2026-09-25 20:00:09 +00:00
doxa-maps-88655c70
90910f32f6 public/ is one folder again: drop the stale doxa-maps/ bundles a rebuild re-added, and make that a hard failure
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
d49b6c80 rebuilt four bundles under the OLD tree name after the rename to dx-maps (09014f8a), putting
public/doxa-maps/{big-picture,doxa-research-map,research-map-plus-grid,wall-of-people-groups}.js back
beside public/dx-maps/. Nothing references them (the live copies are under dx-maps/). Removed.

check:public-hygiene only WARNED about a root entry outside the drop-in contract unless run --strict, so
the build stayed green. public/ holding only the output folder + README.md is a Driver decision
(BUILD-OUTPUT-IS-ONE-FOLDER), so it is now always a finding. Proven both ways: with the orphans the gate
FAILS ('doxa-maps/ — outside the drop-in contract (dx-maps, README.md only)'); without them it passes.
2026-09-25 19:54:59 +00:00
doxa-maps-88655c70
a7c65c2bb9 check:experimental-shelf looks at the map: paint arm with its own poisons, and settled counts
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
Every earlier assertion counted FEATURES, and queryRenderedFeatures answers happily over a white
rectangle (gospel's incident, 0574fe7f). Each map surface now also passes two looks:
  - COLOURS read off the map's own WebGL canvas inside its 'render' event (floor 40), so a legend
    or panel over the map cannot inflate the count and no per-plugin clip is needed;
  - FILL = the map's height over the plugin ELEMENT's (floor 70%), not canvas over container,
    which stayed 1.0 when a stylesheet collapsed both together.
Each arm has its own self-test poison and must be the one that catches it: 'blank' (every layer's
opacity 0) -> "only 1 colour(s) on its canvas ... while 12204 features still count as drawn";
'collapse' (container squeezed to 300px) -> "fills 38% of <all-people-groups>'s height".
Drawn counts are now read SETTLED (equal on two consecutive polls): a first-non-zero read took
1752 of 2107 mid-fit on Cities. The PASS line names only the surfaces that ran.
Measured: 7 bundles, 60 rows PASS, 3 n/a; lowest paint across the 40 map rows 264 colours, 92%.
2026-09-25 19:54:41 +00:00
doxa-maps-88655c70
01096d67d3 Integrate main into bundler-2/work 2026-09-25 19:50:05 +00:00
doxa-maps-88655c70
73fc1582ce Rebuild dashboard-plus-grid into the renamed tree
The tree is now published as dx-maps rather than doxa-maps, and the merge left
this composite's committed bundle behind its source under the new name.
Freshness and SRI are clean.
2026-09-25 19:49:57 +00:00
doxa-maps-88655c70
9fb4f8350a The leak gate proves its exclusions do not blind it
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
The numeric-sibling rule shipped last tick as a rider on the RTL commit
and was never confirmed by a run — both attempts returned RC=75. Live,
plausible and unmeasured is the riskiest state a change can be in, and
a GENERAL exclusion makes it worse: "an element whose own text sits
beside a sibling rendering a number is enumerating data" can go green by
excluding too much, which is the same thing as never checking.

So --self-test INJECTS a real chrome leak in the position the rule is
most likely to swallow: an English catalogue value in a label that HAS a
numeric sibling, plus a control with none. Both must still be caught.
The injected value is read from the catalogue rather than invented, so
the self-test exercises the same comparison the real run does.

  SELF-TEST: PASS — 6 of 6 injected leaks caught, including the one
  beside a number. The data, facet and enumerated-value exclusions
  narrow the population without blinding the gate.

  NORMAL: PASS — 8 of 8 combinations in ar/zh/ru/hi, 74-83 comparable
  keys each, so no combination was vacuous. dashboard-plus-grid is clean
  in all four locales, which closes Big Picture cell E.

⚠ THE SELF-TEST'S OWN FIRST RUN REPORTED NOT MEASURED, and that is worth
keeping: every non-flag argv is a target, so `--self-test` was consumed
as a leaf path and all four locales 404'd. The "no combination
rendered" guard caught my harness instead of reporting 0 leaks caught
out of 0 expected, which I could have read as a pass. Flags are now
filtered out of the target list.
2026-09-25 19:42:51 +00:00
doxa-maps-88655c70
c5cbaf4277 Integrate main into bundler-2/work
# Conflicts:
#	public/dx-maps/dashboard-plus-grid/dashboard-plus-grid.js
#	public/dx-maps/dashboard-plus-grid/dashboard-plus-grid/default/index.html
#	public/dx-maps/dashboard-plus-grid/dashboard-plus-grid/index.html
#	public/dx-maps/doxa-maps-spa/manifest.json
#	public/dx-maps/manifest.json
#	public/dx-maps/research-dashboard/research-dashboard.js
#	public/dx-maps/research-dashboard/research-dashboard/index.html
2026-09-25 19:41:21 +00:00
doxa-maps-88655c70
c727c87411 Plugin Studio header: language, map engine and Settings set the props every plugin gets
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
Driver, 2026-09-25: "the studio at the top needs a localization dropdown, a map dropdown to swap
between Mapbox and MapLibre, and a settings button to add a Mapbox token or the URL it comes from…
the header needs to let you swap between props… so the studio becomes more functionally useful."

The header now reads: Language ▾ · 🗺 Map engine ▾ · ⚙ Settings.
- Map engine: Mapbox (needs a token) / MapLibre (no token needed) / each map's own default. The choice
  is handed to every pane as its profile-config provider and published as window.MAP_PROVIDER for
  maps nested inside composites; changing it re-mounts the maps.
- ⚙ Settings replaces the separate 🔑 button: the Mapbox public token (sk.… refused) or a token-service
  URL, with a status line that says which is in use — and that MapLibre needs none.
- Kept in this browser (localStorage); one menu open at a time. New labels in all 11 locales.

Browser-tested on the built dx-maps tree: the header shows the three controls; MapLibre sets the pane's
provider and window.MAP_PROVIDER and survives a reload; Settings then says no token is needed; Mapbox
plus a pasted token puts both on the pane; "each map's own" clears the provider.
2026-09-25 19:41:20 +00:00
doxa-maps-88655c70
2509a6e347 README: three outputs, not five methods; a Languages section; screenshot gates see every shot again
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
- Deployment is now organised around what the build makes: (1) one web
  folder (536 files, 76 MiB), embedded by iframe and JavaScript (proven) and
  WordPress (just for fun); (2) the mobile app (Capacitor, built); (3) the
  desktop app (Tauri, proof of concept). A hosting table says where the folder
  can go: dx-life's public/, Cloudflare Pages drag-and-drop (fits its 1,000-file
  / 25 MiB limits), GitHub + jsDelivr (JS embeds only: measured, jsDelivr serves
  HTML as text/plain), or any web server, with the one _headers line JS embeds
  need. No Docker. The diagram and the map-cruncher picture say three outputs.
- Languages: 11 locales via ?lang=, Arabic right to left, with an Arabic shot of
  UPG-100 (Big Picture still leaks English in Arabic; reported). The partner
  profile-config locale is deliberately not documented until it is fixed.
- check-shot-freshness and check-shot-distinctness parsed only '...' paths, so
  after 44ef31b2 turned them into `/${TREE_NAME}/...` templates they saw 2 of 26
  shots: distinctness failed, freshness passed vacuously and hid 22 stale shots.
  Both now accept either form and resolve TREE_NAME, and exit 75 (NOT MEASURED)
  unless every `await shot(` call is parsed (the one dynamic dx-life call is
  marked). Negative-tested. The 22 shots are retaken and checked by eye.
- how-to: the shell no longer overwrites the Studio (it now lives in the output
  folder); README serve line points at doxa-maps/index.html.
- Docs follow the rename to dx-maps (09014f8a): every output-folder path and URL in
  README, how-to, reference, CONTRIBUTING, the CLI section and the develop guide now says
  public/dx-maps/. The repo name, the config file name, the WordPress plugin folder and
  the doxa-* tag names are unchanged (tags are a separate landing).
- Hosting: a live example, the dx-life branch research-gallery, which drops the folder
  into dx-life public/ and fills /research with the Research Gallery.
2026-09-25 19:40:03 +00:00
doxa-maps-88655c70
a4a7ddd6fd Rebuild every bundle after resolving artifact conflicts
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 19:36:53 +00:00
doxa-maps-88655c70
d49b6c8093 Rebuild all 25 bundles: my own conflict shortcut had staled seven of them
I have been resolving artifact conflicts with `git checkout --theirs -- public/`
and then rebuilding only the bundles whose SOURCE arrived in the incoming diff.
That is wrong, and it put seven stale composites on main under 6cb61caa:
--theirs replaces my freshly built artifacts with whatever main happened to hold,
including bundles that nothing in the incoming diff mentions. big-picture,
country-posters, dashboard-plus-grid, research-map-plus-grid and my own
regions-dashboard were all committed as bytes their source no longer builds.

Worse, the gate TOLD me. My landing command printed
"research-map-plus-grid is STALE" and pushed anyway, because I ran freshness
next to the push instead of gating the push on it. A check whose output nobody
is obliged to act on is decoration.

Two changes, both in how I land rather than in the product:
  - after taking --theirs for public/, rebuild EVERY bundle, not the incoming
    subset: --theirs can import staleness into any of them, so the incoming diff
    is the wrong trigger here for the same reason it was the wrong trigger when
    regions-dashboard went stale this morning;
  - the push is now conditional on composite-freshness PASS, so this cannot be
    printed and ignored again.

All 25 rebuilt, check:sri PASS across every leaf, composite-freshness PASS.
2026-09-25 19:29:13 +00:00
doxa-maps-88655c70
b122009b96 Merge main into bundler-2/work 2026-09-25 19:27:30 +00:00
doxa-maps-88655c70
588a2a5d3c Translate the map legend's title, without inventing a word
Big Picture in Arabic still showed "UPGs / country" over its choropleth legend.
It is now composed from two catalogue entries that already ship in all eleven
locales — legend.columns.upgs and dashboard.country — so nothing new had to be
written in ten languages to fix it. The English shifts to "UUPGs / Country",
which is how this tree spells both words everywhere else.

It was the only one of the six remaining English strings on that view that
could be done this way. "Export CSV", "Full screen", the detail drawer's title
and empty state, and the legend's "heartbeat zone" bin all need wording that
does not exist yet, so they are on DECIDE-SR-CHROME-WITH-NO-CATALOGUE-WORDING
with one useful note: "Full screen" needs NO new wording either, because
buttons.toggleFullscreen already ships in every locale — only a decision to use
one toggle label instead of a two-state pair.

★ TWO OF THE REPORTED DEFECTS DO NOT REPRODUCE, and the measurement says why.
The table's last column header is neither English nor clipped on this tree: all
nine headers render Arabic and every one of them has scrollWidth equal to
clientWidth at 1440×900. The screenshot that reported "ent" predates the commit
that gave that column a catalogue getter.
2026-09-25 19:27:29 +00:00
doxa-maps-88655c70
09014f8a03 Rename the output folder to dx-maps
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
Driver decision: no embeddable URL carries "doxa" in its folder. The tree's name is one line in
vite.doxa-maps-build-config.json ("name": "dx-maps"); everything else follows it.

- __DOXA_TREE_NAME__ (the vite define rega landed, from MFE_NAME = internal/_tree-name.mjs) bakes the name into the
  bundles for the runtime code that must recognise the tree in a URL. StackLayout.vue's
  resolveBundleSrc anchored on a literal /doxa-maps/ and would have broken every composite's nested
  bundles on a renamed tree; it now anchors on the define. The Research Gallery's dev fallback uses
  it (one reader: rega's define, not a second one).
- The native shells follow via internal/sync-app-shells.mjs: capacitor webDir public/dx-maps, Tauri
  frontendDist ../../public/dx-maps (its window url is tree-relative since the one-folder landing).
- The WordPress contract test reads the name from config instead of carrying three literals.
- check:locale-reachable extracted detectLocale alone and died with "localeFromUrl is not defined"
  after 09d03c60 factored a helper out; it now follows the calls it makes (already red on main).
- generate-tree removes its temp asset pool even when it crashes (a failed build leaked ~50 MB into
  /tmp each time; measured on a full disk).
- Capability exemplars regenerated for the new paths.

Proof: rm -rf public && bun run build -> public/ = README.md + dx-maps/; the 25 built bundles carry
zero 'doxa-maps/' paths (scanned); check:sri, check:drop, check:parse, check:public-hygiene PASS.
Tag names (doxa-* -> dx-*) are the next, separate landing.
2026-09-25 19:26:14 +00:00
doxa-maps-88655c70
cc5252b563 Rebuild Wall of People Groups with the offline notice, and regenerate the tree
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 19:24:09 +00:00
doxa-maps-88655c70
e31738762c Wall of People Groups: say so when the live API cannot be reached, and stop a render error
With pray.doxa.life unreachable (offline, or a local dev server whose 127.0.0.1 origin the API
refuses by CORS) the wall sat at "0 Groups Loaded" with nothing explaining why, and logged
"Cannot read properties of undefined (reading 'length')". That error was not the fetch at all:
SourceSelector's template read `sources`, which does not exist (its computed is
availableSources), so it threw on every render, online as well.
  - SourceSelector: availableSources.
  - PeopleGroupsWall: when nothing loaded and the store holds an error, an in-wall notice says
    what failed and offers Try again (inside the wall, never position:fixed over a host page).
  - The store logs the network failure as a warning: it is an expected state now shown on screen.
Measured with every pray.doxa.life request aborted: the TypeError is gone, the notice renders,
and the only console line left is the browser's own "Failed to load resource". Unblocking and
pressing Try again loads 2107 groups. Live (unblocked): 2107 groups, 0 console errors.
2026-09-25 19:20:48 +00:00
doxa-maps-88655c70
5e7ad5a409 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
2026-09-25 19:16:39 +00:00
doxa-maps-88655c70
4274ec45dd Rebuild the bundles the merge left behind, so no bundle ships an older copy
check:composite-freshness named seven by hash after integrating main. A build
artifact cannot be merged line by line, so they were rebuilt from the merged
sources; freshness and SRI are clean.
2026-09-25 19:16:37 +00:00
doxa-maps-88655c70
6cb61caa62 Merge main: rebuild what the incoming source requires
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 19:08:24 +00:00
doxa-maps-88655c70
57065889f6 Integrate main into bundler-2/work 2026-09-25 19:07:04 +00:00
doxa-maps-88655c70
82d7f21e85 The raw map shapes Arabic, Persian and Hebrew place names
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
upg-100-list builds its map RAW (`new gl.Map`) rather than through
useMapInstance, so it inherits none of the bundle entry's side effects —
including the RTL text plugin. It appeared to work only because the leaf
head PRELOADED the Mapbox peer, which let an entry-time registration
land in time. studio is removing that preload.

⚠ IT IS NOT A UI-LANGUAGE FEATURE, which is why it matters more than it
looks. A basemap draws each place in its OWN script, so over the Middle
East, Iran and Israel MOST labels are Arabic, Persian or Hebrew for
EVERY viewer in EVERY UI language. Unshaped they render reversed — a
WRONG NAME for exactly the groups this map is about — and it fails
silently: no error, no warning, just mirrored text that reads as a font
problem to anyone who does not know the script.

MEASURED with gospel's gate, which PANS to Arabia before judging because
the shaper registers lazily and reads 'deferred' at a world view:

  before   mapbox    ok    58/61 RTL labels shaped
           maplibre  FAIL  62 RTL labels on screen, shaper 'requested'
  after    mapbox    ok    58/61
           maplibre  ok    63/63 shaped

★ THE MAPBOX ARM PASSING BEFOREHAND IS THE FINDING. It is the preload
rescuing an entry-time call, so mapbox looks fine today and breaks the
moment that preload goes. MAPLIBRE ALREADY FAILED, with no preload to
rescue it — this was live for every keyless viewer, not only a future
regression. Measuring just the mapbox arm would have read "already
fine".

The install goes after the engine global resolves and before
new gl.Map: with the engine injected lazily, an index.js call runs
before `gl` exists and does nothing at all.

THE GATE IS PARAMETERISED, NOT FORKED. dx-shepherd routed it to the
other raw-map owners (cities-internet, key-cities, RegionCountryMap), so
it takes --leaf instead of hardcoding gospel's. Copying it would have
given each owner a drifting copy of the pan-first logic and the poison
arm — the two parts that needed corrections to get right.

Also carries the leak gate's third correction: the survivors of the
data and facet exclusions were in .rd-lt inside .rd-li and .rd-bl inside
.rd-bar — DashboardChartsRail renders `{{ s.key }}` beside `{{ s.value }}`
over chart.geometry, the same shape as a facet row. Chasing one class
name at a time is how that went wrong three times, so the rule is now
general: an element whose own text sits beside a sibling rendering a
NUMBER is enumerating data, not naming the UI. Section headings and
column titles have no numeric sibling and stay in scope.
2026-09-25 19:05:59 +00:00
doxa-maps-88655c70
86442aa4c0 The UPG-100 search placeholder speaks the reader's language
repo's Arabic screenshot of the list was clean except for one string:
the search box still read "Search people group, zone, or territory…" in
every locale. It was a hardcoded literal, the last English on an
otherwise localized screen.

NO NEW TERMINOLOGY — composed from wording this product already ships
and has already approved: search.kinds.peopleGroup for the group,
tabs.upg100Zone for what this plugin calls a "zone" (its own legend
names them "UPG-100 Lists" / "قوائم UPG-100" / "UPG-100 名单"), and
search.kinds.region for the territory. That is precisely why this one
could be translated while the UUPGs column header could not: there, no
source established any candidate acronym, and a wrong term looks
authoritative to a reader who cannot check it. Here the words were
already on the page.

⚠ A computed, and declared beside LEGEND_TITLE rather than at the
literal's old position, because t() is not initialised until later in
setup — a const at the original line would have captured the fallback
and never re-evaluated when the locale changed.
2026-09-25 19:05:59 +00:00
doxa-maps-88655c70
ac6347aa15 Merge branch 'main' of https://git.nuxtbase.app/doxa-life/doxa-maps into bundler-3/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/doxa-maps-spa/manifest.json
#	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/manifest.json
2026-09-25 19:04:56 +00:00
doxa-maps-88655c70
d6cdc2771e Rebuild cities-internet and key-cities with RTL shaping, and regenerate the tree
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 19:03:59 +00:00
doxa-maps-88655c70
ac66728a69 Gospel charts: the chart vocabulary comes from the catalogue
Every bar label, tab and legend key in the charts leaf was written inline in
English and never wired to i18n — a surface whose headings were translated while
the words inside the picture were not. Eight of them already had catalogue
entries and were simply not used; the other fourteen are new keys, translated in
all eleven locales from each catalogue's own vocabulary rather than invented
beside it.

The tab strip had to become computed. A plain array is evaluated once at setup,
so it would have kept whichever language the leaf first rendered in and never
followed a locale change; the legend arrays live in the template and already
re-evaluate on render.

Adding a key for "Full Bible" then exposed what the leak gate cannot tell apart.
It read table CELLS as if they were chrome, and a cell whose entire text equals
an English term is indistinguishable from a label by text alone. Both findings it
produced were the language being correct: Source cells carry organisation names —
JESUS Film, Faith Comes By Hearing, Door43 — and Scope cells describe what a
resource contains, one of whose 114 values is the words "Full Bible" while most
of the rest are already in the target language. Translating either would be the
defect. The boundary is structural, so the DOM now draws it.

That leaves the Type column, the one cell column carrying catalogue labels, with
its own arm. Asked the obvious way round it was wrong: requiring every translated
type to be present failed on Gospel audio in all four locales, because 1,027 rows
carry it and none was on the first page of fifty — pagination reported as a
translation defect. It asks instead that no cell equal one of the eight
enumerated type strings, which stays silent for a type that is off-screen and
fails for one that is on-screen and English.
2026-09-25 19:03:44 +00:00
doxa-maps-88655c70
d846af6f59 Rebuild all bundles: the merge brought new i18n catalogues 2026-09-25 19:00:51 +00:00
doxa-maps-88655c70
bd180fac82 Merge main into bundler-2/work
# Conflicts:
#	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/manifest.json
#	public/doxa-maps/research-dashboard/research-dashboard/index.html
2026-09-25 19:00:09 +00:00
doxa-maps-88655c70
ffef2b761e Raw maps shape Arabic, Persian and Hebrew place names: cities-internet and key-cities
These two maps build `new gl.Map` directly, so they never got useMapInstance's RTL install, and
their bundle entries registered nothing either. The basemap draws each place in its OWN script,
so over the Middle East most labels are Arabic, Persian or Hebrew for EVERY viewer: they were
drawn as disconnected glyphs in the wrong order. installRtlTextPlugin(gl) now runs after the
engine global resolves and before new gl.Map (an entry-time call cannot work once the engine is
injected lazily).

Gate: check:rtl-raw (+ :poison), gospel's check-gospel-rtl-shaping method (pan to Arabia first;
no RTL label on screen is NOT MEASURED; one defineProperty per global for hook + poison) over
cities-internet on both engines and key-cities (Mapbox-only, gated, no leaf: mounted on a
same-origin host page). The shared gate's --leaf argument can replace the cities rows once it
lands; the key-cities host page still needs this one.
Measured: before, cities-internet mapbox 58 RTL labels with the shaper 'unavailable' and maplibre
62 stuck at 'requested', both unshaped; after, shaper 'loaded' on both, and key-cities 56 RTL
labels shaped. Poison: 3/3 rows caught.
2026-09-25 18:59:31 +00:00
doxa-maps-88655c70
1828957f83 Rebuild every bundle that compiles in engineFor, and regenerate the tree
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 18:57:57 +00:00
doxa-maps-88655c70
7b9e88754b Merge main into bundler-2/work
# Conflicts:
#	public/doxa-maps/index.html
2026-09-25 18:41:40 +00:00
doxa-maps-88655c70
056942b33f engineFor(map): build markers, popups and poster maps with the map's OWN engine
Every generated leaf <head> preloaded mapbox-gl.js whatever the provider, so code that used the
bare `mapboxgl` global always found one, even on a MapLibre map. Removing that peer (it sends
every visitor's IP to Mapbox under provider=maplibre) turns each such site into an error.
engineFor(map), next to isMapboxMap in useMapProvider.js, asks the map which engine built it:
  - useSelectedPin: the selected-pin Marker
  - useMapEvents: the pin-fallback and region Popups
  - useMapPoster: main already builds the hidden map with the source map's engine (1cbfb3aa);
    its choice now goes through this one helper instead of a second inline sniff
  - pplr-data-maps: glFor is now this helper; the geocoder awaits ensureEngineScript first
  - key-cities: ensureEngineScript('mapbox') before reading the global
Measured with every api.mapbox.com tag stripped from the served leaf (no peer at all for
key-cities), before -> after:
  pin select under MapLibre, on cities-internet, country-posters, doxa-research-map and
  doxa-simple-map: "reading 'Marker'" -> marker drawn; country-posters region popup: none ->
  popup; PPLR search: no input -> suggestions (both engines); key-cities: "mapbox-gl not
  loaded" -> map loaded. Mapbox rows unchanged (they passed before and after).
The poster UI could not be driven: PosterDialog never opens in country-posters or
doxa-research-map (they pass props it does not declare and no modelValue/getMap), reported
separately. useMapPoster itself was rendered from a real map with only its engine on the page:
MapLibre "mapboxgl global not found" on the pre-1cbfb3aa library -> a 400x300 PNG with 88 colours.
2026-09-25 18:38:17 +00:00
doxa-maps-88655c70
966c44fcc0 Plugin Studio: a map-token control (paste a Mapbox public token, or point it at a token service)
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
Driver request, 2026-09-25: "add a spot on the studio for placing a Mapbox token, or setting the URL
for where the Mapbox token API is from, like doxa.life has an API token".

A 🔑 Map token button in the studio header opens a small panel:
- paste a Mapbox PUBLIC token (pk.…) — a secret sk.… token is refused, the page is public;
- or a token-service URL: the studio fetches it (JSON {token|tk|accessToken} or a bare string, the
  leaf pages' contract) and uses the pk.… it returns. The maps never read a token URL themselves, so
  the studio resolves it and hands them the token;
- Clear returns to the token built into the page. A dot and a status line say which is in use.
Precedence: your token > a token from your URL > the host's tk > the page's built-in token. Kept in
this browser's localStorage only; applying re-mounts the open map and publishes window.MAP_TOKEN for
maps nested inside composites. Labels in all 11 locales (i18n parity PASS).

Tested in a browser on the built studio: shows the built-in source; sk.… refused and not stored; a
pasted pk.… reaches the pane's profile-config and window.MAP_TOKEN and survives a reload; Clear
restores the built-in token; a token URL is fetched and applied. https://doxa.life/api/maps/token is
refused cross-origin from another site — the panel says so and keeps the current token.
2026-09-25 18:37:39 +00:00
doxa-maps-88655c70
816deb56a8 Merge main: rebuild what the incoming source requires
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 18:36:28 +00:00
doxa-maps-88655c70
80617877de Rebuild research-dashboard after the merge, so its committed bundle matches its source 2026-09-25 18:25:13 +00:00
doxa-maps-88655c70
32b4e5ec6c Gospel tables: translate the resource types, and let the leak gate see every locale
The gate could only ever read Arabic. Its rule — English prose declares itself
with dir="ltr" — is right about the prose and wrong about the root: the embedded
RTL fix sets direction on the plugin's own root element, so under ar the root was
rtl and the walk ran, while under zh, ru and hi the root matched the prose test
and the entire leaf was skipped. Measured on identical DOM in both locales: 1,359
elements walked and 589 carrying text either way, of which 1 sat in a prose
subtree under ar and all 1,359 did under zh.

It then reported that silence as "rendered no labels at all", which reads as a
product failure and is not one. The Chinese leaf renders correctly, title and
all. Silence is now NOT MEASURED, reported separately, and never counted as a
pass. dir is honoured below the root and ignored on it; the comparison went from
488 key/locale pairs to 1,876.

What that exposed was a translation that gave up halfway. Type is an enumerated
label whose eight values the catalogue already translates, and the heading above
it was translated while the column beneath stayed English. The map is derived
from the catalogue rather than listed in the component, so a ninth resource type
is covered the moment it is added; a hand-written list would have been one entry
short and nothing would have failed. Both code paths localise, including the
filtered view a pin or country click produces.

Two values had no key because the catalogue spelled them differently from the
data — Audio Scripture and Bible Stories, now translated in all eleven locales.
Open Bible Stories stays English: it is a product name, as are the organisations
in the Source column beside it.
2026-09-25 18:24:26 +00:00
doxa-maps-88655c70
fc86f2ed57 Merge branch 'main' of https://git.nuxtbase.app/doxa-life/doxa-maps into bundler-1/work 2026-09-25 18:24:09 +00:00
doxa-maps-88655c70
ce1097da3f Delete the glFor copy: the instance sniff already lives in library, once
I landed glFor as a local copy of pplr-data-maps' helper and flagged the
duplication as a decision on the record rather than drift. It did not need to be
a decision at all: library/composables/useMapProvider.js already exports
isMapboxMap(map) — the same per-instance test, documented for the same reason
("works even on dual-engine pages where both globals are loaded").

So the copy is gone and the part that could drift is delegated. What remains in
the plugin is the namespace choice, one expression with nowhere to drift to.
When exp-shepherd's engineFor(map) lands, that line goes too.

WHAT IS PROVEN, and what is not, because the browser slots have been saturated
for fifteen minutes and RULE 3 says gates green before committing. This is a pure
expression substitution, so its logic is provable without a GPU:
_chk-glfor-equivalence.mjs stubs the globals, builds BOTH implementations, and
compares their decisions across every engine combination — mapbox map with both
globals, maplibre map with both, either alone, and the case the whole fix exists
for: a MapLibre map with no Mapbox global at all. Identical in all of them.
That establishes the refactor cannot have changed the decision, so the pin-click
integration result already measured still applies. It does NOT re-run that click;
the browser re-proof is queued and will run when a slot frees.

check:sri PASS, composite freshness PASS.
2026-09-25 18:23:44 +00:00
doxa-maps-88655c70
652b5a5c95 Integrate main into bundler-2/work
# Conflicts:
#	documentation/README-SECTION-3-cli-commands.md
#	package.json
#	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
#	public/doxa-maps/research-dashboard/research-dashboard/index.html
2026-09-25 18:21:45 +00:00
doxa-maps-88655c70
7965b8dbcd The pin-dim slider works, and it composes with legend dimming instead of fighting it
Dragging the side menu's pin-dim slider changed nothing: it wrote a store field
that did not exist, the same defect as the clustering picker beside it and found
while fixing that one.

The obvious fix would have created a worse bug. The pin layer's circle-opacity
already has an owner — legend dimming writes a ['case', …, 1, 0] expression — so
a flat slider value from a second writer would silently erase dimming, or be
erased by it, depending on which control the reader touched last. Nothing would
have looked broken; the map would just have stopped answering one of them.

So there is now exactly ONE writer and the value is the PRODUCT:

    opacity = slider × (1 for a matched pin, 0 for a dimmed one)

The slider scales the whole layer, dimming still dims relative to it, neither
wins. Seven call sites that each wrote opacity by hand now go through it.

MEASURED, reading the live paint property rather than the store:

    clustering off          1
    clustering on, 0.5      0.5
    + a dimming selection   ['case', <religion is B>, 0.5, 0]
    slider back to 1.0      ['case', <religion is B>, 1, 0]

One thing the gate had to learn, and it is written down in it: a REGION
selection does not dim at all — it sets a layer filter, so non-matching pins are
removed rather than faded. Testing that path would have reported "the slider
erased the dimming" about code that never writes opacity.

★ ALSO: the Globe/Flat control's 24px touch target now applies at EVERY width.
The first fix scoped it to `@media (pointer: coarse)`, reasoning that a mouse
needs no touch floor. Wrong twice: WCAG 2.5.8 is a TARGET SIZE criterion, not a
touch one, and the query meant phones passed at 34px while desktop stayed at
73.5×23 — the same one-pixel failure, now invisible to a phone sweep. Measured
after: 73.5×34 at 390, 430 and 1440.
2026-09-25 18:20:53 +00:00
doxa-maps-88655c70
42f48b7cf5 Merge branch 'main' of https://git.nuxtbase.app/doxa-life/doxa-maps into bundler-3/work
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 17:58:31 +00:00