Commit graph

3176 commits

Author SHA1 Message Date
Doxa Maps
decdf31ed5 Merge remote-tracking branch 'origin/development' into dx-base/handoff-integration 2026-09-29 15:24:30 +00:00
Doxa Maps
76d54e06a2 Merge remote-tracking branch 'origin/development' into dx-base/handoff-integration 2026-09-29 15:04:25 +00:00
Doxa Maps
62afbdcdf3 check:ported-globals-identical compares only the ported rules, not the whole stylesheet
The gate hashed each component's entire emitted CSS, so any unrelated style change
in these four files (Regions giving three :host rules position:relative) failed it,
and could pass only by re-baselining a guard that exists to prove something else.
It now hashes what library/styles/portedGlobals.js emits, found in the executed CSS:
each cyrillicHeading and strongMedium rule with the selectors in front of it, and
each scrollbar-gutter declaration with its rule's selector.

Re-baselined on the tree the old gate passed on. Proven both ways: re-scoping
GoalCard's strong rule, changing the module's Cyrillic letter-spacing and dropping
a file's ported rules each fail it; --write refuses to baseline an empty block.
2026-09-29 15:03:05 +00:00
Doxa Maps
3b41321d18 Merge remote-tracking branch 'origin/development' into dx-base/handoff-integration 2026-09-29 15:00:47 +00:00
Doxa Maps
56f049d11c Merge origin/development into repo/integration at the landing turn 2026-09-29 14:59:51 +00:00
Doxa Maps
d7ec917023 Record as merged, content NOT taken (already on development / superseded / abandoned):
- repo/auto-signin: carried by repo/handoff's 6df2da9c9 (merged above), replayed with its entry.sh conflict against V2 and DEV_SERVER resolved; UNVERIFIED, default off.
- repo/plugin-designer-skill: on development with edits: cde106a55 landed as f66650462, and the other four are in skills/plugin-designer/SKILL.md sections 2, 5, 8 and 9-10 in newer wording.
- repo/gospel-number-revert: abandoned. The numerals hold became the LOC-ENUS warn in check:universal-props, pending the numerals decision, and gospel has since rewritten the files it touches.
- repo/land-shots, repo/land-shots4: superseded by later README screenshot retakes on development (6e5c0874d, 09-27 09:58Z, and after).
2026-09-29 14:58:20 +00:00
Doxa Maps
33864ffac7 Record as merged, content NOT taken (already on development)
- rega/gi-heavy (05b5a3e62): already on development as f9ce2aeb4 (same
  change: the gospel-internet board counted as a heavy Gallery tab);
  all 6 files it touches were changed later on development.
- rega/readme-shots-7a7ca67b (12070ba36): already on development as
  17763cdcc (20 of its 23 shots) and 6e5c0874d (the other 3); all 22
  files it touches were retaken later on development.

Merge strategy "ours": the tree is unchanged.
2026-09-29 14:54:32 +00:00
Doxa Maps
5154c5c7bf Merge rega/readme-shots-8b6f37f6, keeping development's side
The branch retook 25 README shots tokenless from main 8b6f37f6 on 09-26.
Development has since retaken every one of those files (d25974f04,
9caf30e81, 17763cdcc, 6e5c0874d), so its content is superseded: this
merge records the branch as merged and keeps development's tree
unchanged (merge strategy "ours").
2026-09-29 14:51:58 +00:00
Doxa Maps
65e17f614f Merge repo/handoff: OIDC_AUTO_SIGNIN (UNVERIFIED, default off) and the Rudder screenshot commits it carries (already merged above) 2026-09-29 14:50:50 +00:00
Doxa Maps
4d65d399b1 Merge origin/development into the handoff integration 2026-09-29 14:50:47 +00:00
Doxa Maps
506b42fc31 Merge repo/readme-rudder-shot: the Driver's Rudder desktop screenshot in the README (HELD since 01:32Z for his cut-off condition "before you add it to the readme you nee"; merged on the merge-all order) 2026-09-29 14:50:44 +00:00
Doxa Maps
1495fb73c6 Merge repo/v8-reader-2: its content is already on development (studio squashed 53dbba4a4 + f0e965e4d into the R26 README commit); recorded as merged, development's side kept 2026-09-29 14:50:44 +00:00
Doxa Maps
0a813fca35 Merge repo/public-studio-mode: its content is already on development (21330066e landed as 097952b5f with the V2 round); recorded as merged, development's side kept 2026-09-29 14:50:43 +00:00
Doxa Maps
a4cc29e163 Merge dx-base/a9-leaf-hints (content already integrated via cherry-pick; merge for the handoff record)
# Conflicts:
#	package.json
2026-09-29 14:50:34 +00:00
Doxa Maps
b9854dd072 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 14:42:52 +00:00
Doxa Maps
6df2da9c99 Cloud container: OIDC_AUTO_SIGNIN, so the Studio can sign in inside a dashboard frame
With it on, oauth2-proxy skips its button page. For a user already signed in to the provider the sign-in is
then redirects only, and the provider's no-framing headers never apply, so a framed Studio signs itself in.

