Commit graph

217 commits

Author SHA1 Message Date
Doxa Maps
67730457f6 A map leaf must have our data on it, not just a basemap
verify-map-paint.mjs:293 asserts shadow && glContext && attribution &&
!asksForToken && cssOk && no-real-errors. Every one of those is TRUE of a
flawless basemap carrying none of our zones, polygons or pins, so a data-layer
regression lands green. Measured by starve-probe (abort our
/assets/{boundaries,tables,people-groups}/ and people-groups API requests,
leave the basemap untouched, then read exactly what verify-map-paint reads):
NINE of its thirteen leaves still report a live canvas, live GL, attribution
and ZERO page errors with none of our data on them. The hole is the majority of
the suite. The other four lose their canvas entirely, so the existing gate
already fails them and they are deliberately not listed here -- a leaf list is
a claim about where a failure would otherwise go unseen, not a trophy cabinet.

TWO MEASURES, PASS ON EITHER, because each alone produces a false red on a real
leaf. dashboard-plus-grid's map is a 686x340 pane whose basemap already
contributes ~846 distinct colours, so removing a choropleth deletes AREA
(26.4% of pixels) while barely moving the palette (ratio 0.788 -- I nearly
reported that as a defect). demo-map is the mirror: palette collapses to 0.196
while only 5.0% of pixels change. A map with NO data moves NEITHER.
A global floor cannot work here at all: check:map-paints requires 40 distinct
colours and a starved upg-100-list scores 306.

THE INSTRUMENT MATTERS MORE THAN THE THRESHOLD, three findings:
 - drawImage/readPixels straight off a GL canvas returns an EMPTY buffer
   (preserveDrawingBuffer:false). Three leaves read colours:1, nonzero:0 while
   visibly full of colour. page.screenshot({clip}) goes through the compositor.
 - elementHandle.screenshot() waits for the element to be STABLE, which an
   animating dashboard canvas never becomes; it timed out on three leaves.
 - A FIXED WAIT MAKES THE GATE FLAKY AND THE FLAKINESS READS AS A DEFECT. With
   waitForTimeout(10000): countries-map measured 1868 distinct colours then 54,
   and research-dashboard PASSED at 19.1% then timed out, on identical code
   minutes apart. The fix is a readiness predicate, not a longer clock --
   isStyleLoaded() && areTilesLoaded(), reachable on ANY leaf without a
   per-profile handle via host()._instance.provides.mapStore. countries-map
   then reported 1862->194 / 46.6% twice, settling in 12.7s and 10.5s: the old
   10s wait was 2.7s short, which a clock cannot tell you and the map can.

IT REFUSES TO JUDGE RATHER THAN GUESS, exit 75 (never 0): no canvas, a starve
that aborted 0 requests, images that cannot be compared, a readiness that could
not be measured, or the gate itself throwing. That last one is not theoretical
-- the first run died on a Playwright arity error and exited 1, which a wave
runner reads as "these maps have no data on them". Three leaves are EXCLUDED
with their measured reasons rather than left to emit a permanent no-measurement
that reads as coverage.

--poison starves BOTH runs so neither measure can move and every leaf must go
red; it is mandatory because "either measure moved" is a weaker claim than
either half, and a widened assertion that still passes everything reads as
coverage while proving nothing.
2026-09-26 04:38:59 +00:00
Doxa Maps
924a185aaa "MST" clustering draws again: a true spanning tree in under a second instead of a 12-second freeze
Choosing MST on the research map built and sorted every pair of pins (~2.2 million edges for the
2,107-pin roster), ~12 s on the main thread, and then did it all again for a log line. The page froze,
and check:clustering-modes read the map before any line existed: "MST drew no cluster lines".

- MSTClusteringUtil runs Prim over the implicit complete graph: O(n²) time, O(n) memory, no edge list.
  Starting a new tree when the nearest outside point is beyond maxConnectionDistance gives the same
  spanning forest as before.
- useMapClustering no longer recomputes every tree for its stats log line.
- check:mst-clustering (headless, in gates:post): same edge count and total length as a reference
  all-pairs Kruskal on 2-400 pins, capped and uncapped; 2,107 pins in 872 ms here (budget 3 s).
  On the previous code it FAILs at 11,206 ms.
2026-09-26 04:38:59 +00:00
Doxa Maps
1dfd63b631 Maps stop flying for readers who ask for reduced motion: useMapFly decides it for every camera move
A reader whose OS asks for reduced motion (prefers-reduced-motion: reduce) still got flying and easing
maps: callers passed { duration: 0 } themselves, and flyToCoords took no options argument, so theirs was
silently dropped. useMapFly now routes every camera move (flyTo, easeTo, fitBounds: 12 call sites)
through one guard: jumpTo, or fitBounds with animate:false, under reduced motion. flyToCoords takes and
honours caller options. check:map-fly-reduced-motion runs the real composable against a recording map,
headless: 7/7 PASS here; 5 of them FAIL on the previous code.
2026-09-26 04:38:59 +00:00
Doxa Maps
6b05f51391 check:no-free-identifiers joins gates:post: a name used but never bound fails before it ships 2026-09-26 04:21:49 +00:00
Doxa Maps
9fa3b1e484 Composites must mount and their links must exist on both ends, in every landing
check:composites-mount and check:composite-interop join gates:post, and composites-mount is also a landing
precondition (3b, ~20-45 s): 08f89f55 blanked Big Picture and Simple Research while every existing check
passed, so a landing that blanks a composite is now refused before it reaches main.
2026-09-26 04:21:49 +00:00
Doxa Maps
ad61981395 check:composite-interop: a composite's links must exist on both ends, in source and in the built bundles
A composite is a composite because its children talk, and every link is a name
written in two files that nothing ties together. Rename it on one side and the
build stays green while the link silently disappears. The interop matrix also
found the other silent way a link dies: research-map.vue looked up its host with
getCurrentInstance() inside a watcher callback, where Vue returns null, so
map-filtered was never sent, and the only proof had used a synthetic event.

