The boot could not complete. Emitted by app/boot-failure.ts, the first terminal
failure winning. A watchdog expiry is the one PROVISIONAL case: a late reveal may still
follow it, and geoleaf:app:ready is then the recovery signal.
⚠️ Dispatched CANCELABLE: a host that renders its own failure interface calls
preventDefault(), and the default screen is not drawn in #gl-loader. The payload is
JSON-safe, which is what places the key in this map. Subscribe BEFORE GeoLeaf.boot().
reason: "engine" (since 3.13.0): no MapLibre GL JS engine was found when the map was
built — neither globalThis.maplibregl nor the maplibregl boot option. A switch over
this union that was exhaustive before needs the new case.
English, meant for logs and support — not a user-facing string.
Optionalmodule?: stringtrue only for reason: "timeout": the boot may still complete.
Resources the active profile DECLARES could not be obtained — an HTTP error, invalid
JSON, or a declared section absent from the profile bundle. A file the profile does not
declare is never reported. Emitted before geoleaf:profile:loaded; fatal: true means
profile.json itself failed, and geoleaf:profile:loaded does not follow.
⚠️ Cancelable, like geoleaf:boot:failed: preventDefault() suppresses the default
screen that names the failed resources, and the reveal then proceeds untouched.
A module's init() threw during the boot, and the application goes on without it and
without the modules that depend on it (skipped). Emitted by app/boot-failure.ts. A
failure the interface cannot survive is not this event: it is geoleaf:boot:failed with
reason: "module".
⚠️ Cancelable: preventDefault() suppresses the default signal — the screen that holds the
reveal and names the module, or the warning once the application is revealed.
English, meant for logs and support — not a user-facing string.
The modules that did not run because they depend on it, in initialisation order.
Emitted once when the legend control is first mounted on the map (S10 F2).
Emitted when the accent palette changes (capability theme-palette).
Distinct from geoleaf:theme:applied (MAP themes) and from
geoleaf:ui-theme-changed (light/dark) — the three axes are orthogonal.
A layer's store changed — see GeoLeafLayerUpdatedDetail for what does and does not fire it.
A layer's visibility changed — HISTORICAL form.
⚠️ geoleaf:layer:toggle is the CANONICAL event, and it carries the same payload.
This one predates it, has internal subscribers (permalink-sync.ts, the table
plugin) and a declared downstream consumer — so it is typed for what it is, not
promoted. An integrator writing new code takes geoleaf:layer:toggle.
🛑 The two buses do not carry the same thing, and that is measured, not assumed.
visibility-manager.ts fires on the map on EVERY change, but re-dispatches on
document only when source !== "zoom" — a deliberate filter, to avoid flooding
subscribers during a zoom recalculation. A subscriber via Events.on (thus
document) will therefore never see source: "zoom", where map.on does.
The type still keeps the full union: it is exact on the map, and only too wide on
the document. The opposite — narrowing to three values — would become FALSE at the
first loosening of the filter, and silently so, an integrator's exhaustive switch
ceasing to cover a case it receives.
The tabbed side panel just opened a tab.
Emitted on BOTH paths — the tab click and the programmatic call
GeoLeaf.UI.openPanel(tabId) — because an event describing only the programmatic
path would be half a contract: users open with the mouse.
The tabbed side panel just closed a tab.
⚠️ A tab switch yields closed THEN opened, in that order: the old tab is
really closed. Opening while nothing was open yields only opened — there is no
empty closed.
A filter pass has just run — because the filter was applied, or because a layer was written under an active filter: see GeoLeafFiltersAppliedDetail for the two reasons, and for the loop a listener must not close.
A new service worker ACTIVATED: it now serves the pages of its scope.
⚠️ Not "an update is available" — see geoleaf:sw:update-waiting for that. This one
also fires at a first install, when the first worker takes the page.
A new version of the application is installed and WAITS: its service worker will not
take a page that is in use until GeoLeaf.PWA.applyUpdate() is called, or until every
tab it serves is closed.
Emitted once per waiting worker — when its install completes, or when a page boots
onto one that was already waiting. Never at a first install. ⚠️ A listener set after
the boot may have missed it: read GeoLeaf.PWA.isUpdateWaiting() as well.
The session was ended deliberately — Connector.logout().
⚠️ Its counterpart :auth-error says the session DIED; this one says it was
closed. A host that clears its own view on sign-out must be able to tell the two
apart: one calls for a login window, the other precisely does not.
A sign-in was refused or could not conclude — no session was opened.
⚠️ Cancelable — preventDefault() prevents the navigation to url.
⚠️ Cancelable — preventDefault() prevents the navigation to url.
Map of the GeoLeaf custom DOM events whose payload is JSON-serialisable.
The membership criterion is the PAYLOAD, not the carrier — that is what makes this map's boundary a real one and not a stylistic split.
dispatchGeoLeafEventdeep-clones every payload throughJSON.parse(JSON.stringify(detail))(kernel/events/event-bus.ts); a detail holding a DOM node, a function or aMapdoes not survive that round-trip, and belongs in GeoLeafRawEventMap instead. The two maps state one rule from both ends.The
Eventsfacade subscribes to both maps (kernel/events/facade.ts); only emission through the sanitising bus is restricted to this one.⚠️ Being listed here does NOT mean the event travels through that bus — it means it COULD. This sentence used to read « events carried by the sanitising bus », which was narrower than the rule the two maps actually enforce, and it had no answer for the nine
geoleaf:editor:*events: their payloads clone perfectly, but the editor dispatches them as rawCustomEvents anddispatchGeoLeafEventis exported to no plugin. Reading the criterion as the carrier would have pushed them into the raw map, whose own warning — « adding a key here is a statement that the payload CANNOT be JSON-cloned » — they contradict. Arbitré par Mattieu : the payload decides, and the carrier is a separate question this contract deliberately does not type.