Commit graph

2870 commits

Author SHA1 Message Date
Doxa Maps
7c1cfc8596 The language tree could write an SVG to disk; now it only prints
regions' fleet audit found the one poster path in the tree that downloaded a
file. The Driver's rule is that a poster action opens the print dialog and never
downloads, and there is no case for an exception here: printPoster builds the
same poster from the same makePoster(), so the download was a second route to one
artefact rather than a capability only it could offer. A reader who needs a file
prints to PDF — the system's own dialog, not a silent write.

The button, the function and the now-unreferenced tree.posterSvg key in all
eleven locales are gone. The comment left in its place names check:poster-action-
print, which regions is extending with this file as its poison case, so a
download added here again fails a gate rather than shipping quietly.

The two CSV export paths in GiGrid and GiResources are deliberately untouched: a
table has no print equivalent, the export is a documented feature that writes a
header line of sources with the data, and the rule — and that gate's own
description — is about POSTER buttons. Flagged to the shepherd rather than left
for the extended gate to trip over.
2026-09-29 04:28:09 +00:00
Doxa Maps
98fd04c86d The centered set's legend covered the chart before anyone asked for it
A4.3. GiMap and GiTree already default their legends closed and read the host's
`legend: 'open'` knob; the centered set hardcoded ref(true), so it was the one
view whose decoder sat over the drawing from the first frame — on the chart where
seeing the target matters most. That was an inconsistency, not a considered
difference.

Changing the ref alone would have shipped NOTHING: applyWidth ran on mount and
set legendOpen back to `!m.matches`, so at any desktop width it reopened
immediately. The bug was in the width rule, not the initial value. Width now only
ever CLOSES the legend — a wide window is not a request to cover the chart, it is
just the absence of the reason to force it shut — and a choice the reader has
made still wins outright.

Checked as a state machine over the cases that matter: desktop with no host knob
closed, desktop with legend:'open' open, phone closed, phone→desktop stays
closed, and a reader who opens it keeps it through a rotation and a resize.
2026-09-29 04:28:08 +00:00
Doxa Maps
ea610ce691 Rebuild the consumers main left stale (build owner, README rule 5)
check:composite-freshness found stale on main: none. Rebuilt, tree + SRI
regenerated; the landing precondition (fresh, one folder, SRI) passed in main-capture scan.
2026-09-29 04:14:33 +00:00
Doxa Maps
2b05ddce3c check:packets-all asserts zone coverage only where a zone was generated
gates:pre's check:packet-offline writes the four fixture packets into the
same directory this gate reads, so after gates:pre that directory is never
empty. A tree that had generated no zone then failed here on all 28 zones,
and gates:post was red on every landing for nothing anyone could fix.

Fixtures exercise the renderer, not a zone. With only fixtures present the
coverage assertion now says it did not run, and the per-packet checks still
run on the fixtures. One zone packet present and all 28 are required, as
before: planted with laos alone, it fails on the other 27.
2026-09-29 03:53:42 +00:00
Doxa Maps
c62db6ee4c The reference baseline shrinks to the 505 lines still present, so the 181 already reworded cannot return 2026-09-29 03:47:01 +00:00
Doxa Maps
2506f71e67 check:done-evidence counts a card's evidence LIST, so a card that records its proof there can close
The board carries a third card schema: proof in an 'evidence' list. 44 cards use it and 12 of them carry
none of the four fields the gate counted, so each false-failed on closing (one already had to be patched
with a redundant bluf). A non-empty list with at least one non-blank entry now counts. The self-test
covers both sides: a real list passes; an empty list, or one of blank entries, still fails.
2026-09-29 03:46:37 +00:00
Doxa Maps
59c2d1b838 Keep the reason, drop the speaker
One hundred and forty-three comments across the research dashboard, the research
map, Big Picture and the two composites attributed a decision to a person: by
name, by quotation, by date, or as "per Driver". Shipped code should carry the
reason a thing is the way it is, not who said so. A reader of the giveaway repo
has no way to ask that person anything, and the name tells them nothing the
reason does not.

So a quotation becomes the requirement it was quoting, a ruling becomes the
ruling, "the Driver reported" becomes what was reported, and a decision still
waiting on an answer now reads as provisional and pending a product decision
rather than as somebody's pending word. Where a comment already stated the
reason -- most of them did -- only the attribution went.

Two things were removed rather than rewritten. A personal medical fact appeared
twice ("the Driver is photosensitive"), once in a comment and once in a gate's
console output where it printed on every run; the motion-safety requirement
stands on its own and now reads that readers may be photosensitive. Internal card
ids that named nobody stayed, since they point at this repo's own history.

The bulk went in three batched passes of meaning-preserving substitutions, and
that is where the risk was: a blind replacement produces sentences that are
grammatically wrong but still look plausible in a diff. Re-reading every changed
line found thirteen -- "the the report describes", "including the two that were
had just complained about", "because was asked for WHITE rings" -- each repaired
individually. The count of remaining references caught the tail three separate
times, when a file type, an escaped apostrophe, and a scripts/ path each fell
outside a filter I had assumed was total.