Headless, in about a second:
- A: each link's send pattern is in the sender and its listen pattern in the
  receiver (17 links across 7 composites).
- B: the literal is in every built bundle that ships an end of the link.
- C: every capability-4 composite is mapped, or states why it has no links
  (stack-demo).
- D: no getCurrentInstance() inside a plain function in plugin-designs/ or
  library/. The three routed sites are printed as KNOWN, and an entry left
  behind after its fix fails.

Negative-tested on 521f8b18, and each of these fails: a receiver renamed in
source, a built bundle out of step, an unmapped composite, a new late
getCurrentInstance (one-line function, arrow, multi-line), and a stale KNOWN
entry. check:no-orphan-gates, check:script-flags and check:quoted-scripts pass.
It is not wired into gates:post; that is the build owner's call.
2026-09-26 04:21:49 +00:00
Doxa Maps
3735f27296 check:composites-mount: a composite whose setup throws can no longer look green
08f89f55 shipped Big Picture and Simple Research calling getRuntimeLocale,
detectLocale and RTL_LOCALES with no import. The build was green and every
structural gate passed, but setup threw a ReferenceError in every locale and
both composites rendered nothing.

This gate runs the part that failed, with no browser. Vite loads each
capability-4/5 composite the way its bundle entry defines it (configureApp,
plus ProfileLoader's provides where the entry mounts ProfileLoader), and
@vue/runtime-core mounts it on a no-op renderer in en and ar. Any error from
setup, a watcher's first run or a mounted hook fails it, naming the composite.

Measured on 75c4b54e: it fails exactly the two broken composites in both
locales. On 521f8b18 all 20 mounts pass. Throws poisoned into a directly
defined element and into a ProfileLoader profile both fail. It takes about
20 s. check:no-orphan-gates, check:script-flags and check:quoted-scripts pass.
2026-09-26 04:21:49 +00:00
Doxa Maps
0b9b068b44 Gates: pin-dim-slider runs again, heartbeat follows the translated panel, and the new engine checks are registered
- check:pin-dim-slider: its description sat in "scripts" under the same key as
  its command, so the prose won and bash failed on the apostrophe. It moves to
  "//scripts".
- check💓 the panel's bin labels now call T(key, en), defined above the
  slice the gate evaluates, so the gate threw "T is not defined". It now passes
  the same shape, answering in English.
- check:map-props-unit (Node, 26 cases) and check:auto-engine (browser) had no
  script, so check:no-orphan-gates failed. Both are registered; map-props-unit
  joins gates:post. auto-engine stays a browser run, outside gates:post.
2026-09-26 03:35:18 +00:00
Doxa Maps
6989e30f44 check:universal-props (draft): every plugin reads engine, token and locale through the library
Headless static gate for the contract in documentation/reference-universal-props.md: scans each
production and experimental bundle's source (and the shipped bundle for strings the minifier keeps)
for a bypass. The bypasses: a mapbox:// style in setStyle or a raw new gl.Map (a style handed to
useMapInstance is translated there and passes), a bare mapboxgl constructor, a literal provider pinned
into useMapInstance, a window.DOXA_TOKEN read, a pk. token literal, and toLocaleString('en-US')
(warn-only until the Driver sets a numerals policy). Documented design exceptions are ALLOW-listed
with their reason. A self-test plants each bypass and one clean case; a blind or over-eager rule
exits 3. Today: FAIL, 7 bypasses in 4 bundles (routed as defects), 12 locale warnings.
2026-09-26 02:38:06 +00:00
Doxa Maps
8f5bd5efb4 Merge branch 'main' of https://git.nuxtbase.app/doxa-life/doxa-maps into bundler-3/work 2026-09-25 23:57:06 +00:00
Doxa Maps
0b54b0354e Gospel checks: one dev-assets gate, and cover the gap it leaves
The gospel-only dev-assets check is gone in favour of the shared one, which is
better at the job: it tests the /__leaf/<bundle>/<profile> prefix — the one that
actually broke — across all seven map bundles, and confirms each leaf hands its
element the base it claims. The gospel version never looked at that prefix, so
keeping both would have meant two implementations and the weaker one still green
while the map was broken.

What the shared gate samples is the POOL, the files every map shares. It cannot
know this leaf also fetches gospel tables by name, so a table that stopped being
staged would leave it green and this map fetching a 404. That is the only gap
left, and this covers exactly it.

The wanted list is parsed out of resources-map.vue rather than written down,
which immediately earned itself: the hand-written list it replaces was already
two short — pray-index and gospel-link-verification were being fetched and were
not in it. A list maintained by hand is the field-list-goes-stale bug, and this
bundle has produced that three times in one day.

Poison-tested by unstaging a real table: FAIL, exit 1, then restored.
2026-09-25 23:50:31 +00:00
Doxa Maps
87ebbace9b Leaf pages load only the engine their provider asks for: no Mapbox requests under MapLibre
A generated map leaf chose its engine at BUILD time and hard-linked it in <head>: mapbox-gl.css,
the geocoder css, mapbox-gl.js and the geocoder js from api.mapbox.com. The provider is chosen at
RUNTIME (profile-config.provider → ?provider= → window.MAP_PROVIDER), so a partner embedding a leaf
with ?provider=maplibre still had every visitor's browser make 4 requests to Mapbox.

