Commit graph

1658 commits

Author SHA1 Message Date
Doxa Maps
5052031484 Merge origin/development into gospel/fab-clearance 2026-09-30 06:41:34 +00:00
Doxa Maps
151fb8f0eb Prayer Progress's ⓘ carries the approximate-location caveat; rebuild every ⓘ-control consumer
The ⓘ panel now states that a dot marks where a people is found, not a town or a boundary (noteKey
pins.approximate, the catalogue string the map's pin note used, in all 11 locales), so the caveat
stays available to a reader who asks once the phone legend stops stating it.

Also rebuilds every bundle the MapInfoButton / MapInfo change reaches, as the landing precondition
names them: research-dashboard, dashboard-plus-grid, cities-internet, gospel-resources and
doxa-research-map (their behaviour is unchanged: standalone is opt-in), plus doxa-simple-map for the
note. Then tree + SRI.
2026-09-30 06:36:49 +00:00
Doxa Maps
6c2756e5b0 Merge remote-tracking branch 'origin/development' into upg100/land-terr-2up 2026-09-30 06:35:37 +00:00
Doxa Maps
f558b1b43f Cities with Internet and Gospel Resources lift their credit clear of the gallery's round ☰
On a phone the gallery's round ☰ sits in a bottom corner and covered the first ~30px of these maps'
licence credit (the last ~30-40px in Arabic, where it mirrors). The gallery publishes
--map-fab-clearance while the button shows, and every LegendMobile map lifts the engine's bottom
corners by it; these two maps have their own legends, so they never did. They now lift the same
corners, the credit's and a Mapbox wordmark's. The variable is unset without the button, so desktop
is unchanged.

Rebuilds cities-internet and gospel-resources, with their pages and the two manifests.
2026-09-30 06:19:59 +00:00
Doxa Maps
7d2a5f7778 The grid's search row starts beside the gallery's round ☰ on a phone
At rest on a phone the search row sits where the gallery's 44px ☰ circle lands (bottom-left, or
bottom-right in Arabic), so the circle covered the start of the search box. The row now starts at
--map-fab-beside, which the layout publishes only while the circle shows. Everywhere else the
value is 0, so the change does nothing until the circle comes back.
2026-09-30 06:15:30 +00:00
Doxa Maps
d03b24e613 Prayer Progress carries its own "What can this map do?", so the ⓘ works wherever the map is embedded
On a partner page or a standalone preset page the ⓘ control rendered nothing, because only the
gallery answered it and the ⓘ's text lived in the gallery's settings. Now the map carries its info
(plugin-designs/doxa-simple-map/src/mapInfo.js) and a host only adds placement:

- MapInfoButton gains an opt-in `standalone` prop. When no host answers and the map carries info, it
  asks the map for a popup of its own (the 'standalone' event). If a real host appears later, the
  map's popup is dropped and the host takes over. Never inside the gallery (its panes carry
  data-doxa-gallery): there the gallery alone decides, its own info:false included.
- MapInfo gains `popupOnly` (with trigger 'control'): no floating button, since the map's control is
  the only button; it answers that control from inside the map, points at it once per page load,
  and opens the same popup (the bottom sheet on phones). It announces itself as local, so the
  control can tell it from a real host.
- Prayer Progress mounts that popup beside its control column, only when asked.

Per preset: the text describes the prayer map, so a map that shows the Prayer tab has info (the full
profile and the prayer preset); the engagement-only and adoption-only presets have none and show no
ⓘ, as before. A host's profile-config info:false removes the ⓘ. Every other MapInfoButton consumer
is unchanged (standalone is off by default).

doxa-simple-map grows by 20,590 bytes raw (5,699 gzipped), for compiling MapInfo. Rebuilt
doxa-simple-map and doxa-maps-spa, then tree + SRI.
2026-09-30 06:15:11 +00:00
Doxa Maps
f61bce7ea6 Gospel Resources: a country or people-group selection tells the tap hint it can go
The tap hint goes away on the reader's first pin, and it listens for uiStore.selectPeopleGroup,
which this map never calls: it selects through its own refs. A selection here now says so with the
dxmaps:pin-selected event the hint also hears, from inside the map's root. The watch sits after both
refs are declared; above them it would throw at setup.

Rebuilds gospel-resources only, with its pages and the two manifests.
2026-09-30 06:07:54 +00:00
Doxa Maps
bc8bb3345b Merge branch 'upg100/wall-2up' into upg100/land-terr-2up
# Conflicts:
#	public/dx-maps/doxa-maps-spa/manifest.json
#	public/dx-maps/manifest.json
#	public/dx-maps/my-upg-100-list/my-upg-100-list.js
#	public/dx-maps/my-upg-100-list/poster-wall/index.html
#	public/dx-maps/my-upg-100-list/upg-100-list/index.html
#	public/dx-maps/my-upg-100-list/upg-100-mfe/index.html
2026-09-30 06:07:40 +00:00
Doxa Maps
0990c7bbc0 Merge branch 'upg100/territory-counts' into upg100/land-terr-2up
# Conflicts:
#	public/dx-maps/doxa-maps-spa/manifest.json
#	public/dx-maps/manifest.json
#	public/dx-maps/my-upg-100-list/my-upg-100-list.js
#	public/dx-maps/my-upg-100-list/poster-wall/index.html
#	public/dx-maps/my-upg-100-list/upg-100-list/index.html
#	public/dx-maps/my-upg-100-list/upg-100-mfe/index.html
2026-09-30 06:07:38 +00:00
Doxa Maps
3df69dde01 Regions: the country poster prints in English in every locale, like the Country Posters wall
The Regions tab handed the sheet generator its localised card, so an Arabic reader's poster read
"Pray for السودان" and Administrative areas "السودان" beside an English "Countries: Sudan". The poster
is English by recorded decision; the generator now gets the data's own record by id, as the wall does.
2026-09-30 06:01:39 +00:00
Doxa Maps
3aec8b1f8d UPG-100 pins: small when zoomed out, unchanged zoomed in
Reported from a phone: the pins are "comically large" zoomed out. The
radius ramp began at z=1 (5 px), and MapLibre clamps below the first
stop, so the whole-world view drew all 2,107 dots at 5 px with a 1.8 px
halo. India and Africa became solid blobs, and the zone labels vanished
under them. The old sizing reasoned from one hundred dots; the map draws
every group of every list.

The ramp now starts at z=0 (2.6 px), between all-people-groups (2.2)
and the library's standard ramp (3), and reaches the same 9 px by z=8.
The halo shrinks with it (0.9 px at z=0) but stays solid and dark,
because the dots sit on fills of their own zone's hue.

Measured on the frozen item-5 candidate with this bundle overlaid, at
the gallery's opening zoom (1.5) at 390 and 1440: a pin went from
14.1 px across to 8.3 px (radius 5.25 → 3.05, halo 1.81 → 1.09), about
35% of the area. From z=8 on nothing changes.
2026-09-30 05:55:56 +00:00
Doxa Maps
002b256ce1 Cities with Internet and Gospel Resources get the edge scroll strips on their map
Both maps build their map with new gl.Map rather than through useMapInstance, so nothing ever wired the
shared scroll strips for them: on a phone there was no scroll escape at the map's edges, where every
other map has one. They now render the two strips beside the map and hand them to
useEdgeScrollAffordance, UPG-100's pattern, flagged per embed by edgeScrollAffordance (default on).
The strips sit under everything but the canvas, so the licence credit stays on top of them.

Rebuilds cities-internet and gospel-resources, with their pages and the two manifests.
2026-09-30 05:54:22 +00:00
Doxa Maps
7eef2a7393 UPG-100 wall: "Posters per row: 2" shows two on a phone
The select read 2 while the wall showed one. The shared clamp (210px per
card) forced one column at 390 and silently overrode it. The wall now
opts into PosterWall's minCardPx (150), as the country posters did, so a
phone keeps the columns the reader chose.

Measured at 390, en + ar, with this bundle overlaid on the frozen item-5
candidate: 2 columns (select 2), cards 162px, 0 of 12 action labels
clipped. The poster thumbnails are 114px wide, so a poster's title band
renders about 9px tall (25px at one per row). The title is not readable
at that size; the two-up choice was made with that in view. The
two-line "Show on map" pill and the print options overhanging the card
are PosterWall's own (their fixes are separate).
2026-09-30 05:50:40 +00:00
Doxa Maps
cf16866111 Merge remote-tracking branch 'origin/development' into simpleresearch-plugin/filters-icon 2026-09-30 05:49:30 +00:00
Doxa Maps
6dead067b9 Merge origin/development into gospel/phone-legend
# Conflicts:
#	public/dx-maps/doxa-maps-spa/manifest.json
#	public/dx-maps/gospel-resources/charts/index.html
#	public/dx-maps/gospel-resources/data-tables/index.html
#	public/dx-maps/gospel-resources/gospel-dashboard/index.html
#	public/dx-maps/gospel-resources/gospel-resources.js
#	public/dx-maps/gospel-resources/resources-list/index.html
#	public/dx-maps/gospel-resources/resources-map/index.html
#	public/dx-maps/manifest.json
2026-09-30 05:48:44 +00:00
Doxa Maps
08dc0bdbe2 UPG-100: a tapped country counts its people groups again
Select your own territory always found 0 people groups (reported from a
phone). resolveTerritory filed a feature as ADM1 whenever it had a
shapeName. The world ADM0 boundaries now carry one too (Sudan:
{ shapeGroup:'SDN', shapeName:'Sudan', iso3:'SDN' }), so every tapped
country became a province:
- key adm1:Sudan with a null count, and a HUD total of 0 UUPG;
- a Prayer List that refused it: "No people-group data for Sudan —
  select the country instead".

The level now comes from where zone-geometry drew the feature. India
states and China provinces come ONLY from the gb-IND/CHN-ADM1 pools, and
only when properties.listId matches the zone, so an ADM1 feature carries
listId; a country never does. A legacy feature with a shapeName and no
iso3 stays ADM1.

Measured at 390 on the frozen item-5 candidate, with this bundle
overlaid:
- tap Sudan → adm0:SDN, 105; tap Chad → adm0:TCD, 53; total 158;
- Prayer List fetches the groups and prints (1 print, no error).
The same flow as served before the fix: adm1:Sudan, 0 UUPG, the refusal,
0 prints.
2026-09-30 05:47:03 +00:00
Doxa Maps
11cb626de9 Merge origin/development into gospel/info-control
# Conflicts:
#	public/dx-maps/cities-internet/cities-internet.js
#	public/dx-maps/cities-internet/connectivity-list/index.html
#	public/dx-maps/cities-internet/connectivity-map/index.html
#	public/dx-maps/cities-internet/data-tables/index.html
#	public/dx-maps/doxa-maps-spa/doxa-maps-spa.js
#	public/dx-maps/doxa-maps-spa/doxa-maps-spa/app/index.html
#	public/dx-maps/doxa-maps-spa/doxa-maps-spa/index.html
#	public/dx-maps/doxa-maps-spa/manifest.json
#	public/dx-maps/gospel-resources/charts/index.html
#	public/dx-maps/gospel-resources/data-tables/index.html
#	public/dx-maps/gospel-resources/gospel-dashboard/index.html
#	public/dx-maps/gospel-resources/gospel-resources.js
#	public/dx-maps/gospel-resources/resources-list/index.html
#	public/dx-maps/gospel-resources/resources-map/index.html
#	public/dx-maps/manifest.json
2026-09-30 05:42:39 +00:00
Doxa Maps
8761483879 The grid's "Hide filters" icon no longer turns upside down
The filters toggle turned every svg in the button over when the filters were open. So the filter
icon beside the label flipped as well, with its longest line at the bottom, which read as a broken
icon. That shows on a phone, where the grid opens with its filters expanded. Only the chevron turns
over now.
2026-09-30 05:37:07 +00:00
Doxa Maps
2b70820f24 Gospel Resources on a phone: a slimmer folded legend, and an open one that stops at 70%
Folded, the legend took three rows of large pill-round controls, about 30% of a phone's page. The
Gap / Resources found buttons are 32px now and squared to the fold button's corners, the search box
is slimmer, and the fold button shares the mode row: two rows instead of three. Open, the legend stops
at 70% of the map and scrolls inside, instead of covering all of it. 32px stays above WCAG 2.5.8's
24px floor. Desktop is unchanged.

