doxa-maps/library
Doxa Maps a79a92d70b One shared print busy state, and the dashboard waits for its map before printing
Driver test round 2, #2 — the half the @media print rules could not do. The
complaint was "no loading indicator on a click": the reader presses Print, the
dialog takes a moment to rise, and nothing says anything is happening. It is
worse on this page than most, because what the dialog waits for is a MAP —
press it a moment early and the sheet carries a grey rectangle.

printInPlace.js gains printWhenReady({ waitFor, onBusy, timeoutMs }). This
pattern already exists five times in the repo (upg-100-list c54a11d5a, four of
exp's 786a83461, PeopleGroupsWall.printWall); they agree on the easy half —
set a flag, print, clear it — and each invents the hard half, which is what
"ready" means and what happens when ready never comes. The hard half is the one
that strands a reader, so it is the half that graduates:

  · THE TIMEOUT PRINTS. It does not reject and does not cancel. A reader who
    clicked Print and waited has asked for paper; a slow tile server is a reason
    to print a plainer map, not a reason to produce nothing.
  · allSettled, not all — one waiter that rejects must not cost the print.
  · onBusy clears in a finally, on every path: a rejected wait, a throwing
    print, a host with no window.print. A busy state that outlives its own
    operation makes the page look broken rather than slow.

RegionCountryMap answers when it is done, and map.loaded() is not that answer —
it reports true while tiles still stream, which is how gospel's charts printed
blank twice today. The condition is: style applied, camera stopped, tiles for
this camera in. It checks synchronously FIRST and only then subscribes to
'idle', because this component's own header already records that once('load')
measured unreliable in both directions here — an event that fired before you
listened never arrives. Always resolves, always removes its listener.

The button is quiet, sits under the title, and is hidden on paper. Busy state
carries aria-busy and :disabled, not just a changed label: the screen reader is
told, and disabled is what actually stops a second print. Both strings are in
all 11 locales; "Print" was already translated 23 times elsewhere in the tree
under other keys, so this adds the key the dashboard's own catalogue lacked.

Mutation-tested, since the failure this guards is the path nobody takes: a
waiter that never settles returns "timeout" after the ceiling and prints anyway
(201ms measured on a 200ms ceiling), a rejecting waiter still prints, and the
busy flag returns to false in all three.
2026-09-28 21:59:29 +00:00
..
api The people-group list is a universal prop: the same maps can show any project's list 2026-09-26 05:16:02 +00:00
charts The language tree becomes anyone's, and the numbers nobody wrote down get written down 2026-09-28 17:40:34 +00:00
colors The legends mirror now, and their labels never became their values 2026-09-26 22:06:58 +00:00
components The mobile legend's toggles announce their state 2026-09-28 21:59:28 +00:00
composables One shared print busy state, and the dashboard waits for its map before printing 2026-09-28 21:59:29 +00:00
constants Every runtime CDN script and stylesheet is an exact version with an integrity hash 2026-09-26 05:50:24 +00:00
data Country names in the reader's language, for all 242 2026-09-27 04:14:31 +00:00
elements Twenty-four warnings about an overwrite that was the whole point 2026-09-28 17:53:38 +00:00
i18n One shared print busy state, and the dashboard waits for its map before printing 2026-09-28 21:59:29 +00:00
layouts Show a lost WebGL context instead of a blank pane 2026-09-28 21:59:28 +00:00
lists List names speak the reader's language: listLabel and listDefinition 2026-09-26 08:39:52 +00:00
maps The poster sliced its rows in degrees, and Mercator charges more for a degree up north 2026-09-28 17:53:38 +00:00
packet The legend's Export path gets the busy state the HUD already had 2026-09-28 19:41:29 +00:00
packets Number({value:"1"}) is NaN, so the whole reachedness sentence vanished 2026-09-28 15:52:47 +00:00
scripts Hold the packet's ring names to the screen's, and adopt its exact words 2026-09-28 17:53:37 +00:00
stores Make the map's clustering picker real: four options, four different maps 2026-09-25 16:16:46 +00:00
styles Fiord: the MapLibre dark that actually looks like the map it replaces 2026-09-24 02:26:14 +00:00
utils Shipped comments describe the behaviour instead of quoting a person 2026-09-28 17:40:35 +00:00
vendor/cldr Country names in the reader's language, for all 242 2026-09-27 04:14:31 +00:00
index.js Move internal files out of the repo; fix five dead links; a fuller delegation diagram 2026-09-25 05:14:10 +00:00
ProfileLoader.vue ?lang= on the page turns every embedded map's direction and data language, not only its text 2026-09-27 06:42:54 +00:00
README.md Complete app-profiles->plugin-designs sweep across comments/docs; build green; layouts kept at root (load-bearing) 2026-09-17 18:18:19 +00:00

library/ (@map) — the shared map template

The other half of the architecture (see ../plugin-designs/README.md): every map in plugin-designs/<your-map>/ composes the reusable, parameterized pieces that live here. Fix something here and every map benefits on the next build; break something here and every map breaks — read components/WARNING.md before touching shared pieces.

Folder What lives there
ProfileLoader.vue The root of every bundle: parses profile-config, loads the profile, provides the stores + config
components/ Shared UI — map, legends, controls, popup (PeopleGroupDetail), tables, charts, hints (README)
composables/ Shared logic — map engine (useMapInstance), provider switch, shadow-DOM styles, theming
stores/ Per-instance Pinia stores (ui / map / data) — provided by ProfileLoader, injected by children
api/ Data-source registry (sources.json) + fetch layer — the JAMstack runtime wiring
colors/ Color sets maps opt into via profile-config.colorSet
i18n/ The shared string catalogs (all maps speak the same languages)
data/ Small shared datasets baked at build time (big served data lives in ../data/)
constants/ Shared constants
scripts/ Maintainer pipelines (posters, research) — not part of any bundle

The rule of thumb (same as WARNING.md): custom code in your app-profile, reusable code here — and things only move from the first to the second once they've earned it.