Commit graph

76 commits

Author SHA1 Message Date
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
75ea0adec0 doxa-maps-spa: its comments say what the code does and why, not who asked for it
The shell's comments carried 23 of the reference baseline's lines, one more written in capitals that
the baseline's pattern never saw, and four internal card ids. Each is reworded to keep the engineering
reason: the 404s, the grey pane, the scroll trap, the device rule and the curated order all stay
explained. Quotes became the rule they stated.

Comments only. The script (esbuild, whitespace and syntax only, no renaming), the template (compiled
without comments) and the styles (comments removed) are identical before and after. The baseline
shrinks by the 23 lines that are gone.
2026-09-29 10:07:28 +00:00
Doxa Maps
51ef6f7ec4 One phone flag per map, and the leaf page follows it as well as the Gallery
A map says how it behaves on a narrow screen once, in its <profile>.meta.json:
"mobile": "desktop-only" (blocked on a phone) or "larger-screens" (opens,
under a dismissible notice). Absent means phone-ready. Until now the rule
lived only in the Research Gallery, as two hard-coded id sets, so a map
opened standalone on its own leaf page on a phone got neither the block nor
the notice.

- profile-meta.mjs knows the vocabulary; generate-tree and the dev manifest
  carry the flag into each profile's manifest entry, and generate-tree into
  the leaf page's BASE_CONFIG (with the map's name for the desktop-only
  sentence).