- generate-tree: no static engine tags on map leaves; the element's own runtime loaders
  (ensureEngineScript / ensureEngineCSS, the geocoder's on-demand loader) load exactly the engine
  asked for, the path composite leaves already ran on. The forge page drops its api.mapbox.com
  preconnect. A leaf opened with provider=maplibre (same ladder as resolveMapProvider) is token-free,
  so the public copy no longer asks a MapLibre visitor to paste a Mapbox token.
- check:leaf-engine-per-provider (new, browser): every leaf at ?provider=maplibre makes 0 requests
  to *.mapbox.com and still draws; at its default every map leaf draws with the engine it drew with
  before (--baseline <tree> compares against another tree).
- check:dashboard-geocoder-provider is wired into gates:post alongside it; it had been held out
  because this generator's tags made it red.
- verify-map-paint: optional GATE_VIEWPORT for a phone pass.

Measured before this landing (browser runs made before the headless rule): leaf-engine vs main's
tree PASS, 56 leaves, 0 Mapbox requests under MapLibre, same default engine. Pins, region popup,
PPLR search and key-cities PASS on both engines with 0 Mapbox requests under MapLibre. RTL shaping
PASS on both engines wherever RTL labels rendered. Paint under MapLibre: the same 4 leaves fail as
on main (country-posters-mfe, upg-100-mfe, regions-mfe, flower-mesh). Pre-existing, not caused here.
Poster "Render" is missing on country-posters and doxa-research-map under BOTH engines. Pre-existing
on main, read from source: countries-map.vue renders <PosterDialog v-if="showPoster"> without
v-model or getMap, and PosterDialog shows only when modelValue is true.
Headless, on this commit's regenerated tree: 57 leaves, 0 carry api.mapbox.com engine tags (main's
doxa-simple-map leaf carried 5), 56 carry the runtime provider check, check:sri PASS.
2026-09-25 23:44:04 +00:00
Doxa Maps
3ab1d7aad5 Merge branch 'main' of https://git.nuxtbase.app/doxa-life/doxa-maps into bundler-3/work 2026-09-25 23:41:23 +00:00
Doxa Maps
9dc4ce5ecf Gospel map: a headless check that the dev server holds the assets the map fetches
Every gate for this bundle measured the built tree, and the map was found broken
where none of them look: the dev server. Boundary polygons were missing there on
five maps at once, this one included, while every built-tree gate stayed green —
the thing that differs is how a leaf resolves its asset base, not what the build
produces.

Measured without a browser, which is also why it can run on every change rather
than when a slot is free: /assets/boundaries/world-adm0.geojson answers 404 in
dev while /dx-maps/assets/boundaries/world-adm0.geojson answers 200 with 1.9 MB.
The file is there; the base is wrong.

It asserts bytes and JSON, not just status, because a dev server that answers 200
with an HTML fallback for a missing .geojson is the shell trap this repo has been
caught by twice. A bogus asset path is requested as a control: if that also
answers 200 the gate reports NOT MEASURED, since a server answering everything
cannot tell a present asset from a missing one.

It does not prove the leaf asks for the right path. That fallback lives in
library/ and belongs to studio; this is the other half — whether there is
anything at the end of the base once the base is right.
2026-09-25 23:18:50 +00:00
Doxa Maps
c43a8b793c Dev server: every plugin reaches the asset pool again (no polygons, previews, fonts or tables in dev)
Reported from a laptop running `bun run dev`: under the Plugin Studio, maps drew with no boundary
polygons and UPG-100 had no posters. The built tree was fine, and every gate measured the built tree.

Cause: a dev leaf page is /__leaf/<bundle>/<profile>. A plugin that falls back to the page-relative
'./assets' asked for /__leaf/<bundle>/assets/…, and the leaf route answered with its own HTML at
HTTP 200, so JSON.parse failed and the map drew without our data. /<tree>/assets/ was not served
in dev at all. The built leaf never hits this because its page passes assetsBase "../../assets".

- dev-plugin serves the pool at /__leaf/…/assets/ and /<tree>/assets/ too, and the dev leaf page
  passes assetsBase "/assets" in its profile-config, as the built leaf passes its own.
- resolveAssetsBase's last rung is '/assets' under the dev server (import.meta.env.DEV, false in
  every build); the built tree keeps './assets'.
- check:dev-assets (headless): starts a short-lived dev server for this checkout, requires
  boundaries/previews/fonts/images/tables to answer 200 and not HTML at every prefix a dev page can
  produce, and requires the dev leaf to carry the root-absolute assetsBase. It fails on the parent
  commit (4 prefixes and both leaves) and passes here.
2026-09-25 23:17:35 +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
57065889f6 Integrate main into bundler-2/work 2026-09-25 19:07:04 +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
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
12f6567634 Merge remote-tracking branch 'origin/main' into rega/research-gallery-embed
# 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/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/key-cities/key-cities.js
#	public/doxa-maps/manifest.json
#	public/doxa-maps/template-bundle/template-bundle.js
#	public/doxa-maps/wall-of-people-groups/wall-of-people-groups.js
#	public/doxa-maps/wall-of-people-groups/wall-of-people-groups/index.html
2026-09-25 17:46:35 +00:00
doxa-maps-88655c70
79bd6d2ac3 public/ is one folder: the output tree plus a README, with the Plugin Studio as its front page
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
Driver decisions (cards BUILD-OUTPUT-IS-ONE-FOLDER, BUILD-PLUGIN-STUDIO-IS-THE-OUTPUT-FRONT-PAGE).