Rebuilds gospel-resources only, with its pages and the two manifests.
2026-09-30 05:35:14 +00:00
Doxa Maps
ed1091f59b Merge remote-tracking branch 'origin/development' into big-picture-plugin/bp-c3-header
# Conflicts:
#	public/dx-maps/doxa-maps-spa/doxa-maps-spa.js
#	public/dx-maps/doxa-maps-spa/doxa-maps-spa/app/index.html
#	public/dx-maps/doxa-maps-spa/doxa-maps-spa/index.html
#	public/dx-maps/doxa-maps-spa/manifest.json
#	public/dx-maps/doxa-research-map/doxa-research-map.js
#	public/dx-maps/doxa-research-map/research-map/index.html
#	public/dx-maps/doxa-research-map/research-map/maplibre/index.html
#	public/dx-maps/manifest.json
2026-09-30 05:16:14 +00:00
Doxa Maps
7378832cc9 On a phone, Cities' and Gospel Resources' control column starts below the folded legend
The ⓘ is now the column's top control, and its one-time pointer runs sideways from it across the
map's top band. On a phone that band is these maps' legend (title and fold button), and MapToolbar's
phone rule put the column at top:72px inside it, so the pointer lay over the legend's title and
buttons. Below 700px the column now starts 8px under the FOLDED legend's measured bottom, whatever its
height in each language. It is measured only while folded and before the toolbar exists, so opening
the legend never moves the controls. Desktop is unchanged.

Rebuilds cities-internet and gospel-resources, with their pages and the two manifests.
2026-09-30 05:06:12 +00:00
Doxa Maps
b227f76085 Merge remote-tracking branch 'origin/development' into big-picture-plugin/bp-c3-header
# Conflicts:
#	public/dx-maps/doxa-maps-spa/doxa-maps-spa.js
#	public/dx-maps/doxa-maps-spa/doxa-maps-spa/app/index.html
#	public/dx-maps/doxa-maps-spa/doxa-maps-spa/index.html
#	public/dx-maps/doxa-maps-spa/manifest.json
#	public/dx-maps/doxa-simple-map/doxa-simple-map.js
#	public/dx-maps/doxa-simple-map/doxa-simple-map/adoption/index.html
#	public/dx-maps/doxa-simple-map/doxa-simple-map/engagement/index.html
#	public/dx-maps/doxa-simple-map/doxa-simple-map/index.html
#	public/dx-maps/doxa-simple-map/doxa-simple-map/prayer/index.html
#	public/dx-maps/manifest.json
2026-09-30 05:01:31 +00:00
Doxa Maps
6b05bd242c Simple Research: "What can this map do?" becomes the map's top round control
The research map draws the ⓘ as the first button of its own control column (MapInfoButton), and the
gallery's Simple Research row switches to trigger: 'control' in the same commit, so the tab never
shows two ⓘ, and never shows none. At mount the map tells the gallery that its control is coming, so
the floating ⓘ does not flash while a slow phone loads. A failure or an unmount withdraws that
promise, and the floating ⓘ comes back at once. On a page with no gallery nothing answers, and the
map is unchanged.
2026-09-30 04:48:20 +00:00
Doxa Maps
792d4e1ae9 Cities with Internet and Gospel Resources tell the gallery their ⓘ is coming
Their control column renders only once the map exists, after the data, which can take tens of
seconds on a slow phone. At mount each map now tells the gallery's MapInfo that a control is on its
way (Prayer Progress's announcement), so MapInfo waits for the map instead of flashing its floating
ⓘ first. A failure or an unmount before the map exists withdraws the promise, so the floating ⓘ
shows at once. Gospel's single-card page builds no map and promises nothing.

Rebuilds cities-internet and gospel-resources, with their pages and the two manifests.
2026-09-30 04:48:10 +00:00
Doxa Maps
947133d259 Merge prayer-progress-plugin/info-pending (step A) into gospel/info-control
# Conflicts:
#	public/dx-maps/doxa-maps-spa/doxa-maps-spa.js
#	public/dx-maps/doxa-maps-spa/doxa-maps-spa/app/index.html
#	public/dx-maps/doxa-maps-spa/doxa-maps-spa/index.html
#	public/dx-maps/doxa-maps-spa/manifest.json
#	public/dx-maps/manifest.json
2026-09-30 04:46:37 +00:00
Doxa Maps
5cbabe7ffa Cities with Internet and Gospel Resources: "What can this map do?" is the top of their control column
The ⓘ becomes the first round control in each map's own column (MapInfoButton, as Prayer Progress
has it), and each tab's gallery row says trigger: 'control', so there is exactly one ⓘ: the control.
MapInfo keeps its fail-open, so a page where the control never arrives still gets the floating one.

Rebuilds cities-internet, gospel-resources and doxa-maps-spa, with their pages and the two manifests.
2026-09-30 04:44:32 +00:00
Doxa Maps
3d462ec308 The ⓘ control's pointer: once per page load, gone after a click or a tab change, no checkbox
Every map whose ⓘ is its own control (MapInfo trigger 'control') now gets the same pointer:
it shows the first time that control is on screen in a page load, and goes for the rest of the
load once the reader opens the popup or leaves the tab. A full reload shows it again. This is
page-load memory, never storage, so the popup offers no checkbox in control mode: the "Keep
tooltip turned on" box and its string (all 11 locales) are removed, along with the pointerKeep
prop that selected this model per tab. Floating-mode maps keep their stored model and their
"don't point again" box, unchanged.

Leaving the tab is read from the pane (hidden or inert), on the control's intersection change
and the existing 1.5 s measurement tick.

Rebuilt doxa-simple-map (it ships the mapInfo catalogue) and doxa-maps-spa, then tree + SRI.
2026-09-30 04:41:04 +00:00
Doxa Maps
8a271596ce Merge remote-tracking branch 'origin/development' into big-picture-plugin/bp-c3-header
# Conflicts:
#	public/dx-maps/doxa-maps-spa/doxa-maps-spa.js
#	public/dx-maps/doxa-maps-spa/doxa-maps-spa/app/index.html
#	public/dx-maps/doxa-maps-spa/doxa-maps-spa/index.html
#	public/dx-maps/doxa-maps-spa/manifest.json
#	public/dx-maps/manifest.json
2026-09-30 04:34:58 +00:00
Doxa Maps
d7b496c6b1 Prayer Progress tells the gallery its ⓘ is coming, so the floating one never flashes on a slow load
Prayer Progress draws its control column only once the style and the people-group data are in,
which on a slow phone can take longer than any fixed wait, and then the gallery's floating ⓘ
appeared and was swapped for the control. Now the map announces at mount that a control is on its
way ('dxmaps:mapinfo-pending'), and MapInfo waits for the map instead of a clock.

- A known failure (the map reports an error) or an unmount before the map is ready withdraws the
  announcement, and the floating ⓘ shows at once.
- While a control is pending, the 30 s wait becomes a 90 s backstop (time on screen), so an
  unreported failure still gets a floating ⓘ.
- A map that never announces anything keeps the 30 s wait, and pages with no MapInfo are unchanged.

Rebuilt doxa-simple-map and doxa-maps-spa, then tree + SRI.
2026-09-30 04:32:55 +00:00
Doxa Maps
d0de376f4a Big Picture header: rounded-square ⓘ left of a rounded-square Share
The dashboard header gains an info button ("What can this map do for you?") to
the left of Share. Both are rounded squares at the vertical tabs' radius
(--vtl-radius), matching the app's own roundedness; map controls stay round.

Built as a reusable, parameterized header-button form so other dashboards adopt
it by prop, not by copy:
- MapControlButton: new shape="rounded-square" (border-radius: var(--vtl-radius,8px)).
- ShareButton / MapInfoButton: a shape prop forwarded to MapControlButton;
  ShareButton also squares its rotating ring so a round ring never wraps a square.
  Defaults stay 'round' — every existing consumer is unchanged in behaviour.
- research-dashboard DashboardTopBar mounts the ⓘ and Share as one header-button
  group, sized to the bar with the 44px tap target kept for both. DOM order
  [ⓘ, Share] puts ⓘ at the logical start: left in English, right of Share in
  Arabic (the flex row reverses under rtl).
- doxa-maps-spa dashboard-plus-grid tab -> trigger:'control', so the tab's
  MapInfo is driven by the header ⓘ and the gallery's floating ⓘ is suppressed:
  exactly one ⓘ, fail-open. No new strings (reuses mapInfo.open, 11 locales).
2026-09-30 04:27:44 +00:00
Doxa Maps
3b933aefd4 Prayer Progress: round, spaced controls again, and no floating ⓘ while the map loads
The control column goes back to the library's round, spaced buttons at every width; the square
grouped card read too compact, on phones most of all. The ⓘ stays the top control.

In control mode, MapInfo no longer shows its floating button before the map's control has
announced itself. The map draws its column only once it is ready (style and people-group data),
so the floating ⓘ appeared for a moment and was then replaced by the control. The wait is bounded
at 30 s of time on screen, counted only while the pane is visible and not inert: measured column
arrival times run from 7 to 19 s on a local serve, so a shorter wait expired inside a normal load
and flashed late. If no control arrives, the floating button shows, so there is still always a way
to open the popup. Once a control has announced itself, measurement alone decides, as before.

MapInfo's plain <script> block (the page-load pointer memory) now sits above <script setup>, so
check:setup-order no longer reads it as used before its definition; its explanation moves into
<script setup> so the library index keeps the component's own description. Compiled output is
unchanged by the move.

Rebuilt doxa-simple-map and doxa-maps-spa (the only bundle that compiles MapInfo.vue), then
tree + SRI.
2026-09-30 04:17:28 +00:00
Doxa Maps
148f3cffe6 Merge origin/development into exp-shepherd/pp-info-control at the landing turn 2026-09-30 03:48:23 +00:00
Doxa Maps
79a426822c Merge remote-tracking branch 'origin/development' into regions/sparse-consistent 2026-09-30 03:41:13 +00:00
Doxa Maps
48a1a747ba Prayer Progress: "What can this map do?" is the top of its own control column
- Prayer Progress's column becomes the engine's square grouped card (controls="square"). Its first
  control is MapInfoButton, which opens the gallery's MapInfo for the tab. The gallery's tabInfo
  'prayer-map' entry now uses trigger 'control', so the floating button hides while that control is
  on screen, and shows again (fail-open) whenever it is not.
- MapInfo, control mode: the first-visit pointer now points at the control. Its place is measured
  against the pane (which scrolls) and goes on the control's physical inner side, so an Arabic page,
  whose column mirrors to the left, gets the pointer on the right with the arrow reversed. It shows at
  every width: the under-1200px rule was measured for the floating pointer crossing each tab's top
  strip, and does not apply here.
- MapInfo, pointerKeep (prayer-map only): the popup offers "Keep tooltip activated" instead of the
  every-map "don't point again" box. It is off by default, and opening the popup retires the pointer.
  Checked, the pointer keeps showing on every load. It is stored per map.
- mapInfo.keepPointer in all 11 locales.
- Rebuilt doxa-simple-map and doxa-maps-spa, then tree and fix:sri.
2026-09-30 03:31:15 +00:00
Doxa Maps
1728372a1e Reshoot the 173 country previews in the SPARSE layout
The country poster previews were the dense layout -- small facts table, both
vision columns, a smaller map -- while every UPG-100 thumbnail is SPARSE. Now
that the click-time print is SPARSE too, the stored country previews have to be,
or the card and the print disagree.

Generated with SPARSE=1 and checked BEFORE any browser time:
  · the generator's own layout guards: 173 of 173 ok, octilinear, 0 diagonals.
  · SPARSE on all 173: 0 sheets draw a dense-table element; all 173 carry the
    bold "N UPG's" count.
  · real values: 0 sheets show 0, NaN or blank population. Worst cases by UPG
    count: Sudan 105 / ~20.7M, Brazil 103 / ~63K, Laos 95 / ~968K.
