@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
+ * Stage 13 (capability contract) — the frozen type text for `contributes` v1.
3
+ *
4
+ * **Authored at the W2 kickoff, not by a session.** X4a parses these shapes,
5
+ * X4b's loader path consumes the parse result, and X3's settings renderer
6
+ * consumes `NgisSettingsContribution`. Three concurrent agents needing the same
7
+ * type names is exactly the collision that bit W1 (X1 and X2 both declared
8
+ * `NgisResourceType` and clashed on the exported *name*, invisible to either
9
+ * agent alone until merge). So the names live here, owned by the kickoff, and
10
+ * **no W2 session declares any of them**.
11
+ *
12
+ * Every shape below is **F13.3 verbatim**
13
+ * (`docs/capability_contract_stage/README.md` → "Frozen contracts (X0)").
14
+ * A session that needs a change here stops and raises it — it does not diverge
15
+ * locally, because XB1's validator (already merged) and X4a's parser must agree
16
+ * fixture-for-fixture on the six frozen manifests in `src/sdk/__fixtures__/`.
17
+ *
18
+ * Type-only module: no runtime imports, no React. The caps are `const` because
19
+ * they are the same numbers XB1 enforces at publish time — a drift between the
20
+ * two is a silent-truncation bug (F13.10 makes over-cap a *rejection*, never a
21
+ * truncation).
22
+ */
23
+ /** F13.10. Enforced by XB1 at publish time and X4a at parse time. */
24
+ export declare const NGIS_MAX_STATUS_ITEMS_PER_PLUGIN = 2;
25
+ /** F13.10. Host row capacity; the rest collapse into a `+N` popover. */
26
+ export declare const NGIS_MAX_VISIBLE_STATUS_ITEMS = 6;
27
+ /** F13.10. Per plugin, per slot. */
28
+ export declare const NGIS_MAX_MENU_ITEMS_PER_PLUGIN_PER_SLOT = 5;
29
+ /**
30
+ * Every contribution point carries these. `when` false ⇒ not rendered.
31
+ * `enabledWhen` false ⇒ rendered, disabled, with `disabledReason` (or a generic
32
+ * host string). `views` takes `when` only — a disabled-but-visible panel has no
33
+ * meaning, so `NgisViewContribution` does not extend this.
34
+ */
35
+ export interface NgisGatedContribution {
36
+ when?: string;
37
+ enabledWhen?: string;
38
+ disabledReason?: string;
39
+ }
40
+ export interface NgisStatusItemContribution extends NgisGatedContribution {
41
+ /** Required, unique within the plugin. */
42
+ id: string;
43
+ /** Literal, single-locale (F13.11) — plugin strings are never `labelKey`s. */
44
+ label?: string;
45
+ /** lucide icon name. */
46
+ icon?: string;
47
+ tooltip?: string;
48
+ /** A `capabilities.commands` id. */
49
+ command?: string;
50
+ /** Rendered as the item's count. */
51
+ badge?: {
52
+ contextKey: string;
53
+ };
54
+ /** Default 0; higher sorts first. Order is `priority` desc, then plugin id,
55
+ * then item id — deterministic, never install-order (F13.10). */
56
+ priority?: number;
57
+ }
58
+ /** v1 slots — exactly these two. No `map/context`, no `panel/title` (XD19). */
59
+ export type NgisMenuSlot = "layer/context" | "resource/context";
60
+ /** Groups render in this fixed order, separated; within a group, `order` asc
61
+ * then item id. Host items always precede plugin items. */
62
+ export type NgisMenuGroup = "navigation" | "modification" | "export" | "plugin";
63
+ export interface NgisMenuItemContribution extends NgisGatedContribution {
64
+ /** Required — a `capabilities.commands` id. The split rule: `contributes`
65
+ * carries only data, and anything needing code references it by id. */
66
+ command: string;
67
+ /** Required; literal (F13.11). */
68
+ label: string;
69
+ /** Default `"plugin"`. */
70
+ group?: NgisMenuGroup;
71
+ order?: number;
72
+ }
73
+ export type NgisMenuContributions = {
74
+ readonly [S in NgisMenuSlot]?: readonly NgisMenuItemContribution[];
75
+ };
76
+ /**
77
+ * The two existing panel slots (`PanelSlot` from `./panels`, restated here so
78
+ * this module stays import-free).
79
+ *
80
+ * **Amended 2026-07-27 (W2, X4a finding).** F13.3 originally froze this as
81
+ * `"left" | "right"` while describing it as "the two existing panel slots".
82
+ * Both values were wrong: `PanelSlot` has been `"right" | "bottom"` since C7
83
+ * (`src/sdk/panels.ts:36`, and `workspacePanelRegistry` is constructed with
84
+ * exactly those two at `:240`). There is no `left` slot — the GIS workspace's
85
+ * left region is the scene sidebar, a fixed layout column rather than a
86
+ * registry slot — and `bottom` is a real slot a view could legitimately target.
87
+ *
88
+ * The orchestrator amended F13.3 to the union below rather than mapping `left`
89
+ * onto `right`, because rendering a view in a dock its author did not ask for
90
+ * is the failure mode F13.9 elsewhere calls out as worse than refusing.
91
+ * `left` is now an out-of-allowlist value and refuses like any other.
92
+ * `PackageManifestValidator.VIEW_SLOTS` changed with it; **no fixture changed**,
93
+ * since `m2-full-0.4.json` is the only fixture declaring a view slot and it
94
+ * declares `"right"`, which is valid under both unions.
95
+ */
96
+ export type NgisViewSlot = "right" | "bottom";
97
+ export interface NgisViewContribution {
98
+ id: string;
99
+ slot: NgisViewSlot;
100
+ /** Literal (F13.11). */
101
+ title: string;
102
+ icon?: string;
103
+ order?: number;
104
+ /** `when` only — see `NgisGatedContribution`. */
105
+ when?: string;
106
+ }
107
+ export interface NgisToolPreset {
108
+ id: string;
109
+ label: string;
110
+ /** String-valued, matching the task API — the facade does not coerce. */
111
+ params: Readonly<Record<string, string>>;
112
+ }
113
+ /**
114
+ * F13.9. Launch + presets + enablement **only**. A stanza cannot expose a free
115
+ * parameter; generated forms are Stage 14's `ParamForm`. Mounts through the
116
+ * existing rail "More tools" overflow seam — no new host real estate.
117
+ */
118
+ export interface NgisToolContribution extends NgisGatedContribution {
119
+ /** Required, unique within the plugin. */
120
+ id: string;
121
+ /** Required — a backend analysis/workflow tool id. */
122
+ toolId: string;
123
+ /** Required; literal (F13.11). */
124
+ label: string;
125
+ icon?: string;
126
+ presets?: readonly NgisToolPreset[];
127
+ /** Only honoured with exactly one preset. */
128
+ autoRun?: boolean;
129
+ }
130
+ /** Selects the storage leg (F13.7). */
131
+ export type NgisSettingsScope = "user" | "scene";
132
+ export type NgisSettingsPropertyType = "string" | "number" | "integer" | "boolean";
133
+ /**
134
+ * The JSON-Schema **subset** X3 renders — flat object only. No nested objects,
135
+ * no arrays, no `$ref`, no `oneOf`/`allOf`, no `pattern`. Widget mapping is
136
+ * total and fixed, so there is no "unsupported widget" fallback to design:
137
+ *
138
+ * | schema | widget |
139
+ * |---|---|
140
+ * | `enum` present | `<Select>` |
141
+ * | `boolean` | `<Switch>` |
142
+ * | `number` / `integer` | number `<Input>` (with `min`/`max` when given) |
143
+ * | `string` | text `<Input>` |
144
+ *
145
+ * Anything outside the subset is a validation error at publish (XB1) and a
146
+ * parse refusal (X4a) — the renderer never sees it.
147
+ */
148
+ export interface NgisSettingsProperty {
149
+ type: NgisSettingsPropertyType;
150
+ title?: string;
151
+ description?: string;
152
+ default?: string | number | boolean;
153
+ /** string/number only. */
154
+ enum?: readonly (string | number)[];
155
+ /** number/integer only. */
156
+ minimum?: number;
157
+ /** number/integer only. */
158
+ maximum?: number;
159
+ order?: number;
160
+ }
161
+ export interface NgisSettingsContribution {
162
+ scope: NgisSettingsScope;
163
+ properties: Readonly<Record<string, NgisSettingsProperty>>;
164
+ }
165
+ /**
166
+ * A T3 symbol renderer a plugin **declares**. Stage 24, F24.13, which **amends
167
+ * F13.3**: `contributes` gained a sixth key.
168
+ *
169
+ * The split rule holds — this is data, not code. What the bundle exports is
170
+ * `symbolRenderers: Record<id, (ctx: SymbolRenderContext) => Layer[]>`; what the
171
+ * manifest declares is only enough to resolve a symbol reference, draw a param
172
+ * form and intersect ACLs **before any bundle is fetched**. That ordering is the
173
+ * whole point: `plugin:<extId>@<version>/<rendererId>` must be answerable from
174
+ * the manifest alone, or resolution would have to activate every plugin that
175
+ * might own the renderer.
176
+ *
177
+ * Declaring a renderer does **not** force eager activation (F13.4 still holds).
178
+ * The plugin activates on the first *resolution* of a symbol that names it.
179
+ *
180
+ * Shapes are re-declared here rather than imported from
181
+ * `@/domain/models/symbol`: the SDK's contribution types are the plugin-facing
182
+ * surface and must not depend on the symbol registry's domain model, and the
183
+ * manifest's `params`/`slots` are a *declaration subset* — no `layers`, no
184
+ * `fallback`, no `animation`. The two are held together by the fixture pair
185
+ * (`m7`/`m8`), not by a shared import.
186
+ */
187
+ export type NgisSymbolRendererGeometry = "point" | "line" | "polygon";
188
+ export type NgisSymbolRendererParamType = "number" | "color" | "boolean" | "enum" | "angle" | "percent";
189
+ export interface NgisSymbolRendererParam {
190
+ type: NgisSymbolRendererParamType;
191
+ label?: string;
192
+ default: string | number | boolean;
193
+ min?: number;
194
+ max?: number;
195
+ step?: number;
196
+ unit?: string;
197
+ /** `enum` only. */
198
+ values?: readonly string[];
199
+ }
200
+ /** The nine slot names of F24.5. A renderer declares which it reads; a slot it
201
+ * does not declare is not offered a binding in the scene editor. */
202
+ export type NgisSymbolRendererSlot = "size" | "color" | "angle" | "startTime" | "phase" | "data0" | "data1" | "data2" | "data3";
203
+ export interface NgisSymbolRendererContribution {
204
+ /** Unique within the plugin. The reference a symbol writes is
205
+ * `plugin:<extId>@<version>/<id>`. */
206
+ id: string;
207
+ geometry: NgisSymbolRendererGeometry;
208
+ label?: string;
209
+ params?: Readonly<Record<string, NgisSymbolRendererParam>>;
210
+ slots?: readonly NgisSymbolRendererSlot[];
211
+ }
212
+ /** F24.13's cap. Declared here so the two validators quote one number. */
213
+ export declare const NGIS_MAX_SYMBOL_RENDERERS_PER_PLUGIN = 8;
214
+ /**
215
+ * The top-level `contributes` block. A **sibling of `capabilities`**, not a
216
+ * capability key (XD3): `KNOWN_CAPABILITY_KEYS` stays
217
+ * `panels|railTools|layerTypes|commands` in both repos, unchanged.
218
+ *
219
+ * The split rule: `capabilities` carries **code** (render functions, handlers,
220
+ * renderer entries); `contributes` carries **only data**.
221
+ */
222
+ export interface NgisContributes {
223
+ statusItems?: readonly NgisStatusItemContribution[];
224
+ menus?: NgisMenuContributions;
225
+ views?: readonly NgisViewContribution[];
226
+ tools?: readonly NgisToolContribution[];
227
+ settings?: NgisSettingsContribution;
228
+ /** F24.13 (Stage 24) — amends F13.3 five-key list. Max
229
+ * `NGIS_MAX_SYMBOL_RENDERERS_PER_PLUGIN`; duplicate `id` is a validation
230
+ * error in both repos. */
231
+ symbolRenderers?: readonly NgisSymbolRendererContribution[];
232
+ }
233
+ /** F13.5's ten grantable scopes. Frozen at the W1 kickoff as a **flat array of
234
+ * scope strings** on the manifest — duplicates are a validation error, order is
235
+ * insignificant, an empty array means the same as an absent key. */
236
+ export type NgisPermissionScope = "scene:read" | "resources:read" | "models:read" | "models:run" | "tasks:read" | "tasks:cancel" | "viz:write" | "storage:user" | "storage:scene" | "ui:notify";
237
+ /**
238
+ * F13.4. Auto-derived from the manifest; a plugin never writes an activation
239
+ * list. A plugin declaring `capabilities.panels`, `railTools` or `layerTypes` —
240
+ * the three code-carrying points with no declarative twin — **activates
241
+ * eagerly**, which is why `"eager"` is a member of this union rather than a
242
+ * separate flag: the loader branches on one value.
243
+ */
244
+ export type NgisActivationEvent = "eager" | `onCommand:${string}` | `onView:${string}` | `onTool:${string}` | `onSceneType:${string}` | `onResourceType:${string}`;
245
+ /**
246
+ * One refusal, naming its field by full path (`contributes.menus.layer/context[2].command`)
247
+ * — the same discipline XB1's validator errors already follow, so a manifest
248
+ * rejected by the backend and one refused by the parser read the same way.
249
+ */
250
+ export interface NgisContributionParseError {
251
+ /** Full dotted/bracketed path from the manifest root. */
252
+ path: string;
253
+ message: string;
254
+ }
255
+ /**
256
+ * **Total, never throwing.** A malformed `contributes` block must not take the
257
+ * workspace down — the plugin loads with `contributes: null` and its errors
258
+ * surfaced, exactly as a bundle that fails to negotiate becomes a `"failed"`
259
+ * status rather than a boot crash (G-D criterion 6, structural).
260
+ */
261
+ export interface NgisContributesParseResult {
262
+ /** `null` when the block is absent **or** refused — `errors` distinguishes. */
263
+ contributes: NgisContributes | null;
264
+ /** F13.4's derived set. Empty only for a manifest with no capabilities and no
265
+ * contributions at all. */
266
+ activationEvents: readonly NgisActivationEvent[];
267
+ /** Empty on success. Non-empty ⇒ `contributes` is `null`. */
268
+ errors: readonly NgisContributionParseError[];
269
+ }
270
+ /**
271
+ * The seam between X4a (which implements it) and X4b (which calls it from the
272
+ * restructured load path).
273
+ *
274
+ * X4b takes this **by injection** so it compiles and unit-tests standalone
275
+ * against a fake, exactly as X1's publishers and X2's facet sources were
276
+ * dependency-injected in W1. The stub in `./index.ts` satisfies the type until
277
+ * X4a's implementation merges; **X4b must never import that stub in shipping
278
+ * code** — it takes the port as a parameter.
279
+ */
280
+ export interface NgisContributionsPort {
281
+ /**
282
+ * @param manifest the raw manifest `JsonNode` as it arrives on the boot /
283
+ * scene-resolve payload — parsed **before any bundle is fetched** (XD4).
284
+ * That passthrough is the lazy-activation substrate and needs zero backend
285
+ * change.
286
+ */
287
+ parseContributes(manifest: unknown): NgisContributesParseResult;
288
+ }
@@ -0,0 +1,92 @@
1
+ /**
2
+ * The `views` → workspace-panel bridge (Stage 13 X4a, F13.3 / F13.4).
3
+ *
4
+ * A `views` contribution is **data only** (XD3's split rule): an id, a slot, a
5
+ * literal title, an icon name, an order and a `when`. It carries no render
6
+ * function, because the bundle that would provide one has not been imported
7
+ * yet — the manifest arrives on the boot / scene-resolve payload and the whole
8
+ * point of F13.4 is that opening the view is what *triggers* the import.
9
+ *
10
+ * So this bridge registers a real `WorkspacePanelRegistry` entry per visible
11
+ * view — which is what puts it in the right dock's tab strip and the rail's
12
+ * panel group — and renders a **host placeholder** as its body. Opening it
13
+ * fires `onViewOpened`, the seam X4b wires to the derived `onView:<id>`
14
+ * activation event; once the plugin activates, its real
15
+ * `capabilities.panels` registration replaces the placeholder under the same
16
+ * slot (the panel registry is last-wins on id).
17
+ *
18
+ * ## The hand-off is one-way (`V-124`, X9c)
19
+ *
20
+ * "Replaces the placeholder" was only half true. `sync()` used to re-register
21
+ * the placeholder **unconditionally** on every contribution notification, and
22
+ * because `registerPanel` is last-wins on id, an activated plugin's real panel
23
+ * was reverted to the host placeholder the next time anything changed in the
24
+ * registry — a plugin whose panel worked when opened and became an empty title
25
+ * a moment later. The comment justifying the unconditional call was legitimate
26
+ * (title/icon/order move with the manifest), so the fix is not to stop
27
+ * re-registering: it is to re-register only over **this bridge's own**
28
+ * registration, checked by identity against `panels.getPanel(slot, id)`.
29
+ *
30
+ * ## The slot union — amended, not mapped
31
+ *
32
+ * F13.3 originally froze `views[].slot` as `"left" | "right"` while calling
33
+ * them "the two existing panel slots". Both values were wrong: `PanelSlot` has
34
+ * been `"right" | "bottom"` since C7 (`src/sdk/panels.ts:36`). X4a found it,
35
+ * the orchestrator verified it independently and **amended F13.3 to
36
+ * `"right" | "bottom"`** — so `NgisViewSlot` and `PanelSlot` are now the same
37
+ * two values and this bridge does no mapping at all. A `bottom` view registers
38
+ * into the bottom dock, a `right` view into the right dock, and `left` is
39
+ * refused by the parser like any other out-of-allowlist value.
40
+ *
41
+ * `toPanelSlot` survives as an explicit, tested identity so that a future
42
+ * divergence between the two unions fails here rather than silently coercing.
43
+ */
44
+ import type { NgisUnsubscribe } from "../map-api";
45
+ import { type PanelRenderFn, type PanelSlot, type WorkspacePanelRegistry } from "../panels";
46
+ import { type NgisContributionRegistry, type NgisRegisteredView } from "./registry";
47
+ import type { NgisViewSlot } from "./types";
48
+ /**
49
+ * Both names now live in `./panel-id.ts` — a dependency-free module — because
50
+ * F14.12 made the id a contract `activation.ts` must build too, and importing
51
+ * it from here would drag `react-dom/client` into the boot-payload parse path.
52
+ * Re-exported rather than re-implemented: one declaration, every existing
53
+ * import path unchanged (`V-127`'s lesson, applied before it happened).
54
+ */
55
+ export { CONTRIBUTED_VIEW_PANEL_PREFIX, contributedViewPanelId } from "./panel-id";
56
+ /**
57
+ * Maps a declared `NgisViewSlot` onto a real `PanelSlot`.
58
+ *
59
+ * An identity since the F13.3 amendment — the two unions are the same two
60
+ * values. Kept as a named, tested function rather than inlined so that if the
61
+ * unions ever diverge again the compiler catches it in one place.
62
+ */
63
+ export declare function toPanelSlot(slot: NgisViewSlot): PanelSlot;
64
+ export interface ContributedViewPanelsOptions {
65
+ registry?: NgisContributionRegistry;
66
+ panels?: WorkspacePanelRegistry;
67
+ /**
68
+ * Builds the placeholder body for a contributed view. Defaults to a bare
69
+ * text node — the framework-agnostic `PanelRenderFn` contract, so this module
70
+ * needs no React and stays node-testable. The React mount passes a localized
71
+ * `GIS.contrib.*` string in.
72
+ */
73
+ placeholderText?: (view: NgisRegisteredView) => string;
74
+ /** Full render override, for a host that wants more than a text placeholder. */
75
+ renderPanel?: (view: NgisRegisteredView) => PanelRenderFn;
76
+ /**
77
+ * Fired once per open of a contributed panel. **X4b's seam**: this is where
78
+ * the derived `onView:<id>` activation event is dispatched, which is what
79
+ * imports the bundle. Left injectable (and defaulting to a no-op) because
80
+ * the load path is X4b's file ownership, not this session's.
81
+ */
82
+ onViewOpened?: (pluginId: string, viewId: string) => void;
83
+ }
84
+ /**
85
+ * Keeps `workspacePanelRegistry` in sync with the contribution registry's
86
+ * visible `views`, and reports opens.
87
+ *
88
+ * Returns a disposer that unregisters every panel it created. Idempotent under
89
+ * repeated calls: registering the same panel id twice is a replace, not a
90
+ * duplicate (C7 semantics).
91
+ */
92
+ export declare function bindContributedViewPanels(options?: ContributedViewPanelsOptions): NgisUnsubscribe;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * `defineNgisPlugin()` — a typing-only identity helper for plugin authors
3
+ * (Stage 11 E3, DevKit). Exists purely so an author writing an `NgisPlugin`
4
+ * object gets full contextual type inference/autocomplete on `manifest` and
5
+ * `capabilities` (and, if a future capability adds a generic parameter, on
6
+ * that too) without hand-annotating the object's type — the same ergonomic
7
+ * pattern as `defineConfig()` helpers in other tools' SDKs.
8
+ *
9
+ * Zero runtime behavior beyond returning its argument: it does not register,
10
+ * validate, or negotiate anything (that stays the runtime's job, per
11
+ * `plugin-runtime.ts`). Re-exported by `@ngis/plugin-sdk` (the external
12
+ * DevKit distribution, `docs/design-extension-platform.md` §13) alongside the
13
+ * rest of `./plugin`'s types — this file is its single source of truth, not
14
+ * hand-copied into the published package.
15
+ */
16
+ import type { NgisPlugin } from "./plugin";
17
+ export declare function defineNgisPlugin(plugin: NgisPlugin): NgisPlugin;
@@ -0,0 +1,152 @@
1
+ /**
2
+ * `host.storage` (Stage 13 X3) — the two persistence legs of F13.7.
3
+ *
4
+ * | | user leg | scene leg |
5
+ * |---|---|---|
6
+ * | Backing | `ngis_extension_install.settings` (XB2, V4) | `ngis_scene.extension_config` → `plugins[].settings` |
7
+ * | Key | (user, extension) | (scene, extension) |
8
+ * | Write path | `PUT /api/extensions/{extId}/settings` | the composition `PUT`, echoing `revision` |
9
+ * | Who may write | the installer | **the scene owner only** |
10
+ * | Cap | 32 KiB serialized, server-enforced (`400`) | 32 KiB, **new writes only** |
11
+ * | Lifecycle | dies with the install row; survives disable | lives and dies with the composition entry |
12
+ *
13
+ * ### Three rules this module exists to keep, each a data-loss risk
14
+ *
15
+ * 1. **The composition `PUT` carries an opaque `revision` and it must ride the
16
+ * round-trip.** {@link createNgisStorageFacet}'s scene leg is a
17
+ * read-modify-write that *spreads* the composition it just read
18
+ * (`{...composition}`, `{...entry}`), so `revision`, `layout` and every
19
+ * field this client has never heard of survive. Rebuilding the object
20
+ * literally would drop `revision` silently and restore last-write-wins —
21
+ * which is a verified, real data-loss bug (Stage 12 row V-17), not a
22
+ * theoretical one. A refusal of a stale revision surfaces as
23
+ * `NgisStorageError("conflict")`.
24
+ * 2. **Existing `plugins[].settings` rows are unvalidated free-form and must
25
+ * keep parsing.** Nothing here validates on *read*: `get()` returns whatever
26
+ * the entry holds, including shapes no settings schema would accept. The
27
+ * 32 KiB cap is a **write-path** rule. A change that makes an old row
28
+ * unreadable is a data-loss bug.
29
+ * 3. **Refusals are surfaced, never masked.** `canWrite()` reports the truth so
30
+ * a settings form can render read-only instead of failing on submit, and
31
+ * `set()` never silently no-ops — it resolves or it rejects.
32
+ *
33
+ * ### The one place the contract could not be met (recorded, not worked around)
34
+ *
35
+ * F13.7's scene-leg row *"Read by non-installer: yes — a viewer of a shared
36
+ * scene reads the owner's settings"* has **no endpoint behind it this wave**.
37
+ * `GET /api/scenes/{id}/extensions` is owner-only (`403`), and W2 froze
38
+ * `SceneExtensionEntryDto` unchanged, so the scene-resolve payload carries no
39
+ * `settings`. A viewer therefore reads `null` and `canWrite()` is `false`. The
40
+ * UI states that in words rather than pretending the plugin has no settings;
41
+ * the missing projection is backlog row **V-49**.
42
+ */
43
+ import type { NgisUnsubscribe } from "../map-api";
44
+ import type { NgisStorageFacet, NgisStorageLeg, NgisStorageWriteFailure } from "../host-api";
45
+ import type { NgisContextConfigSource } from "../context/publishers";
46
+ import type { NgisContextValue } from "../context/keys";
47
+ import type { SceneExtensionComposition } from "../../types/extensions";
48
+ /**
49
+ * The 32 KiB cap (XD11), measured on the **serialized** form — the same thing
50
+ * the server measures. Not exported from `@/sdk`: the barrel's X3 hunk lists
51
+ * the names this session may claim there, and this constant is an
52
+ * implementation detail the settings UI imports by path.
53
+ */
54
+ export declare const NGIS_STORAGE_MAX_BYTES: number;
55
+ /**
56
+ * F13.2's typed write refusal. The reason union is **closed** — `read-only`,
57
+ * `too-large`, `conflict` — so a caller can branch on it exhaustively.
58
+ */
59
+ export declare class NgisStorageError extends Error {
60
+ readonly reason: NgisStorageWriteFailure;
61
+ constructor(reason: NgisStorageWriteFailure, message?: string);
62
+ }
63
+ /**
64
+ * The user leg's transport — XB2's install-row endpoints, whose contract is
65
+ * frozen in the W2 kickoff §8:
66
+ *
67
+ * - `GET /api/extensions/{extId}/settings` → `200 {"settings": <object|null>}`;
68
+ * **`404` when the caller has no install row** (hence `installed`, which is
69
+ * what `canWrite()` reports — the leg is writable exactly while installed).
70
+ * - `PUT /api/extensions/{extId}/settings` — the settings object *is* the body,
71
+ * bare; the response is the same `{"settings": …}` envelope the `GET` uses.
72
+ * `PUT null` clears. Over-cap is **`400`, not `413`** (the gateway owns 413,
73
+ * and the client must be able to tell "your JSON is too big" from "your
74
+ * request was too big"), and `400` is also the answer for a non-object body.
75
+ *
76
+ * `write` returns the **stored** value, which is *not* byte-comparable with
77
+ * what was sent: the column is Postgres `jsonb` and normalizes key order,
78
+ * whitespace, duplicate keys and number formatting. Nothing in this module
79
+ * diffs the echo against the request — a client that did would decide it had
80
+ * failed to save and re-save forever.
81
+ *
82
+ * Injected rather than imported so this module compiles and unit-tests against
83
+ * a fake.
84
+ */
85
+ export interface NgisUserStorageSource {
86
+ read(extId: string): Promise<{
87
+ installed: boolean;
88
+ settings: unknown | null;
89
+ }>;
90
+ write(extId: string, value: unknown): Promise<{
91
+ settings: unknown | null;
92
+ }>;
93
+ /** Optional push channel. Absent ⇒ `onChange` still fires on local writes. */
94
+ subscribe?(listener: () => void): NgisUnsubscribe;
95
+ }
96
+ /**
97
+ * The scene leg's transport — the existing composition `GET`/`PUT` on
98
+ * `ngis-scene`. `readComposition` reports `forbidden` rather than throwing on a
99
+ * `403`, because "you are a viewer" is a normal state this leg must render, not
100
+ * a failure.
101
+ */
102
+ export interface NgisSceneStorageSource {
103
+ getSceneId(): string | null;
104
+ readComposition(sceneId: string): Promise<{
105
+ forbidden: boolean;
106
+ composition: SceneExtensionComposition | null;
107
+ }>;
108
+ /** Full replace. The body must be the document `readComposition` returned,
109
+ * spread and patched — never rebuilt. */
110
+ writeComposition(sceneId: string, body: SceneExtensionComposition): Promise<SceneExtensionComposition>;
111
+ subscribe?(listener: () => void): NgisUnsubscribe;
112
+ }
113
+ export interface NgisStorageSource {
114
+ /** `NgisHostAPI.pluginId` — the extension id both legs are keyed by. */
115
+ pluginId: string;
116
+ user: NgisUserStorageSource;
117
+ scene: NgisSceneStorageSource;
118
+ }
119
+ /** Serialized byte length — what the cap is measured against. */
120
+ export declare function serializedByteLength(value: unknown): number;
121
+ export declare function createNgisStorageFacet(source: NgisStorageSource): NgisStorageFacet;
122
+ /**
123
+ * Flattens one plugin's stored settings into the flat map
124
+ * `publishConfigContextKeys` turns into `config:<pluginId>:<key>` context keys
125
+ * (F14.11 — the publisher owns the namespacing, and it also owns the skip for a
126
+ * settings key that cannot *be* namespaced).
127
+ *
128
+ * Only what `NgisContextValue` can hold survives: scalars, `null`, and arrays
129
+ * of string/number. A nested object or a mixed array is **skipped**, not
130
+ * stringified — a `when` expression comparing against a JSON blob would be a
131
+ * footgun, and an absent key already evaluates falsy by design (F13.6).
132
+ */
133
+ export declare function flattenConfigValues(value: unknown): Record<string, NgisContextValue>;
134
+ /**
135
+ * The real `config:*` key source X1 left a stub for
136
+ * (`emptyContextConfigSource`, `src/sdk/context/publishers.ts`).
137
+ *
138
+ * `NgisContextConfigSource.read()` is **synchronous** while every storage leg is
139
+ * async, so this holds a cache: `track()` reads the leg once, subscribes to it,
140
+ * and republishes on every change. Untracking removes the plugin's keys, which
141
+ * `publishConfigContextKeys` turns into `undefined` writes — an uninstalled
142
+ * plugin's `config:*` keys stop existing rather than lingering at their last
143
+ * value.
144
+ *
145
+ * Deliberately **not** exported from `@/sdk`: the host call site that would
146
+ * wire it (`GisWorkspace`'s facade construction) is X4b's file this wave, so
147
+ * the barrel export lands with the session that mounts it.
148
+ */
149
+ export declare function createStorageConfigContextSource(): {
150
+ source: NgisContextConfigSource;
151
+ track(pluginId: string, leg: NgisStorageLeg): NgisUnsubscribe;
152
+ };