- useMapTheme: one look rule (any prayer strategy → night/default; everything else →
day/monochrome) applied at boot and on every tab switch in the simple map and the
research map; an unknown strategy keeps the current look
- Legends and the research search pill are 380px again; desktop numeric columns 70px at
13px so every row stays on one line with no overlap
- Map containers carry their map instance for gates (no global)
- data/ tables and the people-groups file: meta blocks reduced to provenance (source, url,
downloaded, rows, grain, attribution); the link-verification table keeps only state and detail
per link; generators write the same shape
- dropin.exclude in the build config: people_groups.csv, open-doors.json, platform-reachability.json
and tables/index.json are not shipped (nothing loads them); sync-dropin honours it
- public-hygiene --strict flags note-style meta keys in shipped data
- Rebuilt bundles and gallery
- internal/playground → internal/plugin-gallery; playground-smoke → plugin-gallery-smoke;
generate-spa → generate-gallery (scripts: gallery, smoke:gallery; smoke:playground stays as alias)
- generate-gallery exports renderGallery(model, { mode }) — the dev plugin serves the same page
at / from the discovered registry (dev mode imports the dev entries, ladder popovers on);
the build writes public/index.html from the manifest (byte-identical to before)
- Docs and probes use the gallery vocabulary
- upg-100-list and poster-wall import the three list arrays by name, so the data file's
bookkeeping fields no longer end up in the bundle
- Share pages: one comment shortened
- index.html is generated by internal/generate-spa.mjs from doxa-maps/manifest.json: one
self-contained page that lists every bundle, profile and instance, mounts the chosen one,
and offers Open page / Copy embed; build ends with `bun run spa` (the material shell is no
longer installed; nav.js, icons.js, languages.js, pane-wrapper/, external/ and _headers
leave public/)
- The website drop-in follows the same contract: dropin.include in the build config,
sync-dropin --from-config --prune, public-hygiene --strict fails on any other root entry
- Production bundles no longer carry CSS comments: a build-only Vite plugin strips them from
every shadow-style block (about 140 per map bundle, 5% smaller); remaining comments reworded
- Share pages and the pin renderer drop their note-style comments
- SemanticTreeLegend: the UUPGs/Population columns were a fixed 44px, sized for the old
9px header / 10.5px numbers; at the desktop scale "POPULATION" collided with "UUPGs"
and "139.0M" spilled into the neighbouring column. Base column is now 60px, desktop
76px (header 10px, numbers 13.5px, no wrapping)
- The panel follows --map-legend-width like the detail card and the geocoder pill; the
research map's wide-host legend is 400px so "Full Prayer Coverage" stays on one line
- New probe: `bun run probe:legend-columns` measures cell boxes vs real text extents and
fails on any spill or overlap (desktop and phone sheet)
- New shared LegendProgressBar: [has-prayer span | needs-prayer remainder] with the
full-coverage colour filling inside the has-prayer span; text "covered of total · %";
each span is a focusable button with the legend's info popover (name · count · %),
hover highlight, and click selects the matching legend row
- useLegendData exposes a `progress` payload for the prayer legend (mutually exclusive
counts from /statistics: covered = has + full); null for every other legend
- SemanticTreeLegend and LegendRows render the bar as the header's second line and title
the prayer legend "Progress" (desktop, mobile sheet, collapsed pill); profiles pass and
provide the payload so the mobile sheet gets it unchanged
- legend.progress.* strings in all 11 locales
- No height transition while dragging; the resize grab zone is the top 44px (and, in detail
mode, the content when it is scrolled to the top); one geometry source for the JS clamp and
the CSS max-height; release snaps with hysteresis (open ↔ fullyOpen ↔ collapsed)
- The sheet publishes its height as --legend-sheet-h on the element host; the Mapbox/MapLibre
bottom controls translate up by it, so the logo and attribution stay visible in every state
- New gate: internal/mobile-sheet-probe.mjs (Firefox, touch) taps a pin, drags the detail
sheet up/down and checks heights, states and control overlaps
- useMapTheme: basemapConfig() is the single source of truth; bootConfig() hands Mapbox the
Standard-style config at construction so the very first frame is already night; keepPinned
re-asserts on style.load and styledata, writing only on drift; MapLibre maps are skipped
- useMapInstance passes that config to new Map() and stores the map in a shallowRef so
Mapbox internals never run through Vue's reactive proxy
- Simple map's loading cover is dark like the research map's — the last light frame at boot
- New gate: internal/theme-flash-probe.mjs samples canvas luminance through boot, legend rows
and tabs and fails on any light frame (21 phases, 0 flashes)
- The Mapbox Map instance is unwrapped (markRaw) at the profile boundary and the layers
composable works on the raw map, so Mapbox internals no longer pay Vue's get/set traps on
every frame; the geocoder control instance is a shallowRef for the same reason
- Prayer glow rings animate at 12 steps/s with paint transitions disabled, constants moved to
addLayer, and the loop pauses while the map is off-screen — the map can idle again
- New gate: internal/long-frames-probe.mjs measures long animation frames, blocking time and
DevTools rAF violations per phase (boot / idle / interaction); idle violations on the prayer
and research maps dropped from ~100 to the no-animation baseline