Shot on the fleet's shared Chromium (no private browser), one page at a time:
173 of 173, 0 failed, all 3000x3000, none blank. Smoke-tested and looked at
first: Sudan shows the bold facts row, no vision columns, "1 of ~170 maps".

ALL 173 CHANGE: 173 source thumbnails, 173 full-size and 173 served previews
differ from development, 0 strays, nothing written to a stray library/ path.

China is deliberately NOT here: its thumbnail stays the dense "map 174 of 174"
it has tonight. Its print becomes SPARSE with "map 1 of 1" via the small-total
guard. The remaining numbering difference is R-CHN-SERIES-TIER, a product call
after the demo.
2026-09-30 03:30:10 +00:00
Doxa Maps
766d829028 Merge origin/development into gospel/cities-legend-fold 2026-09-30 02:57:55 +00:00
Doxa Maps
0288d3d083 Merge remote-tracking branch 'origin/development' into bundler-2/sr-band-first-screen 2026-09-30 02:53:10 +00:00
Doxa Maps
ed374bdb9f Cities with Internet: on a phone the legend folds behind one button, like Gospel Resources'
Fully open, the legend took 297px of a 712px phone map, 86% of its width, with the India-by-state
block and both caveats always showing, and there was no way to shut it. On a phone (under 700px,
Gospel Resources' breakpoint) the title stays and the rest folds behind "Key & notes": folded by
default, remembered for the session, and a remembered choice wins over the width default.
Desktop is unchanged. Two new strings, in all eleven locales.

Rebuilds cities-internet only, with its three pages and the two manifests.
2026-09-30 02:45:14 +00:00
Doxa Maps
b4079cc9d2 Merge remote-tracking branch 'origin/development' into regions/mobile-posters-2up
# Conflicts:
#	public/dx-maps/doxa-maps-spa/manifest.json
#	public/dx-maps/manifest.json
#	public/dx-maps/my-upg-100-list/my-upg-100-list.js
#	public/dx-maps/my-upg-100-list/poster-wall/index.html
#	public/dx-maps/my-upg-100-list/upg-100-list/index.html
#	public/dx-maps/my-upg-100-list/upg-100-mfe/index.html
2026-09-30 02:44:56 +00:00
Doxa Maps
c964c79db8 Merge remote-tracking branch 'origin/development' into upg100/status-badge-credits 2026-09-30 02:40:37 +00:00
Doxa Maps
eab8963a20 Regions: two country posters per row on a phone
Reported from a phone: on the Regions tab, "2 posters per row" for the country
posters did not work -- a 390px phone showed ONE per row.

THE CAUSE is a deliberate clamp in PosterWall. The per-row setting is 2, but the
grid is Math.min(perRow, fits) with fits = floor((viewport - 36) / 210), and at
390 that is floor(354 / 210) = 1. So the setting said two and the phone silently
showed one.

★ WHY THE CLAMP EXISTED, AND WHY IT IS SAFE TO RELAX NOW. It was added when a
two-up card (~162-170px) CLIPPED its action labels -- 'Prayer List' and 'Poster
Map' both read 'Pra...', and Romanian's 'Pachet de cartonase' was measured at 54
of 81 controls clipped at ro/390. That was then fixed separately: the labels WRAP
(.pfs > .pbtn, "a label too long for its box wraps"). With wrapping in place the
clamp no longer protects anything; it only costs the phone a column.

★ IT IS OPT-IN, NOT A GLOBAL CHANGE. PosterWall is shared by twelve files,
several of them frozen experimental maps. A new minCardPx prop defaults to 0,
which means the existing 210px clamp, so every wall that does not set it lays out
byte-for-byte as before. Only regions-list sets 150, which gives two per row at
360, 390 and 414, still one at 320 (which genuinely cannot hold two), and no
change on desktop (already capped at the per-row setting of 2).

MEASURED on the regions-list leaf page, this branch served locally, against
FINAL on :4611 -- by hit test and by eye:
  FINAL  @390 en   1 per row, 367px cards
  AFTER  @390 en   2 per row, 169px cards, 0/123 labels clipped
  AFTER  @390 ro   2 per row, 0/123 labels clipped   <- the old clipping corner
  AFTER  @390 ar   2 per row, RTL, 0/84 labels clipped
  AFTER  @1440 en  2 per row, unchanged
0 horizontal overflow and 0 console errors in every cell. The print buttons now
stack three high on a phone card instead of sharing a line.

KNOWN, COSMETIC, NOT CHANGED: at two-up the card's "53 UPG · Show on map" pill
wraps "Show on / map" onto a second line. Readable, not clipped.
2026-09-30 02:33:24 +00:00
Doxa Maps
76ba9c1a6d Simple Research's map band ends on the first screen
On a phone under DX Life's header, the band ran 32px past the fold (y132 to y876 on an 844px
screen). The legend sheet's bottom and its collapsed strip began below the fold, so a reader who
collapsed the legend could not reopen it without scrolling. The band now takes the height that is
visible below its own top, capped at the old 88svh, which is also the fallback. It is measured once,
and again only on a width change, so the URL bar showing and hiding does not resize the map.
2026-09-30 02:32:00 +00:00
Doxa Maps
a83b836160 Merge origin/development into studio/no-campaign 2026-09-30 02:31:41 +00:00
Doxa Maps
8dbad994f9 Merge remote-tracking branch 'origin/development' into upg100/status-badge-credits 2026-09-30 02:25:30 +00:00
Doxa Maps
c1763ebff9 No "campaign" in the DX Maps UI: the near-neighbor planner speaks of movements
The word is a military term, and the Driver asked for "prayer movement(s)" / "mobilization movement"
instead. The five user-facing strings in all eleven locales (55; the translated word too: Kampagne,
campaña, حملة, अभियान, 宣传活动 …) now say movement, in each language's natural church usage: Russian
"движение" with "вдохновить" rather than "мобилизовать", Arabic "حركة" with "تحفيز" rather than "تعبئة",
Chinese "宣教动员" rather than 宣传 (propaganda) or 运动; the Portuguese lines move to Brazilian forms like
the rest of the catalogue. The instance title and the meta note follow, and the CSV exports' column
headers become bridge_language / bridge_language_code (a user sees them in every spreadsheet).
Identifiers stay: the "view":"campaigns" profile-config value (embeds pass it), the i18n key names,
file and function names, and the campaigns-server references.
2026-09-30 02:17:14 +00:00
Doxa Maps
b609202f5c Keep the UPG-100 status badge off the map credits
The badge sat at bottom:10px right:10px, z-index 5: the corner both engines
keep their attribution in. It won every hit point of the OpenStreetMap
credit on both engines in English and Arabic. In Arabic the list panel
mirrors onto that corner, so the badge also covered the panel's pin note,
and the panel itself lay over the top of the credit line.

- The badge sits at the inline end (right in English, left in Arabic),
  6px above --upg-credit-floor: 30px for the tallest single-line credit
  (the Mapbox wordmark) plus the lift LegendMobile gives the engine
  corners on a phone. z-index 1 keeps it under the engine corners, and it
  takes no pointer events.
- On phones (max-width 767px) it is hidden. It always sat under the
  collapsed legend sheet there (0 of 15 of its points visible), so this
  keeps what a phone showed.
- The batch HUD, which stacks above the badge, follows it.
- The desktop legend panel ends above the credit band where a credit
  shares its corner: Arabic on both engines (the attribution) and Mapbox
  in English (the wordmark). MapLibre in English is unchanged. The rule
  is scoped to the desktop slot; unscoped, it collapsed the phone sheet's
  panel on Mapbox.

Measured on a local serve of the upg-100-mfe leaf, 1440 and 390, both
engines, en and ar. Credit hit points before, at 1440: OpenStreetMap
0/15 in all four cases; the Mapbox wordmark 5/15 in English; in Arabic
on MapLibre every credit was 0/15. After: every credit 15/15, except
'© Mapbox' in Arabic, whose right-to-left box spans the whole credit
line and whose points go only to the neighbouring credit links. At 390
all credits are 15/15 before and after, and the sheet's panel is
unchanged (48px).
2026-09-30 01:30:49 +00:00
Doxa Maps
bff75e0995 Gospel Resources: in Arabic the people-group card opens opposite the legend, not on it
The desktop card was placed with right:14px while the legend sits at the inline start. In a
right-to-left page the legend mirrors to the right and the card stayed there, so opening any
people group or country covered the legend at every desktop width. The card now sits at the
inline end, as Cities' copy of the same rule already did. English is unchanged.

Rebuilds gospel-resources only, with its five pages and the two manifests.
2026-09-30 01:13:23 +00:00
Doxa Maps
072b1ff216 Regions-tab print: pass the same series as the Country Posters wall
Every country poster printed from the Regions tab read "map 1 of 0" and "This
map is one of a 0-map set called the Country Maps" -- nonsense on a printed
poster, on the demo's showcase flow (Regions -> a country -> Poster Map). Found
by the served check on FINAL.

WHY: makeCountrySheet called generateCountrySheet with no series, so
generateSheet fell back to its default { totalGroups:0, totalLists:0, index:1 }.

NOT A REGRESSION from the one-definition change, and worth recording because it
was first reported as one. The plugin-local copy regions-list used before that
change passed no series either; across it regions-list.vue changed by exactly
one line, the import. The defect has been in the Regions print since 168b1f1bd.
The Country Posters wall was never affected because it computes its own.

THE FIX computes the series exactly as the wall does (country-poster-wall.vue):
the tier is every country printed at the same sheet size, the total is that
tier's length, and the index is the country's position in it by id. It lives in
ONE place, seriesFor() in the library, rather than as a second inline copy of the
wall's formula -- the same duplication this branch's parent removed for the sheet
itself. regions-list passes the FULL country list, not a region's sublist:
indexing a region's own list would print "map 3 of 12" beside a thumbnail that
says "map 57 of 173".

PROVED, rendered with the values seriesFor returns:
  Sudan  index 1 of 173  ->  "map 1 of 173"  |  "one of a 170-map set"
  Chad   index 4 of 173  ->  "map 4 of 173"  |  "one of a 170-map set"
which is exactly the text the wall prints for the same two countries on FINAL,
measured on the served page, and what their thumbnails say. A row with no sheet
tier gets undefined, keeping the renderer's own default rather than a fabricated
position.

KNOWN, NOT CHANGED: China is the only 3x4 sheet, so its tier has one member and
it prints "map 1 of 1" / "a 0-map set" -- here AND on the wall, identically,
because this matches the wall by design. Its thumbnail says "174 of 174". Fixing
it means choosing whole-series numbering over per-tier, which is the carded
174-vs-173 question, not a freeze-night change.
2026-09-30 00:00:00 +00:00
Doxa Maps
5484b844c6 Merge remote-tracking branch 'origin/development' into regions/country-sheet-onedef 2026-09-29 23:34:15 +00:00
Doxa Maps
99783ff0f7 Merge remote-tracking branch 'origin/development' into bundler-2/rm-unmount
# Conflicts:
#	public/dx-maps/doxa-maps-spa/manifest.json
#	public/dx-maps/doxa-research-map/doxa-research-map.js
#	public/dx-maps/doxa-research-map/research-map/index.html
#	public/dx-maps/doxa-research-map/research-map/maplibre/index.html
#	public/dx-maps/manifest.json
2026-09-29 23:29:55 +00:00
Doxa Maps
2eb5b38027 Merge origin/development into exp-shepherd/pp-quiet-cancel at the landing turn 2026-09-29 23:22:56 +00:00
Doxa Maps
087f562e04 Reshoot the 174 country previews with the region hue and ink badges
The poster walls' card THUMBNAILS are stored WebP files, not generated at the
click, and all 174 were last shot on 09-25 -- teal. Since the region hue
landed, the PRINT has been hued, so both Country Posters and the Regions tab
showed a teal thumbnail above a hued poster. The Experimental section shows on
Deep Research, so that mismatch was visible.

Shot in one pass on the shared browser: 174 of 174, 0 failed, 0 retries,
23:00:37-23:10:07Z, one engine, DSF 2. Measured by the shooter: none blank (at
least 1,000 distinct colours each), and the count of sheets by their --zc hue
matches the region census exactly -- 41 Africa, 27 Asia, 27 Europe, 21 Middle
East, 18 Other, 17 Oceania, 14 North America, 9 Latin America. Asia is 27, not
28, because India has no sheet.