UNVERIFIED: never run live in a framed dashboard (written 2026-09-28 04:32Z, not landed until the 14:38Z merge-all order).
Checked only statically: bash -n passes, and the argument switch yields --skip-provider-button for 1 and nothing for unset or 0 under set -u.
Replayed onto development: AUTO is now defined inside the OIDC gate block (ACCESS != public), next to the ALLOW list, and passed before --upstream="$ROOT".
Default off, so no running container changes.
2026-09-29 14:40:58 +00:00
doxa-maps
3ea2e3ce69 A9 leaf hints: cover a composite whose engine is in a composed child bundle
dx-shepherd/rega: both production composites should get hints, but 37/27 showed
only regions-mfe was reclassified. research-map-plus-grid was still bare: it
composes doxa-research-map (74 engine refs) but its OWN bundle has 0, so keying
on the own-bundle signal alone missed it.

builtBundleLoadsEngine now also scans the bundles a leaf `composes`, resolving a
composes entry that names its child by element TAG (stack-demo composes
"research-dashboard-map", bundle "research-dashboard") through an
element→bundle map built from the scanned bundles. So a composite is hinted
when its own bundle OR any composed child's bundle loads an engine; big-picture
(composes only non-maps) stays out.

Result: both production composites hinted (regions-mfe via own bundle,
research-map-plus-grid via its child), and stack-demo too (it composes a map).
check:leaf-hints mirrors the same tag→bundle resolution from the manifest, so
gate and generator can't drift: PASS 41 hinted / 23 bare. Poison-tested the
composes-child case (stripped → fails) and the pure-non-map case (big-picture
given a hint → fails). Also carries the comment reword from the token-ladder
correction across every hinted leaf.
2026-09-29 14:40:28 +00:00
doxa-maps
778e990ca4 A9 leaf hints: a token no longer selects Mapbox — engine = provider or MapLibre default
Correction (dx-shepherd 2026-09-29, from regions executing the shipping
resolver, confirmed by rega's A9NET on the wire): useMapProvider's default is
MapLibre unless provider is EXPLICITLY 'mapbox'; a baked token or a tokenUrl
does NOT select Mapbox any more. rega measured all 8 auto leaves — which carry
a pray.doxa.life data base — loading MapLibre from jsdelivr cold.

engineFor now returns 'mapbox' only for provider:'mapbox', else 'maplibre',
ignoring bakedToken/tokenUrl. And the engine preload is emitted unless the leaf
already has a static engine <script> — which only a provider:'maplibre' leaf
does; a provider:'mapbox' leaf emits no static tag (the Mapbox static tags were
removed 2026-09-25), so it now correctly gets a preload too.

No tree change: every production leaf is 'auto', and the old code also resolved
those to MapLibre (tokenUrl is empty, so hasEndpoint was false), so the built
hints were already jsdelivr+openfreemap. This makes the logic correct by reason
rather than by the config happening to be empty. check:leaf-hints PASS 37/27.
2026-09-29 14:40:27 +00:00
doxa-maps
71f36b5079 A9 leaf hints: key on the bundle's engine, not the leaf's isMap flag
rega's A9NET run found two production composite tabs — regions-dashboard/
regions-mfe and research-map-plus-grid — getting NO hints, because my first
cut gated on the leaf's isMap flag and both are isMap:false composites that
mount a map child and load the engine at runtime. Same trap the
check:leaf-engine-per-provider isMap gate fell into.

The gate now keys on whether the leaf's OWN page loads a map engine: a real
map leaf (isMap), OR a cap-4 nested-elements-mfe composite whose BUILT bundle
references an engine (builtBundleLoadsEngine). It excludes a cap-5
nested-mfe-spa shell (maps are iframe children), a genuinely map-less
composite (big-picture, stack-demo: 0 engine refs), and a plain non-map leaf
sharing a bundle with a map (connectivity-list). regions-mfe (33 engine refs)
is now hinted; research-map-plus-grid is not — its own bundle has 0 refs, the
engine loads in a lazily-fetched child (recorded gap; the deeper cause is the
upstream isMap classifier mislabelling these composites, which composes-by-tag
where the child .vue filename differs).

check:leaf-hints rebuilt to the same rule, read from the manifest (profile
type + isMap) and the built bundle, so gate and generator cannot drift. PASS
37 hinted / 27 bare; both new poisons bite (composite stripped of hints;
non-map leaf in a map bundle given a hint).
2026-09-29 14:40:27 +00:00
doxa-maps
fcebf2c589 A9 lever 1: engine-aware resource hints on map leaves
A cold map ran serially: leaf HTML, then the bundle (250-470 KiB gz, parsed),
then only the engine (from cdn.jsdelivr.net or api.mapbox.com), then the style
host, tiles, and data. Four origins discovered one after another, none
pre-connected. That waterfall is the shape behind A9's 3-5 s cold map.

generate-tree now emits, in each MAP leaf's <head>, a preconnect for the
engine origin, the style host and the data origin, and -- only on the 'auto'
leaf, which carries no engine <script> today -- a preload of the engine, so
DNS/TLS and the engine download start alongside the bundle instead of after
it. A non-map leaf (grid/card/chart-table/app-shell/composite) gets nothing.

The engine is decided at build time from the SAME inputs the runtime ladder
reads (profile-config.provider, then a baked token or a token endpoint; 'auto'
with no token path is MapLibre keyless), so the hint and the ladder cannot
disagree. Every URL, integrity and crossorigin comes from
library/constants/cdn.js, so a preload cannot drift from ensureEngineScript's
tag. The one case a static hint cannot foresee -- an explicit ?provider= or
window.MAP_PROVIDER overriding the build default at runtime -- costs one unused
preconnect.

New gate check:leaf-hints (gates:post): a map leaf without hints fails, a
non-map leaf carrying a map-engine hint fails, and a hint origin must be one
the code uses. PASS: 36 map leaves carry hints, 28 non-map leaves carry none.
Poison-tested both sides.

Unlanded (dx-base/a9-leaf-hints); the bundled-vs-shared-vs-today trade-off
report and any landing wait on rega's timing and regions' tiles.
2026-09-29 14:40:27 +00:00
Doxa Maps
5e99195fdf rudder-voice README: the desktop build is no longer "not yet" on Linux
"Not yet" still listed the desktop build with the engine as a Tauri sidecar.
On Linux it builds and runs; what is still not done is macOS and Windows
(the engine cross-compiles for both and has never run there).
2026-09-29 14:40:11 +00:00
Doxa Maps
9227ad8eb7 README: show Rudder running as a desktop app
Output 3 showed only the map app as a desktop app. Rudder is the desktop
example the README describes, and it has now been built with
`bun run desktop:build rudder-voice` and run on Linux: a screenshot of the
running app goes under the map app's, with one line on how to build it.

The PNG carries no text chunks (the original recorded its creation time
and timezone); the pixels are unchanged.

HELD since 2026-09-29 01:32Z for a condition from the Driver whose message was cut off ("before you add it to the readme you nee"). It is merged now on the 14:38Z merge-all order. Drop this commit if that condition is not met.
2026-09-29 14:40:11 +00:00
Doxa Maps
1b19b7478a Register the control-inset gate, now that it has been run and proven
verify-browser-all's rule is that every gate listed there was run before it was
listed, so this waited for a real run rather than being registered on the
strength of the code reading correctly.

Measured, on the browser seat: the positive arm PASSES 9/9, and the mutation arm
-- the maplibre selector inside bottomControlInset broken to a name that matches
nothing -- FAILS on cases 1 and 5, the two whose pages carry only a maplibre
attribution. Cases 2, 3, 6 and 7 pass under the mutation by design: they assert
the inset is 0, so a selector that stops matching cannot make them fail. They
guard the opposite error, reserving space that is not there.

Registered in all three places: the script, its //scripts description, and
verify-browser-all's list. Deliberately NOT in gates:pre -- every assertion rests
on getBoundingClientRect, so it needs a browser and exits 75 NOT MEASURED
without a slot, which is never a pass.
2026-09-29 14:35:29 +00:00
Doxa Maps
315dd78520 A gate that the band the pan reserves is the band the controls occupy
check:popup-pan-controls proves the ARITHMETIC: given a band height, the pan
clears it. Nothing proves the band height is real. If bottomControlInset returns
0 -- a selector typo, an engine renaming its control classes, a control moving
outside the container -- then panPlanFor is handed bottomInset 0, behaves exactly
as it did before the fix, and check:popup-pan-controls still passes every case
because its arithmetic is still correct. The bug would be back with both gates
green. That is the failure this guards.

Seven cases against a real DOM, running the SHIPPED bottomControlInset read out
of the SFC (it is not exported), never a re-typed copy: a visible attribution
reserves a band EQUAL to what the DOM independently says, not merely non-zero,
which a stub returning 1 would satisfy; no controls reserves exactly 0; a
zero-sized control reserves nothing, since counting it would reserve the whole
map and refuse every popup; the taller of two corners wins, because the pin hint
is bottom-left while the attribution is bottom-right; a wrapped attribution
reserves more than one line, which is what a hard-coded constant gets wrong at
390; a control outside the container is ignored; a null container returns 0
rather than throwing.

It needs a real browser: every assertion rests on getBoundingClientRect, and no
DOM library here computes layout, so a headless stand-in would return zeros and
pass vacuously.

NOT REGISTERED. Browsers are rega's seat, so this has not been run, and
verify-browser-all's rule is that every gate listed there was run before it was
listed. It is registered only after rega's PASS.
2026-09-29 14:35:29 +00:00
Doxa Maps
7636404523 check:universal-props measures every bundle the tree has, and fails loud where it used to narrow silently
Its population was a hand list of 19 names, and it had drifted: the PRODUCTION country-map was never on
it, so its LIST-FETCH (the header's list prop never applied) passed unseen, and big-picture fetched the
list correctly with nothing to catch a regression. The population is now every plugin-designs/<b>/
with an index.js, minus an EXCLUDED map with a reason per entry (empty today), so a new bundle is
measured the day it lands: 28 bundles instead of 19.

Two fail-open paths now fail. `--only <name>` naming no measured bundle printed "PASS: 0 bundles"; it
exits 2 and lists what is measured. A listed bundle whose folder moved was skipped by an existsSync;
the population is derived from the folders, and an EXCLUDED entry naming no bundle is a stale decision
and also exits 2.

Measured over the whole tree, only two bypasses surfaced. country-map's LIST-FETCH is fixed by regions'
commit just before this one (same round, as it must be: this alone goes red). demo-map's LIST-DSM gets a
narrow ALLOW with its reason: it is scaffolding that demonstrates the raw DataSourceManager path, off
both reader shelves, and its other props are still measured. PASS: 28 bundles, 9 self-tested rules.
2026-09-29 14:35:28 +00:00
Doxa Maps
1208fdce71 The production Country Map honours the host's people-group list
country-map.vue fetched /api/people-groups/list and mapped the posts straight to
points, without applyPeopleGroupList(). Every other consumer of that endpoint
calls it -- DataSourceManager, useDashboardData, useCsvExport, peopleGroupRecords
and the people-groups grid. So a host that asked for one curated list got the
WHOLE endpoint drawn on its country map, and nothing said so: the pins looked
like data rather than like the wrong list.

The list is applied to the raw posts, before they become points, which is where
every other consumer applies it. That is what keeps this element and a dashboard
showing the same country from disagreeing about which groups are in it -- the
same reason the country filter beside it is client-side.

The default list returns the rows untouched and makes no fetch, so an
unconfigured host pays nothing. A list whose membership cannot be loaded yields
NO groups rather than the wrong ones: applyPeopleGroupList's own contract, and
the safer failure for a prayer surface.

Measured three ways, with a temporary copy of check:universal-props that adds
country-map to its BUNDLES list (the reason the gate never saw this: 19 bundles
listed, this one absent):
  - RED on development: BYPASS country-map LIST-FETCH country-map.vue:98, exit 1;
  - GREEN here: 20 bundles read engine, token, locale and list through the
    library, exit 0, the gate's 9 planted-bypass self-tests still caught;
  - a real list value changes the drawn count, through the shipped
    applyPeopleGroupList and the served membership files: Nepal draws 34 points
    on the default list, 29 on "frontier", 0 on "frontier-500k".

Rebuilt, tree regenerated, SRI refreshed. The list-membership URL is present in
the built bundle; the manifest diff is integrity hashes only, 0 other lines.
2026-09-29 14:35:28 +00:00
Doxa Maps
12078103d1 check:no-orphan-gates sees gate FILES outside internal/: a plugin's own gate that nothing runs fails
The file walk read internal/ only, and the chain arm counts package.json NAMES, so a plugin-local
check-*.mjs that nobody registered was invisible to both: on 2026-09-29 six were run by nothing
(dx-shepherd's census; exp's four have since been registered). Every tracked gate-shaped file under
plugin-designs/ or library/ must now be run by PATH from a script or another file's code, carry
@one-shot, or sit in the shrink-only `files` baseline for its owner.

Two rules keep it honest. Comments are stripped before searching, since a file mentioned in prose is
not run by it. And a basename counts only where no other gate file shares it:
library/scripts/research/verify-map-paint.mjs (199 lines, run by nothing) shares its name with
internal/verify-map-paint.mjs (342 lines, what verify:map-paint runs), so a name match let one file's
registration vouch for the other. Self-tested on planted cases (unregistered, comment-only, shared
basename). On the real tree it finds 4, now baselined for their owners:
  library/scripts/research/verify-map-paint.mjs            likely a stale copy
  plugin-designs/doxa-research-map/scripts/check-reduced-motion-jumps.mjs
  plugin-designs/gospel-resources/scripts/verify-translator-org-links.mjs   a data tool? @one-shot or register
  plugin-designs/research-map-plus-grid/scripts/check-ui-strings-have-keys.mjs
The chain arm's writer now keeps every key of the shared baseline file.
2026-09-29 14:35:28 +00:00
Doxa Maps
dbee30e827 Rebuild pplr-data-maps upg-neighbor, and regenerate the tree 2026-09-29 14:30:26 +00:00
Doxa Maps
a92f03dd43 Key Cities: a row's languages read as a list in the page's grammar, and a shortened one says so
The row meta joined its first three languages with English commas and cut the rest silently, so a truncated list read as complete. It now goes through formatList (Intl.ListFormat) as a conjunction whose LAST item states the cut, from keyCities.list.moreLanguages ("{n} more", landed in R29): "Hindi, Urdu, Bengali, and 2 more" / "Hindi وUrdu وBengali و2 أخرى" / "Hindi、Urdu、Bengali和另外2种". Three or fewer languages show as they are. Not 'unit': in zh it joins names with no separator, and in ar and hi it still adds a conjunction (studio, measured).
2026-09-29 14:29:20 +00:00
Doxa Maps
1d78d83a98 PPLR and UPG Neighbor: two lists read in the page's own list grammar, through formatList
join(', ') is English list grammar. The two user-facing sites now go through the library's formatList (Intl.ListFormat, R29), as conjunctions because each is the FULL list:
- PPLR's Rosetta popup, "Not yet cross-mapped in PPLR: {ids}";
- UPG Neighbor's bridge-language cards, "Believers in: {list}".
Key Cities' shortened language list follows once its "{n} more" key (keyCities.list.moreLanguages) is on development. The joins that stay are not reader lists: a thrown error's details, the bridge-languages CSV columns, and a gate's console line.
2026-09-29 14:29:19 +00:00
Doxa Maps
2dd05a58bf Rebuild my-upg-100-list, and regenerate the tree 2026-09-29 14:22:38 +00:00
Doxa Maps
a6482b9f5d Join the UPG-100 map's name lists the reader's way, not with ', '
Two reader-visible lists were joined with a hardcoded ', ': the ADM1 names
in the selection error ('no people-group data for {names}') and the
selected territories in the batch HUD. Both use formatList now (R29), so
de reads 'Gujarat, Rajasthan und Odisha', zh 'Gujarat、Rajasthan和Odisha',
ar 'Gujarat وRajasthan وOdisha'. The locale is passed from the i18n ref
(packetLang), not left to getRuntimeLocale, so the HUD's list re-renders
on a runtime language switch. formatClipboard's join stays: its only
caller is a test handle.
2026-09-29 14:21:57 +00:00
Doxa Maps
f342590e0d Rebuild dashboard-plus-grid, which compiles the same dashboard
dashboard-plus-grid.vue imports research-dashboard.vue directly, so the list
formatter reaches a second bundle through that import and the composite was
left holding a dashboard that no longer matched its source.

+362 bytes, the same delta as the dashboard's own bundle -- which is what
identifies this as the same change arriving by a shared import rather than
unrelated drift. The formatter appears once in these bytes too, with the two
list types at the counts the source asks for.

Found by the landing precondition, not by inspection: it reported the composite
stale on a commit that named none of its files. The first reading of that was
that it could not be mine. It is: a composite that imports a plugin's component
is downstream of every change to it, and a range that touches no file of the
composite can still be the reason it is stale.
2026-09-29 14:15:25 +00:00
Doxa Maps
d387c6a456 Rebuild research-dashboard for the localised filter lists
The list formatter is a library function the bundle had no reference to before,
so the source change does not reach a reader until the bundle carries it.

+362 bytes. Confirmed in the built output rather than inferred from the delta:
Intl.ListFormat appears exactly once, which is the single formatter
implementation and not a copy per call site, and the two list types appear at
the counts the source asks for. A byte delta alone cannot tell a shipped
conversion from a duplicated helper; counting the shipped strings can.

The tree is regenerated, not only re-hashed. fix:sri rewrote the integrity in
each leaf page and in the manifest's embed snippet, but NOT in the Mapbox
variant of that snippet beside it, which the tree generates and fix:sri does
not touch. The landing precondition refused the commit for exactly that: a
manifest that regenerating the tree would change. After the regeneration the
current bundle hash appears in all three places it belongs, and check:sri
passes across all 65 leaves.
2026-09-29 14:08:04 +00:00
Doxa Maps
090b4eeb16 Research dashboard: filter lists read as lists in the reader's language
Three places joined names with ", ", which is a list only in English. Arabic
separates with a word, Chinese uses a different mark entirely and no comma at
any position, and German and French put a word before the last item. The
separator and the final connector are the grammar, so no catalogue key can fix
them from outside.

Two of the three carried a second, worse error. A multi-value filter holds
ALTERNATIVES -- the bus matches a row if ANY of its values matches, in
useSelectionBus with some() and in the data table with includes() -- so the
chip means "Islam or Hinduism". Joined with a comma it reads as "and" in every
language with an explicit connector, which states the opposite of what the
filter does. Those two become a disjunction.

The third is the screen-reader live region, which names the facets currently
applied. Those hold together and narrow the set in turn, so it is a
conjunction. It is also the one string here that some readers rely on entirely,
having no chips to glance at.

The composable is the single owner: every surface that turns a filter into
words comes through it, so the chip bar, the research-filter chips and the CSV
provenance line now say the same thing in the same language.

Left alone deliberately: five joins in this plugin's gate scripts. Those are
failure messages read in a terminal by whoever runs the gate, not text shown to
a reader, and formatting them per locale would help nobody.

Source only. This needs the bundle rebuilt before it ships.
2026-09-29 14:08:04 +00:00
Doxa Maps
9db9f519d4 Rebuild cities-internet, gospel-internet and gospel-resources
Regenerated on this base rather than carried over: BUNDLE=<name> bun x vite
build for each, then bun run tree and fix:sri.

Three integrity hashes move, one per bundle. The MapLibre and Mapbox CDN pins
are byte-identical on both sides, and the only bundle paths in any changed
manifest line are ./cities-internet/, ./gospel-internet/ and ./gospel-resources/.

Verified in the BUILT output, not only in source: the shipped
cities-internet.js carries position:relative on :host, which is the declaration
the partner-page overlap depends on.
2026-09-29 14:03:14 +00:00
Doxa Maps
c5e2131fc0 Two lists joined with an English comma in every locale
formatList (R29) replaces join(', ') at the two sites that a reader sees. The
separator was never a missing key — no key can fix punctuation between
interpolated values — which is why both survived every i18n gate.

connectivity-map.vue:365, CONJUNCTION. The Indian states with no boundary are
ALL missing one. The names land inside a translated sentence at :595, and the ar
message reads "لا توجد بعد حدود ولاية لـ {names}: …", so English commas were
being printed inside Arabic prose.

GiCombos.vue:49, DISJUNCTION, decided from the matcher and not from what reads
better: useSelectionBus.js:299 tests cur.includes(value) and toggleFilterValue
adds to the list, so two values on one facet WIDEN the result set. "Africa and
Asia" would state the opposite of what the filter does. This changes the English
too — "Africa, Asia" becomes "Africa or Asia" — which is a gain, because a comma
silently reads as ALL.

Measured, the same three names and two facet values through each type:
  en  "Ladakh, Puducherry, and Chandigarh"   "Africa or Asia"
  ar  "Ladakh وPuducherry وChandigarh"        "Africa أو Asia"
  zh  "Ladakh、Puducherry和Chandigarh"        "Africa或Asia"
  was "Ladakh, Puducherry, Chandigarh"        "Africa, Asia"
ar joins with و and no comma at all; zh uses 、. Neither is reachable by keying.

Not converted, and deliberately: useBoard.js:126 is a console.warn for
developers, and rudder-voice.vue:971 is in a component with zero i18n imports and
no catalogue area — English by construction, so it converts with the rest of
Rudder if Rudder is ever localized.

Source only. The rebuilds are regenerated after this rebases onto landed R29.
2026-09-29 14:01:38 +00:00
Doxa Maps
60cb4ff184 The embedded map still painted 478px below its host box
73bc270bc gave :host a height and stopped the ELEMENT overflowing. It did not
stop the MAP, because it never made the host a containing block.

.gm is position:absolute;inset:0, which resolves against the nearest POSITIONED
ancestor. :host had display:block and a height but no position, so on a partner
page .gm resolved against whatever the PARTNER had positioned and the map sized
itself to that. rega measured it in the hostbox run on ca07fc3c8: the element
correct at 420px inside a 420px box, the canvas inside it still 900px, and the
"Tap a coloured dot" pill and the attribution painting 478px below the box —
538px in a 360px box — over the partner's own text, whose top sits at y=464/404.

position:relative on :host is the whole fix, and it is the house pattern:
prayer-walk-planner.vue:628 carries the identical shape with an absolute child.

Worth recording, because the first fix passed every check: a host box that is
the right size proves nothing about what is drawn inside it. Only a partner-page
shot with content BELOW the box can see this, which is why rega's hostbox run
found it and my source reading did not.
2026-09-29 14:01:38 +00:00
Doxa Maps
c9caf68e44 Public Studio: the raw plugin registry is refused too, so the gated bundle's name stays private
In ACCESS=public mode the guard refused a gated bundle's source and its /__leaf/ pages, but not
/__plugin-gallery/registry.json. That file is the raw discovery list for the smoke tools, and it still
names key-cities, its tag and its two profiles to any visitor. No data, but a gated plugin must not reach a
public surface. The Studio reads only manifest.json, which already leaves gated bundles out, so the
registry is now refused in public mode as well. Dev mode is unchanged, and the smoke tools that read the
registry run against a dev server.
Measured with the cloud config run locally:
- before: public mode served registry.json (200) naming key-cities;
- after: public mode answers 404, including with a query string and a percent-encoded name;
- manifest.json and / still answer 200 in public mode, and dev mode still serves registry.json.
2026-09-29 13:54:30 +00:00
Doxa Maps
004d1d3f51 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 13:45:44 +00:00
Doxa Maps
f96576fdf7 check:optional-t-equivalence registers ApproxPinNote on the {t, te, locale} form
ApproxPinNote moved from useOptionalT to useOptionalI18n (which wraps it) so its formatList names follow the sentence's own locale. The per-site register still asserted the old helper and failed. The row now names useOptionalI18n, as SemanticTreeLegend, PosterWall and all-people-groups already do, and still asserts the site passes its own APPROX_FALLBACK.
2026-09-29 13:34:37 +00:00
Doxa Maps
5a73e4d37d Guard the pan's control clearance, which a cut-height check cannot see
The popup auto-pan had no gate. Its founding bug was invisible to the obvious
one: measured at 390 on a 366x732 map, the pan fitted the popup inside the
container, the cut really was zero, and a real click on Full Profile still did
nothing, because the top element at the button's centre was
div.maplibregl-ctrl-attrib-inner on one pin and span.pin-hint-pill on the other.
Two pins that looked fixed and were not.

So this asserts the property that matters -- where the popup ENDS UP relative to
the band that takes the click -- rather than whether it was clipped.

It runs the SHIPPED panPlanFor, read out of the SFC with new Function because it
is not exported, the same way check:optional-t-equivalence reaches useOptionalT.
A re-typed copy of the arithmetic would pass forever while the component drifted,
which is the one failure a geometry gate must not have. POPUP_OFFSET is read from
the source too rather than assumed.

Each case states the resulting geometry independently instead of re-deriving the
expected number from the same formula: a self-consistent oracle cannot see an
error that shifts both sides, which is how a 12px offset hid here once already.

The band is measured at runtime, never hard-coded, so the rule is swept across
0-120px rather than pinned to one number -- 120 is far beyond anything observed
and is there to prove the rule, not the value. Also swept across every width the
component renders at, since below 768px the container is 2x width.

Mutation-tested three ways, each failing the case written for it: ignoring
bottomInset (the pre-fix behaviour) fails 15 cases; reserving only half the band
fails; dropping the Math.max clamp fails exactly the negative-inset case.
24 cases, registered in all three places, verified by re-reading the parsed file.
2026-09-29 13:31:24 +00:00
Doxa Maps
46266a7ecc The popup pan clears the map's bottom controls, not just the clip
Clearing the clip is not the same as being clickable. The attribution and the
pin hint are painted over the canvas and take pointer events, so a popup that
ends fully inside the frame can still have its buttons underneath them.

Measured at 390 on a 366x732 map: the pan cleared the clip exactly (cut 0) and
a real click on Full Profile still did nothing, because at y~701 the top element
at that point was div.maplibregl-ctrl-attrib-inner; on the sibling pin it was
span.pin-hint-pill. Both pins panned, both looked fixed, neither was.

Neither the popup nor the control is at fault. Attribution is a licence term of
the basemap, so raising the popup over it is not available to us. The usable
frame is simply shorter than the container, and the fit test has to say so.

panPlanFor now takes a bottomInset and fits against containerH - bottomInset.
On the measured case the plan changes from -77 (pan up, popup below the pin,
buttons under the attribution) to +119 (pan down, popup above the pin, clear of
the band) -- the planner flips direction because with the band reserved, fitting
above became the smaller move. That is the existing "pick the smaller move" rule
doing its job on a corrected frame, not a new rule.

The inset is MEASURED from the live controls rather than assumed. A constant
would be wrong on the next control we add, at a width where the attribution
wraps to two lines, or when the gallery's clearance transform lifts the band.
Only controls that intercept pointer events are measured: the corner container
is pointer-events:none and spans nearly the full map width at 390, so measuring
the container would over-reserve the whole strip.

Unchanged where it was already right: a pin whose anchor fits still returns 0
(rega measured pan 0 on those in both arms), a popup taller than the usable
frame still refuses rather than jumping the camera, and bottomInset 0 is
byte-identical to the previous behaviour.

7 probe cases, with the post-pan fit asserted from the geometry rather than by
re-calling the planner -- a self-consistent oracle cannot see an error that
shifts both sides.
2026-09-29 13:31:24 +00:00
Doxa Maps
79b02f962a Key Cities keys: the "and N more" that ends a shortened language list, in each language's own list form 2026-09-29 13:31:15 +00:00
Doxa Maps
dd0428823c exp's four headless guards are registered, described and chained in gates:pre
check:wall-wysiwyg (0.9 s), check:wall-print-watch (0.7 s), check:wall-no-bg-photos (13 s) and check:template-i18n (9.7 s), each run once on this round's tip before chaining (all PASS) and each two-sided on its known-bad (dx-base poisoned all four independently). Placement read, not assumed: the two Vite runs use middlewareMode and bind no port, and wall-no-bg-photos serves a committed fixture with fetch stubbed, so no answer depends on the network, the machine or the moment. The orphan arm now counts 219 of 330 reached.
2026-09-29 13:31:14 +00:00
Doxa Maps
201baa3d60 A filter chip with several values says "or", in the reader's language
With no resolver configured, the kit chip bar joined a multi-value filter with ', ', so "Africa,
Asia" read as ALL to most people and carried English commas in every locale. The selection bus
matches several values on one facet with includes() (useSelectionBus isFilterActive), so adding a
value WIDENS the result: they are alternatives. The chip now uses formatList(value, { type:
'disjunction' }): "Africa or Asia", "Africa أو Asia", "Africa或Asia". gospel traced the matcher
and made the call as the only consumer's owner (gospel-internet's GiCombos). The English on this
chip changes from a comma to "or".

check:legend-mirrors lifts this exact expression and drives it with no resolver. It now lifts
formatList from library/i18n the same way (the shipped grammar, not a copy) and expects
"Islam or Hinduism". Poisoned with the old join it fails; restored it passes. formatList's doc now
says to choose the type by what the list MEANS, and never 'unit' for names.
2026-09-29 13:29:19 +00:00
Doxa Maps
62c8a2120c The first-run superuser URL itself reaches the terminal, not only the lines around it
PocketBase's link is /_/#/pbinstall/<token> (two l's). The forward rule matched \bpbinstal\b, which cannot match 'pbinstall', so the terminal showed 'Launch the URL below' with no URL below it (repo, testing live with PocketBase 0.40.4). The rule is now pbinstall?, tested on the line as PocketBase prints it rather than on a guessed spelling: the instruction, the URL and the CLI alternative are shown, the banner and dashboard URL stay quiet.
2026-09-29 13:29:19 +00:00
Doxa Maps
c5723f2b15 The shared-point list follows its sentence's locale, and formatList never throws outside a browser
check:approx-pin-note server-renders ApproxPinNote in Node, and it crashed: with no locale argument
and no runtime locale, formatList fell through to detectLocale(), which reads `document`. The page
lookup is now a guarded last resort. ApproxPinNote also passes its sentence's own locale (the
host's vue-i18n when installed, else '' so the runtime/page chain applies), so a host with its own
i18n can never get an Arabic sentence with a list in another language.

The gate now expects the new English ("Koraga and Maila", the list grammar this change exists for)
and adds the Arabic case: two names joined with و, never an English comma inside Arabic prose.
Poisoned by putting join(', ') back, both of those cases fail; restored, it passes.
2026-09-29 13:29:18 +00:00
Doxa Maps
64debb7f5b The shared-point note lists its groups in the reader's language
ApproxPinNote built pins.sharedPoint's {names} with join(', '), so the Arabic sentence carried English commas between the group names: the case gospel measured. It now uses formatList, which joins by the locale's own grammar (ar 'A وB وC', zh 'A、B和C', en 'A, B, and C').
2026-09-29 13:29:18 +00:00
Doxa Maps
57e3a11e68 The first-run superuser link still reaches the terminal
With the backend's output going to .pb/logs, PocketBase's one-time 'create your first superuser' link and its command-line alternative went only to the log, so the HOWTO's admin screen at /api/pb/_/ was unusable on a fresh .pb without knowing to look there (repo found it testing the change live). Those lines are a step a person must take, so they are forwarded like errors and 'address already in use'. The banner and URLs stay quiet. Tested against the regex read from the source.
2026-09-29 13:29:18 +00:00