- ProfileLoader mounts library/components/LargerScreenNotice.vue on a screen
  under 640px: for desktop-only the profile is never mounted and a centred
  message says why; for larger-screens a slim in-flow bar sits above the map
  (it cannot cover the map's controls), dismissible per profile for the
  session, in the element's own locale and direction. Inside the Gallery it
  stays quiet: every Gallery pane now carries data-doxa-gallery.
- One text: the Gallery's two sentences moved into the library catalogue as
  messages.largerScreenNotice / desktopOnlyNotice (+ noticeDismiss), all 11
  locales moved as they were, and the Gallery reads them from there.
- The Gallery derives its rules from the manifest's flags. On a phone it
  mounts nothing until the manifest has landed: its baked first tab (Big
  Picture) would otherwise mount there before the flags say it is
  desktop-only.
- dashboard-plus-grid is desktop-only and gospel-internet/board is
  larger-screens, the two sets the Gallery held; nothing changes for either.
- check:mobile-gating now asserts the vocabulary, that each flagged leaf page
  carries its flag, the Gallery's derivation and its closed-before-manifest
  rule, that Big Picture stays desktop-only, the 11 locales, and the
  notice's mounting. Its self-test plants one failure per rule.
2026-09-29 07:31:16 +00:00
Doxa Maps
9996bb6fde Comments state their findings instead of citing files outside the repo
Seven comments in the library and the app pointed at probes, checks, a report or research notes that
live outside the repository, where a reader of the code cannot follow them. Each now states what was
found and drops the path: the engine-predicate equivalence, the chooser's globe-fill measurement, the
fit-at-construction probe, the worldview clause, the web-boundaries sizes (1,866 KB to 383 KB, every
feature and part kept). The QA corner and the packet generator lose a dead pointer. The app's nav
comment states the three sections and the third dashboard directly instead of quoting a transcript.
2026-09-29 03:46:37 +00:00
Doxa Maps
2eafd15e1f Experimental shelf: the Wall's "?" describes the Bless poster and Social mode, in every language
Bless mode is the printable poster (a verse under every group, sizes, Print HD poster) and Social mode the shared prayer wall; the texts use each locale's own button names. UPG Neighbor's entry is removed: it has no hosted "?" (its own How it works is its explainer), so the entry showed nowhere and could only drift.
2026-09-29 03:22:40 +00:00
Doxa Maps
65810f872f A back-to-top beside the view-list circle, and lift the Regions attribution clear of it
Two reported mobile bugs (DRV1650 #3 and #4), both settled by rega's
measurements at 390x844 on dx-life /research rather than by reading source.

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

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

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

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

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

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

NOT PROVEN HERE: the headless DOM proof needs a browser (rega's seat). What I
verified is that the button, its scoped CSS and its ar/zh labels are in the
SHIPPED doxa-maps-spa.js, and the fab lift in both map bundles.
2026-09-29 02:35:35 +00:00
Doxa Maps
dcd1c13f55 The app answers each map's own ? and drops the one at the top centre
doxa-maps-spa marks the pane of every tab that has explainer text with data-doxa-help, listens for
doxa:help, and opens the same what/how dialog for the active tab. Closing returns focus to the
exact "?" that opened it (composedPath reaches into the map's shadow root). The top-centre button
and its CSS are gone; a comment for the device-rule toast that sat above the old button's rule
is back on its own rule.

check:help-reachable (gates:pre) keeps it reachable: every map with text has a hosted "?" or a
named help control of its own; the app listens, marks its panes and has no top-centre button; and
the composable, executed on a small fake DOM, dispatches doxa:help bubbling and composed and finds
the host across a shadow root. Six planted defects, each caught.
2026-09-29 02:35:35 +00:00
Doxa Maps
c8227bd994 The pin hint only answers to a map canvas; the Experimental "?" moves to the top centre
PinHint self-gated on any <canvas>, so the Wall's own renderer got a tap-a-dot coach-mark over its
dialogs; it now matches only MapLibre and Mapbox canvases. The "?" sat bottom-right, over the map
attribution on several plugins and the zoom-out button on one; map plugins use the corners, so it
moves to the top centre.
2026-09-28 21:59:29 +00:00
Doxa Maps
7ea66b21a2 Phones: production maps not built for them stay closed and say why; experimental ones open with a notice 2026-09-28 17:53:38 +00:00
Doxa Maps
addf520dbe An Experimental map has a "?" that says what it is for and how to use it
The button shows only on a discovered tab with an entry in experimentalHelp.js. It opens a modal
dialog titled with the tab name: focus moves to Close, Tab stays inside, Escape or Close returns
focus to the button, and switching tab closes it. close is a new shell string in all 11 locales.
2026-09-28 17:53:38 +00:00
Doxa Maps
c56c4d1c35 Experimental shelf: the help button's name, in every language 2026-09-28 17:53:37 +00:00
Doxa Maps
837fcd9b5e Experimental shelf: what each map is for and how to use it, for the help button, in every language 2026-09-28 17:53:37 +00:00
Doxa Maps
d19c1bbf83 Experimental shelf: Prayer Walk Planner, Gospel × Internet, then UPG Neighbor, as the Driver ordered 2026-09-28 17:40:34 +00:00
Doxa Maps
00e505404c The gallery pane was ported from a working one, two lines out of three
rega's census refutes the min-height diagnosis I gave, and the arithmetic is what
settles it. Inside section.dms-pane the CONTENT measures exactly the viewport: 900 in a
794px pane at 1440x900, 844 in a 712px pane at 390x844. The overflow is not "about the
DX-life header", it IS the header -- 106 and 132 px -- and it happens on 12 of 16 tabs
INCLUDING plugins that assert no floor of their own, which is what proves the floor is
not theirs.

library/ProfileLoader.vue:336 floors EVERY profile at
`min-height: var(--profile-min-h, 100dvh)`. That measures against the VIEWPORT while the
pane measures against its HOST, so wherever anything sits above the gallery the profile
is taller than the pane by exactly that chrome, and .dms-pane's overflow-y:auto scrolls
the whole plugin -- tab strip included. ProfileLoader documents the trap and documents
the only lever: the floor is inside the child's SHADOW ROOT, where no host-page selector
reaches, so an inherited custom property is all a host has. `--profile-min-h: 0`.

IT IS TWO THIRDS OF A FIX, NOT A NEW DEFECT. .vtl-host and plugin-studio's .ps-pane
already set it. .dms-pane was copied from .vtl-host -- its own comment says "porting the
same proven shape" -- and took the first two lines: fill the host, own the overflow. The
third, release the child's viewport floor, is the one that makes the other two safe.

So the gate now asserts the THREE AS ONE SHAPE: a rule that is absolutely-filling AND
scrolling must release the floor. It also FAILS if it cannot find such a rule at all,
because a gate that silently stops finding its subject is the failure I spent this
morning removing from check:i18n-keys-exist -- a check that matches nothing reports the
same green as a check that matches everything.

Negative-tested: removing the opt-out from either pane fails it, and changing the pane's
shape so the gate cannot recognise it fails it too rather than passing blind.

MY DIAGNOSIS WAS SECOND-ORDER AND I SHOULD SAY SO PLAINLY. The min-height floors were
real and worth fixing, but they only bite when a plugin's own floor exceeds the pane.
This bites on every tab at every size. I reasoned from source to the first mechanism
that explained the one symptom I had been shown, and the tall-desktop arm -- the one I
asked rega to run precisely because it would refute me -- is what separated them.
2026-09-28 16:49:01 +00:00
Doxa Maps
9fddcb9901 min-height clamps height, so the pane could not hold the plugin it was pinning
The Driver's least favourite bug: on DX Life's research page a capability-4 plugin
with tabs at the top had a second, element-level scrollbar, and scrolling it scrolled
the TAB HEADER out of view.

doxa-maps-spa pins each fixed-viewport plugin with position:absolute;inset:0;height:
100% and its own comment states the invariant: "fills the pane exactly, so
scrollHeight===clientHeight and overflow:auto paints NO scrollbar." CSS never granted
that. A used height is CLAMPED between min-height and max-height, so a child's own
min-height BEATS the height:100% the shell sets -- it is a floor, not a preference.
Wherever the pane was shorter than that floor the pinned element grew taller than the
pane it was meant to fill, .dms-slot grew with it, and .dms-pane's overflow-y:auto
became a real scroll container wrapping the WHOLE plugin, tab strip included. That is
the second scrollbar: not the plugin's content scroller, the PANE scrolling the plugin
bodily. The invariant was written as a description and was only ever a hope.

The shell now pins with min-height:0. An inline style outranks a child's :host rule,
so that one property restores the invariant for every fixed-viewport plugin at once
without editing any of them. It is NOT added to the scroll-stack branch, where
natural height is the design.

A floor set INSIDE a child's shadow root is out of reach from the shell, and two of
mine had one. research-dashboard's .rd-shell and big-picture's .bp-root now say
min-height:inherit, deferring to :host -- which the gallery pins to 0, and which a
naive page leaves at its own floor, so the collapse guard those floors exist for is
kept rather than deleted. big-picture's 480px MOVED UP to :host rather than being
dropped: :host had no floor there, so inheriting would have quietly resolved to 0
everywhere and thrown away the black-screen guard its own comment calls load-bearing.

check-one-scroll-container asserts both halves, because either alone rots: the shell
still pins with min-height:0, and no plugin grows a NEW floor inside its shadow root.
The second half is what matters later -- a fixed shell plus a plugin that adds
min-height:700px next month is this bug again and nobody would connect it.

The rule is a CONTRADICTION IN ONE DECLARATION, not a large min-height by itself: an
element saying height:100% ("I fill whatever holds me") while refusing to be shorter
than N. My first version flagged any big min-height and caught gospel-resources'
.gd-map, a map deliberately sized height:80svh that scrolls inside its root's own
content scroller -- the one scrollbar a plugin is allowed. Flagging it would have sent
its owner after a non-bug, which is worse than not having the gate.

Negative-tested: removing min-height:0 from the shell, and reverting either .rd-shell
or .bp-root to a constant, each add a failure; restored, only the known one remains.

RED ON PURPOSE, and deliberately NOT in gates:pre. gospel-internet's .gb still carries
min-height:640px and that fix is its owner's. Wiring a red gate into gates:pre would
block the fleet on someone else's file, so the gates:pre entry belongs in the same
landing that fixes .gb -- until then this gate is the work item, afterwards the guard.

Source only: doxa-maps-spa, research-dashboard and big-picture need rebuilding, which
is the build owner's to do.
2026-09-28 15:01:19 +00:00
Doxa Maps
d0939c0492 library: one embedScript for the app shells, replacing four private copies
DEDUP-AUDIT.md (measured today: 253 files, 31 cross-plugin duplicate clusters,
622 duplicated lines) ranks this tenth by raw cost and first by suitability. It
is the only cluster where the copies are identical in code, close over nothing,
and sit in two plugins with one owner between them.

loadScriptOnce (13 lines) and whenTagDefined (11) were byte-identical between
doxa-maps-spa and plugin-studio. deriveTreeBase (9) differed by ONE COMMENT LINE
and not one character of code -- drift in the documentation of a shared function,
which is its own argument for having one copy.

They are one concern, not three: load a bundle's script once, wait for its custom
element to register, and work out the tree base to load it from. That is what a
capability-5 app shell does to nest another plugin, and both plugins here ARE the
capability-5 shells. Any future shell needs the same three.

Verbatim move: no parameterisation, no behaviour change, callers import instead
of declaring. The comments travel with the code because each records a
measurement -- particularly the one explaining why onload is not a usable success
signal for a dev-server ES module.

ONE BEHAVIOUR CHANGE, AND IT IS AN IMPROVEMENT: the _loaded cache is now shared.
Two shells on one page previously kept separate caches and each loaded the same
bundle; the second now reuses the first's promise.

deriveTreeBase reads document.currentScript, so it is only valid AT MODULE
EVALUATION TIME. The call sites did not move -- both entries still call it at
module top level -- and the library file documents the constraint, because a
later call silently returns '' and every nested bundle URL would break.

Verified: both bundles build; each built file contains all three functions
exactly once and still derives currentScript; check:sri, check:series-isolation,
check:studio-rail and check:capability-derived all PASS.

library/utils/embedScript.js is the build owner's under rule 5B. The
plugin-designs and public/ changes are in the same commit because they import it
-- landing them apart breaks the build in between.
2026-09-28 07:07:33 +00:00
Doxa Maps
534bbdd5af Rudder stays out of the Research Gallery and makes no network call on a static host
The gallery added every experimental bundle as a tab, so Rudder's engine probes
(/api/rudder/status, /devices, /events) logged three 404s on every embed of the
gallery. The manifest now carries each profile's meta targets, and the gallery
skips a bundle whose profiles are all corner widgets or declare targets without
"web". Rudder itself contacts its engine only where one can exist: the dev
server, the desktop app, or an explicit apiBase.
2026-09-28 03:12:46 +00:00
Doxa Maps
8afea17c6b Capability is derived from what a plugin composes, and the preset-plugin standard is written down
generate-tree derives each profile's tier from its composes edges (composing
anything is capability 4; composing a capability-4 plugin is capability 5). An
explicit type is checked against the derivation and a contradiction fails the
build naming both. check:capability-derived asserts every profile is 1-5 and
agrees. documentation/PRESET-PLUGIN-STANDARD.md describes the one
*.instances.json file a preset plugin declares.
2026-09-27 23:14:15 +00:00
Doxa Maps
f9ce2aeb4e Gallery: count the gospel-internet board as a heavy tab
It owns up to two WebGL contexts (its map, plus the language-tree
whiteboard once opened). Kept mounted by v-show, it spent the context
budget that silently drops the oldest map. go-int's edit here was
dropped at landing.
2026-09-27 06:04:09 +00:00
Doxa Maps
24b9ada48e Plugin Studio and the gallery split production from experimental, from each bundle's own stage
The Driver: a public plugin that is not yet a production map shows under Experimental until it is. A
profile's .meta.json declares "stage": "production" | "experimental" (default experimental; a bundle is
production if any profile says so) and "heavy": true for a live WebGL owner. generate-tree carries both into
manifest.json, so the Studio and the gallery read the same field and neither keeps a list.

The seven production bundles are marked: dashboard-plus-grid, research-map-plus-grid, regions-dashboard,
my-upg-100-list, doxa-simple-map, cities-internet, gospel-resources. The Studio rail keeps its capability
sections for them and puts every experimental bundle (gospel-internet among them) in one final Experimental
section. The gallery's auto-discovered Experimental folder skips a production bundle. gospel-internet-board
joins HEAVY_ELEMENTS (it can own two WebGL contexts). check:studio-rail asserts the split.
2026-09-27 06:04:08 +00:00
Doxa Maps
a267b89583 The Research Gallery loads behind a login gate: same-origin fetches keep their credentials
Behind any login gate (basic auth, a session cookie, OIDC) the gallery said "Could not load Big Picture":
doxa-maps-spa fetched its same-origin manifest, and plugin-studio its token URL, with credentials:'omit', so
the gate's cookie never went and the request was refused (found on the new dev container; dx-shepherd).
Both now use 'same-origin', which never sends credentials cross-origin, so CDN and CORS behaviour is
unchanged.

check:no-credentials-omit (+ :self-test, in gates:pre) fails any credentials:'omit' in library/ or
plugin-designs/ unless the line says why (// credentials-omit: <reason>). Measured before: exactly these two
sites; it fails on the old code and passes on the fix.
2026-09-27 05:05:03 +00:00
Doxa Maps
44a1afba04 Decouple prose: no go-base / nuxtbase / nuxtinator / doxa-base names in plugin comments and docs
Comments and prose only, no code change (DECOUPLE-go-base-mentions.md sections B and C, outside
library/ and README.md): stack-demo's page-flow note becomes 'a normal host page', the share-link
and CORS comments say 'the live test host', embeddability.md's Traefik note says 'a self-hosted
reverse proxy' and drops the internal proxy file path, and how-to's hosting option becomes 'a Git
host you run (for example Forgejo)'. stack-demo.vue's @description feeds the generated tree, so the
landing regenerates the tree. Section A (QA widget), library/ and package.json are left to studio.
2026-09-26 17:28:45 +00:00
Doxa Maps
f6571ed4ea Research Gallery: PPLR Data Maps and Wall of People Groups are named in all 11 languages
The two discovered Experimental tabs fell back to their English bundle titles in every
locale (Arabic rail: 'Pplr Data Maps', 'Wall Of People Groups'). Only doxa-maps-spa's own
bundle is rebuilt; its SRI refreshed in both manifests.
2026-09-25 23:41:17 +00:00
doxa-maps-88655c70
b862c7d61a Export gate reads the tree name from the build config
After the dx-maps rename, its public/doxa-maps literal made it SKIP (exit 2) without measuring.
Validated against its baseline on the renamed tree: 4/4 one-click exports, 1700x2200 PNGs,
0 errors, as before the rename. Also: usage examples and present-tense comments in the
gallery and planner name /dx-maps/ or the configured tree; both bundles rebuild byte-identical.
2026-09-25 21:32:35 +00:00
doxa-maps-88655c70
9792bc9162 Gallery: its dev manifest fallback names the tree from the build config, not a literal
The one tree path compiled into any bundle. Built today it is byte-identical (sha 3d5467fd…);
built with the config renamed it reads /public/<new name>/manifest.json.
2026-09-25 15:52:20 +00:00
doxa-maps-88655c70
27151f39a6 Research Gallery: boot on a partner page under the CORS the live server actually sends
The doxa.life script embed was blocked on a server change: the live root
manifest.json answers without Access-Control-Allow-Origin, and the gallery
fetch()es it cross-origin, so no tab could mount. Measured on nuxtbase,
though, the same static layer DOES send ACAO on every path inside a folder
of the tree (doxa-maps-spa/<anything>, even a 404; assets/…).

So generate-tree writes a byte-identical manifest copy at
doxa-maps-spa/manifest.json in the same step as the root one (they cannot
diverge). The gallery reads it FIRST when the tree is on another origin (no
CORS error in the host's console) and the root first when same-origin.
Script URLs still resolve against the tree root, never the copy's folder.

check:gallery-embed:live-cors serves the tree with the live rule
(serve-strict LIVE_CORS=1: no ACAO on the root manifest only).
- The current gallery under it: "did not boot within 60s" (negative
  control, reproducing doxa.life).
- With this change: js 14, iframe 14, neighbour 14, linked 4. The default
  gate also passes all phases.
check:embed-targets, check:drop and check:public-hygiene pass with the copy
present.

The server-side CORS rule (serving/traefik-embed-headers.yaml) is still the
right long-term fix, and it needs server access. This unblocks the embed
without it once the tree is published.
2026-09-25 08:55:52 +00:00
doxa-maps-88655c70
b78e7d1b52 Prayer Walk Planner: fill the page, and print a real walking map
The Driver: "the prayer walk planner is still a very tiny map, not
expanding the full page", and the printed map was a ~10px sliver.

Tiny map. Measured: the leaf filled (1003x739), but inside the Research
Gallery the map was 747x76 on desktop and 390x0 on a phone. The planner
mounts its SFC as the custom-element root (that is what puts its client
API on the element). So Vue's attribute fallthrough copied the gallery's
inline host style onto the root .pwp, and that inline display:block beat
.pwp{display:flex}. inheritAttrs:false keeps host attributes on the host.
The map now fills at 1280x800 and 390x844, in the leaf and in the gallery.
The gallery gets the same guard: its .vtl had survived only through
display:grid !important.

Sliver print. Print used to snapshot the LIVE map, so the page got that
map's on-screen size and shape. Each zone now renders in its own
temporary MapLibre map:
- page-shaped (1200x1344 at pixelRatio 2, a 2400x2688 image, ~320 dpi on
  the 7.5x8.4in frame)
- keyless OpenFreeMap liberty, with streets and building footprints
- fitted to the zone, with the zone outlined on top
- destroyed once its pixels are taken, and the reader's map never moves
One zone = one letter page, with the map filling it.

Localized. The planner read no language at all. It now follows
language → profile-config locale → ?lang → <html lang>, in all 11
locales (private src/strings.js): toolbar, list, status, default zone
names, and the printed sheet (lang/dir set, so Arabic prints RTL).
Street and place labels prefer the OSM name:<lang>. Two Arabic defects
were found by reading the printed sheet, and fixed:
- coordinates were bidi-scrambled → isolated LTR
- map labels were unshaped glyphs → the RTL text plugin is installed
  for MapLibre, from jsDelivr, and awaited before the print frame
  (library/maps/rtl-text.js only does it for Mapbox)

renderZoneMap(id) joins the client API: the print PNG without the dialog.

New gate check:prayer-walk-planner:
- 4 size cells (leaf/gallery × desktop/phone)
- a real zone in Balat, Istanbul renders a >=2000px non-blank print map
- a Spanish toolbar
- 0 page errors
Both gates now prove the served tree is this checkout's (manifest equals
disk) before measuring, and exit 75 NOT MEASURED otherwise.
check:gallery-embed's Big Picture link now asserts that the grid shows
exactly the facet's own count.
2026-09-25 07:21:42 +00:00
doxa-maps-88655c70
3b8e8e21e7 Research Gallery: name the Experimental maps, in every locale
The Experimental folder's rail rows were the one part of the gallery's own
chrome still in English in every locale. They were also machine-titleized
manifest ids, so even English read "Upg Neighbor".

The six Experimental bundles known today now have proper rail names in all
11 locales (Prayer Walk Planner, UPG Neighbor, Key Cities, All People
Groups, Country Posters, Doxa Vision Card). A future bundle that isn't
listed still appears, under its manifest title. Outside English its
English manifest description is replaced by the translated generic
subtitle.

Measured on the served built tree with the folder opened (7 → 12 rows):
translated in it/de/hi/ru/ar, en "UPG Neighbor", and 0 clipped. The rail
clip sweep with the folder open (11 locales × 1400/390) is 22/22 clean.
check:spa-tab-order passes on my own served tree. check:gallery-embed:
js 13, iframe 13, neighbour 13, linked 4.
2026-09-25 05:48:21 +00:00