★ ALL 174 CHANGE, and that is the check that matters here. make-previews used to
write one directory too high (library/plugin-designs/..., library/data/posters)
while reporting success, so a reshoot could complete, look complete, and change
no served file at all. With that fixed, 174 of 174 source thumbnails, 174 of
174 full-size sources and 174 of 174 served previews differ from development,
with 0 untracked strays.

China was regenerated first with the whole-series count: 'map 174 of 174' and
'one of a 170-map set', where it had printed 'map 1 of 1' and 'a 0-map set'.
2026-09-29 23:18:26 +00:00
Doxa Maps
476989bab5 Merge remote-tracking branch 'origin/development' into regions/country-sheet-onedef
# Conflicts:
#	public/dx-maps/doxa-maps-spa/manifest.json
#	public/dx-maps/manifest.json
#	public/dx-maps/regions-dashboard/region-dashboards/index.html
#	public/dx-maps/regions-dashboard/regions-list/index.html
#	public/dx-maps/regions-dashboard/regions-mfe/index.html
2026-09-29 23:17:40 +00:00
Doxa Maps
2ddd8dc1e8 Merge origin/development into exp-shepherd/pp-quiet-cancel, and rebuild doxa-simple-map on the merged source 2026-09-29 23:17:05 +00:00
Doxa Maps
67fed8b79f Merge remote-tracking branch 'origin/development' into bundler-2/bp-grid-locale 2026-09-29 23:15:51 +00:00
Doxa Maps
e4b5a3d1b1 Prayer Progress released mid-start no longer reports a failed map
Prayer Progress is a heavy gallery tab: switching away empties its slot. If the map
is still starting, initializeMap returns silently (useMapInstance's liveness
checks) and leaves map.value null. onMounted then logged "[doxa-simple-map] Map
failed to initialize, check token and container": a console error that blamed
the token for a tab switch.

Measured on development's tip, served from its committed tree, on the shared
browser, a phone in Arabic (pp-fastswitch.mjs): switched away 150 and 500 ms after
selecting the tab, 2 runs in 10 logged it. The control runs, switched away after a
full start or never switched away, logged nothing.

Same shape as the research map's quiet cancel (0c263fb60): when the map's
container is gone or detached, both failure paths return quietly. A mounted pane
that really fails still logs.
2026-09-29 23:08:17 +00:00
Doxa Maps
baa60ba647 Merge remote-tracking branch 'origin/development' into regions/country-sheet-onedef
# Conflicts:
#	public/dx-maps/doxa-maps-spa/manifest.json
#	public/dx-maps/manifest.json
#	public/dx-maps/regions-dashboard/region-dashboards/index.html
#	public/dx-maps/regions-dashboard/regions-dashboard.js
#	public/dx-maps/regions-dashboard/regions-list/index.html
#	public/dx-maps/regions-dashboard/regions-mfe/index.html
2026-09-29 22:59:27 +00:00
Doxa Maps
2d764b740d Big Picture's info button sits above its filter bar, so it can be clicked
At top 46px the button lay in the empty end of Big Picture's filter area, but that bar is a
positioned layer (z-index 55) painted over the button (z-index 20): nothing opened on click (rega,
1440, en and ar). The filter area wraps with width, so no spot below it is free at every width (at
1024 the stats row moves under a fixed offset). pos.raise lifts the BUTTON to z-index 60; the one-time
pointer stays under the bar, so no filter is covered even visually. Measured on the built tree: no
interactive element or text under the button, the button topmost at its centre, and a click opens the
popup, at 1440, 1024 and 800, en and ar.
2026-09-29 22:48:21 +00:00
Doxa Maps
0c263fb601 A research map released mid-start no longer reports a failed map
When the reader switches tab while the research map is still starting, the gallery releases the
pane, and initializeMap returns quietly with no map. The handler logged that as "Map failed to
initialize — check token and container", so the console error landed in whichever tab was then open
and blamed the token. A released container is now a quiet cancel.
2026-09-29 22:45:32 +00:00
Doxa Maps
e3c631932f Merge origin/development into studio/batch (upg100's example images) 2026-09-29 22:33:34 +00:00
Doxa Maps
1c5a6bd589 On a phone, Cities' info button takes the corner above its zoom column, like Gospel's
At 390 Cities' legend spans the same width as Gospel's (344px), and side 56px put the button on
the end of the legend title's first line (gospel's measurement; a probe that also counts visible
text under the button reports the same, 236px2). narrow side 4px, as Gospel.
2026-09-29 22:28:52 +00:00
Doxa Maps
f4c026501d Big Picture's grid took its language from the page URL, not from Big Picture
The grid is mounted inside Big Picture as a component, so it shares Big Picture's host, and its
direction is written onto Big Picture itself. Its profile-config carried only the list, so its
language fell through to ?lang= on the page. An English page opened with ?lang=ar therefore laid
the whole of Big Picture out right-to-left, English included.

The grid now receives the host's language under both keys, locale and lang.
2026-09-29 22:26:49 +00:00
Doxa Maps
838f6c14b6 Merge remote-tracking branch 'origin/development' into upg100/examples 2026-09-29 22:25:44 +00:00
Doxa Maps
8e081cea44 UPG-100's and Simple Research's popups show the packet and the card deck
UPG-100's info popup lists its Middle East Core poster, then the full packet's 12 pages and the 70-card
deck (upg100's pre-generated files). Simple Research shows the same 12 pages, captioned "Example
packet: Middle East Core": same renderer, method and 'full' variant, but a UPG-100 zone's data, so it
is labelled as an example rather than passed off as a Simple Research selection.
2026-09-29 22:18:35 +00:00
Doxa Maps
c8fde78522 Merge remote-tracking branch 'origin/development' into bundler-2/dirclear-ready 2026-09-29 22:18:12 +00:00
Doxa Maps
e704bdbbf8 Merge remote-tracking branch 'origin/development' into upg100/examples 2026-09-29 22:17:24 +00:00
Doxa Maps
99c22caf72 Merge origin/development into studio/examples 2026-09-29 22:14:22 +00:00
Doxa Maps
9c5ac8094b The UPG-100 info popup's examples: Middle East Core's packet pages and card deck
Twelve packet pages and seventy prayer cards, rendered from the current
renderers (people-based knows-Christ page, page-filling grids and charts,
sharp card photos) and shot in the fleet's shared browser: pages at 1500px
reduced to 1200px wide, cards at 480x672, WebP q80, 2.8 MB. They ship under
previews/examples/middle-east-core/, which the existing previews staging
copies into the tree, and the popup's pages and deck examples read them.
Middle East Core is the packet covering the most people in unengaged groups
(IMB GSEC 0-1).
2026-09-29 22:07:17 +00:00
Doxa Maps
b9514d68d3 On a phone the info button takes each tab's free corner
At 390 the desktop offsets landed on each composite's own top chrome: Regions' and UPG-100's view
selector and Gospel's search box (probe, en and ar). A tab's pos can now carry `narrow`, laid over
it while the shell is narrow (max-width:760px): Regions and UPG-100 put the button at the START of
their strip, which is empty on a phone; Gospel takes the corner above its zoom column. The same
probe then reports no interactive element under the button on any tab, 1440 and 390, en and ar.
The one-time pointer still lies over the selector or search box until first opened; it takes no
clicks (pointer-events:none).
2026-09-29 22:02:08 +00:00
Doxa Maps
c79e26a089 Middle East Core's stored poster and preview, regenerated with tonight's renderer
The stored poster (data/posters) and its wall preview predated the ink pin
badge: 57 pin numbers drawn white on the zone hue, 1.44:1. They are what the
UPG-100 info popup shows and opens ("Open full size"). Regenerated from the
current poster-render in its stored 3x4 format (1500x2000) and published;
the pin numbers are now white on #1c1c17, 17.10:1. The preview and the
1500px download are made from a 3000px shot of the regenerated sheet with
make-previews' own sizes and qualities (a 3x4 sheet now makes a 420x560
thumbnail; the wall letterboxes it). The other 27 stored zone posters are
fallback-only and follow after the demo.
2026-09-29 21:50:51 +00:00
Doxa Maps
81a84138c9 Regions and UPG-100: the info button sits clear of their Share button too
A geometry probe (every visible interactive element in every shadow root, intersected with the
button's box, 7 tabs in en and ar at 1440) found the default position covering each composite's Share
button exactly, which no screenshot showed. They take side 56px like the map tabs. With that, the
probe reports no element under the button on any tab in either direction; before this branch it
reported Zoom in/out under it on Prayer Progress, Cities and Gospel and Share on Big Picture.
2026-09-29 21:40:54 +00:00
Doxa Maps
11554f40b6 The info button sits clear of each map's own controls
At the default 12px from the top and the end of the line, the button covered the zoom-in button on
Prayer Progress, Cities with Internet and Gospel Resources (a control nobody could click), Big
Picture's header buttons, and Simple Research's tab bar, in English and, mirrored, in Arabic
(rega's 1440 captures, exp's second read). Each tab now carries its placement in src/tabInfo.js,
passed as MapInfo's own --map-info-top/--map-info-side: 56px from the end beside a zoom column,
46px down below Big Picture's header bar, 56px down below Simple Research's tab bar. Regions and
UPG-100 keep the default. MapInfo itself now places by the logical end of the line (upg100), so the
gallery passes no direction.
2026-09-29 21:12:54 +00:00
Doxa Maps
520973c1d7 One country-sheet definition: the Regions tab's poster was still teal
exp's second read of 1ab8524cf found a real defect on the demo path, and a census
of the tree found the cause was worse than the symptom.

THE SYMPTOM: the Regions tab printed a TEAL country poster and logged a warn,
while Country Posters printed the same country HUED. That is precisely the "two
different posters for one country" the hue change exists to refuse.

THE CAUSE: regions-list.vue:73 imported a plugin-local copy,
src/country-sheet-print.js, byte-identical to library/packet/countrySheet.js
except for the region key. The region forwarding was added to the LIBRARY file,
so countryRegionColor(undefined) returned the old flat teal and warned.

★ AND THE LIBRARY FILE HAD NO IMPORTERS AT ALL. Its own comment claimed two
consumers -- the country-poster wall and <doxa-country-map> -- and a census found
NEITHER was true: regions-list imported the local copy, and
country-poster-wall.vue:94 defines its own inline countrySheet that calls
generateCountrySheet directly (passing the whole record, which is why THAT path
was correctly hued). So the module was an orphan wearing a comment that said
otherwise, and the region key was added where nothing read it. A move that was
never finished, and the duplicate stayed invisible until one copy was edited.

regions-list now imports the library module, the copy is deleted, and the
comment says what is true. Not re-copied: a duplicate of that file is
indistinguishable from it until one side is edited, and then it is a silent
divergence.

CHAIN VERIFIED TO SOURCE, since the hue is only right if the region survives
every hop: POSTER_BY_ISO3 holds country-posters.json records, which all carry a
region; localisedPoster SPREADS the record in both its branches, so the key
survives a locale switch; postersFor passes it; makeCountrySheet forwards it.
ARTIFACT PROOF: the region forwarding in the rebuilt regions-dashboard bundle
goes from 1 occurrence to 2, and POSTER_REGIONS plus the full region-name list
are present in it.

The no-poster fallback branch in postersFor still builds an object without a
region, deliberately: it has no sheet to draw, and it already carries
unsupportedReason.

NOTE ON HISTORY, not rewritten: f6a3f85a2 is titled "Rebuild the three
consumers…" but it also carries SOURCE changes -- the ink .pin-num-bg in the
shared renderer and the warn-once in posterSheets.js. The title understates it.
It is pushed, so it stands; recorded here instead.
2026-09-29 20:55:27 +00:00
Doxa Maps
9cf5a68696 Big Picture's table headers come from mapInfo, so the gallery stops carrying all of 'dashboard'
The four headers are copied verbatim, in all eleven locales, from dashboard.json into mapInfo.col
(peopleGroup, country, region, population); no string is retranslated. Measured with areasForBundle:
doxa-maps-spa carried [buttons, dashboard, grid, mapInfo, messages] and now carries [buttons, grid,
mapInfo, messages]. The bundle drops from 420,172 to 371,064 bytes (136,998 to 124,005 gzipped).
2026-09-29 20:37:39 +00:00
Doxa Maps
1fd562c28e Read the page's own direction at mount, not at our first write
The clear-only-ours fix recorded the host's dir lazily, inside the rtl branch,
the first time it wrote. That is correct only while nothing else has written a
direction first. If anything set dir="rtl" on the host before that moment, the
element recorded rtl as "the page's own declaration" and then faithfully
restored it on every switch away from Arabic -- so the direction never cleared,
which is the defect this whole change exists to remove.

Measured on the Big Picture leaf: ar left rtl, and zh put rtl straight back,
while its three siblings with the same code passed. The difference was not the
code; it was that something writes rtl on that host before the element does.

Reading the attribute on the FIRST call instead, before any write of ours, is
the only moment it is certainly the page's and not ours. A page that genuinely
declares dir still owns that decision and still gets it back.

Five cases driven against the real function in all four files: no dir at boot,
a page declaring ltr, a page declaring rtl, an element mounting while already
Arabic, and -- the case that reproduces the failure -- a FOREIGN writer setting
rtl after mount and before ours. Restoring the lazy capture to one file fails
that fifth case alone and leaves the other four passing.
2026-09-29 20:31:04 +00:00
Doxa Maps
d975337501 Push the locale under both keys, because every reader prefers one of them
The push wrote `lang`. Every reader takes `locale` FIRST and only then falls
back to `lang`: ProfileLoader.vue:189, and people-groups-grid.vue at :273 and
:380. So writing one key is correct exactly while no child config carries the
other, and stops working silently the day one does -- a `locale` left by a boot
copy or a host-list path would shadow every fresh `lang` we push, for the rest
of the session, with both values sitting in the same object.

Writing both keeps them in step whatever put them there.

Not broken today: the nested configs were measured carrying no locale key at
all. This closes the class rather than a symptom, which is worth one line.

Shown by driving the real function: with a stale {lang:'en', locale:'en'} the
push now yields ar in both keys, and a config carrying only `locale` switches
too. Reverting to the lang-only write leaves {lang:'ar', locale:'en'} -- the
exact shadowing above, where the map reads English while holding a fresh lang.
2026-09-29 20:31:04 +00:00
Doxa Maps
77415cf361 Switching from Arabic to Chinese left the page right-to-left
Every element that sets a reading direction did it one way: if the locale was
RTL it wrote dir="rtl", and there was no else. So a reader who moved from an
RTL locale to an LTR one kept the right-to-left layout for the rest of the
session -- Chinese laid out backwards, which reads as a broken page rather than
a stale setting.

The reason the clear was missing is sound and is kept: a host page that
declares its own dir owns that decision, and blindly removing the attribute
would take that with it. So each element now remembers what it found before it
first wrote, restores that value verbatim when the locale stops being RTL, and
removes the attribute only when there was none to begin with. It clears its
own, never the host's.

FOUR elements, not the three the report named: big-picture.vue,
research-map-plus-grid.vue and people-groups-grid.vue all carry it, and so does
dashboard-plus-grid.vue -- the Big Picture composite, same shape, found by
checking the neighbours of the named lines rather than only the lines
themselves.

The comment above each one said "Only ever SET, never cleared". Left alone it
would now describe the opposite of the code, so it says what the code does.

Verified two-sided against all four shipped files, driving the real extracted
function: it clears what it set, preserves a host's own dir, and still sets rtl.
Restoring the one-way body to one file fails exactly the first two and leaves
the third passing, with the other three files unaffected.
2026-09-29 20:31:04 +00:00
Doxa Maps
4a153d5b7b Big Picture's info popup shows real output: an excerpt of its spreadsheet
Big Picture's export is a spreadsheet, so its example is a kind:'table' excerpt (sr): the six largest
groups by population, of 2,107, with the live table's own column keys (dashboard.people_group,
.country, .region, .population), so the popup header and the table's header cannot drift.