Comment bodies and two console strings only. All twenty-one files parse, both
meta.json files are still valid JSON, and no declaration moved.
2026-09-29 03:46:37 +00:00
Doxa Maps
a4360f6661 Say what the research found, instead of pointing at where it is filed
Five comments in the shipped research dashboard cited _qa-cycle documents by
path: three RESEARCH write-ups, one REFERENCE standard, and one scratch probe.
Those files do not ship. A reader of the built plugin gets a path they cannot
open, and the reasoning it was standing in for is lost to them.

In every case the durable finding was already written out directly above the
citation, which was only ever a trailing "and here is where that came from". So
each now ends on the finding itself and the path is gone: that a comfort-tuned
palette anaesthetises the response the map exists to provoke; that a casing which
scales with zoom is what stops dense low-zoom rings swallowing their own fill;
that the real fix for pin contrast is the lightness-conditional stroke, which
holds whichever ramp is selected; and that the palette values come from the live
site rather than being re-invented, which is what stops an embedded dashboard
reading cooler than the page hosting it.

The scratch probe became the METHOD rather than a filename, since a probe is
worth citing only for what it does: contrast every ramp stop against all three
pin targets under both CVD simulations, and re-solve composited over the real
basemap. Compositing over pure white is what made two handed-over candidates
look like they passed, and that is the part a future reader needs.

Comment-only: no declaration moved, and all three files still parse.
2026-09-29 03:46:37 +00:00
Doxa Maps
51056bac65 Keep the engineering reason, drop the speaker: 31 Driver references to 0
The rule: never quote or cite the Driver in shipped code. Every one of these
comments had a real engineering reason attached, and the reason is what a future
reader needs — so the reason stays and the attribution goes.

COUNT IS THE CHECK: 31 before, 0 after, across 8 files.

I was pointed at 18 in region-dashboards. That was one file. Sweeping my own area
as instructed found 31 in eight files: region-dashboards 7, RegionCountryMap 13,
regions-list 5, UupgsList 2, plus regions-mfe.vue, regions-mfe.meta.json,
index.js and check-regions-uses-library.mjs with one each. Reporting the wider
number rather than the one I was given.

HOW EACH WAS REWRITTEN, by kind:
  · a REPORTED SYMPTOM keeps the symptom, loses the reporter — "the pins were
    reported too small at rest", "the list page was reported slow, with the
    posters suspected", "instead of flying to the region, which is exactly the
    reported symptom".
  · a REQUIREMENT becomes a requirement — the region key is "a map that shows the
    regions and countries AND lets a reader select one" rather than a quotation;
    the missing fullscreen and zoom controls are "part of the cloned surface"
    rather than someone's complaint about a full clone.
  · an OPEN DECISION becomes provisional. Two of these: the stat-card divergence
    from RegionDetail.vue:176 is now "carried back as a product question", and
    the numeral-grouping parity reads "PROVISIONAL, pending a product decision"
    instead of waiting on a named person's ruling.
  · a CARD ID goes with the name: BUILD-REGIONS-MAP-RESTORE-EDGE-SCROLL-STRIPS
    and the round-2 test numbering are internal handles that mean nothing to a
    reader of a published bundle.
  · a SCOPE RULING is stated as scope: "improve the map's LOOK and add the
    all-regions map, which must be intuitive" — the constraint survives, the
    quotation does not.

Nothing lost that a reader needs: every measurement, pixel value, file:line
citation into DX-life's source and engine-specific caveat is untouched. What went
is a name, four dates and two internal ids.

SAME PROOF AS THE CITATION COMMIT, because a prose sweep across eight files is
exactly where a stray edit hides:
  · the only non-comment changes are an HTML comment in a template and the "note"
    field of regions-mfe.meta.json — and `note` is read by nothing: generate-tree
    never touches it, and the string does not appear in the built manifest.
  · meta.json still parses.
  · rebuilding regions-dashboard and country-map reproduces the committed bytes
    EXACTLY, and a full `bun run tree` leaves public/ clean — so NO bundle needs
    rebuilding for this.
  · gates:pre 66/66.
2026-09-29 03:46:37 +00:00
Doxa Maps
565907adea State what the probes found; drop the paths into the scratch area
gospel's citation rule, applied to mine: a comment citing a PROBE or MEASUREMENT
(_probe-*, _chk-*) states the FINDING and drops the path. A reader cannot open
_qa-cycle from a published bundle, and the path outlives the file — so the number
has to be in the comment or it is lost.

FOUR CITATIONS, all of the probe kind, so all get the same treatment. Every one
already carried its finding; only the dead path is removed:

  RegionCountryMap:36   three engineFor implementations "decide identically in
                        every engine combination" — now says WHICH combinations
                        were checked (mapbox-only, maplibre-only, both globals
                        present) rather than naming the script that checked them.
  RegionCountryMap:462  the globe-fill measurement keeps its numbers (zoom 1.229
                        -> 390.61px = 50.1% of the frame, half the frame dead
                        space) and loses the probe path.
  RegionCountryMap:552  the fit-at-construction comparison keeps both readings
                        (bounds-at-construction zoom 3.210 / mercator at t=0 vs
                        fitBounds zoom 2.777 / globe) and the engine version.
  region-dashboards:522 "4 of 10 app properties OK", with the five failures that
                        were one missing thing already spelled out below it.