- public/ holds only <tree>/ and a generated README.md. The build no longer writes public/index.html,
  public/assets/ or public/__gallery/; `rm -rf public && bun run build` recreates exactly these two.
- The Plugin Studio is <tree>/index.html (tree-base "."), replacing the staging-forge chooser; nothing
  is written above the tree. check:staging-lang, which tested that chooser, is retired with it.
- Assets are gathered in a temp directory and the fetched allowlist staged into <tree>/assets/ as
  before. fonts/, images/, brand/ and legacy/ existed ONLY in public/assets: they are sources and move
  to data/ (git mv). Poster previews keep their tracked sources in plugin-designs/*/assets/previews
  (all 378 verified present).
- __gallery/ is archived out of public/ (a copy kept in the umbrella's _qa-cycle/archive) and out of
  the build; generate-gallery renders only on request, into a directory you name, never public/.
- Native shells load the output folder: capacitor webDir public/<tree> (cap copy needs an index.html
  there), Tauri frontendDist + window url relative to it. internal/sync-app-shells.mjs keeps both in
  step with the tree's name on every build, so the coming rename stays one config line; the Capacitor
  start page resolves its leaf tree-relative.
- dropin.include now lists only extras (README.md): the tree is always shipped and never named twice;
  excludes are tree-relative ({tree}/...). clean.mjs removes the old root entries from existing
  checkouts; the build lock and public/.gitignore's rules move out of public/.
- check:wp-embed read the BUILT manifest from gates:pre, before the build, so a from-scratch build
  could never pass; it runs in gates:post and reads the tree name from config.
- Stale bundles refreshed by the clean build (key-cities, template-bundle, wall-of-people-groups);
  the CLI doc's quoted scripts re-synced; capability exemplars regenerated; the '#' note leaves
  check:stale-sweep-claim's command (it was already in //scripts).

Proof: rm -rf public && bun run build -> public/ = README.md + doxa-maps/; check:sri, check:drop,
check:parse, check:public-hygiene, check:quoted-scripts, check:capability-exemplars PASS; all 57 pages
(the front page + 56 leaves) mount under /doxa-maps/ on this tree. check:facet-cascade is red as on main.
2026-09-25 17:41:56 +00:00
doxa-maps-88655c70
2a2ea36e98 A leak check that can be pointed at any leaf, and two false positives it taught me
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
Cell E verification for Big Picture and Simple Research needed the
gospel leak gate's method on two other bundles. That gate hardcodes its
four leaves, its element tag and its single catalogue file, so pointed
elsewhere it finds no shadow root and silently compares nothing. This
one takes the leaf, the tag and the locales as arguments and compares
against EVERY catalogue area — a COMPOSITE has no t() calls of its own,
so its chrome comes from the children it nests.

SIMPLE RESEARCH CELL E IS CLEAN, independently measured:
research-map-plus-grid in ar/zh/ru/hi, tree identity checked first, no
leaks, 0 page errors, and 86/34/27/43 comparable keys — so the
comparison was non-vacuous in every locale rather than "0 leaks over
nothing". A second instrument agreeing with the owner's reading is what
the verifier role is for.

★ TWO FALSE POSITIVES, BOTH OF WHICH LOOKED LIKE TEXTBOOK DEFECTS. The
first run reported 3 leaks x 4 locales on Big Picture, and all three
keys ARE translated in every catalogue, so the pages really were showing
English where a translation existed.

  DATA CELLS. "Unengaged" rendered in .rd-td inside .rd-grid — table
  data. people-groups-grid.vue:150 records the field as
  {value:'unengaged', label:'Unengaged'}, 100% populated, and
  dashboard.config.js:132 counts 2,081 Unengaged / 27 Engaged. It is an
  API VALUE that happens to equal a catalogue string, which is the
  population rega's method excludes by name. Whole-label matching stops
  a substring impersonating a label; it cannot stop a data cell
  rendering the same text.

  FACET VALUES. "Other" rendered in span.fr-lbl in the facet rail, which
  LOOKS like chrome. The template is
  `<span class="fr-lbl">{{ r.label }}</span>` with `{{ r.count }}`
  beside it, and the component's own comment calls it "the 'Other'
  bucket". The string comes from row data, not from t('regions.Other').

Both are excluded BY ROLE rather than by class. The facet exclusion keys
on the row carrying a COUNT, so the rail's own section headings stay in
scope and a real leak there is still caught — excluding .fr-lbl
wholesale would have blinded it.

⚠ NEITHER IS GOSPEL-SPECIFIC. Any bundle with a data grid or a facet
rail produces these, and whether "Unengaged" and "Other" as DATA should
be localised is a genuine product question — but a different one from
cell E, and filing it as a leak would send someone to fix correct code.

⚠ BIG PICTURE CELL E IS NOT CLAIMED CLEAN. The confirming run after the
facet exclusion came back RC=75 (no browser slot within 900s), and the
run before it still reported regions.Other. Whether the exclusion takes
effect is unverified, so the cell stays open rather than being marked
on an unconfirmed reading.
2026-09-25 17:26:29 +00:00
doxa-maps-88655c70
d633f496d7 Gospel map: shape Arabic, Hebrew and Persian place names
The map is built raw, for the provider switch, so it never installed the RTL
text plugin — the same opt-out that cost it the engine stylesheet. Without the
plugin those labels draw as disconnected glyphs in the wrong order, which is
unreadable rather than merely ugly.

This was never an Arabic-locale bug, which is why it went unseen. The leaf does
not localise basemap labels, so every place is drawn in its own script: over
Arabia, 57 of 61 rendered symbols are Arabic for every viewer whatever their UI
language. An English reader sees broken Arabic they cannot evaluate.

It installs after the engine global resolves rather than at bundle entry. This
bundle injects its engine lazily, so at entry time there is no engine to install
into and the call would silently do nothing.

The gate pans to Arabia before judging. Registration alone proves little, since
the plugin is registered lazily and a freshly loaded map reports "deferred"
whether or not the shaper can actually be fetched; only real RTL text on screen
turns it "loaded". Asserting without panning would have failed a working map.
2026-09-25 17:10:04 +00:00
doxa-maps-88655c70
87cc608b65 Merge remote-tracking branch 'origin/main' into rega/research-gallery-embed
# 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/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 16:32:46 +00:00
doxa-maps-88655c70
8e8e3b3e82 Integrate main into bundler-2/work
# Conflicts:
#	public/doxa-maps/cities-internet/cities-internet.js
#	public/doxa-maps/cities-internet/connectivity-list/index.html
#	public/doxa-maps/cities-internet/connectivity-map/index.html
#	public/doxa-maps/cities-internet/data-tables/index.html
#	public/doxa-maps/doxa-maps-spa/manifest.json
#	public/doxa-maps/index.html
#	public/doxa-maps/manifest.json
2026-09-25 16:17:07 +00:00
doxa-maps-88655c70
849cd61022 Make the map's clustering picker real: four options, four different maps
Choosing "Native" in the research map's side menu drew nothing. So did MST, and
so did Network. A control that does nothing is worse than one that is absent:
it tells the reader the map cannot do what it can.

TWO CAUSES, AND FIXING EITHER ALONE WOULD HAVE LOOKED LIKE A FIX.

  1. mapStore never declared `clusteringMode`. The picker wrote to a property no
     reactive system was watching, so useMapClustering's `mode` kept its own
     internal default and the click changed nothing.
  2. The legend's Clusters checkbox force-set 'mst' "regardless of what's in the
     store", with the mode watcher "kept as a no-op for forwards compat" — its
     own words. Even with (1) fixed, one tick of that checkbox would have snapped
     Native back to MST: a control that works until you touch the other one.

So the two controls are now two views of one state. clusteringMode IS the state
('off' means disabled); the picker sets it; the checkbox maps onto it, promoting
only 'off' to 'mst' so the checkbox alone behaves exactly as it always has. They
can no longer show different things. The dim-slider's clusterPinOpacity got a
real home in the same pass, for the same reason it was missing.

★ THE GATE ASSERTS ON WHAT IS DRAWN, NOT ON WHAT IS SET. `clusteringMode ===
'mapbox'` would have passed the moment (1) was fixed, while the map still drew
MST lines — state was exactly the evidence that let an inert control ship. The
map now reports, per clustering layer, whether it exists, whether it is visible
and how many features are rendered; the gate clicks the real buttons and reads
that. MEASURED, before and after, on the same tree:

    before   off 0/0/0   native 0/0/0     mst 0/0/0       network 0/0/0
    after    off 0/0/0   native 37/37/0   mst 0/0/1700    network 0/0/23896
                        (circles/counts/lines)

It also asserts no orphan layers between modes, and that MST and Network differ
— two labels for one behaviour is the same defect as a dead option.

One measurement bug caught on the way, worth recording: a fixed 6-second pause
reported MST as drawing nothing while Network drew thousands of lines. MST over
~2,100 pins is quadratic and had simply not finished. The gate now waits for the
drawing to settle rather than guessing a duration — a gate that calls a slow
feature broken is worse than no gate.
2026-09-25 16:16:46 +00:00
doxa-maps-88655c70
7c9e3d9fc4 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 15:54:57 +00:00
doxa-maps-88655c70
0574fe7ffa A gate that looks at the map, because every other one passed over a blank rectangle
Injecting mapbox-gl.css collapsed this profile's canvas to a white strip, and
every assertion I owned went green over it: verify-gospel-map counted 2,107 of
2,107 pins "rendered", leaves-mount found a full shadow root, the i18n gates
found every label translated and nothing clipped. queryRenderedFeatures does not
care whether anything is visible. I caught it by opening the screenshot, which is
not a system.

So: screenshot the map region and count distinct colours — a basemap has
hundreds, a blank rectangle one or two — and check the map still fills its
element.

THE POISON ARM CORRECTED THIS GATE TWICE, which is the argument for having one.

First it hid the canvas, and the region still showed 316 colours: the sample was
taken over the whole custom element, and the legend card is itself full of
colour. The sample is now clipped to the right of the legend, so it looks at map
alone.

Then it collapsed the container, and the gate still passed — because I was
comparing the canvas to its own container, and in the real incident BOTH
collapsed together, leaving that ratio at 1.0. The check would have passed the
exact defect it was written for. The ratio that matters is map-to-ELEMENT, and
the poison now reproduces the incident rather than approximating it: 1440x300
inside a 1440x900 host, caught at 33%.

Measured after: 644 distinct colours, map filling 100% of the element, and the
poison arm red.
2026-09-25 15:54:03 +00:00
doxa-maps-88655c70
52fb65e324 Merge remote-tracking branch 'origin/main' into rega/research-gallery-embed
# 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-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/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
2026-09-25 15:27:08 +00:00
doxa-maps-88655c70
560e73abb9 Export poster: titled by the view, real legend rows, RTL text, the reader's place names, honest attribution; MapLibre style wait names its cause 2026-09-25 15:26:54 +00:00
doxa-maps-88655c70
0f6d504915 An RTL locale turns the plugin, not the partner's page
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
Arabic text was rendering inside a left-to-right layout: readable glyphs
in the wrong place, which reads as a styling quirk rather than the bug
it is.

TWO DEFECTS IN ONE BLOCK (library/i18n/index.js, createAppI18n):

  TARGET — it wrote document.documentElement.dir. An EMBEDDED widget
  doing that flips the PARTNER'S WHOLE PAGE: their headings, their nav,
  their footer. The existing comment already worried about the host
  owning that decision, but guarded only against CLEARING dir, never
  against setting it.

  TIMING — it ran at createAppI18n() construction, reading
  detectLocale(), which sees only the URL and <html lang>. A locale
  arriving later through profile-config — the channel a HOST uses —
  never reached it. Measured across the previous landing: htmlDir null
  on every host-channel run, including leaves whose chrome was fully
  Arabic.

Direction now binds on ProfileLoader's own root, driven by
effectiveLocale — the locale actually in force AFTER profile-config, so
it re-evaluates when a host changes the language. This element cannot
tell "I am the page" from "I am a widget", and guessing wrong is exactly
the failure above, so it no longer touches the document at all.

MEASURED on a partner page at a FOREIGN ORIGIN with
profile-config.locale='ar' (same gate, before and after):

  before   upg-100-mfe   plugin root ltr, arabic 1765   FAIL
           poster-wall   plugin root ltr, arabic 1738   FAIL
  after    upg-100-mfe   plugin root RTL, arabic 1765   pass
           poster-wall   plugin root RTL, arabic 1738   pass
  partner <html>/<body>/<h1> stayed ltr throughout.

Standalone leaves are unchanged and still get <html dir="rtl"> from
?lang=ar — verified, both profiles — because a leaf page DOES own its
document. createAppI18n's document write therefore stays for now;
replacing it needs a page-ownership signal that touches every generated
leaf, which is its own change rather than a rider on this one.

⚠ THE GATE REFUSES TO JUDGE WHAT IT CANNOT SEE, and that guard earned
its place twice before this was measurable. Its first two runs reported
"the element never mounted" — a HARNESS gap, not the product: a partner
embed uses crossorigin="anonymous" (SRI requires it), so the tree must
send Access-Control-Allow-Origin, which `bun run serve` does not. The
gate now proxies the tree through its own CORS-enabled port. Without
that guard those runs would have read as fails-first evidence for a
defect nothing had actually exercised. It also fails if the locale
produced zero Arabic chrome, because direction cannot be assessed on a
page whose language never applied.
2026-09-25 15:14:20 +00:00
doxa-maps-88655c70
069d75dba7 check:element-census-vs-live: what is on one side and not the other, ours vs the live page we clone
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
Every clone-fidelity gate compares properties of NAMED selectors, so none of them can see an element
that exists on only one side; regions found an extra control and an invented chart by eye, past eight
green gates. This executes the Driver's binary scope test (promoted from regions' probe).

- Harvests every visible text node through every shadow root on both sides and diffs the sets:
  OURS-ONLY unaccounted (scope violation) and THEIRS-ONLY (omission) fail; SANCTIONED entries must
  carry the Driver's words, date and card or the preset is refused; same-label digit drift is
  reported, not failed.
- The negative control is mandatory: a known ours-only string must land in the ours-only set, or the
  run prints CENSUS IS BLIND and exits 75. An unreachable live page is also 75.
- Presets live in internal/element-census/<name>.json; ad hoc via --leaf/--live/--neg.
- Measured: regions passes (list 229 vs 228, the only difference its sanctioned poster toggle; Africa
  97 vs 97 with 3 live-vs-snapshot figures). With the allowlist removed the toggle FAILS as
  unaccounted; a negative-control string not on the page gives CENSUS IS BLIND, exit 75.
2026-09-25 14:15:19 +00:00
doxa-maps-88655c70
e920378689 Merge remote-tracking branch 'origin/main' into rega/research-gallery-embed
# Conflicts:
#	package.json
2026-09-25 13:37:06 +00:00
doxa-maps-88655c70
b0c7b52243 Take main
# Conflicts:
#	package.json
#	public/doxa-maps/index.html
2026-09-25 12:38:20 +00:00
doxa-maps-88655c70
d26e416d03 Translate the list and the table headers; correct a claim I had not checked
resources-list's chrome is translated in all eleven — the three filter tabs, the
generic resource names, the people-group count — and the table's column headers
are localised at render in data-tables. resources-map, data-tables and charts now
report zero leaks by the corrected gate; what remains is in resources-list's
data-driven rows.

A CLAIM I MADE AND HAD NOT MEASURED. The previous commit said the CSV export
"keeps the data file's English field names". It does not: Tabulator exports the
column titles it is given, so a Russian reader's CSV heads "Народ","Страна",
"Регион". I checked only after asserting the opposite. Left translated on
purpose — this is a gift for people to use and someone who exports what is on
their screen should get what was on their screen — with the stable machine
contract being data/tables/ itself, which is untouched and English.

THE GATE NEEDED A THIRD CORRECTION, and this one mattered most. Substring
matching over the rendered text reported keys that were never rendered at all:
"Language" matched inside the DATA — the people-group language "Sign Language" —
and "Region" inside a region name. No length filter separates a label from a
word sitting inside one. The unit is now an element's OWN label, and a leak is a
label that EQUALS the English value while a translation exists. A language name
can no longer impersonate a UI string.

Scripts are alphabetised per the fleet convention, so parallel landings stop
colliding on the last line of package.json.
2026-09-25 12:36:41 +00:00
doxa-maps-88655c70
d78a42f2df Integrate 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 12:21:20 +00:00
doxa-maps-88655c70
cfdbea7f9c Host locale: gate the profile-config contract, forward ?lang= into staging embeds, stop losing gate stderr
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
- check:host-locale-contract: every leaf loaded with ONLY profile-config.locale="ar" must draw at
  least half the Arabic the same page draws at ?lang=ar; regions-list is the positive control.
  Fails on this commit for 18 leaves (bundles that never provide appI18n, and every composite).
- check:staging-lang: the staging page at ?lang=ar embeds its leaf with lang=ar and the Gospel map
  renders gospel.mode.gap in Arabic. staging-forge.js (iframe + open link) and
  staging-reference-v2.html now forward ?lang= / ?locale= via withLang(); fails on the parent.
- browser-slot.sh: `exec 9>f 2>/dev/null` redirected the whole script's stderr, and the gate
  exec'd after it, to /dev/null — FAIL lines and "not measuring" messages were being discarded.
  The redirection is now scoped to the open.
- verify-map-paint: the two regions map leaves join the default list (both paint, local and live).
- Vision card reads ?lang= / ?locale= like the rest of the library.
2026-09-25 12:18:26 +00:00
doxa-maps-88655c70
471bd05cea Stop showing English where the catalogue already holds the translation
A reader on an Arabic page saw a handful of English words among the Arabic:
the table's "People Groups" title, its count line, the Engagement column, the
Gospel Access chart title, and the side drawer's close button. Every one of them
had a correct Arabic entry sitting in the catalogue; nothing looked it up.

THE CHECK CAME FIRST, AND IT REPLACES A SWEEP THAT PASSED A BROKEN TREE.
The old method sorted every rendered string into third-party / data / chrome and
counted only chrome. That split is a guess, and it guessed wrong exactly where
the defect lived: single words like "Region", "Prayer" and "Country" are as
plausible as values as they are as labels, so they scored as data and the sweep
reported zero on a page that was visibly leaking.

check:composite-locale-leaks asks a question with an answer instead:

    the English catalogue holds this exact string as a value, AND
    this locale holds a DIFFERENT value for that key, AND
    the page still renders the English one.

No judgement about what kind of word it is — if the translation exists and the
reader is not getting it, that is the defect. The two surfaces a lookup cannot
judge (vendor map chrome, and table/facet cells carrying API values) are muted
by NAME with a stated reason, never by word, so muting one can never hide the
next real leak that happens to reuse it. The gate failed on this tree naming 9
strings before any of them were fixed.

Also threaded here: both composites now accept `lang` / `locale` in
profile-config and pass it to the children they mount, alongside `tk` and
`provider`. A partner writing {"lang":"ar"} reached nothing before — the knob
was documented and read by nobody.

The prayer facet's label in DashboardResearchFilters is now a catalogue getter
too. Measured before touching it: dashboard.config.js does define prayer_level
and FACETS spreads the config last, so that literal never rendered for this
config — it is a default for a config that omits the facet, and it stays, with
the merge order written down so the next reader need not re-derive it.

Four strings remain English with no catalogue entry at all — "Top countries",
"Select all {n}", the table dock's title, and the singular "group". They need
wording in ten languages that cannot be composed from existing entries, so they
are a DECIDE card for the Driver rather than translations invented here.
2026-09-25 12:16:59 +00:00
doxa-maps-88655c70
178d7ac3e8 Merge remote-tracking branch 'origin/main' into rega/research-gallery-embed 2026-09-25 12:15:20 +00:00
doxa-maps-88655c70
1cbfb3aadf WIP: Simple Research one-click export (toolbar button, shared poster path, engine-aware hidden map, gate) 2026-09-25 12:15:20 +00:00
doxa-maps-88655c70
d78d67f197 Merge remote-tracking branch 'origin/main' into exp-shepherd/shelf-gate
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
# Conflicts:
#	package.json
2026-09-25 11:48:59 +00:00
doxa-maps-88655c70
07e2e96ec4 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
# Conflicts:
#	package.json
2026-09-25 11:44:59 +00:00
doxa-maps-88655c70
299adaa9e3 A leak gate that had to be corrected twice before it could be trusted
rega's method: a string is a LEAK when the catalogue has a different value for
the locale and the page still shows the English one — a render fault, not a
missing translation. This measures that across four leaves and four locales.

IT TOOK THREE VERSIONS AND THE FIRST TWO WERE WRONG IN INSTRUCTIVE WAYS.

One: it failed with no explanation. process.exit(1) terminates before pending
stderr flushes, so the run returned 1 with every reason line missing. A gate that
cannot say why it failed is barely better than no gate. It sets exitCode now and
lets the process end.

Two: it read the whole shadow root and reported 113 leaks, nearly all false.
"JESUS Film" and "Scripture" appear inside the explanatory paragraphs this bundle
deliberately keeps in English, and short entries like "for" match inside ordinary
sentences. Crying 113 when a handful are real is worse than silence — someone
would have gone and "fixed" strings that were already correct. Every English
prose block here declares itself dir="ltr", so the check now walks the shadow
root and skips those subtrees, and ignores catalogue entries under six
characters.

WHAT IT FINDS, now that it is trustworthy: 70 real leaks, and every one is in
resources-list. resources-map, data-tables and charts are clean in ar, zh, ru and
hi. That is exactly the debt I declared two commits ago — that leaf got its title
translated and nothing else — now measured instead of estimated.

Scope is the leaf on purpose. Six keys were reported leaking in the gallery and
the leaf is clean for all six: the gallery builds its iframe src with no lang
parameter, so the embed never learns the locale and correctly falls back to
English. Embedder defect, not a bundle one, and a check that conflated them would
send someone to translate what is already translated.
2026-09-25 11:43:16 +00:00
doxa-maps-88655c70
ff36d0dfcc Merge remote-tracking branch 'origin/main' into exp-shepherd/shelf-gate
# Conflicts:
#	package.json
#	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 11:36:00 +00:00
doxa-maps-88655c70
b1e0c7fa61 check:experimental-shelf: every experimental plugin, measured working, and two label fonts that 404'd
The Driver asked for "all experimental plugins working"; this makes it a
measurement. The gate derives the shelf from the Research Gallery's own rules
(manifest minus curated tabs and NOT_EXPERIMENTAL), then checks every bundle on
its leaf page and on its Gallery tab, under mapbox and maplibre. It requires
content inside the plugin's own element, 0 page errors, 0 failed requests, no
loading text, and for each map 'load' fired AND its own GeoJSON data drawn. An
empty source does not count. Maps are found by wrapping the engine's Map class
in an init script, so no bundle changes for it. It serves this checkout on an
OS-assigned port, and self-tests first: a deliberately broken bundle must fail,
or it exits 3.

Prayer Walk Planner's data is the user's own (zones in localStorage), so the
gate seeds one zone and requires it drawn, printing the seed and reason on every
run rather than exempting the bundle.

It found two real defects on the way. Both are fixed here, and both failed the
gate before the fix:
- UPG Neighbor: every label layer asked MapLibre's default fonts (or Mapbox's
  DIN) from OpenFreeMap, which 404s, so no label drew under maplibre. labelFont()
  now picks Noto Sans under MapLibre; Mapbox is unchanged.
- Prayer Walk Planner (always MapLibre): zone titles never drew, for the same
  reason. The label layer now names Noto Sans Regular.

Result: 28/28 PASS across all 7 shelf bundles.
2026-09-25 11:35:18 +00:00
doxa-maps-88655c70
cd53582ea4 The shared poster wall's locale is asserted, in locales that can show a leak
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 carried a ❌ for a PosterWall i18n leak: useI18n() said to throw
where the wall mounts, leaving the print buttons English in every
locale on upg-100-mfe, poster-wall and country-poster-wall.

IT DOES NOT REPRODUCE. Measured on the current build, three leaves x
ro/ar/zh, tree identity checked against the served manifest first: zero
English leaks, zero page errors. ro "Listă de rugăciune / Harta afișului
/ Pachet de cartonașe", ar "قائمة الصلاة / خريطة الملصق", zh "禱告名單 /
海报地图", legend "Opțiuni de tipărire" / "خيارات الطباعة" / "列印選項".
The diagnosis does not fit either: this bundle installs i18n per element
via configureApp, and createI18n is legacy:false, so useI18n() resolves
for every descendant including the wall.

THE REAL DEFECT WAS THE MISSING ASSERTION, and it is worse than the bug
would have been: nothing in the repo checked the shared wall's RENDERED
locale, so a ❌ could sit on the scoreboard with nothing able to confirm
or refute it.

★ AND IT CANNOT BE CHECKED IN ENGLISH. The wall's guard HUMANIZES the
key when useI18n() throws - buttons.prayerList becomes "Prayer List" -
which is deliberate, so a bare host shows readable English rather than a
raw key. It also means "the button says Prayer List" is true both when
i18n worked and when it threw. An en-only check passes forever while
every other locale leaks. This gate therefore runs ONLY in non-English
locales, where the translated string differs from the humanized key and
seeing the key IS the defect.

It covers FIVE consumer leaves, not the three the report named:
key-cities and regions-dashboard mount the same shared wall and were
never checked. A gate that copies a report's sampling inherits its
blind spot.

TWO WAYS IT COULD HAVE PASSED WITHOUT CHECKING ANYTHING, both real on
the first run and both now hard skips rather than green ticks: a wall
with no cards renders no chrome, and regions-list rendered 7 cards with
ZERO wall chrome because that profile mounts the wall without the print
row. Skips are named in the output with their reason - a summary that
counts only the green ones reads as full coverage.

MUTATION TESTED by reproducing the reported condition exactly - forcing
the useI18n() guard to throw - rather than corrupting a string: the gate
goes red with 28-33 leaked strings on every measured combination, which
proves both that it detects the defect and that the defect is genuinely
absent from the unmutated build. Restored, 9 combinations PASS, 6
reported as not covered.

No source change: there was nothing to fix.
2026-09-25 10:50:46 +00:00
doxa-maps-88655c70
c77207b53a check:basemap-labels: the basemap's place names follow the reader's language on both engines
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
BUILD-LOCALIZATION-SURVIVES-THE-PROVIDER-SWAP asked for a check; the
behaviour landed in 319064a1 (library/maps/localize-labels.js) without
one. This reads the labels the map actually DRAWS, at ?lang=en and
?lang=ru on the same view, under both providers:
  Mapbox    the Standard basemap's 'place-labels' featureset (a plain
            queryRenderedFeatures cannot see the imported basemap):
            Germany→Германия, Ukraine→Украина, Egypt→Египет… must flip.
  MapLibre  each country symbol's evaluated text-field: under ru it must
            be the feature's name:ru, under en not. English there
            restores OSM's own label, so no English word is expected.
It serves this checkout on an OS-assigned port. --dir points it at
another tree for the negative control.

Negative-tested: doxa-simple-map built from 319064a1's parent (no
localize-labels) and served with --dir FAILS on both engines (Mapbox:
68 country labels, none flip; MapLibre: 0/49 in Russian). This tree
PASSES on both (5 known countries flip on Mapbox; 49/49 on MapLibre,
e.g. United Kingdom → Великобритания). A first version counted the
pre-fix Mapbox run as NOT MEASURED instead of FAIL, since it saw no
English names. "Measured" now means enough country labels were drawn.
2026-09-25 10:48:42 +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