Cost, measured: those keys bring the 'dashboard' area into the gallery bundle, which grows from
369,635 to 420,172 bytes (123,536 to 136,998 gzipped, +13.5 KB). The alternative, header keys
duplicated into 'mapInfo' in eleven locales, is a translation job for after the demo.
2026-09-29 20:25:42 +00:00
Doxa Maps
fa69209035 The gallery's published section order: Prayer Progress before the UPG-100 List
The Deep Research page publishes the rail as Dashboards (Big Picture, Simple Research, Regions),
Prayer Mobilization (Prayer Progress, UPG-100 List), Gospel Research (Cities with Internet, Gospel
Resources), then the Experimental folder. Only the Prayer Mobilization pair moves. Big Picture now
says "desktop only" on every width, not only once a phone has disabled it.

check:spa-tab-order asserted the old four-name order with UPG-100 first; it now asserts all seven
published rows in order (still relative, with tab 1 positional). It needs a browser: rega runs it.
2026-09-29 20:00:36 +00:00
Doxa Maps
a569868fef Merge remote-tracking branch 'origin/development' into regions/country-map-refetch 2026-09-29 19:44:14 +00:00
Doxa Maps
b3e67c7f2b Merge remote-tracking branch 'origin/development' into bundler-2/panelang 2026-09-29 19:38:36 +00:00
Doxa Maps
2b210b78f0 Merge remote-tracking branch 'origin/development' into regions/country-map-refetch
# Conflicts:
#	package.json
2026-09-29 19:36:16 +00:00
Doxa Maps
cff06fb8e6 Merge origin/development into studio/deep-research 2026-09-29 19:34:32 +00:00
Doxa Maps
b0d08224c1 A live language switch never reached the panes inside Simple Research
The composite re-pushed its FILTER state to the map and the grid on every
change, and nothing else. So a host switching language moved the composite's own
chrome and stopped there: the outer element received the new profile-config,
the push re-parsed each child's EXISTING attribute, and the boot `lang` survived
every write. Measured on a live en to ar switch: the map and the grid both stayed
English while the frame around them changed.

The panes were never at fault. Each child's own chain is fully reactive -- it is
reactive to its own prop, and its prop was frozen.

The map now carries the locale in the same push that carries the filters. The
grid needs its own line, because its three linked facets travel as separate
attributes while its language travels in profile-config, which this composite
set only when a host supplied a list, and never again. And a locale change now
triggers a push at all: the existing watchers cover filter state only.

The grid write is conditional so an unchanged locale writes nothing. That
matters here more than it looks: setting a pane's attribute makes it re-filter
and emit, and the composite reads those emissions -- which is what the `pushing`
guard above exists for, after a measured round trip once cancelled a selection
the composite had just applied.

Verified two-sided by driving the real pushToChildren against stub elements:
the locale reaches both panes, and a repeat at the same locale adds no write.
Removing the map's line fails that assertion alone and leaves the grid's passing.
2026-09-29 19:30:31 +00:00
Doxa Maps
37f3aa1221 All seven gallery tabs carry their info entry; a tab whose text is not bundled shows no button
Each production tab reads mapInfo.tab.<tabId>.lead and .takeaway, and Regions also its note, with
the India/China reasoning beside it. Only files already in the tree are listed as examples (the
UPG-100 poster); the rest join when their shots land. A tab's button renders only when its lead
key resolves in the bundled catalogue, so a key that has not landed yet hides the button instead of
opening a popup of raw key names.
2026-09-29 19:17:42 +00:00
Doxa Maps
a1d9a1e318 Merge origin/development into studio/deep-research (MapInfo as landed; upg100's files take development's side) 2026-09-29 19:16:45 +00:00
Doxa Maps
656503a28d Merge remote-tracking branch 'origin/development' into regions/pane-config-land
# Conflicts:
#	package.json
2026-09-29 19:12:46 +00:00
Doxa Maps
e643e8a30e country-map: key the refetch by value, let only the newest response win, show the wait
Three defects on country-map/country-map, which is production -- DX Life's
country pages. Found by auditing the element rather than from a report.

1. ANY profile-config change refetched all 2,106 records. The watch getter
   returned a NEW ARRAY literal, and Vue compares a watch value with Object.is,
   so a fresh array always reads as changed; both computeds read the injected
   cfg, so the effect re-ran on every config change and the callback then fired
   unconditionally. MEASURED with real Vue: flipping isDark refetched the whole
   endpoint and redrew the map, and so did a host adding pinHint. A joined
   STRING compares by value, so only a real language or country change refetches
   -- which must still refetch, because the API localises the names.
   This was latent waste until the composite re-forward made config writes
   routine. It is now on every upstream locale switch, so the two changes are
   complementary rather than independent.

2. A SLOW EARLIER RESPONSE OVERWROTE A NEWER ONE. loadPoints is async and had no
   sequencing at all -- 0 abort or generation tokens in the file -- so two runs
   could be in flight and the one that STARTED first could FINISH last. Both
   assigned points, so the map showed the previous country's pins with no error
   and nothing for the reader to notice. Every run now takes a ticket and a stale
   ticket writes nothing: not the points, not loadFailed, not the spinner the
   current run is still using. fetch is left to complete rather than aborted,
   because the response is cached for the next switch back.

3. `loading` WAS WRITE-ONLY: assigned in three places and read in none, zero
   template references. A reader on a country page waited on an empty map -- no
   outline, no pins, no message -- which is indistinguishable from a country that
   genuinely has no people groups, and this element fetches the whole list before
   it can draw one pin. "Loading results..." was already in the catalogue in all
   eleven locales, so saying so cost no new string and no parity debt.

check:country-map-refetch guards all three. It drives the REAL reactive chain
with real Vue and takes the watch key expression OUT OF THE SHIPPED SFC, because
bug 1 is entirely a question of what Vue considers changed and is invisible to
any test that fakes reactivity or re-types the watch.

Mutation-tested, 5 mutations, all caught by name. The first version of the gate
MISSED one: a bare /if \(!current\(\)\) return/ grep matched the guard in the
catch block, so removing only the guard before the points write still passed.
The assertions are positional now -- the guard must sit immediately before the
assignment it protects -- and the catch guard is asserted separately. That hole
existed because the behavioural half of the test reproduced the guard's shape
inside the gate instead of reading the component, which is the self-consistent
oracle this repo's other geometry gate explicitly warns against.

Registered in scripts and gates:pre, verified by re-parsing: one key, 107
checks. Bundle rebuilt (BUNDLE=country-map), then tree, then fix:sri.
2026-09-29 19:11:37 +00:00
Doxa Maps
b33d970cd4 The gallery's tab keys live in mapInfo only, so the gallery bundles that one area
A key in a map's own area either renders blank in the gallery or, named literally, pulls that map's
whole catalogue into the gallery bundle (internal/i18n-areas.mjs). Measured with areasForBundle:
development's doxa-maps-spa carries buttons, grid, messages; this branch carries buttons, grid,
mapInfo, messages, and no upgMap. The UPG-100 entry reads mapInfo.tab.upg-100.lead and .takeaway,
which upg100 adds with every tab's strings.
2026-09-29 19:05:52 +00:00
Doxa Maps
32cf3f2354 Merge remote-tracking branch 'origin/development' into exp-shepherd/simple-map-embed 2026-09-29 19:02:39 +00:00
Doxa Maps
462c0babf7 Each gallery tab can explain itself: the MapInfo button, per tab, from src/tabInfo.js
The library MapInfo (an info button, a one-time pointer and a popup of real, pre-generated output)
is mounted in each pane whose tab has an entry in src/tabInfo.js, with the gallery's live locale and
its assets base. UPG-100 is the first entry, with its poster; its packet pages and card deck join
when their files are in the tree, because a missing file would show "not available" to the audience.
Regions and the other tabs follow as their owners' keys land. profile-config info:false removes the
buttons.
2026-09-29 19:00:06 +00:00
Doxa Maps
76f35d8ccd Merge origin/development into studio/deep-research 2026-09-29 18:57:33 +00:00
Doxa Maps
a85afc8b71 Prayer Progress: a multi-tab bar is a tablist, and says which tab is selected
The research page now gives this map three tabs (engagement, prayer,
adoption). With two or more tabs the bar was a row of plain buttons: a screen
reader heard three buttons, with nothing saying they are tabs or which one is
showing. The bar is now role=tablist, each button is role=tab with
aria-selected, and the buttons are type=button. A one-tab instance shows no
bar, so it is unchanged.
2026-09-29 18:56:53 +00:00
Doxa Maps
a2809b31fc Merge origin/development into studio/hygiene 2026-09-29 18:52:35 +00:00
Doxa Maps
f054b487cf A live language switch remounts the gallery's tab, instead of re-setting its attribute
The gallery re-drove the visible tab by re-setting its profile-config, which works only for a map
that re-reads its whole config live. Two production maps do not: Big Picture resolves its tStatic
strings once (2 of 352 changed on en to ar, measured) and Simple Research copies its config into its
panes once (0 of 43, 0 of 105). A fresh element reads the new locale at mount, so the visible tab is
now rebuilt in the new language whatever the map does inside, the way a provider change rebuilds.
View state is lost on a language switch; that is accepted.

