Picking a region from the all-regions table, or a country from a region, kept the old scroll
height, so the new page opened at the bottom; the dashboard now scrolls its own container to the
top when the subject changes (switching to the list and back changes no subject and keeps its
place). The region and country maps take the all-regions map's square shape under 768px; the shared
map component keeps its portrait default for the country page.
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.
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).
The gate began from keys like 'adm0:IND', so it never ran resolveTerritory
on what the map hands the click handler, and it passed while every tapped
country resolved as a province. Section 4 feeds the resolver three real
tapestry shapes: a country (shapeName + iso3, no listId), an India state
(listId + shapeName + iso3), and a country drawn inside an Asia zone
(zone.adm0).
Two-sided: on the previous resolver it fails three ways (Sudan and Nepal
filed adm1 with null counts); on the fixed one it passes.
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.
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.
On phones (up to 767px) the "What can this map do?" panel opened as a full-screen page: a few
lines of text at the top of an empty white screen, reading as a new page rather than a panel.
It is now a bottom sheet:
- it sizes to its content, up to 70% of the small viewport (svh, so a collapsing URL bar does not
resize it), and scrolls inside, with its scroll kept to itself;
- the close button is 44px and stays in view in the sheet's header;
- the dimmed map above it stays visible, and a tap there closes the panel, as Escape does.
Desktop is unchanged. Rebuilt doxa-maps-spa (the only bundle that compiles MapInfo.vue), then
tree + SRI.
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.
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.
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.
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.
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.
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.
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).
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.
Three rules for work that spans plugins, in section 3 (Reuse library/ first):
- A component that could serve more than one plugin is built once in library/,
driven by props, and landed through the build owner, so other plugins adopt it
instead of copying it. This replaces "Graduate late".
- A new pattern starts on one plugin, built by its owner. A maintainer approves it on
the dev server, and only then do the other owners adopt it in their own plugins.
- Map controls are round and spaced: MapToolbar's default column of round
MapControlButtons, not its grouped square card. Dashboard header-bar buttons are
rounded squares with the vertical tabs' radius, var(--vtl-radius, 8px).
- pointerKeep maps (Prayer Progress only) point ONCE PER PAGE LOAD: the pointer shows the first time
the map is on screen in a load. Opening the popup hides it for the rest of that load, and the next
full load shows it again. The memory is a module-scope Set (pointerDoneThisLoad), because the gallery
remounts a heavy tab's MapInfo on every switch back. It is marked when the pointer is on screen, not
on mount. Nothing about it is persisted any more (no seen write in show() or hide()).
- The keep box (still stored, per map) keeps the pointer after the popup closes and on every
appearance. A page-load pointer ignores the every-map off switch, which is not offered beside it.
- The label is "Keep tooltip turned on" (en), with de retuned to match; the other nine already say
"keep ... on".
- Rebuilt doxa-simple-map and doxa-maps-spa, then tree and fix:sri.
The floating ☰ circle sat in the bottom-left corner of whatever map was open.
It covered Simple Research's search box and the licence credit on Cities and
Gospel Resources (in Arabic, mirrored, the credit's other end). On a phone
(narrow and headerless, the circle's own condition) the ☰ now sits in a 44px
bar above the pane, beside the open view's name. The layout no longer
publishes a fab clearance, so the maps' bottom corners return to their plain
insets. Desktop and header-on layouts are unchanged.
In the detail view a thumb on the content resized the sheet whenever the
content could not scroll that way, so reading the list moved the sheet. Only
the handle (the drag strip and the title row) starts a resize now; below it
the content scrolls, in every mode.
The sheet's list gets overscroll-behavior: contain (the list only, not the
pane), so a fling that reaches its end stops there instead of scrolling the
page behind the map.
The open state was a fixed 30% of the map: a three-row legend sat in a mostly
empty sheet, and a long tree scrolled inside one. At rest in the open state the
sheet now takes its content's height, capped at 45% of the map. A drag still
sets an explicit height; the collapsed and fully open states are unchanged. The
detail view's mini image follows the open sheet's real top edge.
The start state was a race. Every map's own mount code means collapsed on a
phone, but uiStore.init() measures the breakpoint in a ResizeObserver whose
first callback runs after mount, so Simple Research read the 'desktop' default
and started open on every load at 390, while Prayer Progress started
collapsed. The sheet now applies the collapsed start state itself, once, the
first time it sees the phone breakpoint, unless a people group is selected.
The open sheet covered the licence credit and the Mapbox wordmark (measured at
390 on Simple Research: the top element at both was the sheet's content). The
bottom corners now stay at the map's bottom edge and the sheet sits above a
strip measured from the real boxes of both engines' bottom controls.
The approximate-location caption sits in that strip, under the sheet. It shows
once whether the sheet is open or collapsed (it was 0 when collapsed), and the
pin-hint pill, which lifts itself clear of the sheet's top, never lands on it.