@ngis/plugin-ui 0.4.0 → 0.4.1

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 (42) hide show
  1. package/dist/index.cjs +1 -1
  2. package/dist/index.d.ts +2 -2
  3. package/dist/index.mjs +1 -1
  4. package/dist/src/api/analysis.d.ts +38 -1
  5. package/dist/src/api/crs.d.ts +110 -0
  6. package/dist/src/api/resources.d.ts +174 -10
  7. package/dist/src/api/scenes.d.ts +103 -4
  8. package/dist/src/api/workflows.d.ts +10 -1
  9. package/dist/src/components/plugin-ui/ChartCanvas.d.ts +3 -0
  10. package/dist/src/components/plugin-ui/ParamForm.d.ts +67 -5
  11. package/dist/src/components/plugin-ui/PickerField.d.ts +17 -0
  12. package/dist/src/components/plugin-ui/RunButton.d.ts +8 -0
  13. package/dist/src/components/plugin-ui/crs-issue-messages.d.ts +157 -0
  14. package/dist/src/components/plugin-ui/flow-ports.d.ts +17 -0
  15. package/dist/src/components/plugin-ui/param-widgets.d.ts +2 -2
  16. package/dist/src/components/plugin-ui/picker-model.d.ts +49 -0
  17. package/dist/src/components/plugin-ui/run-data.d.ts +32 -4
  18. package/dist/src/components/plugin-ui/w2-contracts.d.ts +61 -0
  19. package/dist/src/components/plugin-ui/w3-contracts.d.ts +16 -1
  20. package/dist/src/components/resources/CrsPicker/CrsChip.d.ts +60 -0
  21. package/dist/src/components/resources/CrsPicker/CrsParamControl.d.ts +10 -0
  22. package/dist/src/components/resources/CrsPicker/CrsPicker.d.ts +41 -0
  23. package/dist/src/components/resources/CrsPicker/crs-recent.d.ts +40 -0
  24. package/dist/src/components/resources/CrsPicker/index.d.ts +14 -0
  25. package/dist/src/components/resources/resource-library-crs.d.ts +117 -0
  26. package/dist/src/components/ui/dialog.d.ts +25 -0
  27. package/dist/src/domain/algorithms/flow-field-style.d.ts +68 -0
  28. package/dist/src/domain/map/types.d.ts +2 -0
  29. package/dist/src/domain/models/crs.d.ts +187 -0
  30. package/dist/src/domain/models/dataset-kind.d.ts +256 -0
  31. package/dist/src/domain/models/flow-field-style.d.ts +32 -0
  32. package/dist/src/features/gis/components/GisAttributeTable.d.ts +11 -3
  33. package/dist/src/features/gis/hooks/useDatasetFeatureWindow.d.ts +5 -9
  34. package/dist/src/features/gis/lib/attributeFilters.d.ts +24 -0
  35. package/dist/src/sdk/facets/types.d.ts +73 -2
  36. package/dist/src/sdk/map-api.d.ts +5 -2
  37. package/dist/src/types/analysis.d.ts +111 -7
  38. package/dist/src/types/extensions.d.ts +43 -0
  39. package/dist/src/types/gis.d.ts +1 -1
  40. package/package.json +6 -37
  41. package/LICENSE +0 -13
  42. package/README.md +0 -25