Hidden tabs that are still mounted (a light tab stays mounted when hidden, and select() never
remounts a mounted tab) are reset too, so their next open mounts in the new language. The old comment
said they picked it up lazily; for light tabs they did not. Tabs are reset in place, keeping the slot
ref, because a light pane renders once and releaseTab's deleted ref would never come back.
2026-09-29 18:50:13 +00:00
Doxa Maps
9c6347aad6 Mark resources-map production, because a production page already ships it
resources-map.meta.json carried NO stage, so the manifest defaulted it to
experimental — while doxa-maps-spa, the gallery that becomes Deep Research,
mounts it: doxa-maps-spa.meta.json composes "gospel-resources/resources-map"
and TAB_DEFS names it as the Gospel Resources tab. The record disagreed with
what ships, and the record was the part that was wrong.

WHAT THIS CHANGES: the manifest stage, and nothing a reader sees. Verified —
the diff moves one "experimental" to one "production" and ZERO sha384 hashes.
No bundle is rebuilt and no byte of any map changes.

The stricter production gates now apply to this profile and it passes them:
gates:pre 103 PASS / 0 FAIL / 1 NOT MEASURED of 104. That is the part worth
having, because marking something production is only honest if it then has to
meet the production bar.

NOT a decision about which profile the Deep Research page SHOULD show.
gospel-dashboard is the production composite and remains the better end state;
it is not switched to today because its .gd-map is sized 80svh — a viewport
unit inside a pane that is not the viewport — which against rega's measured
panes gives the map 91% of the pane at 1440x900 and 95% at 390x844, and
because it has never been shot inside the gallery. That fix is written and
held local for after the deadline.
2026-09-29 18:42:06 +00:00
Doxa Maps
7d816f5eab Four more gates:post reds, hidden until a failed gate printed why
- research-map.vue's openPacket read and wrote _packetBusy, a name 8ab3dc0f5 replaced with the
  reactive packetBusy ref. Reading an undeclared name throws, so the packet export threw a
  ReferenceError on every click, in doxa-research-map and in research-map-plus-grid, the production
  map that compiles it. It uses packetBusy.value now (check:no-free-identifiers).
- check:list-prop-unit and check:optional-t-equivalence read every entry of library/i18n/locales as
  a locale, and the folder has held a README.md since bba33e783. They read locale FOLDERS now.
- people-groups-grid's regions-look instance previewed two props (filters-toggle, backdrop) of a grid
  merge that is not on development, so the page showed nothing different and
  check:instance-config-keys failed. It returns with that merge.
2026-09-29 18:40:27 +00:00
Doxa Maps
b435a7a548 The gallery is Deep Research, and a host can hide its Experimental folder
DX Life publishes the gallery shell on a hidden page named Deep Research, so that is now the
default heading (profile-config.heading or the heading prop still override it), and the meta,
instance and demo page say the same.

profile-config experimental:false (or "hidden") hides the Experimental folder: the manifest is not
mined for experimental tabs, the folder has no rows, and a tab= request for one falls back to the
first visible tab. It is off only on request, so every other host keeps the folder as before.
2026-09-29 18:37:12 +00:00
Doxa Maps
99816626af Every shadow stylesheet gets its own label, and a gate keeps it that way
useShadowStyles keys its <style> by the label and overwrites an existing
tag with the same key, so two components sharing a label keep only the
last one's CSS in a shared shadow root. The regions list lost its sheet
this way to the region card, which had kept the list's label when it was
extracted.

check:shadow-style-labels-unique (gates:pre) fails any label two calls
share, globally rather than per shadow root, so a copied or forked
component is caught when it is copied. It reads the real parsers,
classifies every reference to the function (a rebinding, .call, a
re-export or a namespace import fails instead of hiding a call),
cross-checks the AST against the token stream, and covers the other
ways two calls share a key: unlabelled calls under the dev server's
per-component key, and a fixed label over CSS that varies per caller.

countries-map.vue, a fork of research-map.vue, carried the same label;
it is now 'countries-map', with the country-posters bundle rebuilt and
the tree regenerated. The composable's JSDoc called the label a
debugging name; it now says it is the key.
2026-09-29 17:42:44 +00:00
Doxa Maps
6493872170 Track every pane's getter, prune detached ones, and say when one throws
Studio's 5B review found a dependency-tracking hole by testing the helper with
real Vue rather than reading it. entries was a plain array, so the watchEffect
tracked only what the FIRST registered getter read: with two panes reading
different refs, changing pane 2's source produced ZERO writes, and pane 2 caught
up only when pane 1's source happened to change. That is this helper's own bug
reappearing for any adopter whose panes read different sources. regions-mfe is
safe today because every getter reads the same cfg and spec -- which is exactly
why a reading could not find it.

entries is now shallowReactive, so register() re-runs the effect and every
getter is tracked. Also from the review:
  - stale entries are PRUNED, not skipped: an adopter that rebuilds its pane
    column would otherwise keep detached elements and re-run their getters on
    every pass forever;
  - a getter that throws is reported once per entry instead of being swallowed,
    since silence recreates the symptom this helper exists to fix;
  - the unused `tag` parameter is gone -- a documented check that does not exist
    misleads the next adopter;
  - the host argument now documents that it must be in the SAME tree as the
    panes, because Element.contains() does not cross a shadow boundary and
    passing the custom element would silently skip every write.

regions-dashboard rebuilt so public/ is servable at this sha for the browser
A/B. It is the only bundle that compiles the helper.
2026-09-29 17:32:33 +00:00
Doxa Maps
89bf1feb54 A composite's panes follow the outer profile-config, instead of freezing at mount
regions-mfe built each pane by creating the child element and writing its
profile-config once, inside onMounted, and nothing rewrote it. The pane is handed
a JSON STRING rather than a reactive object, so a later change to the OUTER
element's profile-config updated the composite's own cfg and reached nothing
below it. regions-dashboard is kept alive by v-show rather than remounted, so the
one code path that would have rewritten the attribute never runs again and the
panes stay in the old language for the whole session.

The leaf was never at fault: ProfileLoader's config is a computed over its prop,
lang is a computed over config, and a watchEffect drives appI18n on every change.
Every link is reactive -- it simply never receives a new prop.

library/composables/usePaneConfig.js owns the missing half: a composite registers
each pane element with a getter for its config, and a watchEffect re-forwards
whenever any reactive source that getter reads changes. It writes only when the
serialised config actually differs, because setting profile-config re-parses it
in the child and can remount what it drives -- an unconditional write per tick
would be a rerender loop. Stale elements (a rebuilt pane column) are skipped.

It is a library helper rather than three copies because regions-mfe, upg-100-mfe
and country-posters-mfe all assemble a config and set the attribute once; they
differ only in which keys they inherit, so each keeps its own
INHERITED_PAGE_KEYS and hands the result to the helper. This commit adopts it in
regions-mfe only; the other two are upg100's to adopt.

Two-sided, headlessly: a check that fails when a composite writes profile-config
on a child without re-forwarding names all THREE composites on development, and
on this branch regions-dashboard reports "re-forwarding=usePaneConfig" while the
other two still fail.

NOT VERIFIED IN A BROWSER YET. rega's LIVEDIAG is the falsifier: the nested
[profile-config] should show the OLD locale on development and the NEW one here.
2026-09-29 17:27:25 +00:00
Doxa Maps
57671ca465 Merge remote-tracking branch 'origin/development' into regions/handoff-integration
# Conflicts:
#	public/dx-maps/cities-internet/cities-internet.js
#	public/dx-maps/cities-internet/connectivity-list/index.html
#	public/dx-maps/cities-internet/connectivity-map/index.html
#	public/dx-maps/cities-internet/data-tables/index.html
#	public/dx-maps/country-map/country-map.js
#	public/dx-maps/country-map/country-map/index.html
#	public/dx-maps/country-posters/countries-map/index.html
#	public/dx-maps/country-posters/country-poster-wall/index.html
#	public/dx-maps/country-posters/country-posters-mfe/index.html
#	public/dx-maps/country-posters/country-posters.js
#	public/dx-maps/demo-map/demo-map.js
#	public/dx-maps/demo-map/demo-map/index.html
#	public/dx-maps/demo-map/demo-map/maplibre/index.html
#	public/dx-maps/doxa-maps-spa/manifest.json
#	public/dx-maps/doxa-research-map/doxa-research-map.js
#	public/dx-maps/doxa-research-map/research-map/index.html
#	public/dx-maps/doxa-research-map/research-map/maplibre/index.html
#	public/dx-maps/doxa-simple-map/doxa-simple-map.js
#	public/dx-maps/doxa-simple-map/doxa-simple-map/adoption/index.html
#	public/dx-maps/doxa-simple-map/doxa-simple-map/engagement/index.html
#	public/dx-maps/doxa-simple-map/doxa-simple-map/index.html
#	public/dx-maps/doxa-simple-map/doxa-simple-map/prayer/index.html
#	public/dx-maps/gospel-internet/board/board/index.html
#	public/dx-maps/gospel-internet/board/index.html
#	public/dx-maps/gospel-internet/board/india-china/index.html
#	public/dx-maps/gospel-internet/board/language-tree/index.html
#	public/dx-maps/gospel-internet/board/platforms/index.html
#	public/dx-maps/gospel-internet/board/project-700/index.html
#	public/dx-maps/gospel-internet/board/zero/index.html
#	public/dx-maps/gospel-internet/gospel-internet.js
#	public/dx-maps/manifest.json
#	public/dx-maps/regions-dashboard/region-dashboards/index.html
#	public/dx-maps/regions-dashboard/regions-dashboard.js
#	public/dx-maps/regions-dashboard/regions-list/index.html
#	public/dx-maps/regions-dashboard/regions-mfe/index.html
2026-09-29 16:33:55 +00:00
Doxa Maps
e21c4a5d57 Merge remote-tracking branch 'origin/development' into regions/handoff-integration
# Conflicts:
#	public/dx-maps/country-posters/countries-map/index.html
#	public/dx-maps/country-posters/country-poster-wall/index.html
#	public/dx-maps/country-posters/country-posters-mfe/index.html
#	public/dx-maps/doxa-maps-spa/manifest.json
#	public/dx-maps/manifest.json
#	public/dx-maps/regions-dashboard/region-dashboards/index.html
#	public/dx-maps/regions-dashboard/regions-list/index.html
#	public/dx-maps/regions-dashboard/regions-mfe/index.html
2026-09-29 16:12:45 +00:00
Doxa Maps
a6279064c4 Drop the country-map poster wall: +292 KB on every country page
country-map.vue's poster-wall wiring put the poster table, peopleGroupRecords
and the sheet code into the bundle of EVERY DX Life country page, for a wall
that is hidden by default. Measured: country-map.js 391,475 bytes on
development against 683,401 here -- +291,926, a 74% increase -- and with it
removed the bundle is 391,625, back within 150 bytes of development.

Making the three imports lazy does not help, and I measured that rather than
assuming it: with all three behind await import() and the table fetched at
mount, the bundle grew to 684,700. vite.config.js:282 sets
inlineDynamicImports, so a dynamic import is flattened straight back in and only
the wrapper is added. Removing the weight needs the pinned-served-asset pattern
that poster-render.js already uses -- card
PERF-LOAD-THE-PRINTING-SUBSYSTEM-ON-DEMAND.

