@ngis/plugin-sdk 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/LICENSE +13 -0
  2. package/README.md +31 -0
  3. package/dist/index.cjs +76 -0
  4. package/dist/index.d.ts +77 -0
  5. package/dist/index.mjs +102 -0
  6. package/dist/src/api/analysis.d.ts +140 -0
  7. package/dist/src/api/resources.d.ts +530 -0
  8. package/dist/src/api/scenes.d.ts +251 -0
  9. package/dist/src/api/users.d.ts +22 -0
  10. package/dist/src/domain/algorithms/deck-layer-style.d.ts +34 -0
  11. package/dist/src/domain/algorithms/maplibre-style.d.ts +63 -0
  12. package/dist/src/domain/algorithms/raster-color-ramps.d.ts +37 -0
  13. package/dist/src/domain/algorithms/scene-layer-type.d.ts +25 -0
  14. package/dist/src/domain/algorithms/symbol-compile.d.ts +100 -0
  15. package/dist/src/domain/algorithms/symbol-params.d.ts +90 -0
  16. package/dist/src/domain/map/bounds.d.ts +8 -0
  17. package/dist/src/domain/map/constants.d.ts +40 -0
  18. package/dist/src/domain/map/layer-builders.d.ts +32 -0
  19. package/dist/src/domain/map/rendered-layers.d.ts +30 -0
  20. package/dist/src/domain/map/style-ready.d.ts +24 -0
  21. package/dist/src/domain/map/tile-url.d.ts +20 -0
  22. package/dist/src/domain/map/types.d.ts +128 -0
  23. package/dist/src/domain/models/deck-layer-style.d.ts +211 -0
  24. package/dist/src/domain/models/scene-layer-type.d.ts +23 -0
  25. package/dist/src/domain/models/symbol.d.ts +424 -0
  26. package/dist/src/lib/api.d.ts +82 -0
  27. package/dist/src/lib/auth-session.d.ts +22 -0
  28. package/dist/src/lib/raster-color-layer/RasterColorLayer.d.ts +48 -0
  29. package/dist/src/lib/raster-color-layer/TileGrid.d.ts +31 -0
  30. package/dist/src/lib/raster-color-layer/colorRamp.d.ts +18 -0
  31. package/dist/src/lib/raster-color-layer/shaders/raster-color.frag.d.ts +2 -0
  32. package/dist/src/lib/raster-color-layer/shaders/raster-color.vert.d.ts +2 -0
  33. package/dist/src/lib/raster-color-layer/zoomInterpolate.d.ts +2 -0
  34. package/dist/src/sdk/commands.d.ts +113 -0
  35. package/dist/src/sdk/context/keys.d.ts +148 -0
  36. package/dist/src/sdk/context/publishers.d.ts +198 -0
  37. package/dist/src/sdk/context/store.d.ts +78 -0
  38. package/dist/src/sdk/context/when.d.ts +69 -0
  39. package/dist/src/sdk/contributions/dispatch.d.ts +78 -0
  40. package/dist/src/sdk/contributions/panel-id.d.ts +25 -0
  41. package/dist/src/sdk/contributions/registry.d.ts +199 -0
  42. package/dist/src/sdk/contributions/symbol-renderers.d.ts +107 -0
  43. package/dist/src/sdk/contributions/types.d.ts +288 -0
  44. package/dist/src/sdk/contributions/views-bridge.d.ts +92 -0
  45. package/dist/src/sdk/define-plugin.d.ts +17 -0
  46. package/dist/src/sdk/facets/storage.d.ts +152 -0
  47. package/dist/src/sdk/facets/types.d.ts +370 -0
  48. package/dist/src/sdk/host-api.d.ts +256 -0
  49. package/dist/src/sdk/map-api.d.ts +288 -0
  50. package/dist/src/sdk/panels.d.ts +130 -0
  51. package/dist/src/sdk/plugin.d.ts +145 -0
  52. package/dist/src/sdk/rail-tools.d.ts +58 -0
  53. package/dist/src/sdk/scopes.d.ts +158 -0
  54. package/dist/src/systems/renderer/DeckSceneRenderer.d.ts +26 -0
  55. package/dist/src/systems/renderer/FlowFieldLayer.d.ts +68 -0
  56. package/dist/src/systems/renderer/LayerRendererRegistry.d.ts +132 -0
  57. package/dist/src/systems/renderer/authenticated-deck-load.d.ts +4 -0
  58. package/dist/src/systems/renderer/deck/deck-layer-builders.d.ts +29 -0
  59. package/dist/src/systems/renderer/deck/deck-layer-helpers.d.ts +21 -0
  60. package/dist/src/systems/renderer/deck/mvt-point-aggregation-layer.d.ts +107 -0
  61. package/dist/src/systems/renderer/flow-field/flow-field-resources.d.ts +84 -0
  62. package/dist/src/systems/renderer/flow-field/flow-field-shaders.d.ts +4 -0
  63. package/dist/src/systems/renderer/symbol/SpriteRegistry.d.ts +34 -0
  64. package/dist/src/systems/renderer/symbol/entries.d.ts +67 -0
  65. package/dist/src/types/analysis.d.ts +205 -0
  66. package/dist/src/types/common.d.ts +17 -0
  67. package/dist/src/types/extensions.d.ts +560 -0
  68. package/dist/src/types/gis.d.ts +242 -0
  69. package/dist/src/types/market-social.d.ts +378 -0
  70. package/package.json +47 -0