@@ -0,0 +1,256 @@
1
+ /**
2
+ * Stage 29 (F29.1 / F29.4 / F29.5 / F29.7 / F29.9) — the frontend's dataset-kind
3
+ * domain model.
4
+ *
5
+ * **This module is the one place in the client that names a dataset kind.** It is
6
+ * the F25.1 `crs.ts` precedent applied to the second vocabulary this platform
7
+ * spelled seven ways: a dataset's kind was spelled seven different ways, in three
8
+ * casings, derived from four different source fields, and no two of the seven had
9
+ * the same membership. After Stage 29 there is **one** derivation chain — the
10
+ * server's `datasetType` — and `G-KIND` is the rule that nothing outside a
11
+ * registry module branches on a kind id.
12
+ *
13
+ * Every shape below mirrors the frozen fixtures in
14
+ * `src/domain/models/__fixtures__/dataset-kind/`, which are byte-identical copies
15
+ * of `ngis-backend/stages/29-resource-plane/fixtures/` (F29.10 — compare with
16
+ * `diff --strip-trailing-cr`, never an on-disk hash: the two repos normalise line
17
+ * endings differently).
18
+ *
19
+ * ## The three-state rule lives here
20
+ *
21
+ * {@link NgisKindCapabilities} is a `Partial<>` and that is **load-bearing**
22
+ * (F29.4, inheriting F25.8). A flag **absent** from the block means *the server
23
+ * has not said*; `{supported: false, reasonKey}` means *this kind cannot*;
24
+ * `{supported: true, reasonKey: null}` means *it can*. A client that coalesces
25
+ * absent into `false` has re-introduced exactly the defect Stage 26 paid to
26
+ * learn — which is why {@link datasetKindCapability} is the only supported way to
27
+ * read a flag and why it returns `undefined` rather than fabricating a record.
28
+ *
29
+ * @see ngis-backend/stages/29-resource-plane/F29-contract.md — F29.1, F29.4,
30
+ * F29.5, F29.7, F29.9 (frozen names)
31
+ * @see docs/resource_plane_stage/README.md — the frontend plan of record
32
+ */
33
+ /**
34
+ * **The one client dataset-kind type (F29.1).** The wire field keeps its shipped
35
+ * name, `datasetType`, and its value is a registered kind id in UPPERCASE
36
+ * `SNAKE_CASE`.
37
+ *
38
+ * **The union is OPEN, and that is the whole point.** `D29-1` makes a kind
39
+ * *registered data*, so a client shipped today must render a kind registered
40
+ * tomorrow. The three named members exist for autocomplete and for the
41
+ * exhaustiveness of *today's* switch statements — they are **not** a membership
42
+ * assertion, and **no test may assert that the set of kinds equals those three**.
43
+ * (Stage 15's `Market.*` sub-namespace lesson, which cost a session on three
44
+ * consecutive waves: a closed-world assertion breaks on every sibling merge.)
45
+ *
46
+ * `null` — not a member of this union — keeps exactly one meaning after F29.3:
47
+ * *no kind resolved* (`status: "NEEDS_KIND"`). `"GENERIC"` is gone from the wire;
48
+ * a non-geo file reports `datasetType: null` with `status: "N/A"`.
49
+ */
50
+ export type NgisDatasetKind = "VECTOR" | "RASTER" | "FLOW_FIELD" | (string & {});
51
+ /**
52
+ * **The six capability flags, frozen (F29.4).**
53
+ *
54
+ * `renderable` is deliberately **not** a member, and that is not an oversight:
55
+ * `renderable` is a server-computed property of a dataset *row*
56
+ * (`ready && geometryType != null && capabilities.tileable.supported === true`,
57
+ * F29.6), never a declared capability of a *kind*. Stage 28 ships a
58
+ * `capabilities.renderable` on a **symbol template**, an unrelated object that
59
+ * happens to share the field name; keeping `renderable` out of this union is what
60
+ * stops a symbol payload from satisfying a dataset capability block
61
+ * (`S29-V073`).
62
+ */
63
+ export type NgisCapabilityFlag = "tileable" | "analyzable" | "shareable" | "copyable" | "quotaCountable" | "exportable";
64
+ /** The six flags in the F29.4 order. */
65
+ export declare const NGIS_CAPABILITY_FLAGS: readonly NgisCapabilityFlag[];
66
+ /**
67
+ * One declared capability of a kind (F29.4 / F29.5).
68
+ *
69
+ * **A boolean cannot say why a control is greyed**, which is why this is a record
70
+ * and never a bare boolean. `reasonKey` is an **i18n key the client localises** —
71
+ * the server never sends display text for a capability refusal — and `params`
72
+ * carries its interpolation values. The in-repo precedent is shipped:
73
+ * `ValidationIssue.message_key` / `params_json` and `IssueFix.label_key` /
74
+ * `params_json` (`ngis-protos/geoanalysis.proto:553-572`); `IssueFix.params_json`
75
+ * exists *because* a key without its interpolation values made the gRPC and REST
76
+ * twins of one fixture disagree.
77
+ */
78
+ export interface NgisCapability {
79
+ supported: boolean;
80
+ /** Required when `supported === false`; **must** be `null` when `true`. */
81
+ reasonKey: string | null;
82
+ /** Interpolation values for `reasonKey`; `{}` when the key takes none. */
83
+ params: Record<string, unknown>;
84
+ }
85
+ /**
86
+ * A kind's declared capability block (F29.4).
87
+ *
88
+ * **`Partial<>` is load-bearing — there are three states, not two.** An absent
89
+ * flag means *the server has not said*, and it must never render as
90
+ * *this kind cannot*. Read a flag with {@link datasetKindCapability} and nothing
91
+ * else.
92
+ *
93
+ * `{}` is the **empty** block and differs from an absent block: it means *no kind
94
+ * resolved, therefore no declared capabilities* (`fx-ds-0003`, `status:
95
+ * "NEEDS_KIND"`). Every flag is then in its third state.
96
+ *
97
+ * The block describes the **kind**, not the row's readiness. A `NEEDS_CRS` vector
98
+ * is still `tileable: true`; it is `ready: false` that stops it rendering.
99
+ * Conflating the two is how `renderable` came to name three kinds.
100
+ */
101
+ export type NgisKindCapabilities = Partial<Record<NgisCapabilityFlag, NgisCapability>>;
102
+ /**
103
+ * Read one declared capability — **the only supported way to read a flag.**
104
+ *
105
+ * Returns `undefined` for an absent flag and **never fabricates
106
+ * `{supported: false}`**. That is F29.4's three-state rule enforced in one
107
+ * function so no call site can get it wrong; a caller that wants a boolean must
108
+ * decide, explicitly and at its own site, what *"the server has not said"* means
109
+ * for the control it is drawing.
110
+ *
111
+ * The flag argument is typed {@link NgisCapabilityFlag}, so
112
+ * `datasetKindCapability(caps, "renderable")` is a **compile error**
113
+ * (`S29-V073`).
114
+ */
115
+ export declare function datasetKindCapability(caps: NgisKindCapabilities | null | undefined, flag: NgisCapabilityFlag): NgisCapability | undefined;
116
+ /**
117
+ * Why a dataset's time axis is or is not known (F29.7, `D29-3` generalised from
118
+ * CRS).
119
+ *
120
+ * There are **four** states, and the fourth is the `temporalExtent` field itself
121
+ * being `null`, which means *the server has not said* (F25.8) — distinct from
122
+ * `NOT_APPLICABLE`, which means *this kind has no time axis*.
123
+ */
124
+ export type NgisTemporalExtentState = "RESOLVED" | "UNRESOLVABLE" | "NOT_APPLICABLE";
125
+ /**
126
+ * A dataset's temporal extent (F29.7).
127
+ *
128
+ * **Never a bare range.** A defaulted range — epoch-to-now, the file's mtime,
129
+ * anything — is a contract violation, not a fallback (`D29-3`). `fx-ds-0005` is
130
+ * the fixture that proves it: `start` and `end` are `null` **and** `state` says
131
+ * why.
132
+ *
133
+ * What each state renders as (F29.7, frozen because `D29-3` forbids a default):
134
+ *
135
+ * | State | Renders as |
136
+ * |---|---|
137
+ * | the field is `null` | the temporal row is **not rendered at all** |
138
+ * | `NOT_APPLICABLE` | the temporal row is **not rendered at all** |
139
+ * | `UNRESOLVABLE` | the row renders, with the localised `reasonKey` where the range would be — never a dash, never an empty range |
140
+ * | `RESOLVED` | the range |
141
+ *
142
+ * `null` and `NOT_APPLICABLE` render identically **on purpose**: there is nothing
143
+ * useful to say to a user in either case. They stay distinct **on the wire**,
144
+ * because a test must be able to tell them apart and the array stage will need to.
145
+ */
146
+ export interface NgisTemporalExtent {
147
+ state: NgisTemporalExtentState;
148
+ /** ISO 8601; non-null iff `state === "RESOLVED"`. */
149
+ start: string | null;
150
+ /** ISO 8601; non-null iff `state === "RESOLVED"`. */
151
+ end: string | null;
152
+ /** An i18n key; non-null iff `state === "UNRESOLVABLE"`. */
153
+ reasonKey: string | null;
154
+ params: Record<string, unknown>;
155
+ }
156
+ /**
157
+ * Where a dataset came from (F29.7, `D29-5`).
158
+ *
159
+ * `authority` is `AUTHORITY` or `DERIVED`, never both. A `DERIVED` dataset
160
+ * carries `rebuildableFrom`; an `AUTHORITY` one carries `null` there.
161
+ *
162
+ * `sourceFileId` is **not** unique across datasets: `fx-ds-0005` and `fx-ds-0006`
163
+ * share one. That is `D29-2` — one file, many datasets.
164
+ */
165
+ export interface NgisDatasetLineage {
166
+ authority: "AUTHORITY" | "DERIVED";
167
+ sourceFileId: string | null;
168
+ derivedFromFileId: string | null;
169
+ producedByTaskId: string | null;
170
+ /** Non-null iff `authority === "DERIVED"`. */
171
+ rebuildableFrom: string | null;
172
+ }
173
+ /**
174
+ * The array plane's placeholder (F29.7, `D29-S1`).
175
+ *
176
+ * **Deliberately typed `null` and nothing else.** `dimensions` is declared on the
177
+ * wire so the array stage needs no second migration and no second contract
178
+ * freeze, and it is `null` for every kind Stage 29 registers. Typing it as the
179
+ * `null` literal makes *"a session that builds a time slider off it is out of
180
+ * contract"* a compile error rather than a code-review note; widening this alias
181
+ * is the array stage's job and nobody else's.
182
+ */
183
+ export type NgisDatasetDimensions = null;
184
+ /**
185
+ * **The dataset spine (F29.7), as one nominal shape.**
186
+ *
187
+ * *Recorded extra export, not in F29.9's table — see the S29-14 session note.*
188
+ * It exists for `S29-V073`: `renderable` and `ready` are members of a record that
189
+ * also requires `datasetType`, `status` and `lineage`, so a Stage 28 **symbol
190
+ * template** — which carries `capabilities.renderable` and none of those — cannot
191
+ * resolve against the client's **dataset** `renderable`. Two unrelated objects
192
+ * share one field name; this type and {@link NgisCapabilityFlag}'s exclusion of
193
+ * `renderable` are the two independent guards that keep them apart.
194
+ *
195
+ * `ready` and `renderable` are server-computed and keep their names (F29.6):
196
+ *
197
+ * ```
198
+ * ready = status ∈ kind.readyStates
199
+ * renderable = ready && geometryType != null && capabilities.tileable.supported === true
200
+ * ```
201
+ *
202
+ * Both are optional, and `undefined` ("the server has not said") must never render
203
+ * the same as `false` ("this kind cannot be tiled") — `SceneWorkspaceLayers.tsx`'s
204
+ * `resolveLayerCrs` is the in-repo precedent and its comment states the rule.
205
+ */
206
+ export interface NgisDatasetSpine {
207
+ /** `null` means *no kind resolved* — the only meaning `null` carries (F29.3). */
208
+ datasetType: NgisDatasetKind | null;
209
+ /**
210
+ * **An OPEN vocabulary (F29.7 R2).** A consumer that does not recognise a
211
+ * status renders the **raw token**, never `UNKNOWN`. The measured hazard is
212
+ * live: `ngis-file`'s `FileService` normalises a status through a whitelist
213
+ * whose default arm is `UNKNOWN`, so a `NEEDS_CRS` dataset already reports as
214
+ * `UNKNOWN` on the library listing — *"we know exactly what is wrong with it"*
215
+ * collapsed into *"we do not know"*, which is the very thing `D29-3` forbids.
216
+ */
217
+ status: string;
218
+ capabilities?: NgisKindCapabilities;
219
+ temporalExtent?: NgisTemporalExtent | null;
220
+ dimensions?: NgisDatasetDimensions;
221
+ lineage?: NgisDatasetLineage | null;
222
+ ready?: boolean;
223
+ renderable?: boolean;
224
+ }
225
+ /** The minimum an issue must carry for {@link isKindAdaptableIssue} to judge it. */
226
+ export interface NgisIssueCodeCarrier {
227
+ code?: string | null;
228
+ }
229
+ /**
230
+ * F29.8's rule A1, in one place: is this the **kind** adaptation, or Stage 26's
231
+ * **CRS** one?
232
+ *
233
+ * `"ADAPTABLE"` is the same verdict token for both. **Only the issue code
234
+ * distinguishes them** — a test that asserts only `verdict === "ADAPTABLE"`
235
+ * passes on a CRS adaptation and proves Stage 26's code path, not this stage's.
236
+ *
237
+ * The client never re-classifies a server verdict or severity (`G-U`): this is a
238
+ * predicate over a code the server sent, and nothing more.
239
+ */
240
+ export declare function isKindAdaptableIssue(issue: NgisIssueCodeCarrier | null | undefined): boolean;
241
+ /**
242
+ * The kind → scene-layer resource-kind projection.
243
+ *
244
+ * *Recorded extra export, not in F29.9's table — see the S29-14 session note.*
245
+ * It is what F29.1 row 6 means by *"S29-14 delegates it to `NgisDatasetKind`"*:
246
+ * `SceneLayerResourceKind` used to be reached by `geometry.includes("raster")`, a
247
+ * substring match on a geometry string, and that is a guess. The spine now
248
+ * carries the answer, and this is the one place the answer is projected onto the
249
+ * two-value vocabulary the scene-layer type table is written in.
250
+ *
251
+ * Returns `null` for a kind that is neither — including an unregistered kind the
252
+ * client has never seen, which is `NgisDatasetKind` being open — so a caller must
253
+ * decide what *"I cannot place this kind"* means rather than being handed
254
+ * `"vector"` by default.
255
+ */
256
+ export declare function sceneLayerResourceKindOfDatasetKind(kind: NgisDatasetKind | null | undefined): "vector" | "raster" | null;
@@ -0,0 +1,32 @@
1
+ /** Canonical, per-scene-layer presentation state for a FLOW_FIELD resource. */
2
+ export interface FlowFieldColorRamp {
3
+ /** A UI hint only. `colors` is the renderer authority. */
4
+ presetId: string;
5
+ /** Low-speed to high-speed colors. */
6
+ colors: string[];
7
+ }
8
+ export type FlowFieldSpeedRange = {
9
+ mode: "data";
10
+ } | {
11
+ mode: "manual";
12
+ min: number;
13
+ max: number;
14
+ };
15
+ export interface FlowFieldStyle {
16
+ renderer: "flow-field";
17
+ styleVersion: 1;
18
+ /** Compatibility-only identity for scenes created before SceneResource links. */
19
+ flowFieldId?: string;
20
+ opacity: number;
21
+ colorRamp: FlowFieldColorRamp;
22
+ speedRange: FlowFieldSpeedRange;
23
+ speedFactor: number;
24
+ particleCount: number;
25
+ lineWidth: number;
26
+ trailLength: number;
27
+ frameDurationMs: number;
28
+ paused: boolean;
29
+ maxParticleAge: number;
30
+ dropRate: number;
31
+ dropRateBump: number;
32
+ }
@@ -1,4 +1,4 @@
1
- import { type DatasetAttributeSelection, type DatasetFeatureRow } from "../../../api/resources";
1
+ import { type DatasetAttributeFilter, type DatasetAttributeSelection, type DatasetFeatureRow } from "../../../api/resources";
2
2
  /**
3
3
  * **Exported for `@ngis/plugin-ui`'s `<AttributeTable>` (Stage 14 Y4b).** It was
4
4
  * file-private while this component had exactly one call site
@@ -30,20 +30,28 @@ export interface GisAttributeTableProps {
30
30
  onRunTool: (selection: DatasetAttributeSelection) => Promise<void>;
31
31
  /** A selection made with the scene's feature-select tool, keyed by stable row id. */