library/packet/countrySheet.js stays: it is unreached at runtime (0 hits in the
built bundle) so it adds nothing, and it is the library half this element will
use once the printing code loads on demand.

ProfileLoader's comment now names both populations -- 20 of the 25 bundles are
single-profile, 10 of the 17 that actually mount this loader, which are the only
ones the default affects.
2026-09-29 16:10:00 +00:00
Doxa Maps
45d931fa40 Repair what the union resolution duplicated
Resolving package.json conflicts by unioning unique lines is right for a file
that holds one entry per line. Applied to ordinary source it duplicates
declarations, because both sides of a hunk keep their copy and nothing
de-duplicates across the join.

Eleven plugin entry points ended with the same import twice, and the poster
renderer with two identical `const sheet`. Each is a SyntaxError: every one of
those bundles would have failed to build, and the poster export would not run.

Found by parse-checking every changed .js against the merge base, not by the
gates -- gates:pre passed 101 of 102 with all twelve files broken, because its
parse gates do not cover plugin entry points.

One measurement trap worth recording. `node --check` on a .js file parses it as
CommonJS, so a bare `import` is a syntax error and ALL 28 entry points appear
broken, on development too. That reading says the tree is on fire and is simply
wrong. Copying each file to a .mjs and checking that parses it as a module,
which separates the eleven that are genuinely broken here from the rest that
are fine.
2026-09-29 16:01:45 +00:00
Doxa Maps
0417e46fbd Merge remote-tracking branch 'origin/development' into bundler-2/handoff
# Conflicts:
#	internal/check-poster-layout.mjs
#	library/composables/poster/useMapPoster.js
#	package.json
2026-09-29 16:00:18 +00:00
Doxa Maps
212ec20816 Merge remote-tracking branch 'origin/development' into bundler-2/handoff
# Conflicts:
#	package.json
2026-09-29 15:54:16 +00:00
Doxa Maps
14096e19c2 Merge remote-tracking branch 'origin/development' into upg100/merge
# Conflicts:
#	package.json
2026-09-29 15:54:05 +00:00
Doxa Maps
d3ec784c1e Merge remote-tracking branch 'origin/development' into regions/handoff-integration 2026-09-29 15:31:29 +00:00
Doxa Maps
e5b3334ea6 Merge remote-tracking branch 'origin/development' into upg100/merge 2026-09-29 15:21:38 +00:00
Doxa Maps
d5549cf58f Merge development into the gospel handoff branch 2026-09-29 15:19:40 +00:00
Doxa Maps
726f979079 Merge remote-tracking branch 'origin/development' into regions/handoff-integration 2026-09-29 15:01:53 +00:00
Doxa Maps
5539710503 Merge remote-tracking branch 'origin/development' into bundler-2/handoff 2026-09-29 15:01:31 +00:00
Doxa Maps
a8bbc6131e Keep the reason, drop the speaker, in three merged-in comments
The handoff merges brought in three references that name the Driver in shipped
text: two comments in CenteredSetTarget.vue and one _why in the people-groups-grid
instances. Reworded to state the engineering reason without the speaker -- an
open decision now reads as pending a product decision, which is what the rule
asks for. The instances file still parses as JSON.
2026-09-29 14:59:23 +00:00
Doxa Maps
cfd03d0250 PATTERN-A-LIST-OF-ZONES keeps the rule and drops the speaker
Three lines brought in with the list-pattern doc cited the Driver; the
reference gate refuses them. The rule, the scope and the open exception
stay as stated, without the attribution.
2026-09-29 14:54:47 +00:00
Doxa Maps
4dd858fb9a Merge upg100/list-pattern into development (handoff)
# Conflicts:
#	internal/check-ported-globals-identical.mjs
#	internal/fixtures/ported-globals-baseline.json
2026-09-29 14:54:21 +00:00
Doxa Maps
cbdf5546c4 Keep the reason, drop the speaker, in two comments the merge restored
Merging gospel/a5-cpu and gospel/panel-followup brought back two comments that
quote the Driver directly, and check:no-new-driver-references failed on them —
correctly. Development had already swept these; the merge reintroduced them from
an older base.