@@ -0,0 +1,288 @@
1
+ /**
2
+ * `NgisMapAPI` — the typed facade a plugin programs against (L3 SDK contract).
3
+ *
4
+ * This is the "GeoLibre `GeoLibreAppAPI`" equivalent (see
5
+ * `../../GEOLIBRE_VS_NGIS.md` §3, one level up from the repo). It is a **binding
6
+ * layer, not a fourth subsystem**: every method delegates to something that
7
+ * already exists —
8
+ *
9
+ * - the scene controllers' imperative surface (draw / measure / camera),
10
+ * reached today through the composed `GisMapHandle` the shell exposes
11
+ * (which itself reads the *live* scene-map-context method registry
12
+ * `SceneMapHandleRegistry` — see `components/scenes/map/scene-map-context.ts`,
13
+ * C2/C4/C5 — so the facade never captures a stale controller instance);
14
+ * - the GIS scene store (`store/gis-scene.ts`) — the `layers` facet;
15
+ * - the `WorkspacePanelRegistry` (`sdk/panels.ts`, C7) — the `panels` facet;
16
+ * - workspace-owned highlight + view state — the `selection.highlight` and
17
+ * `camera` facets.
18
+ *
19
+ * The facade introduces **zero new direct `maplibregl.Map` access**. The one
20
+ * raw touch it exposes — `getMap()` — is a deliberately-unstable escape hatch
21
+ * that stays `null` until the host wires a raw-map getter into it (the one-line
22
+ * `SceneMapCanvas.tsx` provider hookup applied after C5 lands; see
23
+ * `docs/componentization_stage/session-C8-map-api-facade.md`).
24
+ *
25
+ * GeoLibre's proven decisions, adopted verbatim in shape:
26
+ * 1. **Every subscription returns an unsubscribe function** (`onX(cb) => () => void`).
27
+ * 2. **Newer / not-yet-proven methods are optional-typed** (`api.x?.()`) so
28
+ * plugin code written against v0 keeps compiling as the surface grows.
29
+ * As of 0.2.0 (session P3, plugin-runtime stage) every method is either
30
+ * **real and required** or was deleted; the lone remaining optional member
31
+ * is `draw.snapshot` (see its doc for the stated reason).
32
+ * 3. **A raw-MapLibre escape hatch** (`getMap()`), documented as unstable.
33
+ *
34
+ * Framework-agnostic on purpose: this module imports no React and only
35
+ * type-imports its domain types, so the pure binding/bookkeeping logic is
36
+ * unit-tested directly with `node:test` (`map-api.test.ts`).
37
+ */
38
+ import type { Map as MapLibreMap } from "maplibre-gl";
39
+ import type { SceneLayerHighlight, SceneMapView } from "../domain/map/types";
40
+ import type { GisDrawMode, GisMeasureKind } from "../types/gis";
41
+ import type { WorkspacePanelRegistry } from "./panels";
42
+ /** Current facade contract version — bumped when the frozen surface changes.
43
+ * A plugin's `minNgisVersion` (`docs/frontend-map-sdk.md` §3) is
44
+ * checked against this. Pre-1.0: the surface may still grow (additively).
45
+ *
46
+ * 0.2.0 (plugin-runtime stage, P3): promoted `camera.flyTo`/`fitBounds`,
47
+ * `layers.onChange` and `draw.onDrawChange` to required; deleted the never-
48
+ * implemented `selection.get`/`clear`/`onChange` (+ the `"selectionchange"`
49
+ * event) per decision D10; bound `draw.snapshot` through the imperative
50
+ * handle (kept optional — see its doc). See `docs/frontend-map-sdk.md` §2.
51
+ *
52
+ * 0.3.0 (Stage 11 E7, design doc §19, ED19): added the `commands` capability
53
+ * (`src/sdk/commands.ts`) — a plugin-declared, externally-invocable seam the
54
+ * codeenv SDK bridge's plugin-invocation executor calls by id. Contract-safe
55
+ * in both directions (the runtime's unknown-capability refusal covers an old
56
+ * runtime seeing a `commands`-declaring plugin; `minNgisVersion` covers a new
57
+ * plugin requiring a runtime this old). The bump lands atomically with this
58
+ * capability's only consumer, per ED19 — never staged ahead of it.
59
+ *
60
+ * 0.4.0 (Stage 13, X4b; F13.13 + ED19): the **`NgisHostAPI` capability
61
+ * contract**. `NgisPluginHost` widens from `{ api }` to the full nine-facet
62
+ * facade (`src/sdk/host-api.ts`), the runtime's first-wins `register` gains an
63
+ * `unregister`/`replace` seam (F13.8), and `contributes` v1 + derived
64
+ * activation events land beside `capabilities`. **`host.api` — this whole
65
+ * module — is unchanged and stays ungated**, which is the back-compat rule: a
66
+ * 0.3-era plugin declares `minNgisVersion: "0.3.0"`, negotiates identically,
67
+ * and keeps working byte-for-byte.
68
+ *
69
+ * Bumped atomically with its consumers per ED19: `plugin.ts`'s alias,
70
+ * `plugin-runtime.ts`, `GisWorkspace.tsx`'s host construction and the loader
71
+ * path all land in the same commit. Note the reverse leg of F13.13 —
72
+ * a 0.4-requiring manifest on a 0.3 host is refused by the `minNgisVersion`
73
+ * semver compare, **not** by the unknown-capability check: `contributes` and
74
+ * `permissions` are top-level manifest siblings, and a 0.3 runtime inspects
75
+ * only `manifest.capabilities`. */
76
+ export declare const NGIS_MAP_API_VERSION = "0.4.0";
77
+ /** A scene layer as the SDK exposes it — a stable projection of the store's
78
+ * `NgisSceneLayer`, so plugins never depend on the store's internal shape. */
79
+ export interface NgisLayerSummary {
80
+ id: string;
81
+ name: string;
82
+ /** Scene layer type id (e.g. `"MAPLIBRE_FILL"`); `null` when unset. */
83
+ type: string | null;
84
+ visible: boolean;
85
+ }
86
+ /** Camera state — identical shape to the scene's persisted `SceneMapView`. */
87
+ export type NgisMapView = SceneMapView;
88
+ /** Geographic bounds `[west, south, east, north]`. */
89
+ export type NgisBounds = [number, number, number, number];
90
+ /** An unsubscribe function — the return value of every `onX` subscription. */
91
+ export type NgisUnsubscribe = () => void;
92
+ /** `layers` facet — list / inspect / focus / toggle scene layers. */
93
+ export interface NgisLayersFacet {
94
+ list(): NgisLayerSummary[];
95
+ get(id: string): NgisLayerSummary | null;
96
+ /** Fit/ease the camera to a layer's extent (the layer-list "focus" action). */
97
+ focus(id: string): void;
98
+ /** Show/hide a layer (persists via the scene store, like the layer list). */
99
+ setVisibility(id: string, visible: boolean): void;
100
+ /** Notified when the effective layer set changes (add/remove/reorder/
101
+ * visibility *and* live style-draft edits — anything that replaces the
102
+ * store's `effectiveLayers` array). The host's binding equality-guards the
103
+ * subscription so listeners only fire on real changes, never on unrelated
104
+ * store updates. Required since 0.2.0. */
105
+ onChange(listener: () => void): NgisUnsubscribe;
106
+ }
107
+ /** `camera` facet — read/move the viewport + observe view changes. */
108
+ export interface NgisCameraFacet {
109
+ /** Current view, or `null` when the raw map isn't bound yet (`getMap()`). */
110
+ getView(): NgisMapView | null;
111
+ /** Subscribe to camera moves (drives view persistence). Returns unsubscribe. */
112
+ onViewChange(listener: (view: NgisMapView) => void): NgisUnsubscribe;
113
+ zoomIn(): void;
114
+ zoomOut(): void;
115
+ /** Ease back to the scene's default/initial view. */
116
+ zoomHome(): void;
117
+ /** Fly to an explicit view. Escape-hatch-backed: delegates to the raw map,
118
+ * so it is a documented **no-op while `getMap()` returns `null`** (i.e.
119
+ * until the host binds a raw-map getter — the first-party canvas does via
120
+ * `useBindNgisRawMap`). Required since 0.2.0. */
121
+ flyTo(view: NgisMapView): void;
122
+ /** Fit the camera to bounds. Escape-hatch-backed — same getMap-null no-op
123
+ * behavior as `flyTo`. Required since 0.2.0. */
124
+ fitBounds(bounds: NgisBounds): void;
125
+ }
126
+ /** Enumerated feature highlight — identical to the scene's `SceneLayerHighlight`. */
127
+ export type NgisHighlight = SceneLayerHighlight;
128
+ /** The highlight sub-facet under `selection` — set/clear the feature-state
129
+ * highlight the attribute table and layer list drive. */
130
+ export interface NgisHighlightFacet {
131
+ get(): NgisHighlight | null;
132
+ set(highlight: NgisHighlight | null): void;
133
+ clear(): void;
134
+ onChange(listener: (highlight: NgisHighlight | null) => void): NgisUnsubscribe;
135
+ }
136
+ /** `selection` facet — feature highlight only.
137
+ *
138
+ * 0.2.0 deleted the declared-but-dead drawn-feature-selection surface
139
+ * (`get?`/`clear?`/`onChange?` + the `"selectionchange"` event): nothing ever
140
+ * implemented or emitted it (decision D10 — contract-safe pre-1.0), and the
141
+ * one cheap first-party emit source (`SelectionController`'s feature-select
142
+ * enable/disable) signals an interaction-mode toggle, not a selection-set
143
+ * change. If a real Terra Draw selection source lands, the members return
144
+ * additively. */
145
+ export interface NgisSelectionFacet {
146
+ highlight: NgisHighlightFacet;
147
+ }
148
+ /** `draw` facet — Terra Draw mode + one-shot geometry actions. */
149
+ export interface NgisDrawFacet {
150
+ setMode(mode: GisDrawMode): void;
151
+ /** Leave the active mode, back to idle. */
152
+ cancelMode(): void;
153
+ getActiveMode(): string | null;
154
+ /** Remove every drawn feature. */
155
+ clear(): void;
156
+ /** Delete the currently selected drawn feature(s). */
157
+ deleteSelection(): void;
158
+ undo(): void;
159
+ redo(): void;
160
+ /** Download the drawn features as GeoJSON. */
161
+ export(): void;
162
+ /** Snapshot the raw Terra Draw features. Bound (0.2.0) through the
163
+ * imperative handle's optional `getDrawSnapshot` — returns `[]` when the
164
+ * handle is absent or doesn't expose the service.
165
+ *
166
+ * **Live since Stage 14 (F14.18).** The first-party shell's composed
167
+ * `GisMapHandle` now surfaces `getDrawSnapshot`, so in the shipping host this
168
+ * returns the real feature list rather than always `[]`. The member **stays
169
+ * optional-typed** because the 0.3 surface is frozen and a host is still
170
+ * allowed to bind a handle without the service; callers keep writing
171
+ * `snapshot?.() ?? []`. */
172
+ snapshot?(): unknown[];
173
+ /** Observe draw changes (drawn-feature/query-result changes and draw-mode
174
+ * transitions). Wired since 0.2.0: the host feeds its draw-result/draw-mode
175
+ * handlers into `NgisMapAPIHandle.emitDrawChange`. Required. */
176
+ onDrawChange(listener: () => void): NgisUnsubscribe;
177
+ }
178
+ /** `measure` facet — the ephemeral distance/area overlay. (Not in GeoLibre's
179
+ * original facet list; added because the rail's measure tools are a real v0
180
+ * consumer — measuring is a sibling Terra Draw control, never a drawn feature.) */
181
+ export interface NgisMeasureFacet {
182
+ start(kind: GisMeasureKind): void;
183
+ clear(): void;
184
+ }
185
+ /** Named lifecycle events a plugin can observe generically.
186
+ * (`"selectionchange"` was deleted in 0.2.0 alongside the dead
187
+ * drawn-feature-selection surface — see `NgisSelectionFacet`.) */
188
+ export type NgisMapEventName = "viewchange" | "highlightchange" | "drawchange";
189
+ /** `events` facet — a generic named-event bus over the same emitters the typed
190
+ * facet subscriptions use (a forward-compat convenience for plugins). */
191
+ export interface NgisEventsFacet {
192
+ on(event: NgisMapEventName, listener: () => void): NgisUnsubscribe;
193
+ }
194
+ /**
195
+ * The typed facade. v0 — the surface a plugin (and, today, the three first-party
196
+ * consumers) program against. Concrete methods are frozen; optional-typed
197
+ * methods may light up as the map subsystems expose more.
198
+ */
199
+ export interface NgisMapAPI {
200
+ readonly version: string;
201
+ layers: NgisLayersFacet;
202
+ camera: NgisCameraFacet;
203
+ selection: NgisSelectionFacet;
204
+ draw: NgisDrawFacet;
205
+ measure: NgisMeasureFacet;
206
+ /** The C7 workspace panel registry, re-exposed so a plugin registers UI slots
207
+ * through the same SDK object. */
208
+ panels: WorkspacePanelRegistry;
209
+ events: NgisEventsFacet;
210
+ /** Raw MapLibre instance — the **unstable escape hatch**. `null` until the
211
+ * host binds a getter (post-C5 `SceneMapCanvas` hookup). Anything a plugin
212
+ * does with this is outside the stability contract. */
213
+ getMap(): MapLibreMap | null;
214
+ }
215
+ /** The imperative surface the facade delegates draw/measure/camera to — a
216
+ * structural subset of the shell's `GisMapHandle`, kept structural so this
217
+ * module doesn't import the (large) `SceneMapCanvas` component. */
218
+ export interface NgisMapImperativeHandle {
219
+ setDrawMode(mode: GisDrawMode): void;
220
+ cancelMode(): void;
221
+ getActiveMode(): string | null;
222
+ clearDraw(): void;
223
+ deleteSelection(): void;
224
+ undo(): void;
225
+ redo(): void;
226
+ exportDraw(): void;
227
+ startMeasure(kind: GisMeasureKind): void;
228
+ clearMeasure(): void;
229
+ zoomIn(): void;
230
+ zoomOut(): void;
231
+ zoomHome(): void;
232
+ /** Raw Terra Draw feature snapshot (the scene-map-context `getDrawSnapshot`
233
+ * service). **The first-party shell supplies it since Stage 14 (F14.18)**, so
234
+ * `draw.snapshot` returns real data there; it stays optional so a host may
235
+ * still bind a handle without the service. */
236
+ getDrawSnapshot?(): unknown[];
237
+ }
238
+ /** The layer operations the `layers` facet needs (a thin adapter over the
239
+ * scene store, supplied by the host so the SDK stays store-agnostic). */
240
+ export interface NgisLayersBinding {
241
+ list(): NgisLayerSummary[];
242
+ focus(id: string): void;
243
+ setVisibility(id: string, visible: boolean): void;
244
+ /** Store-change subscription backing `layers.onChange`. The host must
245
+ * equality-guard it (fire only when the effective layer set really changed).
246
+ * Required since 0.2.0 — `onChange` is part of the frozen surface. */
247
+ subscribe(listener: () => void): NgisUnsubscribe;
248
+ }
249
+ /** The highlight sink — the workspace-owned highlight state (the exact data
250
+ * path the attribute table uses today: this ultimately calls `setHighlight`). */
251
+ export interface NgisHighlightBinding {
252
+ set(highlight: NgisHighlight | null): void;
253
+ /** Initial value (defaults to `null`). */
254
+ initial?: NgisHighlight | null;
255
+ }
256
+ export interface NgisMapAPIBindings {
257
+ /** Live composed imperative handle (`mapRef.current`), or `null` before ready. */
258
+ getHandle(): NgisMapImperativeHandle | null;
259
+ layers: NgisLayersBinding;
260
+ panels: WorkspacePanelRegistry;
261
+ highlight: NgisHighlightBinding;
262
+ /** Raw-map getter for `getMap()`. Usually omitted at construction and wired
263
+ * later via `NgisMapAPIHandle.bindRawMap` (the post-C5 hookup). */
264
+ getRawMap?(): MapLibreMap | null;
265
+ }
266
+ /**
267
+ * The factory's return value: the public `api` plus the host-side "driver"
268
+ * controls (feed-in points the host wires that are *not* part of the public
269
+ * plugin surface).
270
+ */
271
+ export interface NgisMapAPIHandle {
272
+ api: NgisMapAPI;
273
+ /** Feed a camera-view change into the facade (called from the canvas's
274
+ * `onViewChange` prop today; from `CameraController` post-hookup). */
275
+ emitViewChange(view: NgisMapView): void;
276
+ /** Feed a draw change into the facade — drives `draw.onDrawChange` and the
277
+ * `"drawchange"` event. The host calls this from its draw-result and
278
+ * draw-mode handlers (0.2.0's first-party emit source). */
279
+ emitDrawChange(): void;
280
+ /** Bind (or replace) the raw-map getter behind `getMap()` — the one-line
281
+ * `SceneMapCanvas` provider hookup applied after rebasing onto merged C5. */
282
+ bindRawMap(getter: () => MapLibreMap | null): void;
283
+ }
284
+ /**
285
+ * Build a facade over the supplied bindings. Pure and framework-agnostic —
286
+ * create it once per workspace (the host memoizes it) and keep it stable.
287
+ */
288
+ export declare function createNgisMapAPI(bindings: NgisMapAPIBindings): NgisMapAPIHandle;
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Workspace panel registry — the plugin-readiness seam for GIS workspace UI
3
+ * "slots" (today: the right dock and the bottom docks). Formalizes what used
4
+ * to be a hand-wired `GisRightMode` enum plus three independent booleans
5
+ * (`attributeOpen`/`workflowOpen`/`codeOpen`) into a generic registration API:
6
+ *
7
+ * const unregister = registerPanel({ id, slot, icon, labelKey, render });
8
+ *
9
+ * Design notes (see `docs/componentization_stage/session-C7-*.md`):
10
+ * - `render(container, ctx)` is a **DOM-based, framework-agnostic** contract —
11
+ * modeled on GeoLibre's lesson that forcing every panel to hand back a React
12
+ * element makes a registry impossible for a future non-React plugin loader
13
+ * to target. A real plugin only needs to mutate a `container: HTMLElement`
14
+ * and (optionally) return a cleanup function.
15
+ * - `reactPanel()` below is the React *adapter* the design doc calls for.
16
+ * First-party panels stay ordinary React components; the adapter's returned
17
+ * `render` function still matches the generic signature (so a naive host
18
+ * invoking it imperatively still works, via an internal `createRoot`
19
+ * fallback), but it also carries a `reactNode` escape hatch that a
20
+ * React-aware host (`GisPanelHost`) uses to mount the panel as a normal
21
+ * child in the app's own React tree — preserving context (next-intl, theme)
22
+ * and ordinary prop-driven reactivity, which an isolated `createRoot` tree
23
+ * cannot (separate roots don't share React context).
24
+ * - A slot still shows only one active panel at a time, but it also tracks the
25
+ * panels opened during the current workspace session. Both GIS docks present
26
+ * that list as tabs and keep inactive opened bodies mounted so local UI state
27
+ * survives tab switches.
28
+ */
29
+ import { type ReactNode } from "react";
30
+ /** Slots this stage. Order matters only for future additions, not behavior. */
31
+ export type PanelSlot = "right" | "bottom";
32
+ export interface PanelRenderContext {
33
+ /** Close this panel in its slot (and its tab, when the host presents tabs). */
34
+ close: () => void;
35
+ }
36
+ export type PanelDisposer = () => void;
37
+ /**
38
+ * The framework-agnostic render contract. A real (future) plugin panel would
39
+ * implement this directly: mutate `container`, return a cleanup function.
40
+ */
41
+ export type PanelRenderFn = (container: HTMLElement, ctx: PanelRenderContext) => PanelDisposer | void;
42
+ /** `reactPanel()`'s return type — same call signature, plus the fast path. */
43
+ export interface ReactPanelRenderFn extends PanelRenderFn {
44
+ /** Build the React node for this panel "live" — called on every render of
45
+ * the React-aware host, so ordinary prop/state reactivity works exactly
46
+ * like a normal child component. */
47
+ reactNode: (ctx: PanelRenderContext) => ReactNode;
48
+ }
49
+ export interface PanelRegistration {
50
+ id: string;
51
+ slot: PanelSlot;
52
+ /** Plain string identifier (e.g. a lucide icon name) — kept string-typed,
53
+ * not a React node, so the registration shape never requires React. The
54
+ * consuming UI (e.g. `GisCommandRail`'s right-panel entries) resolves it. */
55
+ icon?: string;
56
+ /** i18n key (e.g. `"GIS.panels.ai"`) resolved by the consumer's `t()`. */
57
+ labelKey?: string;
58
+ /** Literal label, for callers without an i18n key (e.g. a future plugin). */
59
+ label?: string;
60
+ /** Lower sorts first. Ties fall back to registration order. */
61
+ order?: number;
62
+ render: PanelRenderFn;
63
+ }
64
+ type Listener = () => void;
65
+ /**
66
+ * A registry instance. `panels.ts` exports a process-wide singleton
67
+ * (`workspacePanelRegistry`) for the app to use, but the class is exported too
68
+ * so tests (or a future second workspace) can build an isolated instance.
69
+ */
70
+ export declare class WorkspacePanelRegistry {
71
+ private readonly panelsBySlot;
72
+ private readonly orderedCache;
73
+ private readonly activeBySlot;
74
+ private readonly openBySlot;
75
+ private readonly listeners;
76
+ private seq;
77
+ private readonly seqById;
78
+ constructor(slots?: readonly PanelSlot[]);
79
+ /** Register a panel into its slot. Returns an unregister function — the
80
+ * same "every registration returns an unregister function" convention the
81
+ * `NgisMapAPI` facade (C8) uses. */
82
+ registerPanel(def: PanelRegistration): PanelDisposer;
83
+ /** Registered panels in a slot, sorted by `order` then registration order.
84
+ * Cached so repeated calls between mutations return the same array
85
+ * reference (required for `useSyncExternalStore`'s snapshot contract). */
86
+ getPanels(slot: PanelSlot): PanelRegistration[];
87
+ getPanel(slot: PanelSlot, id: string): PanelRegistration | null;
88
+ getActive(slot: PanelSlot): string | null;
89
+ /** Panel ids opened in this slot, in first-opened order. The returned array
90
+ * is referentially stable until the open set changes. */
91
+ getOpenPanelIds(slot: PanelSlot): readonly string[];
92
+ /** Open a registered panel and make it active. Reopening an existing panel
93
+ * only activates it; it does not duplicate or reorder its tab. */
94
+ open(slot: PanelSlot, id: string): void;
95
+ setActive(slot: PanelSlot, id: string | null): void;
96
+ /** Close one opened panel. When it was active, prefer the nearest tab on
97
+ * its left, then the nearest tab on its right. */
98
+ close(slot: PanelSlot, id: string): void;
99
+ /** Invoking the already-active panel closes it — the "toggle-off" parity
100
+ * every rail tool and dock icon relies on. */
101
+ toggle(slot: PanelSlot, id: string): void;
102
+ subscribe(listener: Listener): PanelDisposer;
103
+ private slotMap;
104
+ private invalidate;
105
+ private removeOpenPanel;
106
+ private notify;
107
+ }
108
+ /** Process-wide registry for the `/gis` workspace. */
109
+ export declare const workspacePanelRegistry: WorkspacePanelRegistry;
110
+ /**
111
+ * React adapter: wraps a `(ctx) => ReactNode` factory as a `PanelRenderFn`.
112
+ * First-party panels use this. See the module doc comment for why the
113
+ * returned function carries both an imperative `createRoot` fallback (the
114
+ * literal, framework-agnostic contract) and a `reactNode` fast path
115
+ * (`GisPanelHost` uses this one, to stay inside the app's own React tree).
116
+ */
117
+ export declare function reactPanel(factory: (ctx: PanelRenderContext) => ReactNode): ReactPanelRenderFn;
118
+ export declare function isReactPanelRenderFn(fn: PanelRenderFn): fn is ReactPanelRenderFn;
119
+ /** Convenience: build a `reactPanel` from a fixed element (no live `ctx`
120
+ * dependency needed) — most first-party panels want the factory form instead
121
+ * so they can pass `ctx.close`, but this keeps trivial cases terse. */
122
+ export declare function staticReactPanel(node: ReactNode): ReactPanelRenderFn;
123
+ /** Reactive list of a slot's registered panels, sorted — for building things
124
+ * like `GisCommandRail`'s right-panel entry group generically. */
125
+ export declare function usePanels(registry: WorkspacePanelRegistry, slot: PanelSlot): readonly PanelRegistration[];
126
+ /** Reactive active-panel id for a slot (`null` when the slot is closed). */
127
+ export declare function usePanelActive(registry: WorkspacePanelRegistry, slot: PanelSlot): string | null;
128
+ /** Reactive first-opened list for hosts that present a slot as tabs. */
129
+ export declare function useOpenPanelIds(registry: WorkspacePanelRegistry, slot: PanelSlot): readonly string[];
130
+ export {};
@@ -0,0 +1,145 @@
1
+ /**
2
+ * NGIS plugin manifest + plugin object types (plugin-runtime stage, session
3
+ * P4). This is the *implemented* version of the `docs/frontend-map-sdk.md` §3
4
+ * draft, with the stage's decisions of record applied
5
+ * (`docs/plugin_runtime_stage/README.md`):
6
+ *
7
+ * - **D3 — typed TS object, not JSON.** First-party plugins compile in-repo,
8
+ * so the manifest gets compile-time checking free. `entry` stays in the type
9
+ * but is optional/reserved — JSON parsing + dynamic bundle loading is
10
+ * exactly the third-party leg this stage excludes (gated on backend auth
11
+ * hardening; see §3.5 of the SDK doc).
12
+ * - **D4 — declarative capabilities, runtime-owned teardown** (deliberate
13
+ * deviation from the original §3 draft): capability fields carry the actual
14
+ * registration definitions, and the **runtime** performs every registration
15
+ * and owns the collected unregister functions. `activate`/`deactivate`
16
+ * demote to optional imperative extras. This eliminates the draft's
17
+ * plugin-kept-unregister honor system — a plugin cannot forget (or refuse)
18
+ * to tear down what it never registered itself.
19
+ *
20
+ * Framework-agnostic on purpose: type-only imports, no React — consumed by
21
+ * the node-tested runtime (`plugin-runtime.ts`).
22
+ */
23
+ import type { LayerRendererEntry } from "../systems/renderer/LayerRendererRegistry";
24
+ import type { CommandRegistration } from "./commands";
25
+ import type { NgisContributes, NgisPermissionScope } from "./contributions/types";
26
+ import type { NgisSymbolRendererModule } from "./contributions/symbol-renderers";
27
+ import type { PanelRegistration } from "./panels";
28
+ import type { GisDynamicToolDef } from "./rail-tools";
29
+ /**
30
+ * **F13.1 / ED19 (Stage 13, X4b) — the `NgisPluginHost` widening.**
31
+ *
32
+ * Until 0.3.0 this module *declared* `NgisPluginHost` as `{ api: NgisMapAPI }`.
33
+ * At 0.4.0 the name becomes a back-compat alias of the full nine-facet
34
+ * `NgisHostAPI` (`./host-api`), and this is the only line in the repo that says
35
+ * so — `@/sdk`'s barrel keeps re-exporting the name from here, so every
36
+ * `activate(host: NgisPluginHost)` signature is untouched.
37
+ *
38
+ * Why an alias rather than a rename: `host.api` keeps its name **and** its
39
+ * position (XD1's amendment on record — design-v2 §4 spells it `host.map`, and
40
+ * renaming would move every first-party plugin, the E7 command executor and the
41
+ * loader path for zero capability). A 0.3-era plugin that only ever reads
42
+ * `host.api.layers` therefore still type-checks and still runs, unchanged.
43
+ *
44
+ * Type-only re-export, so it is erased at build time and introduces no module
45
+ * cycle: `host-api.ts` does not import this file.
46
+ */
47
+ import type { NgisPluginHost } from "./host-api";
48
+ export type { NgisPluginHost };
49
+ /** A teardown function collected and invoked by the runtime. */
50
+ export type NgisPluginDisposer = () => void;
51
+ /**
52
+ * Declarative capabilities (D4) — each field carries the real registration
53
+ * definitions for one of the three SDK extension points, wired by the runtime:
54
+ *
55
+ * - `panels` → `host.api.panels.registerPanel(...)` (C7 registry)
56
+ * - `railTools` → `gisRailToolRegistry.register(...)` (D6 seam — ids must be
57
+ * namespaced, "More tools" overflow only)
58
+ * - `layerTypes` → the contributed-entries bridge (D5 — applied after
59
+ * built-ins into `SceneLayersController`'s per-mount renderer registry)
60
+ * - `commands` → `gisCommandRegistry.register(...)` (Stage 11 E7 seam —
61
+ * externally-invocable capabilities a scene script can call through the
62
+ * codeenv plugin-invocation bridge; ids must be namespaced like railTools)
63
+ *
64
+ * An unknown key here refuses activation (`incompatible`), so a plugin built
65
+ * against a newer runtime never half-activates on an older one.
66
+ */
67
+ export interface NgisPluginCapabilities {
68
+ panels?: PanelRegistration[];
69
+ railTools?: GisDynamicToolDef[];
70
+ layerTypes?: LayerRendererEntry[];
71
+ commands?: CommandRegistration[];
72
+ }
73
+ export interface NgisPluginManifest {
74
+ /** Stable, namespaced, reverse-DNS-ish — **must contain a `.`**
75
+ * (e.g. `"ngis.workflow"`, `"org.acme.heatmap"`); validated at registration. */
76
+ id: string;
77
+ /** Display name (literal — shown in the dev "plugins" panel). */
78
+ name: string;
79
+ /** Plugin's own semver. Informational this stage. */
80
+ version: string;
81
+ /** Minimum `NGIS_MAP_API_VERSION` the plugin needs — semver-compared by the
82
+ * runtime; a too-new requirement marks the plugin `incompatible`. */
83
+ minNgisVersion: string;
84
+ capabilities: NgisPluginCapabilities;
85
+ /**
86
+ * **F13.3 — a top-level sibling of `capabilities`, not a capability key.**
87
+ * `KNOWN_CAPABILITY_KEYS` stays `panels|railTools|layerTypes|commands` in
88
+ * both repos, which is precisely why F13.13's reverse leg lands on the
89
+ * `minNgisVersion` semver compare: a 0.3 runtime inspects only
90
+ * `manifest.capabilities` and never sees this key at all.
91
+ *
92
+ * The split rule: `capabilities` carries **code**, `contributes` carries
93
+ * **only data**, and a contribution that needs code references it by id.
94
+ * Parsed by `parseContributes` (`@/sdk/contributions`, X4a) from the raw
95
+ * manifest the boot / scene-resolve payload already carries — **before any
96
+ * bundle is fetched** (XD4). That passthrough is the lazy-activation
97
+ * substrate.
98
+ */
99
+ contributes?: NgisContributes;
100
+ /**
101
+ * **F13.5 — the declared permission scopes**, encoded as a flat array of
102
+ * scope strings (frozen at the W1 kickoff: duplicates are a validation
103
+ * error, order is insignificant, an empty array means the same as an absent
104
+ * key). Enforcement is X5's (W3) and is facade-level; the whole 0.3
105
+ * `host.api` surface stays ungated, so a 0.3-era plugin declares nothing
106
+ * here and is never prompted.
107
+ */
108
+ permissions?: readonly NgisPermissionScope[];
109
+ /** **Reserved, never read this stage** (D3): the future third-party loader's
110
+ * ESM-bundle path. First-party plugins are statically imported instead. */
111
+ entry?: string;
112
+ }
113
+ /**
114
+ * A plugin = manifest + optional imperative extras (D4). Most first-party
115
+ * plugins are expected to be fully declarative (capabilities only).
116
+ *
117
+ * - `activate?(host)` runs after the runtime registered the declared
118
+ * capabilities. Return `false` to signal a failed activation (the runtime
119
+ * rolls back the capability registrations it already made); return a
120
+ * disposer to have the runtime run it on deactivation; throwing is treated
121
+ * like `false`.
122
+ * - `deactivate?()` runs on teardown, after the runtime ran every collected
123
+ * unregister function.
124
+ */
125
+ export interface NgisPlugin {
126
+ manifest: NgisPluginManifest;
127
+ /**
128
+ * **F24.13 (Stage 24) — the code half of the T3 bridge.** `manifest
129
+ * .contributes.symbolRenderers` declares `{ id, geometry, params?, slots? }`
130
+ * as data; this is what the bundle exports against those ids.
131
+ *
132
+ * A sibling of `manifest`, not a capability key, for the reason
133
+ * `./contributions/symbol-renderers.ts` sets out: `KNOWN_CAPABILITY_KEYS` is
134
+ * frozen in both repos and an unknown key refuses activation outright, so a
135
+ * sixth one would make a T3 plugin `incompatible` on every older host.
136
+ *
137
+ * Published by the runtime at activation and disposed with every other
138
+ * registration it made — which is what keeps declaring a renderer from
139
+ * forcing eager activation (F13.4): the plugin activates on the first
140
+ * *resolution* of a symbol that names it.
141
+ */
142
+ symbolRenderers?: NgisSymbolRendererModule;
143
+ activate?(host: NgisPluginHost): void | false | NgisPluginDisposer;
144
+ deactivate?(): void;
145
+ }
@@ -0,0 +1,58 @@
1
+ import type { GisRailInvokeContext } from "../types/gis";
2
+ /** A plugin-contributed rail tool. v1 supports one-shot `kind: "action"` only
3
+ * (no draw-mode/measure lit-state integration — that bookkeeping is keyed to
4
+ * `GisToolId` and stays closed per D6). `invoke` receives the exact same ctx
5
+ * shape as catalog tools, so a dynamic tool drives the map through `ctx.api`
6
+ * like any first-party tool. */
7
+ export interface GisDynamicToolDef {
8
+ /** Namespaced id — **must contain a `.`** (validated at registration), which
9
+ * makes collision with the dot-free `GisToolId` union impossible. */
10
+ id: string;
11
+ /** Literal display label (not an i18n key — third-party path). */
12
+ label: string;
13
+ /** Optional lucide icon name; unmapped names render without an icon. */
14
+ icon?: string;
15
+ /** v1: one-shot actions only. */
16
+ kind: "action";
17
+ /**
18
+ * Rendered greyed-out instead of hidden, and `invoke` is not called
19
+ * (Stage 13 X9, F13.9 / XD12).
20
+ *
21
+ * Additive to D6: the overflow used to have no way to say "this exists but
22
+ * cannot run right now", so a contributed `tools` stanza whose backend tool
23
+ * is missing or whose `enabledWhen` is false would have had to be *dropped*
24
+ * from the list — and hiding it makes a broken install indistinguishable from
25
+ * a feature that was never installed. Nothing about D6's frozen rules
26
+ * changes: ids stay namespaced, the catalog stays closed, and a def that
27
+ * omits this field behaves exactly as before.
28
+ */
29
+ disabled?: boolean;
30
+ /** Why, shown beside a disabled entry. A plugin's literal string (F13.11) or
31
+ * a host-authored one; never a message key. */
32
+ disabledReason?: string;
33
+ invoke: (ctx: GisRailInvokeContext) => void;
34
+ }
35
+ type Listener = () => void;
36
+ type Disposer = () => void;
37
+ /**
38
+ * Registry instance. A process-wide singleton (`gisRailToolRegistry`) is what
39
+ * the rail and the plugin runtime use; the factory is exported so tests build
40
+ * isolated instances.
41
+ */
42
+ export interface GisRailToolRegistry {
43
+ /** Register a dynamic tool. Throws if `def.id` does not contain a `.`.
44
+ * Returns an unregister function (idempotent; never removes a different
45
+ * def re-registered under the same id). */
46
+ register(def: GisDynamicToolDef): Disposer;
47
+ /** Registered tools in registration order — cached snapshot between
48
+ * mutations (safe as a `useSyncExternalStore` snapshot). */
49
+ list(): readonly GisDynamicToolDef[];
50
+ subscribe(listener: Listener): Disposer;
51
+ }
52
+ export declare function createGisRailToolRegistry(): GisRailToolRegistry;
53
+ /** Process-wide dynamic rail-tool registry for the `/gis` workspace. */
54
+ export declare const gisRailToolRegistry: GisRailToolRegistry;
55
+ /** Reactive list of registered dynamic tools — `GisCommandRail` merges these
56
+ * into its "More tools" overflow. */
57
+ export declare function useDynamicRailTools(registry?: GisRailToolRegistry): readonly GisDynamicToolDef[];
58
+ export {};