32
32
  mapSelection?: {
33
- featureIds: string[];
33
+ selection: GisAttributeTableSelectionHighlight;
34
34
  /** Attribute values present in the selected vector-tile features. */
35
35
  rows?: DatasetFeatureRow[];
36
+ /** Opens this selection in the table's normal “only selected” mode. */
37
+ showSelectedOnly?: boolean;
36
38
  serial: number;
37
39
  } | null;
38
40
  }
41
+ /** Whether a fresh external selection must keep the selected-only view enabled. */
42
+ export declare function isApplyingMapSelectedOnly(mapSelection: GisAttributeTableProps["mapSelection"], appliedSerial: number | null): boolean;
39
43
  /** The table's complete selection meaning, forwarded to the map highlight. */
40
44
  export type GisAttributeTableSelectionHighlight = {
41
45
  featureIds: string[];
42
46
  allFeatures?: false;
47
+ selectedCount: number;
43
48
  } | {
44
49
  featureIds: [];
45
50
  allFeatures: true;
46
51
  excludedFeatureIds: string[];
52
+ selectedCount: number | null;
53
+ /** The same API filter that defines this all-matching selection. */
54
+ filter?: DatasetAttributeFilter;
47
55
  };
48
56
  /**
49
57
  * Attribute table for the selected vector layer's dataset. Server-driven:
@@ -53,4 +61,4 @@ export type GisAttributeTableSelectionHighlight = {
53
61
  * a column picker, a filter builder, and dataset-wide selection keyed by the
54
62
  * stable feature `id` (so it spans windows that aren't even loaded).
55
63
  */
