A host may move the engine's credit to the TOP of the map (a phone layout with the sheet flush at the
bottom). creditFloor() would then have used a credit near y=10 as the floor, and syncAttribClearance()
would have reserved the whole map height (capped at 120px), so the pill lifted itself for nothing. Now
a credit in the upper half of the pill's layout parent is not counted: no floor, and the 20px default
clearance. Every credit is at the bottom today, so this changes nothing yet.
Every bundle rebuilt (PinHint ships through ProfileLoader), composites after their children, then the
tree and SRI.
This reverts d83c89f28. On a phone the gallery's menu is again the round ☰
button in the pane's bottom corner (bottom-left; bottom-right in Arabic), with
no bar above the pane. While it shows, the layout again publishes
--map-fab-clearance and --map-fab-beside, and the maps keep their credits,
wordmarks, pin captions and search boxes clear of the button through them.
- It is dismissed when the reader taps a pin, the gesture it teaches. The signals are
uiStore.selectedPeopleGroup (the maps that select through uiStore.selectPeopleGroup) or a
'dxmaps:pin-selected' event inside the map's root (a map with its own selection). It was left
standing over the detail card that the tap had opened. A pan still does not dismiss it.
- Wide, the pill reached the gallery's round menu button (bottom-left). Its bottom now takes the
larger of the credit clearance and --map-fab-clearance, which the layout publishes only while the
button is rendered.
- Every bundle rebuilt (PinHint ships through ProfileLoader), composites after their children, then
the tree and SRI.
Every poster, prayer list and card deck printed from a hidden iframe (openPrintFrame), and phone browsers
do not honour a print() inside a subframe: no print dialog ever opened on a phone. printDocument() keeps
that iframe path unchanged on desktop; on a phone or tablet it mounts the document into the page under a
print-only stylesheet that hides everything else, and calls the page's own window.print(). When preparing
the document outlasted the tap's activation, the wall's progress dialog shows a Print button so the print
comes from a fresh tap. The poster wall (Regions, UPG-100, Country Posters) is the first user.
- left:50% alone capped the absolutely placed pill at HALF the map, about 195px at 390, so the
sentence broke into four lines. width:max-content, with max-width 16px short of each edge, lets it
take the width it needs: at most two lines on a phone, never touching the sides.
- z-index 2147482000 -> 12. In the gallery, plugin content shares one stacking context with
VerticalTabsLayout's chrome (the menu button at 15, the drawer at 20, and 1000 while it is open on
a phone), so the pill painted over the open drawer. 12 keeps it above the map's own base layers
(canvas, scroll edges 2, control column 10) and below that chrome.
- The pill's lines are centred.
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.
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.
- 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.
- 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.
A v-show-hidden button is still a child, so it took the square toolbar's
:last-child corners (MapToolbar controls="square") from the last button a
reader can see, on every page where no host answers (a standalone leaf, a
partner page). Found by exp reading the bridge.
The control now asks from the toolbar's own element (MapToolbar provides it
as mapToolbar.el), or from its placeholder's parent, and renders its button
only once a host has answered (v-if). The rendered button then announces
itself, so the host measures and focuses the real element.
Cities with Internet's and Gospel Resources' fold buttons translated "key" literally: "Clave" and
"المفتاح" name a key for a lock. They now use each language's word for a map legend, the one the
maps already use elsewhere: "Leyenda" and "وسيلة الإيضاح".
Rebuilds cities-internet and gospel-resources, with their pages and the two manifests.
The "what can this map do for you?" button can now live IN a map's control
column instead of floating over it. This is the bridge only: nothing changes
until a map adds the control and its gallery tab sets trigger: 'control'.
- map-controls/MapInfoButton.vue (new): a MapControlButton with the info
icon and the localized mapInfo.open label. Clicking it sends a bubbling,
composed 'dxmaps:mapinfo-open' event out of the plugin's shadow root to
the gallery pane. On mount it asks 'dxmaps:mapinfo-control' and shows
ONLY if a MapInfo on the same pane answers (ack), so a standalone leaf or
a partner page never shows a button that opens nothing. A MapInfo that
mounts later broadcasts 'dxmaps:mapinfo-host' and the button asks again.
- info/MapInfo.vue: new prop trigger, 'floating' (default, unchanged) |
'control'. In control mode it listens on its own pane: it acts only on
events whose composedPath contains the pane, and stops them there. Its own
floating button and pointer hide ONLY while the announced control is
MEASURED usable: connected, a non-zero on-screen rect, and in no
inert/aria-hidden subtree. It is re-measured by IntersectionObserver and
ResizeObserver plus a 1.5 s fallback timer, never inferred from an unmount
event. Escape and close return focus to the control when it is on screen,
else to the floating button. aria-expanded is kept on the control.
Proved on a UPG-100 pilot served locally (gallery page, 1440, MapLibre and
Mapbox, en and ar), exactly one info button on screen at every step:
wall 1 floating; Map 1 control (it wins its hit test and sits inside the
toolbar column); opened from the control, Escape closes it with focus back
on the control; toolbar hidden 1 floating, shown again 1 control; tab away
and back 1; back to the wall 1 floating. 0 page errors. That pilot's wiring
is not in this commit.
The SPARSE series line rounds its total to tens -- 173 reads as ~170, 28 as ~30
-- because a series that large is read as a size, not counted. Below ten the
rounding breaks: China is the only 3x4 country sheet, so its tier has one member,
and Math.round(1 / 10) * 10 = 0 printed "1 of ~0 maps".
Under ten the line now states the exact count, "map 1 of 1" -- the same text
the dense layout already prints for China, so the move to SPARSE is not a
regression for it on the print.
PROVED: China rendered SPARSE reads "— map 1 of 1" with 0 occurrences of "~0";
and Sudan rendered after this change is BYTE-IDENTICAL to Sudan rendered before
it, so no series of ten or more changes -- the 173 country sheets and the
UPG-100 zones are unaffected.
Not decided here: China's thumbnail says 174 of 174 and its print 1 of 1. That
is per-tier against whole-series numbering, the pre-existing R-CHN-SERIES-TIER,
left for a product decision after the demo.
poster-render.js is sha384-pinned; re-pinned so check:poster-render-pinned
accepts it.
The bold header on a poster -- the UPG count and population set large, a
bigger map, and no "Jesus' Strategy to Feed the 5000" / "What is a Prayer List"
columns -- is the SPARSE layout, the "thumbnail-worthy" design. The `posters`
script generates with SPARSE=1, so every stored thumbnail has it. But the env
handed to the generator in the browser carried only FORMAT and POSTER_DATE, so
EVERY sheet drawn at the click -- UPG-100 and country alike -- fell back to the
dense layout.
MEASURED on FINAL (two UPG-100 zones, Northwest and South-Central India): the
thumbnail shows the SPARSE header; the print the reader actually gets shows the
small facts table, both vision columns and a smaller map. That is the reported
"the country poster looks worse than the UPG-100 poster" -- the comparison was a
SPARSE thumbnail against a dense sheet.
(a) posterSheets.js passes SPARSE for every POSTER format and NOT for 'packet'.
That is exactly the split the two build scripts make: `posters` sets
SPARSE=1, `packets` does not. The renderer's formats are 3x4, 3x3, letter and
packet, so 'packet' alone keeps the full layout its pages were built around.
(b) check:poster-browser-equiv's poster case now runs the Node side with
SPARSE=1 as well. It used to compare dense with dense, so it passed while no
reader ever saw that layout on a card. The packet case stays dense on both
sides. PASS: country-npl 909026 bytes on each; asia-india-gujarat (packet)
835047 on each.
The stored UPG-100 thumbnails are untouched by this. What changes is the PRINT,
which now matches them.
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.
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.
LegendMobile already shows "The dot marks where this people is found, not a town
or a boundary." once, floating above the sheet, and placed so that it takes no room
from the options inside. The SemanticTreeLegend it renders inside the sheet printed
the same sentence again, so with the sheet open a phone reader saw it twice.
Measured on Prayer Progress at 390 on the shared browser, with the sheet opened
through its own toggle (pp-caption.mjs):
- before, on development as served by DX Life: 2 visible copies on / and on the
Deep Research tab, the floating note plus the tree's note inside the sheet;
- after, Prayer Progress rebuilt locally with this change: 1 visible copy, the
floating note, in en and ar;
- desktop, 1440: still 1 copy, the desktop legend's own tree, unchanged.
The sheet's tree now gets :pin-note="false". The detail view keeps its own note,
which carries the shared-point wording.
Library only. Every bundle that renders LegendMobile needs a rebuild to ship it.
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.
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.
PinHint's first-visit pill sits at z-index 2147482000, so with MapInfo's scrim at 1000 the pill floated
bright and clickable over the dimmed backdrop while the popup was open (gospel; measured on the live
build: the pill's centre hit the pill, not the scrim, on Cities). The scrim goes to 2147483000; the
dialog is its child, inside the scrim's stacking context, so it stays on top of the scrim. The root fix
is the top layer (<dialog>.showModal()), carded.
The pill's clearance above the licence credit is measured in syncAttribClearance,
and that runs again only when the credit RESIZES. On a phone the credit is also
MOVED, lifted above the legend bar, without resizing. The clearance then stays at a
stale value, and the pill covers the credit. MEASURED at 390, on the shared
browser: on DX Life's home page the pill covered 36px of the 37px credit with the
clearance at 43px; on the leaf in ar, 36px.
clampIntoView now takes the credit's measured top as a floor, next to the fold and
the phone legend sheet. It already runs from the settle timers (1.2, 3 and 6 s)
and from the sheet and host observers, so it reads where the credit is, whatever
the clearance says.
Measured on a served tree, Prayer Progress rebuilt with this and the 120px cap,
at 390: the leaf and the gallery tab, in en and ar.
- before (development's tip): overlaps of 7, 36, 7 and 7px;
- after: 0px in all 4, with the pill's bottom 13px above the credit's top.
Library only. Every ProfileLoader consumer needs a rebuild to ship it.
The clearance is the strip from the credit's top to the box's bottom edge, capped
so a mis-measure cannot throw the pill up the map. The cap was 72px. On a 390px
phone MapLibre's credit wraps to two lines, and on the leaf it sits above the
bottom bar, 90px up. With the selector now finding it, the clearance hit the cap
and the pill still overlapped the credit by 6px, in en and ar (was 35px). The cap
becomes 120px: the measured need fits, and a runaway is still bounded.
Library only. Every ProfileLoader consumer needs a rebuild to ship it.