Commit graph

9 commits

Author SHA1 Message Date
Doxa Maps
2c397d25ef Source comments state design history and measurements as facts, without quoting or naming anyone
Comments in library/ and internal/ that quoted a person, attributed a decision to one, used first person,
named fleet agents or carried private host names now state the same technical fact in neutral words:
poster-render (including the comments it writes into generated posters), packet-render, the poster and
research scripts, PosterWall, DataTable, the legends, the layouts and the gates. The network-policy note
states the policy itself. Behaviour is unchanged: a few printed gate messages and reason labels drop an
agent name, the poster-render integrity pin follows its comment-only edit, and series.json changes only in
its _meta and _why notes. The historical poster design snapshots (library/scripts/posters/snapshots/),
which nothing reads, are removed.
2026-10-01 00:37:17 +00:00
doxa-maps-88655c70
44ef31b2c6 Tooling reads the tree's name from the build config, in one place
The output tree's name has always been configurable (vite.doxa-maps-build-config.json "name"),
but 61 tooling files ignored it and hardcoded 'doxa-maps' (181 literals), so a rename would have
moved the build while the gates kept reading the old folder.

- internal/_tree-name.mjs reads it once (validated as one URL-safe path segment; falls back to the
  historical name) and exports TREE_NAME / TREE_NAME_RE; `node internal/_tree-name.mjs` prints it
  for shell scripts.
- A codemod rewrote 171 literals in internal/ to TREE_NAME; the regex literals, messages with
  backticks, atomic-public-swap.sh (fails loudly if the name cannot be read), build-lock.sh and
  vite.config.js (its duplicate readMfeName is gone) were done by hand.
- The name is unchanged: old and new tooling produce all 1408 files under public/ byte for byte.
  Code lines still naming the tree in internal/ + vite.config.js: 2, both intended (the helper's
  fallback, the private-repo-name pattern in public-hygiene-check).

This renames nothing visible; the eventual rename becomes a one-line config change.
2026-09-25 16:25:22 +00:00
doxa-maps-88655c70
319064a11b Map place names in the reader's language, on both engines
Every map translated its own chrome but left the basemap's country, city
and street names in the style default (English on Mapbox, Latin + native
on OpenFreeMap) in all eleven locales. A reader in Arabic saw an Arabic
legend over an English map.

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

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

Measured on the Prayer Progress map, ar/zh/ru/hi:
- Mapbox getLanguage() == locale in all four.
- MapLibre: 13/14 symbol layers localized, and the plugin's own label
  layer untouched.
Read in the Arabic screenshots: القاهرة, الإسكندرية, السويس, الأردن on
both engines. check:gallery-embed (+ :maplibre) and
check:prayer-walk-planner pass. The shared change reaches all 11 bundles
that compile initializeMap, and all are rebuilt here. check:shared-reached
marker basemap-labels-localized: 10/10 consumers.
2026-09-25 09:46:21 +00:00
doxa-maps-88655c70
4fb8176b00 Shape Arabic, Hebrew and Persian map labels on MapLibre maps too
library/maps/rtl-text.js installed the RTL text plugin for MAPBOX only
(window.mapboxgl, from api.mapbox.com). So every MapLibre map, i.e. every
keyless embed, drew RTL place names as disconnected, reversed glyphs.
Measured on the Prayer Walk Planner's printed Arabic sheet.

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

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

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

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

Also: the new Experimental bundle pplr-data-maps loads the Mapbox geocoder
under maplibre (pplrData.js:38). It is an owned, printed MAPBOX_KNOWN entry
routed to its owner; delete it in their fix commit.
2026-09-25 08:20:12 +00:00
doxa-maps-88655c70
cb6ee50e7a check:shared-reached: a bundle that never compiles the changed code is not "missing" the fix
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
ensureEngineCSS is only called by initializeMap. A bundle that imports
pieces of useMapInstance (ensureEngineScript, engineGlobal…) but never
initializeMap has ensureEngineCSS tree-shaken away, so the guard can
never appear in it and the race cannot happen there. The check counted
those bundles as missing the fix. upg100 caught it on my-upg-100-list.

The map-unmount-guard entry now also declares the unguarded shape. A
bundle is missing the fix only if it still compiles ensureEngineCSS
without the guard. With neither shape it is reported as not affected.
Entries without a staleRe behave exactly as before.