56
- export declare function GisAttributeTable({ fileId, layerName, active, onHighlight, onSelectionChange, onRunTool, mapSelection, }: GisAttributeTableProps): import("react/jsx-runtime").JSX.Element;
64
+ export declare function GisAttributeTable({ fileId, active, onHighlight, onSelectionChange, onRunTool, mapSelection, }: GisAttributeTableProps): import("react/jsx-runtime").JSX.Element;
@@ -1,23 +1,19 @@
1
- import { type DatasetAttributeQueryParams, type DatasetAttributeWhereOp, type DatasetFeatureRow } from "../../../api/resources";
1
+ import { type DatasetAttributeFilter, type DatasetAttributeQueryParams, type DatasetFeatureRow } from "../../../api/resources";
2
2
  /** One server-side sort directive (single-column for now). */
3
3
  export interface FeatureSort {
4
4
  field: string;
5
5
  dir: "asc" | "desc";
6
6
  }
7
- /** One server-side filter condition (mirrors the query `where` clause). */
8
- export interface FeatureFilter {
9
- field: string;
10
- op: DatasetAttributeWhereOp;
11
- value: unknown;
12
- }
13
7
  export interface DatasetFeatureWindowParams {
14
8
  fileId: string;
15
9
  /** Active sort (null = server default order). */
16
10
  sort: FeatureSort | null;
17
- /** Active filter conditions (AND-combined). */
18
- filters: FeatureFilter[];
11
+ /** Final API FilterSpec, shared with count and selection actions. */
12
+ filter?: DatasetAttributeFilter;
19
13
  /** Columns to request (empty = all). `id` is always returned regardless. */
20
14
  select: string[];
15
+ /** Enumerated stable ids used by normal attribute queries and pagination. */
16
+ featureIds?: string[];
21
17
  enabled?: boolean;
22
18
  }