I did NOT only fix the one line I was pointed at: I swept my own plugin files,
the six library files I own or moved this session, and the seven gates I wrote.
Four citations total, all in these two files; the gates and the other library
files carry none.

COMMENTS ONLY, and proved rather than asserted: the diff filtered to non-comment
lines is empty; check:ported-globals-identical still reports 4/4 byte-identical
emitted CSS; and rebuilding regions-dashboard and country-map reproduces the
committed bytes exactly — comments are stripped at build, so NO bundle needs
rebuilding for this and public/ stays untouched. gates:pre 66/66.

scripts/strip-code.mjs:5 is deliberately left alone: per the same ruling it
explains why a committed gate cannot import from the scratch area, which is a
fact about the boundary rather than a citation across it.
2026-09-29 03:46:37 +00:00
Doxa Maps
e9e16c7aaf Cloud container: PULL_INTERVAL=0 never pulls, so a mounted checkout is never reset
The pull loop follows BRANCH with `git reset --hard FETCH_HEAD` whenever the branch moves.
In the cloud that is the point. On a checkout mounted from someone's machine it would wipe
their commits and uncommitted work. PULL_INTERVAL=0 now skips the loop entirely: no fetch,
no reset, one line in /tmp/pull.log saying so. Every other value behaves as before.

Checked both ways on a throwaway repo whose remote had moved on and whose working copy held an
uncommitted edit: with 0 the commit and the edit stay; with 1 (the control) the loop resets
both, as it always has.
2026-09-29 03:46:37 +00:00
Doxa Maps
7d2decab5b Shipped text stops gaining references to the Driver: a forward-only gate with a baseline that may only shrink
The standing rule is that comments, docs and UI strings say what the code does and why, never who said
what. Several hundred existing lines break it; owners remove them in one sweep per area before the public
giveaway. Until then check:no-new-driver-references (gates:pre) fails any NEW line that refers to the
Driver and baselines the rest, keyed by file and line text with counts rather than line numbers, so
parallel sweeps only ever shrink it. It matches the role word, not a GPU or device driver, and its
self-test proves both that and that a new line beside baselined ones is isolated.
2026-09-29 03:46:37 +00:00
Doxa Maps
1b2c596011 The share menu's tabs mirrored the wrong way, and the check could not see them
Five physical CSS declarations in the chrome every production map renders, so
under Arabic each of them lands on the wrong side for every map at once:
SemanticTreeLegend's active tab chip drew its border on the left, the share
menu's step list padded from the left, its tab spacing and its auto-spacer both
pushed from the right, and the geocoder's search icon sat at left:7px inside an
input that mirrors. All five become logical properties, which in a left-to-right
reading resolve to exactly what they resolved to before: four insertions against
four deletions in the components, one property each, and the English view cannot
move.

The reason they survived is the more useful half. check-legend-logical-insets
existed and passed, but it read only library/components/legends -- the share menu
is one directory away, and a reader in Arabic does not know which folder a control
came from. So the scan now covers map-controls as well.