On today's tree: 7 bundles carry it, 2 are really stale
(doxa-research-map, research-dashboard), and 3 are not affected
(cities-internet, gospel-resources, my-upg-100-list). Before this, all
three were reported as stale.
2026-09-25 06:11:17 +00:00
doxa-maps-88655c70
dc11cdbb6a check:shared-reached: pin the unmount-guard marker to ensureEngineCSS only
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
The first regex for map-unmount-guard also matched useShadowStyles as
the current source compiles it:
  if(!n)return;const i=n.getRootNode();if(!(i instanceof ShadowRoot)||…
The freshly built plugin-studio bundle, which has no useMapInstance at
all, matched it. So any consumer rebuilt against today's useShadowStyles
would have been counted as carrying the fix whether or not it did.

The marker now requires ensureEngineCSS's early exit, "…ShadowRoot))return",
where useShadowStyles continues with "||". Proven both ways on a rebuilt
doxa-simple-map: built from the guarded source it matches, built with
the guard stripped it does not. The studio bundle no longer matches.
2026-09-25 05:17:07 +00:00
doxa-maps-88655c70
0c1a91cd02 useMapInstance: a map unmounted mid-initialise returns quietly instead of throwing
Some checks are pending
build-gate / build (push) Waiting to run
initializeMap is async. If its component unmounts while it waits (a
studio tab switch, a composite swapping views), Vue nulls containerRef
and the next dereference threw "Cannot read properties of null (reading
'getRootNode')" from ensureEngineCSS. check:studio-all hit it about 1
run in 4 (gospel-dashboard); gospel-plugin found a second site one await
later (containerRef.value.clientWidth after the animation frame).

initializeMap now checks the ref after each of its three awaits and
returns silently when it is gone, and ensureEngineCSS returns on a null
element. An unmounted pane is normal, not an error, so nothing is
logged. The ref is the only liveness signal: requiring isConnected too
would silently stop maps that mount into a subtree before it is
attached.

Proof, deterministic in both directions:
- check:map-unmount-mid-init holds mapbox-gl.js, opens a map plugin in
  the studio, switches away (destroying the pane), then releases the
  script. Before: FAIL 2/2 runs (research-map, doxa-simple-map threw
  the production error). After: PASS 2/2 across research-map,
  gospel-dashboard and doxa-simple-map.
- check:map-unmount-race (gospel-plugin's browser-free test, adopted):
  the guarded ensureEngineCSS resolves; with the guard stripped it
  throws the exact message.
- No regression: check:studio-all passes 31/31, and 19 plugins still
  create a live map canvas, as before.

check:shared-reached now tracks this change with a regex marker (a
control-flow fix has no literal that survives minification). It
reports 12 consumer bundles still to rebuild. Verified: rebuilding
doxa-simple-map moves it to "carries it".
2026-09-25 05:01:13 +00:00
doxa-maps-88655c70
8c3576b4ed gate: a consumer is one that IMPORTS the component, not one that mentions it
Some checks are pending
build-gate / build (push) Waiting to run
The consumer list was derived with a plain substring test, and a substring
cannot tell an import from a comment ABOUT the component.

MEASURED: it reported research-dashboard as a stale consumer of
SemanticTreeLegend for three ticks, and I routed that "drift" to its owner as
work to do. It was a FALSE POSITIVE. research-dashboard imports the component
ZERO times and mentions it four times in prose -- DashboardFacetRail.vue:3 says
"the floating @map SemanticTreeLegend is DROPPED", followed by the root cause it
was dropped for. A bundle that deliberately removed a component is CORRECT to
lack its translation keys. The gate was reading the comment explaining the
removal as evidence of use, and the more carefully that removal was documented,
the more certain the false positive became.

★ A GATE THAT MANUFACTURES WORK FOR ANOTHER AGENT IS WORSE THAN ONE THAT MISSES
DRIFT. A missed regression is found by the next reader; an invented one costs
someone a tick discovering there was nothing to fix, and it discredits the gate
that raised it.

Now requires real import syntax -- `import ... from '<path>'` or a dynamic
`import('<path>')` -- matching either the @map path or any path ending in
/<Name>.vue.

MUTATION-TESTED THREE WAYS, because narrowing a detector can quietly blind it:
  false positive gone  -- research-dashboard no longer listed; legend-a11y goes
                          4 of 5 to 4 of 4, and every genuine consumer is
                          unchanged (PosterWall still finds all four)
  real drift caught    -- stripping the marker from doxa-simple-map's built
                          bundle fails it, exit 1
  new consumer found   -- injecting a real import into big-picture lists it
                          immediately as missing the keys

The second and third are the ones that matter: it would have been easy to fix
the false positive by narrowing until the gate saw nothing at all.
2026-09-25 00:24:26 +00:00
doxa-maps-88655c70
6f8ff0ebb7 gate: a shared fix is only as deployed as the last rebuild of each consumer
Some checks are pending
build-gate / build (push) Waiting to run
library/ is the single source of truth, but EVERY BUNDLE SHIPS ITS OWN COMPILED
COPY of the components it imports. So a one-line fix in a shared component is
live in whichever bundles happened to be rebuilt afterwards and silently absent
from the rest -- and nothing detected it, because every other gate measures the
RENDERED BEHAVIOUR OF ONE LEAF, and a leaf whose bundle is stale renders the old
behaviour perfectly.

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

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

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

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

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

key-cities is rebuilt here because it was the mutation subject and the rebuild
is the fix the gate prescribes. The two bundles still red -- country-posters and
regions-dashboard -- belong to regions, and research-dashboard's legend drift is
older than this work; all three are reported, not rebuilt for them.
2026-09-24 23:22:18 +00:00