23
19
  export interface DatasetFeatureWindow {
@@ -0,0 +1,24 @@
1
+ import type { DatasetAttributeFilter, DatasetAttributeWhereOp } from "../../../api/resources";
2
+ import type { HighlightFilterExpression } from "../../../domain/map/types";
3
+ /** UI-only `between` expands to the two comparison operators the API supports. */
4
+ export type AttributeFilterOperator = DatasetAttributeWhereOp | "between";
5
+ export interface AttributeFilterCondition {
6
+ field: string;
7
+ op: AttributeFilterOperator;
8
+ value: unknown;
9
+ }
10
+ /** Escape SQL LIKE metacharacters before surrounding a user keyword with `%`. */
11
+ export declare function escapeLikeKeyword(keyword: string): string;
12
+ /**
13
+ * Converts UI filter conditions into the API's supported, AND-combined where
14
+ * clauses. This is the sole FilterSpec used by query, count, and all-matching
15
+ * selection actions.
16
+ */
17
+ export declare function buildDatasetAttributeFilter(conditions: readonly AttributeFilterCondition[]): DatasetAttributeFilter | undefined;
18
+ /**
19
+ * Converts the API filter already used for the table query into the equivalent
20
+ * MapLibre predicate for the attribute-selection overlay. `like` arrives here
21
+ * in the frontend's deliberate `%escaped keyword%` form, so it maps to a
22
+ * literal substring match rather than a wildcard expression.
23
+ */
24
+ export declare function buildAttributeHighlightFilter(filter: DatasetAttributeFilter | undefined): HighlightFilterExpression | undefined;
@@ -32,7 +32,9 @@
32
32
  */
33
33
  import type { AnalysisToolSchema } from "../../api/analysis";
34
34
  import type { SceneType } from "../../api/scenes";
35
+ import type { NgisCrsRef } from "../../domain/models/crs";
35
36
  import type { NgisExecutionValidation, NgisToolUiSchema } from "../../types/analysis";
37
+ import type { NgisResolvedToolDependency } from "../../types/extensions";
36
38
  import type { NgisBounds, NgisUnsubscribe } from "../map-api";
37
39
  export interface NgisSceneInfo {
38
40
  id: string;
@@ -59,6 +61,18 @@ export interface NgisSceneFacet {
59
61
  * `"table"` would need `NgisFile.fileType`, i.e. a second
60
62
  * `GET /api/files/{fileId}` per resource — an N+1 fan-out for a distinction no
61
63
  * v1 consumer needs.
64
+ *
65
+ * **SURVIVES Stage 29 UNCHANGED (F29.1 row 3).** It is the SDK's v1
66
+ * plugin-facing classification, frozen by XD7/F13.6 and enforced by two live
67
+ * gates, and Stage 29 registers **no table dataset model** — so nothing about it
68
+ * moves (F29.2). It is a *projection* of `NgisDatasetKind` onto the coarser
69
+ * vocabulary v1 plugins compile against, never a second spelling of it: its
70
+ * membership is closed because a plugin's `when` expression is, and
71
+ * `NgisDatasetKind` is open because a kind is registered data. Widening this one
72
+ * to match would break every shipped plugin fixture.
73
+ *
74
+ * *(`GENERIC` no longer reaches the classifier — F29.3 removed it from the wire —
75
+ * but the "everything else" arm is unchanged and still catches it.)*
62
76
  */
63
77
  export type NgisResourceType = "vector" | "raster" | "table" | "file";
64
78
  /**
@@ -89,6 +103,13 @@ export type NgisResourceType = "vector" | "raster" | "table" | "file";
89
103
  * against a builtin node's `"Raster"` and `400`s at workflow save. Y6 applies
90
104
  * that column at, and **only** at, the `ngis-workflow` boundary. Nowhere else
91
105
  * in either repo is Pascal correct.
106
+ *
107
+ * **SURVIVES Stage 29 UNCHANGED (F29.1 row 4).** It is a **tool port** data type
108
+ * (F14.1), not a dataset kind. The two are related by the compatibility matrix
109
+ * and **never by equality**: `table` and `pointcloud` are legal port types that
110
+ * no registered kind emits, and a registered kind the client has never seen binds
111
+ * to a port through the matrix rather than by name. Renaming or merging them
112
+ * would assert an identity that does not hold.
92
113
  */
93
114
  export type NgisDataType = "raster" | "vector" | "table" | "pointcloud" | "raster3d" | "file";
94
115
  export declare const NGIS_DATA_TYPES: readonly ["raster", "vector", "table", "pointcloud", "raster3d", "file"];
@@ -130,6 +151,25 @@ export interface NgisResourceSummary {
130
151
  geometryType: string | null;
131
152
  layerCount: number;
132
153
  rendered: boolean;
154
+ /**
155
+ * **Stage 25 (F25.1 / F25.9, `S25-V216`).** The dataset's one CRS, or `null`
156
+ * when the server has none for it (`NEEDS_CRS`).
157
+ *
158
+ * It is on the **summary**, not only the detail, and that placement is the
159
+ * point of the row. `ResourceSelect` lists datasets from `resources.list()`
160
+ * and must draw each row's projection state at pick time (F25.5); resolving a
161
+ * ref through `resources.get()` per row is the N+1 XD7 already refused for
162
+ * `resources.types`. Without this field the picker could draw only the
163
+ * *absent* state — which is what S25-22 shipped, with the limit recorded
164
+ * rather than papered over — because a dataset that *has* a projection had
165
+ * nothing to draw a chip from and inventing one is what G-CRS forbids.
166
+ *
167
+ * `null` is a value and not a gap: it is the server saying `NEEDS_CRS`. There
168
+ * is no `undefined` arm — the facet always answers, and a dataset it could not
169
+ * read at all reports `datasetStatus: "N/A"` with `crs: null`, which is the
170
+ * same honest "we cannot say what this is in".
171
+ */
172
+ crs: NgisCrsRef | null;
133
173
  /**
134
174
  * Scene layers that render this resource, when the host can supply them.
135
175
  *
@@ -160,7 +200,6 @@ export interface NgisResourceField {
160
200
  type: string;
161
201
  }
162
202
  export interface NgisResourceDetail extends NgisResourceSummary {
163
- srid: number | null;
164
203
  /** Vector only; `null` otherwise. */
165
204
  featureCount: number | null;
166
205
  /** Vector only; `[]` otherwise. */
@@ -244,7 +283,21 @@ export interface NgisModelValidateRequest {
244
283
  expectedSceneGeneration?: number;
245
284
  outputMode?: "ADD_LAYER" | "UPDATE_RESOURCE";
246
285
  targetResourceId?: string;
247
- targetSrid?: number;
286
+ /**
287
+ * **Stage 25 (F25.4, and the `S25-V124` ruling that AMENDS F25.12).**
288
+ *
289
+ * Replaces `targetSrid?: number`. It is an {@link NgisCrsRef} **key** — the
290
+ * string `"EPSG:32650"` or `"custom:<ulid>"` — and never an integer, because
291
+ * an integer cannot name a custom CRS at all and because a bare number is the
292
+ * silent-default spelling G-CRS refuses.
293
+ *
294
+ * `targetSrid` was typed, forwarded to `validate-execution` and set by no
295
+ * component: dead plumbing. This member is the live one — `run-data.ts` fills
296
+ * it from the form's `ngis:outputCrs` parameter and the facade adapter puts it
297
+ * on the wire as `outputCrs` (the REST leg is camelCase here; the proto
298
+ * spelling is `output_crs`).
299
+ */
300
+ outputCrs?: string;
248
301
  }
249
302
  export interface NgisModelSearchRequest {
250
303
  query?: string;
@@ -300,6 +353,12 @@ export interface NgisModelsFacet {
300
353
  /** The one normalized UI schema (F14.2). `null` when the tool or its schema
301
354
  * is absent. Scope `models:read`. */
302
355
  uiSchema(toolId: string): Promise<NgisToolUiSchema | null>;
356
+ /**
357
+ * Resolves a declared `manifest.toolDependencies[].alias` for this plugin and
358
+ * viewer. The unavailable arm is a normal, renderable result; it never leaks
359
+ * implementation UUIDs into plugin manifests. Scope `models:read`.
360
+ */
361
+ dependency(alias: string): Promise<NgisResolvedToolDependency>;
303
362
  /** REST projection of `ValidateToolExecution`. Returns the response DTO
304
363
  * **verbatim**. Scope `models:read`, **not** `models:run` — it runs nothing,
305
364
  * and returns only registry facts plus dataset metadata the caller can
@@ -324,6 +383,18 @@ export interface NgisTaskRunRequest {
324
383
  expectedSceneGeneration?: number;
325
384
  outputMode?: "ADD_LAYER" | "UPDATE_RESOURCE";
326
385
  targetResourceId?: string;
386
+ /**
387
+ * **Stage 25 (F25.4).** The output CRS this run computes in and stores its
388
+ * result as — an {@link NgisCrsRef} key, never an integer. Absent means
389
+ * "compute in the primary input's own CRS", which is a real choice and not a
390
+ * default: the server never substitutes one (G-CRS).
391
+ *
392
+ * This is the member that makes exit criterion 6 true. `submitRun` fills it,
393
+ * the tasks facet spreads the request through to the DIRECT source, and
394
+ * `createGisTasksSource` hands it to `startTrackedAnalysisTask`, which puts
395
+ * `outputCrs` in the `POST /api/analysis/tasks` body.
396
+ */
397
+ outputCrs?: string;
327
398
  }
328
399
  export interface NgisTaskHandle {
329
400
  /** DIRECT: the analysis task id. WORKFLOW: the workflow instance id. */
@@ -38,7 +38,7 @@
38
38
  import type { Map as MapLibreMap } from "maplibre-gl";
39
39
  import type { SceneLayerHighlight, SceneMapView } from "../domain/map/types";
40
40
  import type { GisDrawMode, GisMeasureKind } from "../types/gis";
41
- import type { WorkspacePanelRegistry } from "./panels";
41
+ import type { WorkspacePanelRegistry } from "@ngis/plugin-sdk";
42
42
  /** Current facade contract version — bumped when the frozen surface changes.
43
43
  * A plugin's `minNgisVersion` (`docs/frontend-map-sdk.md` §3) is
44
44
  * checked against this. Pre-1.0: the surface may still grow (additively).
@@ -73,7 +73,7 @@ import type { WorkspacePanelRegistry } from "./panels";
73
73
  * semver compare, **not** by the unknown-capability check: `contributes` and
74
74
  * `permissions` are top-level manifest siblings, and a 0.3 runtime inspects
75
75
  * only `manifest.capabilities`. */
76
- export declare const NGIS_MAP_API_VERSION = "0.4.0";
76
+ export declare const NGIS_MAP_API_VERSION = "0.4.1";
77
77
  /** A scene layer as the SDK exposes it — a stable projection of the store's
78
78
  * `NgisSceneLayer`, so plugins never depend on the store's internal shape. */
79
79
  export interface NgisLayerSummary {
@@ -262,6 +262,9 @@ export interface NgisMapAPIBindings {
262
262
  /** Raw-map getter for `getMap()`. Usually omitted at construction and wired
263
263
  * later via `NgisMapAPIHandle.bindRawMap` (the post-C5 hookup). */
264
264
  getRawMap?(): MapLibreMap | null;
265
+ /** Host hook run before direct raw-map camera commands. Keeps host-owned
266
+ * temporary camera controllers from racing this SDK escape hatch. */
267
+ beforeCameraCommand?(map: MapLibreMap): void;
265
268
  }
266
269
  /**
267
270
  * The factory's return value: the public `api` plus the host-side "driver"