Widening it was not enough. It still passed, because its CSS extractor matched a
template literal with the pattern `([^`]*\{[^`]*\}[^`]*)`, which describes a
literal containing ONE brace pair. ShareButton keeps its styles in a 600-line
useShadowStyles literal with hundreds, so the scan captured seven fragments
totalling 7,003 characters of a 629-line file and never saw .share-tab at all. It
was reporting PASS on a directory it was barely reading, which is worse than
having no check: a green gate is read as evidence. Template literals are now
taken whole and kept only when they look like CSS -- 2 blocks, 8,462 characters,
and the rule is finally in scope.

The self-test poisons BOTH directories. Poisoning only a legend would have proved
the check works on the half it always covered, and passed unchanged the day the
wider scan silently narrowed again. Its map-controls arm puts back the exact
declaration this commit removes, and its failure message says what a failure
means.
2026-09-29 03:46:37 +00:00
Doxa Maps
9996bb6fde Comments state their findings instead of citing files outside the repo
Seven comments in the library and the app pointed at probes, checks, a report or research notes that
live outside the repository, where a reader of the code cannot follow them. Each now states what was
found and drops the path: the engine-predicate equivalence, the chooser's globe-fill measurement, the
fit-at-construction probe, the worldview clause, the web-boundaries sizes (1,866 KB to 383 KB, every
feature and part kept). The QA corner and the packet generator lose a dead pointer. The app's nav
comment states the three sections and the third dashboard directly instead of quoting a transcript.
2026-09-29 03:46:37 +00:00
Doxa Maps
68ce355973 Rebuild doxa-simple-map wall-of-people-groups pplr-data-maps, and regenerate the tree 2026-09-29 03:42:27 +00:00
Doxa Maps
88cd390b27 PPLR Data Maps: on a phone every view tab is on screen, and the legend's collapse button is a real target in the reader's language
rega at 390 px: the tab strip ran past the right edge with no scroll cue, and the flat legend's collapse button measured 19×15 with an English title in every locale. On a phone the tabs wrap (the brand steps aside); the button is 24 px and named by the shared aria keys.
2026-09-29 03:38:57 +00:00
Doxa Maps
cc5d592b02 Wall of People Groups: on a phone every control is on screen
rega measured the header at 390 px: one row, so the title wrapped to five lines and the poster-size select (x=393) and the Bless/Social toggle (x=553) ran off the edge, unreachable. On a phone the title takes one short line and the controls wrap onto rows.
2026-09-29 03:38:57 +00:00
Doxa Maps
26a351757a Prayer Progress: the basemap colours' comment states the measurement instead of citing a folder outside the repo 2026-09-29 03:38:57 +00:00
Doxa Maps
e5b7dc8afe Rebuild gospel-internet for the caption and the citation landing
The caption and its eleven locale keys reach the bundle, so this is a real
rebuild rather than a docs-only change. fix:sri reports 127 hashes already
agreed, 0 rewritten; manifest churn is this bundle and the spa manifest's own
self-reference.
2026-09-29 03:35:10 +00:00
Doxa Maps
cfdb0fc606 Say what the two fill modes actually cost on the shipped data
The README described fit and fill-all qualitatively but never said what choosing
one costs, which is the number a reader deciding between them needs: on the
default list, fit draws 931 of 2,074 groups and reports the other 1,143, while
fill-all draws all 2,074 with none outside its band and none under a label.

It also records the evidence for WHY this is a mode rather than a setting: 931 at
separation 7, 1,369 at 5, and 1,369 again at 4. That plateau is what shows the
band's depth is binding and not the spacing, and without it the next reader will
reasonably try to tune the number that cannot help.

Every figure here was re-measured against the shipped tables through the
component's own join before being written down, because a stale number in a
README is worse than no number: it reads as fact and nobody re-checks it.
2026-09-29 03:34:23 +00:00
Doxa Maps
c73a0be9da Shipped code cited a document nobody outside this machine can open
Seven comments across six files pointed at SPEC-GOSPEL-INTERNET-DASHBOARD, which
lives only in _qa-cycle: never cloned, never published. A reader of the public
source met a citation they could not follow, which is worse than no citation —
it implies the reasoning is written down somewhere they may look for.

The durable part is now plugin-designs/gospel-internet/README.md, written as
BUILT rather than as specced: the views and their shared URL state, the list
model and its fail-closed rule, the two set charts (rings, bands, the label
keep-out, fit vs fill-all and why band depth is the binding constraint), why the
charts fit their box, the data joins, the columns, and the gates that guard each
of those. Section 13's honesty rules are carried over close to verbatim, because
they are constraints on the product and not notes: pins are approximate, never
"reached", never who works where, internet figures are the country's, public data
only.

Two citations did not become README links, deliberately. The facet rail's quoted
a design INSTRUCTION ("comes from the kit; do not re-implement"), so the
instruction is kept and only the unreachable document name dropped — inventing a
README section to host it would be citing myself. board.meta.json's and the
instances file's now point at the README section that actually describes the
URL-state behaviour they depend on.

The instances file is edited through the JSON parser: my first attempt did string
surgery and put unescaped double quotes inside a string value, which JSON.parse
caught before it could ship.
2026-09-29 03:34:22 +00:00
Doxa Maps
334b3d6fa1 Stage the row 130 flip, and caption the chart with a total that does not move
STAGED, NOT FLIPPED. SET_FILL_MODE in GiCenteredSetMap is 'fit' — today's
behaviour, unchanged — with the alternative named beside it so the ruling is one
line and a rebuild. 'fit' is not a placeholder: it is the honest mode for a page
that cannot zoom, so the POSTER should keep it whatever the screen chooses.

The binding constraint is recorded there because it is not obvious from the
numbers: separation can never fall below twice the pin floor (5.2), and an
18.5-unit band therefore holds at most three rows, so no minSep reaches ring 2's
658 groups. minSep 7 places 931 of 2,074; minSep 5 places 1,369; minSep 4 places
1,369 — identical, which is what proves depth rather than separation is binding.
'fill-all' places all 2,074 with none under a label and ring 2 using 18 rows.

THE CAPTION states the TRUE total — the groups that have a ring — not the number
of pins currently drawn. In the dense mode the same groups read as an arc of
colour zoomed out and as separate pins zoomed in, so a caption counting what is
visible would change as the reader moved: true at every instant and misleading
overall. It joins the paragraph that already accounts for the centre, the
no-relation groups and the unmatched ones, so those four numbers reconcile
instead of competing.

centered.shown added in all eleven locales; parity and keys-exist pass, and
hardcoded-english did not rise (257, and that fall is studio's, not mine).
2026-09-29 03:34:22 +00:00
Doxa Maps
8cf7a48060 Rebuild the consumers main left stale (build owner, README rule 5)
check:composite-freshness found stale on main: none. Rebuilt, tree + SRI
regenerated; the landing precondition (fresh, one folder, SRI) passed in main-capture scan.
2026-09-29 03:28:59 +00:00
Doxa Maps
2eafd15e1f Experimental shelf: the Wall's "?" describes the Bless poster and Social mode, in every language
Bless mode is the printable poster (a verse under every group, sizes, Print HD poster) and Social mode the shared prayer wall; the texts use each locale's own button names. UPG Neighbor's entry is removed: it has no hosted "?" (its own How it works is its explainer), so the entry showed nowhere and could only drift.
2026-09-29 03:22:40 +00:00
Doxa Maps
83131c64b7 UPG Neighbor keys: How it works names the letter and the prayer packet, beside the CSV 2026-09-29 03:22:40 +00:00
Doxa Maps
9947c95978 check:help-reachable keeps a map whose own control explains it in scope, even with no entry in the help file 2026-09-29 03:22:40 +00:00
Doxa Maps
444f89456c check:universal-props runs on every landing, and says what it checks
It had a script but no description and sat in no gate list, so a map fetching the people-group list
around the library (upg-neighbor's photo lookup) stayed red on development until someone ran it by
hand. With that lookup allowed by name (the commit before), it passes; it goes into gates:post, where
it reads the shipped bundles too, and now carries its description.
2026-09-29 03:22:40 +00:00
Doxa Maps
4f52889692 The Plugin Studio's tree says why it imitates VS Code without pointing outside the repo
Its header cited research notes in a folder that is not part of the repository, named an internal
directive, and quoted a reader's complaint. It keeps the reasoning and the list of what was copied.
2026-09-29 03:22:40 +00:00
Doxa Maps
bfce5fb95e A drop-in destination change no longer makes a landing verify every bundle
landing-preconditions verified EVERY bundle whenever vite.doxa-maps-build-config.json changed, but two
of its keys build nothing: dropin (where sync-dropin copies to) and the "//" comment keys. Changing
dropin.dest cost a full verification, about 10 minutes, for no possible staleness. The config now counts
as a build change only when another key differs. Checked on real history both ways: the dropin.dest fix
is not a build change; the posterSources flip is.
2026-09-29 03:22:40 +00:00
Doxa Maps
e210ca7ef7 check:universal-props: allow UPG Neighbor's photo lookup, which fetches slugs and photos, not a list 2026-09-29 03:22:31 +00:00
Doxa Maps
51ef8523e4 Two library composables point at the dashboard kit contract in the repo, not a spec outside it
useSelectionBus and useDashboardLayout cited sections of a design spec that lives outside the repository.
The contract as built is now library/dashboards/README.md, so both point there. Comments only.
2026-09-29 03:03:52 +00:00
Doxa Maps
30db4bb763 The people-group list is documented in the repo, library/lists/README.md, not in a file outside it
Four places in the repo (the registry, the resolver, the packet renderer and a gate's allowance) cited a
research spec that lives outside the repository, so a reader of the code could not follow the reference.
library/lists/README.md now documents the list as built: how a list is chosen, the registry and its five
lists with their measured counts, where membership is generated, the two tiers (narrowing DOXA's rows,
failing closed; IMB rows per map), the data sources and their terms, and the questions still open. The
four citations point at it.
2026-09-29 03:03:52 +00:00
Doxa Maps
7adf480f3d README: dx-maps ships two ways, the drop-in first and the subdomain container second
The deployment section named four hosting options, three of them CDN-shaped (Cloudflare Pages,
jsDelivr, Netlify). dx-maps now ships two routes, and the section says so in that order:

- The easiest path: drop-in. The folder goes into your own site's public/ and your site's server serves
  every map; bun run build && bun run dropin. One server, no PocketBase. It is the route the DX Life
  branches were proven on.
- Subdomain: the dx-maps container with PocketBase, for maps that need a backend of their own; the
  Dockerfile and compose.yaml at the repo root.

The notes that still hold stay (relative links, iframes, the one CORS header for JS embeds). The
quick start's own drop-in steps become a pointer to the route, and the cloud dev server is described
as following development, not main.
2026-09-29 03:03:52 +00:00
Doxa Maps
af22670dfd Offline packets take page 1's map from the tracked posters on a fresh checkout
packet.mjs looked for page 1's map sheet only in
library/scripts/posters/out/posters/, which exists only after
"bun run posters". On a fresh checkout every offline packet printed the
missing-map notice, and check:packets-all failed each fixture packet
with map=NO.

The same 28 zone poster sources are tracked in data/posters/ and ship
with the tree. The generator now looks in out/posters first (a local
run's fresh sheets win), then in data/posters. With out/posters absent,
all four fixture packets now carry their map. Messages that named the
sheet give its real path.
2026-09-29 03:03:51 +00:00
Doxa Maps
867c559095 One server for a site and its maps: bun run dropin, and the drop-in goes straight into the site's public/
The drop-in was proven on a laptop with one server: the three DX Life branches rendered their maps,
7 of 7 checks passed, with internal/sync-dropin.mjs --from-config --dest <doxa-life>/public. This
makes that the default path:

- dropin.dest is ../doxa-life/public. The old ../doxa-life/public/embed/doxa-maps-build was stale.
- bun run dropin = sync-dropin --from-config; deploy:dev (build + drop-in) now uses it.
- The README gains "The easiest path": the studio for the work, dropin.dest at your site's public/,
  then build and drop in.

One hazard came with the new destination, and it is closed here. --prune deletes every file in the
destination outside the drop-in's allowlist, which was right while that was a folder of its own. Into
a site's public/, it would delete the site. deploy:dev passed --prune, so it no longer does, and
sync-dropin refuses --prune into any folder named public/. Measured in a scratch site: --prune into
site/public exits 1 with the reason; a plain drop-in copies dx-maps/ and leaves the site's own
file in place.
2026-09-29 03:03:51 +00:00
Doxa Maps
a841dbd322 Rebuild prayer-walk-planner, and regenerate the tree 2026-09-29 02:59:02 +00:00
Doxa Maps
7b5de8bcfa Prayer Walk Planner: on a phone the zone list opens from a ☰ Zones button instead of being hidden
On a narrow screen the list used to be display:none, so a walker could not see, select, rename or delete a zone there. It is now a drawer over the map from the reading-end edge (right in English, left in Arabic), behind a ☰ Zones (n) button that leads the toolbar; a tap outside, × or Escape closes it and focus returns to the button. On a wide screen it is the sidebar as before, its border now on the reading side. Two words in the planner's own 11-locale strings: zones, closeZones.
2026-09-29 02:58:03 +00:00
Doxa Maps
1bbc5ff871 Rebuild all-people-groups, and regenerate the tree 2026-09-29 02:54:11 +00:00
Doxa Maps
cf962e4065 All People Groups: no DOXA group is drawn twice; the base is re-sent slim without them
The feature-state approach left an invisible twin per DOXA group (2,616 circles drawn at alpha 0, and counted by queryRenderedFeatures). When the DOXA set arrives the base is re-sent once, slim (1.25 MB, not the old 3.7 MB), without the DOXA rows; the black pins stay on their own top layer. Popup fields and counts unchanged.
2026-09-29 02:53:16 +00:00
Doxa Maps
fd118fa2b5 Rebuild the consumers main left stale (build owner, README rule 5)
check:composite-freshness found stale on main: none. Rebuilt, tree + SRI
regenerated; the landing precondition (fresh, one folder, SRI) passed in main-capture scan.
2026-09-29 02:47:12 +00:00
Doxa Maps
1226cbeb0a Say what <doxa-country-map> is, so the gate and the gallery both have an answer
check:descriptions failed on development: country-map.vue had a rich prose header
and no @description TAG, which extracts to an empty string. My header explained
at length why the bundle exists and never said, in one line, what it IS — so the
plugin-studio, the gallery and the SPA's rail had nothing to show a reader.

The tag matters more than the prose: generate-tree matches
/^\s*\*\s*@([\w-]+)\s+(.+?)\s*$/gm, so a paragraph with no asterisked tag is
invisible to it. Verified the text reaches the BUILT manifest, not just the
source, since the manifest is what the gate and every consumer read.

37 of 37 profiles now described. country-map rebuilt, tree and SRI refreshed.
2026-09-29 02:35:36 +00:00
Doxa Maps
38e36e7de4 Fold the dangling-selector gate into the portedGlobals migration
One sha instead of two, per the shepherd: both branches fixed the SAME
regions-list rule, because a gate is useless on a tree where its poison case is
still live. Landing them separately would have applied that fix twice.

★ I HAD CLAIMED THE TWO COPIES WERE IDENTICAL. They were not — I checked before
folding rather than after, and the hashes differed. The CODE line is the same in
both (`cyrillicHeading('.rl-title')`, one occurrence each); only the explanatory
comment differed. I kept the css-dangling wording because it names the arm that
now catches the bug, which is what the next reader needs. The emitted CSS is
unchanged either way — comments are not CSS — and the byte-compare baseline still
passes, which is the assertion that would have caught me if I were wrong.

WHAT THIS SHA CARRIES, both halves verified after combining rather than assumed:

  THE MIGRATION (was 4a4bc96fc). The harness stub's `.value` is now the empty
  string, so an injected-config read and a locale interpolation both behave as
  they do in the browser. Measured components 2 -> 4, no NOT MEASURED left.
  region-dashboards is byte-identical (a pure de-duplication); regions-list
  changed because its old bytes were the bug, so its baseline was re-taken.
  strongMedium() deliberately NOT adopted — the helper emits bare `strong{...}`
  where region-dashboards scopes `.rd-root strong{...}`, and swapping would widen
  the rule.

  THE GATE (was 023ca2d08). check:shadow-css-parses now fails on a selector that
  never receives a block, gated on `endsLiteral` — the LAST fragment — rather than
  on `complete`, which is false for nearly every sheet here and therefore
  suppressed the very bug the arm was written for. Four self-tests live in the
  file: the exact regions-list string is caught, a last rule WITH a block is not,
  a trailing comment is not, whitespace is not.

Re-verified on the combined tree: ported-globals 4/4 byte-identical, shadow-css
80/80, the poison case still produces exactly 1 dangling hit when reintroduced,
gates:pre 56/56, and the broken form is absent from the shipped minified bundle.
2026-09-29 02:35:36 +00:00
Doxa Maps
7dc634275e Put the cloud Dockerfile at the repo root, so a platform pointed at the repo finds it
Railway, DigitalOcean and `docker build` use a root Dockerfile with no setting, and it is
Coolify's and Dokploy's default path. The file moves; nothing is copied.

- VOLUME /data is gone. Railway rejects the line, and a named volume is needed anyway
  (VOLUME alone gives each new container a fresh anonymous one). The platforms mount /data.
- The baked PocketBase gate's comment named the old path. It now says the repo-root
  Dockerfile, and the gate's SHA-256 pin moves with it (2987055b... -> 2eceef03...). The
  hook's code is unchanged: the exact RUN passes `sha256sum -c`, and the old block still
  passes its old pin.
- .dockerignore excludes everything: the Dockerfile copies nothing from the build context,
  so no platform sends the whole repository to the builder.
- compose.yaml: the same image with its /data volume and memory limit; settings from .env.
- deployment/cloud/README.md gains "Where it runs": Coolify, Dokploy, Railway, a server with
  Docker, Kubernetes, and why not DigitalOcean App Platform (no persistent volumes).
  DEPLOY.md and two comments follow the move.
2026-09-29 02:35:36 +00:00
Doxa Maps
9b4db40443 Three post-landing gates green again: the library index, the capability table, Path2D
gates:post had gone red on development in three places that no landing runs, because landings run
gates:pre. The library index and the capability-exemplars table are derived documents: regenerated,
they now list the country-map bundle, BackToTopButton and useHostedHelp. check:no-free-identifiers
flagged the Wall's new Path2D (a canvas built-in every browser has) as never bound; it knows the
canvas built-ins now. No code changes.
2026-09-29 02:35:35 +00:00
Doxa Maps
0ec437585d check:window-globals-released and check:legend-data-contract run on every landing
Both were registered but in no gate list, because each was red on development: wiring either in
would have failed every landing. They are clean now (the plugin-studio widget release fixed the
first, the country map's legend rows the second), so they go into gates:pre, where a regression
fails the landing instead of waiting for someone to run them by hand.
2026-09-29 02:35:35 +00:00
Doxa Maps
fc3b64bbe6 Migrate the regions pages onto portedGlobals, and fix a Cyrillic rule that was inert
upg100's harness recorded my two files as NOT MEASURED because its stubs threw on
them. Both were FIDELITY gaps — the stub modelled states that cannot occur — so
they are fixed by making the stub more like the browser, never by relaxing a
comparison:

  · `.value` was undefined, so a component reading an INJECTED ref threw
    "Cannot read properties of undefined" (region-dashboards.vue:199,
    cfg.value.regionSlug — ProfileLoader always provides that object).
  · the stub threw on ANY string coercion, because a Proxy over a function has no
    primitive conversion. That alone produced regions-list's "Cannot convert
    object to primitive value".

`.value` is now the EMPTY STRING, which is the one shape that serves both real
cases honestly: a locale ref's .value IS a string, so `${locale.value || 'en'}`
correctly yields 'en'; and reading a property off '' returns undefined instead of
throwing, so an injected-config read falls through its own `|| ''` exactly as in
the browser. Symbol.toPrimitive makes any remaining stub render empty.
Measured components: 2 -> 4, with no NOT MEASURED left.

★ AND THE MIGRATION FOUND A REAL BUG, which is the part worth reading.
regions-list's Cyrillic heading rule ended with a trailing comma and NO
declaration block, so the concatenation emitted:

    [lang="ru"] .rl-title, [lang="ru-RU"] .rl-title,regions-list

A selector list with nothing to apply — the Russian heading font was silently
never applied — AND it swallowed useShadowStyles' style-id argument as CSS,
leaving that sheet unlabelled. Braces stayed balanced, so
check:shadow-css-parses could not see it, and it passed 80/80 both before and
after. cyrillicHeading('.rl-title') replaces it and emits a real rule.

SO THE BYTE-COMPARE SPLITS, deliberately, and I did not paper over it:
  region-dashboards.vue  IDENTICAL — cyrillicHeading('.rd-title','.rd-h2')
                         emits exactly the literal it replaced, verified
                         character-for-character before the swap. A pure
                         de-duplication.
  regions-list.vue       CHANGED, and the change IS the fix. The old bytes were
                         the bug, so its baseline is re-taken rather than
                         preserved. Preserving it would have pinned a broken
                         rule forever, which is the one thing a byte-identical
                         gate must not be used for.

NOT MIGRATED, on purpose: strongMedium(). The helper emits bare `strong{...}`
while region-dashboards emits `.rd-root strong{...}` — a narrower selector.
Swapping it would widen the rule to every <strong> in the shadow root, a
behaviour change disguised as a de-duplication. scrollbarGutter() is likewise
left inline: both files carry it inside a larger .rd-scroll/.rl-scroll rule, not
as a standalone declaration the helper could replace.

Both files now gated (owned:true) instead of recorded-only, so a future edit that
changes their emitted CSS fails rather than being noted.
2026-09-29 02:35:35 +00:00
Doxa Maps
65810f872f A back-to-top beside the view-list circle, and lift the Regions attribution clear of it
Two reported mobile bugs (DRV1650 #3 and #4), both settled by rega's
measurements at 390x844 on dx-life /research rather than by reading source.

★ #3 — THERE WAS NO BACK-TO-TOP AT ALL, and I had reported otherwise. I said
VerticalTabsLayout already rendered one targeting the active .vtl-host. rega:
ZERO .vtl-host elements in the live build, button.btt ABSENT from the DOM. The
chain: VTL renders .vtl-host only for `mountedElementRows`, which filters rows
carrying an `element` key — and this SPA passes :groups whose rows carry none,
mounting its own .dms-pane panes imperatively. So activeHostEl is null, and the
layout's button correctly renders nothing. That button is for layouts hosting
their own element rows; it is inert for the composition the gallery uses. I had
read the source and not the configuration.

The SPA now renders its own, bound to the ACTIVE .dms-pane — the scroller rega
measured (clientHeight 712, scrollHeight 12,373, overflow-y:auto) while the
document does not scroll at all. The gallery owns both the circle and the
scroller, so no shadow-root walking is needed. Placement reads --map-fab-beside,
published by the layout only while its circle renders, so the arrow sits BESIDE
the circle and falls back to the plain inset where there is none.
BackToTopButton already refuses to appear when its target does not scroll.

The label goes in all ELEVEN of the SPA's locales. Its translate() falls back to
the KEY, so a missing entry would have rendered the literal string "backToTop"
to every reader — en/ar/zh/ru/hi verified, and the Arabic and Chinese strings
are present in the built bundle.

★ #4 — THE REGIONS ATTRIBUTION SITS UNDER THE CIRCLE, and the geometry was not
what I assumed. rega: at 390 the attribution is details.maplibregl-ctrl-attrib in
the BOTTOM-RIGHT container, expanded to 347x44 at (22,791) — nearly the full map
width — so its LEFT END runs under the 44px fab at (12,788), 1,394 px² overlap.
The bottom-left container is EMPTY (0x0), so a bottom-left-only fix would have
changed nothing.

Both bottom containers are lifted off the published --map-fab-clearance under a
767px query, so a standalone leaf or a <doxa-country-map> with no gallery around
it reads the 0px fallback and nothing moves. The existing z-index:3 cannot help:
the circle lives in the layout, outside this shadow root, so the two are
different stacking contexts. Attribution is a LICENCE TERM of the basemap, which
is why this is a fix and not a polish item.

REBASED ONTO 0428f279c BY REPLAYING SOURCE, not by resolving a conflict: the
rebase collided only in generated bundles, where a conflict is never resolvable
by hand. Three files applied clean; RegionCountryMap.vue had MOVED to
library/components/maps in that same round (my own country-map work), so its rule
was re-applied at the new path by hand — a straight patch would have failed with
"No such file or directory", which is exactly what studio would have hit.
country-map, regions-dashboard and doxa-maps-spa rebuilt; both fixes verified in
the shipped minified bundles, including the new country-map bundle that now
inherits the lift.

NOT PROVEN HERE: the headless DOM proof needs a browser (rega's seat). What I
verified is that the button, its scoped CSS and its ar/zh labels are in the
SHIPPED doxa-maps-spa.js, and the fab lift in both map bundles.
2026-09-29 02:35:35 +00:00
Doxa Maps
b8166218b9 The country map's live poster prints its legend: it read rows the legend composable never had
countries-map's print path set the poster's legend from legend.rows, but useLegendData returns items,
never rows, so the renderer got an empty list and page 1's live map printed with no legend. It now
takes the items, with their child tiers flattened in, as research-map's poster does since A1.
check:legend-data-contract FAILED on this file and PASSES now; it goes into gates:pre with this round.
2026-09-29 02:35:35 +00:00
Doxa Maps
24f965b6a4 A gated bundle builds outside public/, and a gate keeps it out of the tree
key-cities is a gated profile: generate-tree leaves it out of the manifest and the pages. Its
bundle file was still in public/dx-maps/key-cities/key-cities.js, committed, and the landing
freshness pass rebuilt it into public/ every time it looked "stale", so any host that serves or
drops in the tree folder whole shipped it.

- vite.config.js builds a gated bundle into .gated/<tree>/ (ignored by git) instead of public/.
- check:composite-freshness skips gated bundles: they have no committed bytes to be fresh against.
- The committed public/dx-maps/key-cities/ is removed. History is not rewritten.
- generate-tree removes a gated folder it finds in the tree (an older build, or a landing that kept the
  tree's side of a conflict), so the tree it writes is clean whatever came before. Measured: a planted
  public/dx-maps/key-cities/key-cities.js fails the gate, and one regeneration removes it.
- The gated list is read in one place, internal/_tree-name.mjs, beside the tree name.
- check:gated-not-in-tree (gates:pre) fails if a gated name has a folder in the tree, a manifest
  entry, or a page referencing its script, and proves each arm on a planted leak first. It FAILED
  on the tree before this change (key-cities/ is in the tree) and PASSES after.

The data tables are left alone on purpose: key-cities.json also feeds the production Cities with
Internet map, and whether that dataset should be public is a separate decision.
2026-09-29 02:35:35 +00:00
Doxa Maps
dcd1c13f55 The app answers each map's own ? and drops the one at the top centre
doxa-maps-spa marks the pane of every tab that has explainer text with data-doxa-help, listens for
doxa:help, and opens the same what/how dialog for the active tab. Closing returns focus to the
exact "?" that opened it (composedPath reaches into the map's shadow root). The top-centre button
and its CSS are gone; a comment for the device-rule toast that sat above the old button's rule
is back on its own rule.

check:help-reachable (gates:pre) keeps it reachable: every map with text has a hosted "?" or a
named help control of its own; the app listens, marks its panes and has no top-centre button; and
the composable, executed on a small fake DOM, dispatches doxa:help bubbling and composed and finds
the host across a shadow root. Six planted defects, each caught.
2026-09-29 02:35:35 +00:00