Both keep their reasoning and lose the attribution: a recording pill that
changes width on every word reads as instability, and on a phone the header can
take most of the panel, leaving the draft unscrollable and its last lines
unreachable. Those are the facts the rules encode; who said them is not.
2026-09-29 14:54:15 +00:00
Doxa Maps
7869f28ebc Merge branch 'regions/grid-merge' into regions/handoff-integration
# Conflicts:
#	package.json
#	plugin-designs/people-groups-grid/people-groups-grid.vue
#	public/dx-maps/doxa-maps-spa/manifest.json
#	public/dx-maps/manifest.json
#	public/dx-maps/people-groups-grid/people-groups-grid.js
#	public/dx-maps/people-groups-grid/people-groups-grid/driven/index.html
#	public/dx-maps/people-groups-grid/people-groups-grid/index.html
#	public/dx-maps/people-groups-grid/people-groups-grid/people-groups-grid/index.html
2026-09-29 14:53:55 +00:00
Doxa Maps
844fc60d0e Merge upg100/group-set-standard into development (handoff)
# Conflicts:
#	library/scripts/posters/packet.mjs
2026-09-29 14:53:36 +00:00
Doxa Maps
bf07e0eb61 Merge branch 'regions/country-map-posters' into regions/handoff-integration
# Conflicts:
#	plugin-designs/regions-dashboard/regions-list.vue
#	public/dx-maps/doxa-maps-spa/manifest.json
#	public/dx-maps/manifest.json
#	public/dx-maps/regions-dashboard/region-dashboards/index.html
#	public/dx-maps/regions-dashboard/regions-dashboard.js
#	public/dx-maps/regions-dashboard/regions-list/index.html
#	public/dx-maps/regions-dashboard/regions-mfe/index.html
2026-09-29 14:53:10 +00:00
Doxa Maps
ed1aa635ce Merge development into the gospel handoff branch 2026-09-29 14:51:04 +00:00
Doxa Maps
bdb74ff69f Merge remote-tracking branch 'origin/development' into bundler-2/handoff 2026-09-29 14:50:51 +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
c9ffa34e93 Merge branch 'bundler-2/provide-warnings' into bundler-2/handoff
# Conflicts:
#	internal/check-element-bootstrap.mjs
#	internal/check-provide-shadowing.mjs
#	library/elements/defineMapElement.js
#	library/elements/provide-own.js
#	package.json
#	plugin-designs/all-people-groups/index.js
#	plugin-designs/cities-internet/index.js
#	plugin-designs/country-posters/index.js
#	plugin-designs/doxa-research-map/index.js
#	plugin-designs/gospel-internet/index.js
#	plugin-designs/gospel-resources/index.js
#	plugin-designs/key-cities/index.js
#	plugin-designs/my-upg-100-list/index.js
#	plugin-designs/pplr-data-maps/index.js
#	plugin-designs/regions-dashboard/index.js
#	plugin-designs/upg-neighbor/index.js
2026-09-29 14:50:37 +00:00
Doxa Maps
59345a0aaa Merge gospel/board768 for the handoff (superseded; development's side kept)
# Conflicts:
#	plugin-designs/gospel-internet/src/components/GiBoundedSetMap.vue
#	plugin-designs/gospel-internet/src/components/GiCenteredSetMap.vue
#	plugin-designs/gospel-internet/src/setChartFeatures.js
#	public/dx-maps/doxa-maps-spa/manifest.json
#	public/dx-maps/gospel-internet/board/board/index.html
#	public/dx-maps/gospel-internet/board/index.html
#	public/dx-maps/gospel-internet/board/india-china/index.html
#	public/dx-maps/gospel-internet/board/language-tree/index.html
#	public/dx-maps/gospel-internet/board/platforms/index.html
#	public/dx-maps/gospel-internet/board/project-700/index.html
#	public/dx-maps/gospel-internet/board/zero/index.html
#	public/dx-maps/gospel-internet/gospel-internet.js
#	public/dx-maps/manifest.json
2026-09-29 14:50:21 +00:00
Doxa Maps
d6f2f10355 Merge gospel/a5-cpu for the handoff (superseded; development's side kept)
# Conflicts:
#	internal/desktop-build.mjs
2026-09-29 14:50:19 +00:00
Doxa Maps
2e72d59f35 Keep the reasons, drop the speaker, in the two merged branches
The consolidation brought back two Driver citations from older branches that
the reference sweep had already cleared from development. Both keep their
engineering reason and lose the speaker: a gate's rationale for existing, and
the mechanism of the print collapse.

Caught by check:no-new-driver-references, which is the point of running the
full chain rather than the seven gates the merged package.json had left.
2026-09-29 14:46:07 +00:00
Doxa Maps
964b4c4ccf Merge branch 'bundler-2/rm-plugin-only' into bundler-2/handoff
# Conflicts:
#	plugin-designs/doxa-research-map/research-map.vue
2026-09-29 14:39:50 +00:00
Doxa Maps
4571596cf1 Merge branch 'bundler-2/grid-lazy-images' into bundler-2/handoff 2026-09-29 14:39:13 +00:00
Doxa Maps
5ff4da6460 The three Regions roots resolve against their own host box, not the page
Each of these shadow roots is position:absolute; inset:0, while its :host had
display:block with no position. An absolutely positioned element resolves against
its nearest POSITIONED ancestor, and an unpositioned host is not one -- so the
root fell through to whatever the partner page happened to have, or to the
viewport. It only looked right standalone, where the page fills the viewport
anyway, and inside a composite's .pane, which is itself positioned. A partner
embedding the element directly gets it painted outside its box.

All three are the shadow root's own root element, with only comments between
<template> and them, so none of them has a positioned ancestor to fall back on:
  regions-list.vue:408      .rl-root  at :409
  region-dashboards.vue:733 .rd-root  at :734
  regions-mfe.vue:194       .rmfe     at :199

dx-shepherd's census named the first as confirmed and the other two as
candidates to check. Checked: both are roots, not children of something already
positioned, so all three are real.

The fix is the house pattern -- position:relative on :host -- matching the
already-fixed cities-internet/connectivity-map byte for byte in shape, and
prayer-walk-planner.vue:628.

One line changed per file. Verified in the ARTIFACT rather than the source: the
rebuilt regions-dashboard.js carries all three corrected host rules (two at
min-height 320px, one at 420px). check:sfc-parse, check:synced-css and
check:one-scroll-container pass; the manifest diff is integrity hashes only,
0 other lines.
2026-09-29 14:38:56 +00:00
Doxa Maps
0ab2fe7c87 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 the base: 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". That last zero
    is data, not a failed load: the same membership matches 2 India rows.

Rebuilt on this base rather than carrying the earlier bundle, because
development rebuilt the same files in between and generated output is
regenerated, never merged. The list-membership URL is present in the built
bundle; the manifest diff is integrity hashes only, 0 other lines.
2026-09-29 14:38:56 +00:00
Doxa Maps
ce40a1eb87 Rudder's corner panel kept its notices above the draft, and they never yielded
Two defects, one cause: on a short panel the engine-offline notice took the room
the draft needed.

FIRST, A PHONE RULE THAT NEVER RAN. bac2431d0 (19:59) put notices below the
draft "so the draft stays in view". 4e94e9216 (21:58) added the corner widget
with `.compact .notice { order: 0 }`. A media query adds NO specificity, so
`.compact .notice` (0,2,0) beat `@media { .notice }` (0,1,0) at every width and
the phone rule has been dead since the day it landed. Ordering the compact rule
to 1 fixes the widget at every width, not only under 480px, because the corner
panel is short regardless of the viewport.

SECOND, ORDER ALONE WAS NOT ENOUGH, which I only found by doing the arithmetic
rather than trusting the first fix. .notice is flex:0 1 auto and .compact
.compose is flex:1 1 0, so the draft yields whatever the notice's content asks
for no matter where it sits. The offline notice is ~132px at 375 and ~179px at
390 — reference text a reader needs once, holding a third of a short panel
forever. Capping it at 96px with its own scroll gives the draft 58px at 375x450
and 124px at 390x560, against rega's measured 22px and 41px.

The panel is 60vh (plugin-studio.vue:466), NOT the viewport. Sizing off the
viewport is what made my earlier estimate wrong by 58px; the model that uses the
real panel reproduces rega's 22px exactly.

No floor anywhere: a floor is what clipped the draft to begin with.

(cherry picked from commit 79cbb41bc3)
2026-09-29 14:38:53 +00:00
Doxa Maps
4dbeb257ab The gospel data table escaped its host box when embedded alone
Same defect as cities-internet's list and table: no :host rule at all, so the
element falls back to display:inline with no height while .gt is
position:absolute;inset:0 — which then resolves against whatever the partner
page has positioned.

TWO MOUNTS, ONE RULE, and they do not collide:
- STANDALONE (data-tables.meta.json) this is its own custom element, so :host
  applies and .gt is finally contained.
- COMPOSED, gospel-dashboard renders <DataTables/> as an inline Vue component
  with no shadow host of its own, so :host matches nothing there. Its
  '.gd-tables .gt{position:static;…}' override (gospel-dashboard.vue:81) keeps
  working, which is what lets the stacked section take its natural height and the
  page scroll. That override records a MEASURED failure — docScrollHeight 900
  against clientHeight 900, a wheel gesture moving 0px — and must not return.

min-height, not height alone: with no height, .gt's inset:0 would give the table
0px in an auto-height box, so it would stop escaping by becoming invisible. A
floor gives an auto host something to grow from; a definite-height slot still
decides through height:100%.

NOT the ProfileLoader 100dvh floor (ProfileLoader.vue:410), which is the separate
upstream cause of the connectivity-map overflow and is studio's card. .gt is not
that wrapper, so this fix stands on its own.

(cherry picked from commit 10f2a5c13f8d08c0086ada8eafa4ad819863bd0a)
2026-09-29 14:38:46 +00:00
Doxa Maps
eec16a8fd8 The list and the table painted thousands of pixels below their host box
Neither element had a :host rule at all, so each fell back to the custom-element
default — display:inline, no height of its own — while its root div carried
position:absolute;inset:0. Absolute positioning resolves against the nearest
POSITIONED ancestor, so on a partner page both resolved against the PARTNER's
layout rather than against this element.

rega measured it on a partner page with a 420px host box: connectivity-list
painted to y=16,799 and data-tables to y=1,698 — 16,355px and 1,254px below the
box, over whatever the partner had underneath.

The same fix as connectivity-map's (60cb4ff18), for the same reason:
  :host{display:block;position:relative;width:100%;height:100%;min-height:320px}
display:block gives the element a box; position:relative makes that box the
containing block; height:100% lets a definite-height slot decide; min-height is
the floor so an auto-height host grows to fit instead of collapsing.

NO NEW SCROLL CONTAINER WAS NEEDED. .ci and .ct already carry overflow-y:auto —
the scroller existed, it simply had no host to be bounded by. Once contained,
the list and the table scroll inside the box, which is what an embed should do
in a definite-height slot.

min-height is 320px here, not the map's 420px: regions-list.vue:408 is the house
pattern for list profiles, and a list is legible shorter than a map is.

(cherry picked from commit 08ad95fa86e64dfe043befb156e358c91319e511)
2026-09-29 14:38:46 +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
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
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
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
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
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
8793d47f0e countries-map paints idle regions with the library's regionIdle, not its own copy
useLegendMapBridge.js now exports regionIdle (sr, pair 1). The local
function is deleted and the name imported beside the bridge's other
exports; all 15 calls keep their values, the dynamic regionIdle(v)
included. Built bundle: the idle expression occurs exactly once before
and after (the local copy before, the library's after), 1 byte apart;
the country-posters composite still mounts. check:region-paint-single-
source PASS. Also drops a comment left behind when clearCountryHighlight
moved to the same library module.
2026-09-29 13:29:18 +00:00
Doxa Maps
1fde4b9cc4 The regions idle paint expression moves to the library that already painted with it
useLegendMapBridge took the idle paint helper as an injected dependency and
called it five times, while each consuming map defined its own copy of the
one-line body and handed it over. The library owned the paint logic and not the
expression it painted with, so the copies could drift apart silently and the
two maps would render the same idle state differently.

Export it from the composable instead, and take it from there in this map. The
injected parameter is deliberately KEPT for one round: inside the composable
the parameter shadows the module export, so what the bridge paints with is
still exactly what its callers pass, and this change cannot move behaviour. It
is a deletion in the plugin and an addition in the library, nothing more. The
parameter goes when the second map has adopted the export too.

This map held a THIRD copy under a different name -- a function-scoped helper
in the paint prewarm, byte-identical body, used once. It now refers to the
library helper.

Verified rather than assumed: the library version and both original plugin
versions produce identical output for every value the maps actually pass,
including the dynamic one. check:region-paint-single-source is green for this
map and stays red for the other until it adopts the export, which is the state
the gate is meant to report.

The second map is the other plugin's file and is not touched here; its owner
takes that half.
2026-09-29 13:29:18 +00:00
Doxa Maps
f50b641b28 The Wall's and the template's proofs become guards: four two-sided headless checks, in the plugins' own scripts/
Until now these ran from _qa-cycle, so they proved a landing once and guarded nothing after it. Each now lives with its plugin, locates the repo from its own path, and fails on its known-bad:
- wall-of-people-groups/scripts/check-wall-wysiwyg.mjs: the screen poster and the print are one layout on every preset (sheets, 2,107 card rects to 0.002 in, name size and wrap, reference size, chrome blocks). Planted: a card moved 0.01 in, a name a point larger, the footer missing on screen. About 0.6 s.
- wall-of-people-groups/scripts/check-wall-print-watch.mjs: the HD print counts its photos while they load, counts the ones that could not load, finishes at load or the 10-minute ceiling, and ends when its frame is gone. Planted copies of printWatch.js now go to the OS temp folder, never into the tree. About 0.3 s.
- wall-of-people-groups/scripts/check-wall-no-bg-photos.mjs + fixtures/pray-list-sample.json: the real store (through Vite) never requests a photo on its own, a group with no photo URL gets none, and one with a URL keeps it. Planted: the old background test, added through the store's $onAction. The store from before L12 (d9e9aa448) fails both arms: 12 photo requests in 3.5 s and 3 groups on the 404 placeholder. The fixture is a trimmed sample of the live list: 200 rows (120 with has_photo, 77 without, 3 synthetic with no image_url), only the fields the check reads, 66 KB. About 10 s.
- template-bundle/scripts/check-template-i18n.mjs: profile B renders from template.json in en, ar and zh, one sentence in each language's own order with both <code> islands, and no raw key. Planted: the English catalogue served as Arabic. About 4 s.

All four are headless: no browser seat.
2026-09-29 13:14:46 +00:00
Doxa Maps
a4622781a2 Key Cities: all nine region headings read in the page's language, and the region stays the key
This corrects my own "Other" fix (9d12e59b6), which could never fire. gospel's cities-internet analysis (87a168195) showed the same shape here: key-cities.json has no row with an empty region (1,646 rows), "Other" (2) and "N/A" (19) are region VALUES, and every section heading printed its raw English data string in every locale. The fallback I translated also put a label into the grouping key.

Now, as in cities-internet: the raw region string stays the grouping key (sections, :key, collapse state), with 'Other' as the data guard; the heading is projected at render by regionLabel.js from the keys the data's strings already are the English values of (dashboard.wagfRegion.*, dashboard.other, wall.na), written out in full so the build ships those areas. The poster wall, whose PosterWall prints its section name as given, groups by that projected heading, as country-posters' wall does. keyCities.posters.otherRegion is no longer used.

Headless (_qa-cycle/exp-shepherd/check-keycities-regions.mjs): every one of the nine region values maps to its catalogue heading in ar, zh, es and ru, and an unknown value passes through as data. key-cities is gated, so nothing in public/ changes.
2026-09-29 13:14:46 +00:00
Doxa Maps
b1c4c03956 Four resource labels on the pin sheet read English in every locale
The chips a reader sees on a group's sheet — "Scripture,", "Bible video",
"Training", "Gospel presentation" — were plain template text. All four already
had keys with real ar and zh translations, so this routes them through
gospel.res.* and adds nothing: scripture, bibleVideo, training,
gospelPresentation.

WHAT IS DELIBERATELY LEFT ALONE, because it is not a label:
- g.audio.m keeps its raw value and its 'audio' default. That is the table's own
  media descriptor, data rather than UI text.
- gospel-resource-rows.js is already correct. add(title, type, …) translates
  title through tx() and keeps type as the category identifier the rows are
  grouped by; the gate flags those type strings, but translating them would
  break the grouping — the same value/label trap as the region headings.
- d.s.replace('Open Bible Stories', 'Bible Stories') at :2754 rewrites a DATA
  value, not a label.
- data-tables.vue:122 already carries labelKey; its `en:` is the fallback beside
  it, by design.
- The caveat prose (Driver row 80) and the title= tooltips (row 120) are
  untouched and stay that way until those rows are answered.

check:hardcoded-english on gospel-resources: 91 -> 87, which is these four.
2026-09-29 13:08:53 +00:00
Doxa Maps
488d34836a Wall of People Groups: the level-of-detail hook reports the zoom it actually draws at
rega's L12 photo probe read host.__wallLod().cardPx as 0 after 46 wheel ticks: the field was set once when the cards were first laid out and never again, since the canvas renderer replaced the LOD component that used to update it. The canvas now records one card's on-screen width at every frame it draws (stats.cardPx, both the grid and the poster), and the hook returns that. The container's header comment no longer claims Font Awesome is loaded: the canvas draws its own icons, and d3 is pinned and kept off window.d3 (src/wallD3.js).
2026-09-29 12:35:22 +00:00
Doxa Maps
87a1681956 Nine region headings read English in every locale, not just "Other"
rega saw "Other" in ar and zh. The reported cause — `c.region || 'Other'` at
:65 — is not one: of 175 rows in connectivity.json NONE has a falsy region, so
that fallback never fires. The "Other" section is one row (United States) whose
region IS the string "Other". Patching :65 would have changed nothing on screen.

The render site is `{{ sec.name }}`, and it printed the raw data value for ALL
NINE regions, so this was nine English headings rather than one.

All nine already had translated keys — dashboard.wagfRegion.* for the seven WAGF
regions, dashboard.other, wall.na — in all 11 locales. None needed adding.

THE LABEL IS PROJECTED AT RENDER, NEVER AT GROUPING. sec.name keys the Map, the
:key, and the collapse state, so translating it where the rows are grouped would
reset every open section on a locale switch and filter on a value no row carries.
That is the value/label trap useFacetAggregation.js documents at OTHER_KEY; this
follows the same shape.

The name->key map is built by asking the ENGLISH catalogue what each key says,
rather than writing the English phrases here: the data's region strings are the
catalogue's own English values, so English is the join and never has to be
duplicated. Written the other way it added two check:hardcoded-english findings
to a file that had none — an English phrase used as a lookup key reads exactly
like a user-visible string.

tStatic(path, 'en'), not t(). t()'s signature is (key, params, plural), so
t(k, 'en') passes 'en' as params and returns the RUNTIME locale's value: the map
would be built from Arabic values and match no region at all.

The keys are written out in full because internal/i18n-areas.mjs bundles an area
only when its name appears literally in a reached file. cities-internet shipped
14 areas and neither dashboard nor wall; assembling these at runtime would have
left all nine unresolved in the shipped bundle while working in the dev tree.
Measured both ways — written out, the bundle takes 16.

`|| 'Other'` stays as a data guard so a future row with no region cannot key the
Map on undefined, and 'Other' is the sentinel dashboard.other projects.

9 of 9 headings translate in ar and zh, verified against the real catalogues.
2026-09-29 12:24:45 +00:00
Doxa Maps
53ed778d71 PPLR Data Maps: the fullscreen button is named in the page's language
rega's L15 (e7f343d4c, ar + zh): every PPLR map view's fullscreen button read "Toggle fullscreen" in English, title and aria-label. It is PPLR's own control (FullscreenToggleControl in src/utils/pplrMap.js, not MapLibre's), and it wrote the English itself. It now takes the shared buttons.toggleFullscreen, already translated in all 11 locales, when the map is built.
2026-09-29 12:20:03 +00:00