GeoLeaf Core API - v3.19.0
    Preparing search index...

    Interface GeoLeafRawEventMap

    Map of the GeoLeaf DOM events that carry a live HTMLElement and are therefore dispatched as raw CustomEvents, never through dispatchGeoLeafEvent.

    dispatchGeoLeafEvent sanitises through JSON.parse(JSON.stringify(detail)). Put geoleaf:toolbar:action in the sanitised map and dispatchGeoLeafEvent("geoleaf:toolbar:action", { action, element }) becomes type-legal and runtime-wrong: element arrives at every listener as {}. The type system would be actively inviting the bug that two files in this repo already go out of their way to warn about — kernel/ui/desktop/desktop-tabs-seam.ts and kernel/layer-manager/item-controls-seam.ts both describe themselves as mirroring the existing geoleaf:toolbar:action seam for precisely this reason.

    Splitting the map keeps both halves honest: listening is typed for everyone (the Events facade accepts keys of both maps), and emission stays impossible to get wrong.

    ⚠ Adding a key here is a statement that at least one field of the payload cannot survive JSON.parse(JSON.stringify(…)). If every field can, it belongs in GeoLeafEventMap, where it also gets a typed emitter.

    ⚠️ This sentence read "the payload cannot be JSON-cloned" until 14/08/2026, and geoleaf:popup:action is the first key that makes the difference matter: five of its eight fields are plain JSON, three are not (button, setBusy, close). The all-or-nothing wording would have argued for splitting one event into two — a JSON half and a DOM half — which is exactly the shape the CDC asked us not to build. The criterion is the field, not the payload.

    GeoLeafEventMap — the sanitised bus.

    interface GeoLeafRawEventMap {
        "geoleaf:layer-manager:panel": {
            container: HTMLElement;
            mainWrapper: HTMLElement;
            headerWrapper: HTMLElement;
        };
        "geoleaf:toolbar:action": { action: string; element: HTMLElement };
        "geoleaf:popup:action": GeoLeafPopupActionDetail;
        "geoleaf:boot:aborted": { reason: unknown };
    }
    Index
    "geoleaf:layer-manager:panel": {
        container: HTMLElement;
        mainWrapper: HTMLElement;
        headerWrapper: HTMLElement;
    }

    The layer-manager panel seam. Emitted by kernel/layer-manager/control.ts once the panel structure is built, so capabilities (e.g. profile-switcher) can insert their own controls without the kernel importing them.

    Lives HERE, not in GeoLeafEventMap: the detail carries live DOM nodes, which the sanitising bus (JSON-only) would destroy.

    ⚠️ Fires again on every panel rebuild (destroy → recreate): subscribers must be idempotent.

    "geoleaf:toolbar:action": { action: string; element: HTMLElement }

    The plugin ↔ toolbar seam. Emitted by kernel/ui/toolbar-dispatch.ts (mobile pill, desktop tab strip).

    ⚠️ This seam is one-way, and the direction matters. Plugins and capabilities LISTEN; they do not dispatch. The kernel-side helper dispatchToolbarAction(action, element) is the entry point for the kernel's own emitters — it is deliberately NOT exported: absent from kernel/ui/index.ts, from kernel-exports.ts, from the global namespace, and from every published exports subpath. Reaching it from capabilities/** is refused by the R.8 boundary (eslint.config.mjs), and widening the barrel to reach it would drag components.js + pill-search.js + theme.js into the importer's bundle closure. That trade was measured, then declined, and the decision is recorded as accepted debt in the workshop's debt register — do not re-litigate it from this line.

    ⚠️ This comment called that helper "the canonical entry point" until 09/08/2026, with no qualifier. The sentence was true for the kernel and false for every other reader of this contract — who cannot reach it at all. A contract that names an entry point its audience cannot call sends that audience looking for an export that was withheld on purpose.

    element is the button that was clicked — plugins read it to anchor a floating menu (editor, measure, geocoding all do). Both fields are always set by the emitter, so neither is optional here even though several listeners historically declared them so.

    "geoleaf:popup:action": GeoLeafPopupActionDetail

    The popup/side-panel action seam. Emitted by capabilities/feature-info/render/widget-dispatch.ts when a user clicks a button declared as type: "action" in popup.fields[].

    Moved here from GeoLeafEventMap on 14/08/2026, when the detail gained button, setBusy() and close() — the three members GeoLeaf.Popup.registerActionHandler used to provide before ADR-07 removed it. Through the sanitising bus, button arrived as {} and the two functions as undefined, silently: measured on the real path before the move, not reasoned about.

    ⚠️ Subscribers are unaffected — the Events facade accepts keys of both maps. What the move buys is that dispatchGeoLeafEvent("geoleaf:popup:action", …) no longer compiles.

    GeoLeafPopupActionDetail — the payload, and the confidentiality rule on properties.

    "geoleaf:boot:aborted": { reason: unknown }

    The host ended the boot — it is not a failure. Three causes:

    • the beforeBoot hook threw (typically an authentication gate): reason is whatever it threw;
    • GeoLeaf.Core.destroy() removed the boot's map while the boot was still running (a component unmounted right after mounting): reason is "destroyed";
    • the application was unmounted while it was still starting — unmount() on the handle GeoLeaf.mount() returned, or a later mount() taking over: reason is "unmounted" (since 3.13.0).

    Emitted by app/boot-core.ts and app/mount.ts, which then hide the loading veil and draw no failure screen: that interface belongs to the host.

    Lives HERE because reason may be whatever the hook threw — usually an Error, which the sanitising bus would flatten to {}.