@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
package/dist/index.cjs CHANGED
@@ -14,7 +14,7 @@ function hostComponent(name) {
14
14
  }
15
15
  return ui[name];
16
16
  }
17
- exports.NGIS_PLUGIN_UI_VERSION = "0.4.0";
17
+ exports.NGIS_PLUGIN_UI_VERSION = "0.4.1";
18
18
  exports.ResourceSelect = function ResourceSelect(props) {
19
19
  return hostComponent("ResourceSelect")(props);
20
20
  };
package/dist/index.d.ts CHANGED
@@ -19,7 +19,7 @@
19
19
  * ## Versions are contractually locked, not conventionally aligned (F14.13)
20
20
  *
21
21
  * ```
22
- * @ngis/plugin-sdk version === @ngis/plugin-ui version === NGIS_MAP_API_VERSION === "0.4.0"
22
+ * @ngis/plugin-sdk version === @ngis/plugin-ui version === NGIS_MAP_API_VERSION === "0.4.1"
23
23
  * ```
24
24
  *
25
25
  * ## Distribution (F14.14)
@@ -78,7 +78,7 @@
78
78
  * aligned). One string; any divergence is a lie about what the tarball
79
79
  * contains.
80
80
  */
81
- export declare const NGIS_PLUGIN_UI_VERSION = "0.4.0";
81
+ export declare const NGIS_PLUGIN_UI_VERSION = "0.4.1";
82
82
  export { ResourceSelect } from "./src/components/plugin-ui/ResourceSelect";
83
83
  export type { ResourceSelectBaseProps, ResourceSelectProps, } from "./src/components/plugin-ui/ResourceSelect";
84
84
  export { ModelSelect } from "./src/components/plugin-ui/ModelSelect";
package/dist/index.mjs CHANGED
@@ -12,7 +12,7 @@ function hostComponent(name) {
12
12
  }
13
13
  return ui[name];
14
14
  }
15
- export const NGIS_PLUGIN_UI_VERSION = "0.4.0";
15
+ export const NGIS_PLUGIN_UI_VERSION = "0.4.1";
16
16
  export function ResourceSelect(props) {
17
17
  return hostComponent("ResourceSelect")(props);
18
18
  }
@@ -1,3 +1,4 @@
1
+ import { type ApiRequestOptions } from "../lib/api";
1
2
  import type { NgisBindingValidation, NgisExecutionValidation, NgisToolSearchResponse, NgisToolUiSchema, NgisValidateBindingsBody, NgisValidateExecutionBody } from "../types/analysis";
2
3
  import type { PageResult } from "../types/common";
3
4
  export interface AnalysisModelboxNode {
@@ -18,6 +19,19 @@ export interface AnalysisTool {
18
19
  enabled?: boolean | null;
19
20
  modelbox_id?: string | null;
20
21
  capability_key?: string | null;
22
+ /**
23
+ * Caller-specific access metadata. The server derives this from the
24
+ * authenticated principal; it is a display affordance, never authorization.
25
+ */
26
+ can_manage_access?: boolean;
27
+ /** `null` for platform-built tools, which have no mutable audience. */
28
+ visibility?: "PUBLIC" | "PRIVATE" | null;
29
+ /** Stable semantic identity; `id` remains an opaque implementation id. */
30
+ reference?: {
31
+ namespace: string;
32
+ toolKey: string;
33
+ contractVersion: string;
34
+ } | null;
21
35
  }
22
36
  export interface AnalysisToolSchemaProperty {
23
37
  title?: string | null;
@@ -57,6 +71,27 @@ export interface CreateAnalysisTaskParams {
57
71
  expected_scene_generation?: number;
58
72
  output_mode?: AnalysisTaskOutputMode;
59
73
  target_resource_id?: string;
74
+ /**
75
+ * **Stage 25 (F25.4) — the submit body's half of "the parameter reaches the
76
+ * run"** (exit criterion 6).
77
+ *
78
+ * An `NgisCrsRef` key (`"EPSG:32650"` / `"custom:<ulid>"`), never an integer.
79
+ * **camelCase in a snake_case body on purpose:** the W2 dispatch fixes the
80
+ * REST spelling as `outputCrs` while the proto carries `output_crs`, and
81
+ * matching the neighbours here would send a field the server does not read.
82
+ *
83
+ * Absent = compute in the primary input's own CRS. Nothing on this path
84
+ * invents a value for it.
85
+ *
86
+ * **`S25-V232` closed (S25-32, W3).** This field and
87
+ * `analysis-task-runtime.ts`'s parameter were added by S25-23 as a forced
88
+ * spillover — the last two hops to `POST /api/analysis/tasks`, owned by no W2
89
+ * session, without which exit criterion 6 had no wire to assert on. The W3
90
+ * dispatch assigned both files to S25-32; reviewed, unchanged, and pinned by
91
+ * `run-host.test.ts`'s assertion through the real `createGisPluginHostFactory`
92
+ * → `createGisTasksSource` → `startTrackedAnalysisTask` → `analysisApi` chain.
93
+ */
94
+ outputCrs?: string;
60
95
  }
61
96
  export interface AnalysisTask {
62
97
  id: string;
@@ -106,8 +141,10 @@ export declare const analysisApi: {
106
141
  capability_key?: string;
107
142
  modelbox_id?: string;
108
143
  recursive?: boolean;
144
+ /** Caller-relative catalog partition; defaults to the normal ACL-visible set. */
145
+ scope?: "ALL" | "MINE" | "PUBLIC" | "SHARED";
109
146
  }, signal?: AbortSignal): Promise<PageResult<AnalysisTool>>;
110
- getTool(toolId: string, signal?: AbortSignal): Promise<AnalysisTool>;
147
+ getTool(toolId: string, signal?: AbortSignal | ApiRequestOptions): Promise<AnalysisTool>;
111
148
  getToolSchema(toolId: string, signal?: AbortSignal): Promise<AnalysisToolSchema>;
112
149
  createTask(params: CreateAnalysisTaskParams, signal?: AbortSignal): Promise<AnalysisTask>;
113
150
  getTask(taskId: string, signal?: AbortSignal): Promise<AnalysisTask>;
@@ -0,0 +1,110 @@
1
+ import type { ApiRequestOptions } from "../lib/api";
2
+ import type { NgisCrsRef, NgisCrsRequirement } from "../domain/models/crs";
3
+ /**
4
+ * The CRS oracle client (Stage 25, **F25.2 / F25.13**).
5
+ *
6
+ * Four legs, and every one of them answers the same question — *what is this
7
+ * coordinate reference system?* — from a different starting point: a search
8
+ * string, a definition the user pasted, an extent, or a dataset that has none.
9
+ *
10
+ * ## Verb-first, deliberately
11
+ *
12
+ * F25.13 froze `searchCrs` / `resolveCrs` / `suggestCrs` / `assignDatasetCrs`
13
+ * rather than the design's bare `search` / `resolve` / `suggest`. Two reasons.
14
+ * It matches the house style of every other module here (`listScenes`,
15
+ * `getResource`), and a bare `search` collides with `models.search` at any call
16
+ * site that imports both — which the analysis surfaces do.
17
+ *
18
+ * ## The oracle never answers permissively
19
+ *
20
+ * An unreachable oracle must not be indistinguishable from "the CRS is 4326"
21
+ * (G-CRS). None of these legs has a fallback: a failed call rejects with the
22
+ * `ApiError` `@/lib/api` built, and the caller renders that. `resolveCrs`
23
+ * returning `null` means *the server read the definition and it is not a CRS* —
24
+ * a verdict, not a transport failure — which is why it is a distinct arm rather
25
+ * than a rejection the picker would have to guess at.
26
+ */
27
+ /** One row of `GET /api/geo/crs/search`. The blurb is server-authored copy. */
28
+ export interface NgisCrsSearchHit {
29
+ readonly crs: NgisCrsRef;
30
+ /** e.g. `"Between 114°E and 120°E, northern hemisphere"`. `null` when the oracle has none. */
31
+ readonly areaOfUse: string | null;
32
+ }
33
+ /** One row of `POST /api/geo/crs/suggest`, best first. */
34
+ export interface NgisCrsSuggestion extends NgisCrsSearchHit {
35
+ /**
36
+ * Why this one fits — `"Best fit for this extent"`. Server-authored, because
37
+ * the ranking rule lives in the oracle and a client that restated it would
38
+ * drift from the ranking it is labelling.
39
+ */
40
+ readonly reason: string | null;
41
+ }
42
+ /** `[west, south, east, north]` in EPSG:4326, the one form the suggest leg takes. */
43
+ export type NgisCrsBbox = readonly [number, number, number, number];
44
+ /** What `POST /api/geo/crs/resolve` made of a pasted definition. */
45
+ export type NgisCrsResolution = {
46
+ readonly kind: "resolved";
47
+ readonly crs: NgisCrsRef;
48
+ }
49
+ /** The server read it and it is not a usable CRS. A verdict, not a failure. */
50
+ | {
51
+ readonly kind: "rejected";
52
+ readonly reason: string | null;
53
+ };
54
+ /**
55
+ * The body of `PUT /api/datasets/{fileId}/crs`. **Exactly one of the two**, per
56
+ * F25.5: a registry key for a known CRS, or a WKT2 definition the server
57
+ * registers as a custom one. There is no third form and no numeric `srid`.
58
+ */
59
+ export type NgisAssignCrsBody = {
60
+ readonly key: string;
61
+ } | {
62
+ readonly wkt2: string;
63
+ };
64
+ /**
65
+ * `202` — the assign was accepted and the re-import is dispatched. The dataset
66
+ * shows the pipeline state until `DONE`; the assign **declared** the CRS and did
67
+ * not move a coordinate (G-A).
68
+ */
69
+ export interface NgisAssignCrsAccepted {
70
+ readonly fileId: string;
71
+ readonly status: string;
72
+ readonly crs: NgisCrsRef | null;
73
+ }
74
+ export declare const crsApi: {
75
+ /**
76
+ * Free-text search over the EPSG registry plus the caller's saved custom
77
+ * CRSs. `kind` narrows to the tool's `x-crs-requirement`, server-side — the
78
+ * picker says so in one line rather than silently returning fewer results
79
+ * (F25.2).
80
+ */
81
+ searchCrs(query: string, options?: {
82
+ kind?: NgisCrsRequirement;
83
+ limit?: number;
84
+ } & ApiRequestOptions): Promise<readonly NgisCrsSearchHit[]>;
85
+ /**
86
+ * Read a pasted WKT2 / PROJ string / `urn:` and report what it is — **without
87
+ * saving anything.** The picker previews `name` / kind / unit before "Use
88
+ * this", so the user commits to a definition they have seen resolved rather
89
+ * than to text they hope parses.
90
+ */
91
+ resolveCrs(definition: string, options?: ApiRequestOptions): Promise<NgisCrsResolution>;
92
+ /**
93
+ * What fits this extent, best first. The server already knows which zone the
94
+ * data sits in; making the user type "utm" to discover that is the ergonomic
95
+ * defect this stage removes, which is why Suggested is the picker's default
96
+ * tab whenever a caller supplies an extent (F25.2).
97
+ */
98
+ suggestCrs(bbox: NgisCrsBbox, options?: {
99
+ kind?: NgisCrsRequirement;
100
+ } & ApiRequestOptions): Promise<readonly NgisCrsSuggestion[]>;
101
+ /**
102
+ * Declare what a dataset's coordinates already are (F25.5). **This does not
103
+ * reproject** — G-A keeps the assign flow and the output-CRS parameter from
104
+ * ever sharing a verb, a label or a component mode.
105
+ *
106
+ * The body carries `{ key }` or `{ wkt2 }` under a `crs` envelope and never a
107
+ * numeric srid. Answers `202`; the row shows its pipeline state until `DONE`.
108
+ */
109
+ assignDatasetCrs(fileId: string, crs: NgisAssignCrsBody, options?: ApiRequestOptions): Promise<NgisAssignCrsAccepted>;
110
+ };
@@ -1,4 +1,14 @@
1
+ import type { NgisCrsRef } from "../domain/models/crs";
2
+ import { type NgisDatasetDimensions, type NgisDatasetKind, type NgisDatasetLineage, type NgisKindCapabilities, type NgisTemporalExtent } from "../domain/models/dataset-kind";
1
3
  export type ResourceScope = "mine" | "shared" | "public" | "trash";
4
+ /**
5
+ * **SURVIVES Stage 29, narrowed in meaning (F29.1 row 1).** It answers *"what
6
+ * shape of file is this"*, **not** *"what kind of dataset is this"* — it is
7
+ * derived from `fileType` (the extension) at {@link toResourceType}, so its
8
+ * `"table"` means *spreadsheet-shaped file* and has nothing to do with a dataset
9
+ * model (F29.2). The dataset kind is {@link NgisDatasetKind}. Keeping its name
10
+ * and its lowercase spelling is deliberate; renaming it is out of contract.
11
+ */
2
12
  export type ResourceType = "vector" | "raster" | "flow-field" | "table" | "file";
3
13
  export type ResourceStatus = "ready" | "processing" | "failed";
4
14
  export type ResourceAccess = "private" | "shared" | "team" | "public";
@@ -209,21 +219,125 @@ export interface DatasetInfo {
209
219
  snapshot?: boolean | null;
210
220
  snapshotId?: string | null;
211
221
  mode?: ResourceShareMode | string | null;
212
- datasetType: "VECTOR" | "RASTER" | "GENERIC" | string;
213
- status: "PENDING" | "PROCESSING" | "DONE" | "FAILED" | "NOT_REGISTERED" | "N/A" | string;
222
+ /**
223
+ * **Stage 29 (F29.1 row 2 / F29.3):** this field's own hand-written union is
224
+ * **RETIRED into {@link NgisDatasetKind}** — it was already the wire field, so
225
+ * the retirement is of the *second spelling*, not of the field.
226
+ *
227
+ * `"GENERIC"` is **removed**: a non-geo file now reports `null` with
228
+ * `status: "N/A"`, and `null` therefore carries exactly one meaning — *no kind
229
+ * resolved* (`status: "NEEDS_KIND"`, `fx-ds-0003`). A flow field that used to
230
+ * report `null` or `"GENERIC"` on two REST surfaces now reports `"FLOW_FIELD"`
231
+ * on all four (`fx-ds-0004`); that is a contract change, not a tidy-up.
232
+ *
233
+ * The union is **open** — do not write a test asserting the set of kinds.
234
+ */
235
+ datasetType: NgisDatasetKind | null;
236
+ /**
237
+ * **Stage 25 (F25.1):** `"NEEDS_CRS"` joins the union. It is a *state*, not a
238
+ * failure — the bytes are intact and every other fact about the dataset is
239
+ * known; what is missing is the one declaration nothing can be derived without.
240
+ *
241
+ * **Stage 29 (F29.7 R2):** `"READY"`, `"NEEDS_KIND"` and
242
+ * `"NEEDS_TEMPORAL_EXTENT"` join it, and the vocabulary is **OPEN**. A consumer
243
+ * that does not recognise a status renders the **raw token**, never `UNKNOWN` —
244
+ * the hazard is live rather than hypothetical, because `ngis-file` already
245
+ * normalises `NEEDS_CRS` to `UNKNOWN` on the library listing (`S29-V041`).
246
+ */
247
+ status: "PENDING" | "PROCESSING" | "DONE" | "READY" | "FAILED" | "NOT_REGISTERED" | "NEEDS_CRS" | "NEEDS_KIND" | "NEEDS_TEMPORAL_EXTENT" | "N/A" | string;
248
+ /**
249
+ * **Stage 25 (F25.1), replacing `vector.srid` / `raster.srid`.**
250
+ *
251
+ * One CRS per dataset, in one place, with an identity (`key`) rather than an
252
+ * integer. `null` means the server does not know — never `EPSG:4326`, and
253
+ * never `0`. The two numeric fields it replaces are gone from the wire as
254
+ * well as from this type: they were per-subtype, so a caller had to know
255
+ * whether it held a vector or a raster before it could ask a question that has
256
+ * the same answer either way, and neither could express "unknown" except as a
257
+ * falsy number every consumer read as a CRS.
258
+ */
259
+ crs?: NgisCrsRef | null;
260
+ /**
261
+ * Server-computed from the extent and **never stored** — what the dataset
262
+ * *would* sensibly be projected into. Present only when it would differ from
263
+ * `crs`; it is a suggestion the user may take, not a value in effect.
264
+ */
265
+ suggestedProjectedCrs?: NgisCrsRef | null;
266
+ /**
267
+ * **`S29-V039`, second half — a client defect, fixed here.** The server has
268
+ * emitted this field since Stage 26 (`DatasetService.java:410`) and the guide
269
+ * documented it, but **no TypeScript type in this repo declared it**, so every
270
+ * consumer that wanted it had to reach for `unknown`. It carries in six places
271
+ * in `__fixtures__/dataset-kind/dataset-spine.states.json`.
272
+ *
273
+ * It is the CRS the backfill *proposes* for a `NEEDS_CRS` dataset — a
274
+ * candidate, not a value in effect — and it is distinct from
275
+ * {@link DatasetInfo.suggestedProjectedCrs}, which proposes a **projection for
276
+ * a dataset whose CRS is already known**. A row may carry either, both or
277
+ * neither; they are never interchangeable.
278
+ */
279
+ crsCandidate?: NgisCrsRef | null;
280
+ /**
281
+ * **Stage 29 (F29.4).** The **kind's** declared capability block.
282
+ *
283
+ * **Three states, not two.** An absent flag means *the server has not said*;
284
+ * `{supported: false, reasonKey}` means *this kind cannot*. Read it with
285
+ * `datasetKindCapability` and never with `caps?.[flag]?.supported ?? false` —
286
+ * coalescing absent into `false` re-introduces the defect Stage 26 paid to
287
+ * learn. `{}` is the **empty** block (no kind resolved, `fx-ds-0003`) and is
288
+ * different again from an absent block.
289
+ *
290
+ * It describes the **kind**, not this row's readiness: a `NEEDS_CRS` vector is
291
+ * still `tileable: true`; it is {@link DatasetInfo.ready} that stops it
292
+ * rendering.
293
+ */
294
+ capabilities?: NgisKindCapabilities;
295
+ /**
296
+ * **Stage 29 (F29.7).** Never a bare range, and never absent-meaning-static —
297
+ * a defaulted range is a contract violation, not a fallback (`D29-3`). `null`
298
+ * is the fourth state and means *the server has not said*, which is distinct
299
+ * from `NOT_APPLICABLE` (*this kind has no time axis*).
300
+ */
301
+ temporalExtent?: NgisTemporalExtent | null;
302
+ /**
303
+ * **Stage 29 (F29.7).** Declared, and `null` for every kind this stage
304
+ * registers — `D29-S1` defers the array plane. Typed as the `null` literal on
305
+ * purpose: **a session that builds a time slider off this field is out of
306
+ * contract**, and this makes that a compile error.
307
+ */
308
+ dimensions?: NgisDatasetDimensions;
309
+ /** **Stage 29 (F29.7 / `D29-5`).** Where this dataset came from. */
310
+ lineage?: NgisDatasetLineage | null;
311
+ /**
312
+ * **Stage 29 (F29.6).** Server-computed: `status ∈ kind.readyStates`. Keeps its
313
+ * name and stays server-computed; it is **not** derived client-side.
314
+ *
315
+ * `undefined` ("the server has not said") and `false` ("this row is not ready")
316
+ * must never render the same.
317
+ */
318
+ ready?: boolean;
319
+ /**
320
+ * **Stage 29 (F29.6).** Server-computed:
321
+ * `ready && geometryType != null && capabilities.tileable.supported === true`.
322
+ *
323
+ * **This is a property of a DATASET.** Stage 28 ships an unrelated
324
+ * `capabilities.renderable` on a **symbol template**; the two share a field
325
+ * name and nothing else (`S29-V073`). `renderable` is not a member of
326
+ * `NgisCapabilityFlag`, so a symbol template's block is not an
327
+ * `NgisKindCapabilities`.
328
+ *
329
+ * `undefined` and `false` must never render the same.
330
+ */
331
+ renderable?: boolean;
214
332
  vector?: {
215
333
  geometryType?: string | null;
216
334
  featureCount?: number | null;
217
- srid?: number | null;
218
- nativeSrid?: number | null;
219
- workingSrid?: number | null;
220
335
  fieldsSchema?: DatasetField[] | string | null;
221
336
  pgTableName?: string | null;
222
337
  errorMessage?: string | null;
223
338
  } | null;
224
339
  raster?: {
225
340
  cogVariantId?: string | null;
226
- srid?: number | null;
227
341
  bandCount?: number | null;
228
342
  noDataValue?: number | null;
229
343
  pixelSizeX?: number | null;
@@ -296,7 +410,7 @@ export interface DatasetAttributeClassifyResult {
296
410
  classes: number;
297
411
  breaks: number[];
298
412
  }
299
- export type DatasetAttributeWhereOp = "=" | "!=" | ">" | ">=" | "<" | "<=" | "like" | "in" | "between";
413
+ export type DatasetAttributeWhereOp = "=" | "!=" | ">" | ">=" | "<" | "<=" | "like";
300
414
  export interface DatasetAttributeQueryParams {
301
415
  select?: string[];
302
416
  filter?: {
@@ -317,6 +431,8 @@ export interface DatasetAttributeQueryParams {
317
431
  cursor?: string;
318
432
  };
319
433
  includeGeometry?: boolean;
434
+ /** Enumerated stable feature ids, combined with any filter/sort before paging. */
435
+ ids?: string[];
320
436
  }
321
437
  /** One feature row from `attributes/query`. `id` is a stable per-feature UUID,
322
438
  * returned on every row regardless of `select`, and usable for selection. */
@@ -400,7 +516,13 @@ export interface ResourceListItem {
400
516
  sizeBytes: number;
401
517
  size: string;
402
518
  features: string;
403
- srid: string;
519
+ /**
520
+ * **Stage 25 (F25.1):** was `srid: string`, a pre-formatted label that
521
+ * rendered "unknown" and "not applicable" identically as `"-"`. It is now the
522
+ * ref itself, so the row can draw the CRS chip, the unit sigil and the
523
+ * "No projection" badge from one value instead of parsing a string back apart.
524
+ */
525
+ crs: NgisCrsRef | null;
404
526
  geometry: string;
405
527
  format: string;
406
528
  bbox: string;
@@ -417,10 +539,29 @@ export interface ResourceListItem {
417
539
  readMode: ResourceReadMode;
418
540
  snapshotId?: string | null;
419
541
  shareMode: ResourceShareMode;
542
+ /**
543
+ * **Stage 29 (F29.4).** The kind's declared capability block, carried through
544
+ * unchanged so the row can render *why* a control is refused instead of hiding
545
+ * it (`S29-V030`, owned by `S29-15`).
546
+ *
547
+ * **Three states.** `undefined` is *the server has not said*; `{}` is *no kind
548
+ * resolved, therefore no declared capabilities*; a present flag is the answer.
549
+ * The `can*` booleans below collapse all three into a decision, which is what a
550
+ * boolean is for — this field is what carries the reason the boolean cannot.
551
+ */
552
+ capabilities?: NgisKindCapabilities;
420
553
  canCopy: boolean;
421
554
  canDelete: boolean;
422
555
  canManageSharing: boolean;
423
556
  canRepublish: boolean;
557
+ /**
558
+ * **Keeps its kind branch, with a stated reason (F29.4).** Retry is not one of
559
+ * F29.4's six flags and no kind declares it, so there is nothing to delegate
560
+ * to. The branch is a *pipeline* fact — a flow field's build is retried through
561
+ * its own builder, not through the dataset re-ingest this control calls — and
562
+ * inventing a seventh flag to carry it would be exactly the "invent a
563
+ * vocabulary" move this stage exists to stop. Carried as `S29-V111`.
564
+ */
424
565
  canRetry: boolean;
425
566
  }
426
567
  export interface ResourceListResult {
@@ -489,6 +630,10 @@ export declare const resourcesApi: {
489
630
  cancelUpload(uploadId: string): Promise<void>;
490
631
  copyToLibrary(fileId: string, folderId?: string | null): Promise<NgisFileRef>;
491
632
  listGroups(): Promise<NgisUserGroup[]>;
633
+ /** Existing permission evaluator: layer WRITE differs from owner-only config. */
634
+ checkSceneWritePermission(sceneId: string): Promise<{
635
+ allowed: boolean;
636
+ }>;
492
637
  listSharingPermissions(resourceId: string, resourceType?: SharingResourceType): Promise<NgisResourcePermission[]>;
493
638
  grantSharingPermission(params: GrantSharingParams): Promise<NgisResourcePermission>;
494
639
  revokeSharingPermission(resourceId: string, principalType: SharingPrincipalType, principalId?: string | null, resourceType?: SharingResourceType): Promise<void>;
@@ -499,7 +644,7 @@ export declare const resourcesApi: {
499
644
  getDatasetAttributeMeta(fileId: string): Promise<DatasetAttributeMeta>;
500
645
  queryDatasetAttributes(fileId: string, params: DatasetAttributeQueryParams, signal?: AbortSignal): Promise<DatasetAttributeQueryResult>;
501
646
  /** Exact count of rows matching `filter` (omit/`{}` → unfiltered total). */
502
- countDatasetAttributes(fileId: string, filter?: DatasetAttributeFilter, signal?: AbortSignal): Promise<DatasetAttributeCountResult>;
647
+ countDatasetAttributes(fileId: string, filter?: DatasetAttributeFilter, ids?: string[], signal?: AbortSignal): Promise<DatasetAttributeCountResult>;
503
648
  /** Stream the selection as a CSV / GeoJSON file download. */
504
649
  exportDatasetAttributes(fileId: string, params: ExportDatasetAttributesParams): Promise<ExportDatasetAttributesResult>;
505
650
  /** Materialize the selection into a new derived dataset (import pipeline). */
@@ -508,7 +653,26 @@ export declare const resourcesApi: {
508
653
  getDatasetAttributeStats(fileId: string, field: string): Promise<DatasetAttributeStats>;
509
654
  classifyDatasetAttribute(fileId: string, params: ClassifyDatasetAttributeParams): Promise<DatasetAttributeClassifyResult>;
510
655
  uploadFile(file: File, options?: UploadFileOptions): Promise<NgisFile>;
511
- download(fileId: string, scope: ResourceScope): Promise<Response>;
656
+ /**
657
+ * **Stage 25 (F25.5 / F25.12):** the download leg takes an optional
658
+ * `format` · `crs` pair — the two fields of the "Download as…" dialog.
659
+ *
660
+ * Both are **omitted when they are the default**: no `format` means the
661
+ * original bytes, and no `crs` means the dataset's own. That is what keeps
662
+ * this call byte-identical to the pre-stage one for every caller that does not
663
+ * ask for a conversion, and it is why the caller decides — `downloadQuery` in
664
+ * `resource-library-crs.ts` — rather than this function defaulting anything.
665
+ *
666
+ * `crs` is a **`CrsRef` key** (`"EPSG:4547"` / `"custom:<ulid>"`), never an
667
+ * integer: an integer cannot name a custom CRS, which is the whole reason the
668
+ * key exists. `?srid=` survives the stage only as a deprecated alias the
669
+ * server accepts from older clients; nothing here emits it.
670
+ */
671
+ download(fileId: string, scope: ResourceScope, options?: {
672
+ format?: string;
673
+ crs?: string;
674
+ signal?: AbortSignal;
675
+ }): Promise<Response>;
512
676
  };
513
677
  export declare const storageQuotaApi: {
514
678
  get(): Promise<StorageQuota>;
@@ -1,4 +1,6 @@
1
1
  import type { ResourceShareMode } from "./resources";
2
+ import type { NgisCrsRef } from "../domain/models/crs";
3
+ import type { NgisDatasetKind, NgisKindCapabilities } from "../domain/models/dataset-kind";
2
4
  import type { ApiRequestOptions } from "../lib/api";
3
5
  export type SceneScope = "mine" | "shared" | "public";
4
6
  export type SceneType = "MAP" | "ANALYSIS" | "GIS";
@@ -17,6 +19,7 @@ export interface NgisScene {
17
19
  description?: string | null;
18
20
  thumbnailFileId?: string | null;
19
21
  configJson?: string | null;
22
+ configRevision?: string | null;
20
23
  /** True for an unsaved scratch scene (auto-deleted after the TTL window). */
21
24
  ephemeral?: boolean | null;
22
25
  purpose?: "STANDARD" | "GIS_WORKSPACE" | "PLUGIN_UI_LAB" | string | null;
@@ -78,17 +81,91 @@ export interface SceneResourceDto {
78
81
  addedAt?: string | null;
79
82
  layerCount: number;
80
83
  rendered: boolean;
81
- datasetType?: string | null;
84
+ /**
85
+ * **Stage 29 (F29.1 row 2).** The registered kind id — the one derivation chain
86
+ * for a dataset's kind. `null` means *no kind resolved* and is the only meaning
87
+ * `null` carries after F29.3; `"GENERIC"` is gone from the wire.
88
+ *
89
+ * The union is **open** (`D29-1` makes a kind registered data), so a client
90
+ * shipped today must render a kind registered tomorrow. Do not assert its
91
+ * membership.
92
+ */
93
+ datasetType?: NgisDatasetKind | null;
94
+ /**
95
+ * **Stage 29 (F29.7 R2): an OPEN vocabulary.** A consumer that does not
96
+ * recognise a status renders the **raw token**, never `UNKNOWN`.
97
+ */
82
98
  datasetStatus?: string | null;
83
99
  /**
84
100
  * Selection-safe projection supplied by the scene service: VECTOR | RASTER |
85
101
  * FILE. Resource pickers must use this rather than fan out to Dataset APIs.
102
+ *
103
+ * **SURVIVES Stage 29, server-derived (F29.1 row 5).** It is a picker-routing
104
+ * hint, not a kind: it keeps its name and its three values, and after F29.3 the
105
+ * server derives it from the kind id rather than from the old three-value
106
+ * string. It is deliberately *coarser* than `datasetType` — a picker needs to
107
+ * know which control to draw, not which kind it is looking at.
86
108
  */
87
109
  selectionType?: string | null;
88
110
  /** AVAILABLE | MISSING | UNAVAILABLE | UNKNOWN. */
89
111
  metadataState?: string | null;
112
+ /**
113
+ * **Stage 29 (F29.6).** Server-computed: `status ∈ kind.readyStates`. Kept,
114
+ * not deprecated, and **not** derived client-side.
115
+ *
116
+ * Its defect was never a kind carve-out — `SceneResourceDto.java` hardcoded
117
+ * `"DONE".equals(s) || "READY".equals(s)` for *every* kind, a **status**
118
+ * vocabulary carve-out granted to all kinds because `READY` was added for one
119
+ * (FE C2, corrected by F29.6). The kind now declares its own `readyStates`.
120
+ *
121
+ * **Read by nothing in this client today.** Starting to consume it is
122
+ * `S29-V029b` (`S29-15`), and it is new UI behaviour with its own copy:
123
+ * `undefined` ("the server has not said") and `false` ("this row is not ready")
124
+ * must never render the same.
125
+ */
90
126
  ready?: boolean;
127
+ /**
128
+ * **Stage 29 (F29.6).** Server-computed:
129
+ * `ready && geometryType != null && capabilities.tileable.supported === true`.
130
+ * The hardcoded VECTOR/RASTER/FLOW_FIELD list it used to carry lives in
131
+ * `ngis-scene` and is a **backend** deliverable (FE C1) — the client never saw
132
+ * the list and cannot swap it.
133
+ *
134
+ * **This is a property of a DATASET, and `PluginUiLabSample.renderable` below
135
+ * is a different object entirely.** Stage 28 additionally ships a
136
+ * `capabilities.renderable` on a **symbol template** (`S29-V073`); dataset
137
+ * `renderable` is a top-level boolean on this spine and `renderable` is not a
138
+ * member of `NgisCapabilityFlag`, so neither shape can resolve against the
139
+ * other. Two unrelated objects, one field name — do not unify them.
140
+ *
141
+ * **Read by nothing in this client today** (`S29-V029b`, `S29-15`).
142
+ */
91
143
  renderable?: boolean;
144
+ /**
145
+ * **Stage 29 (F29.4).** The **kind's** declared capability block, carried on
146
+ * the scene resource as well as on `GET /api/datasets/{fileId}` — both, and
147
+ * deliberately **not** a third `GET /api/datasets/{id}/capabilities` endpoint,
148
+ * which would be an N+1 against data the caller already has.
149
+ *
150
+ * **Three states, not two.** Read a flag with `datasetKindCapability`: absent
151
+ * means *the server has not said*, `{supported: false}` means *this kind
152
+ * cannot*, and they must never render the same. `{}` is the empty block.
153
+ */
154
+ capabilities?: NgisKindCapabilities;
155
+ /**
156
+ * **Stage 25 (F25.8).** The source dataset's one CRS, enriched onto the scene
157
+ * resource by `ngis-scene` (S25-30, `V17__scene_resource_crs.sql`).
158
+ *
159
+ * `null` is the `NEEDS_CRS` state and is a *value*, not a gap: the kickoff's
160
+ * `CrsDto.fromProto` returns `null` on a blank `key` precisely because proto3
161
+ * scalars have no presence, so a field-for-field mapper would have put
162
+ * `EPSG:0` on the layer list. `undefined` is the third state — a server that
163
+ * has not shipped S25-30 yet — and the layer row draws no chip at all for it
164
+ * rather than drawing the absent-CRS sigil, because "the server did not say"
165
+ * and "the dataset has no projection" are different facts and G-CRS is the
166
+ * rule that they never render the same.
167
+ */
168
+ crs?: NgisCrsRef | null;
92
169
  }
93
170
  export interface ListSceneResourcesOptions {
94
171
  /** Ask the scene service to refresh its bounded, persisted dataset summaries. */
@@ -107,8 +184,14 @@ export interface NgisSceneLayer {
107
184
  layerType?: string | null;
108
185
  styleJson?: string | null;
109
186
  visible?: boolean | null;
187
+ /** S28-15: content lock for a plotting layer. */
188
+ locked?: boolean | null;
110
189
  sortOrder?: number | null;
111
190
  groupId?: string | null;
191
+ /** S28-15: optimistic revision required for plotting-layer mutations. */
192
+ revision?: number | null;
193
+ popupJson?: Record<string, unknown> | null;
194
+ dynamicLineJson?: Record<string, unknown> | null;
112
195
  }
113
196
  export interface AddSceneLayerParams {
114
197
  resourceId?: string;
@@ -116,13 +199,18 @@ export interface AddSceneLayerParams {
116
199
  name: string;
117
200
  layerType?: string;
118
201
  styleJson?: string;
202
+ dynamicLineJson?: Record<string, unknown>;
119
203
  }
120
204
  export interface UpdateSceneLayerParams {
121
205
  name?: string;
122
206
  visible?: boolean;
207
+ locked?: boolean;
123
208
  sortOrder?: number;
124
209
  styleJson?: string;
125
210
  groupId?: string;
211
+ revision?: number;
212
+ popupJson?: Record<string, unknown> | null;
213
+ dynamicLineJson?: Record<string, unknown> | null;
126
214
  }
127
215
  export interface NgisSceneLayerGroup {
128
216
  id: string;
@@ -161,6 +249,17 @@ export interface PluginUiLabResponse {
161
249
  cleanupPhase?: string | null;
162
250
  cleanupError?: string | null;
163
251
  }
252
+ /**
253
+ * A Plugin UI Lab sample row.
254
+ *
255
+ * **Its `ready` / `renderable` / `datasetType` are NOT the dataset spine's
256
+ * (`S29-V073`, F29.6).** They belong to `PluginUiLabSampleDto`, a lab fixture
257
+ * row, and F29.6 explicitly declined to deprecate `renderable` partly because
258
+ * this surface has no stake in the stage. Do not route this shape through
259
+ * anything that expects {@link SceneResourceDto} or `NgisDatasetSpine`, and do
260
+ * not widen `datasetType` here to {@link NgisDatasetKind}: this row's value is a
261
+ * lab sample's declared type, not a registered kind id.
262
+ */
164
263
  export interface PluginUiLabSample {
165
264
  id: string;
166
265
  version: string;
@@ -225,7 +324,7 @@ export declare const scenesApi: {
225
324
  promoteScene(sceneId: string, name?: string): Promise<NgisScene>;
226
325
  create(params: CreateSceneParams): Promise<NgisScene>;
227
326
  update(sceneId: string, params: UpdateSceneParams): Promise<NgisScene>;
228
- updateConfig(sceneId: string, config: string | object): Promise<NgisScene>;
327
+ updateConfig(sceneId: string, config: string | object, configRevision?: string | null): Promise<NgisScene>;
229
328
  updateCapabilities(sceneId: string, params: UpdateSceneCapabilitiesParams): Promise<NgisScene>;
230
329
  delete(sceneId: string): Promise<void>;
231
330
  publish(sceneId: string): Promise<void>;
@@ -242,8 +341,8 @@ export declare const scenesApi: {
242
341
  addLayer(sceneId: string, params: AddSceneLayerParams, options?: ApiRequestOptions): Promise<NgisSceneLayer>;
243
342
  updateLayer(sceneId: string, layerId: string, params: UpdateSceneLayerParams, options?: ApiRequestOptions): Promise<NgisSceneLayer>;
244
343
  renameLayer(sceneId: string, layerId: string, name: string): Promise<NgisSceneLayer>;
245
- deleteLayer(sceneId: string, layerId: string, options?: ApiRequestOptions): Promise<void>;
246
- reorderLayers(sceneId: string, layerIds: string[], options?: ApiRequestOptions): Promise<void>;
344
+ deleteLayer(sceneId: string, layerId: string, revisionOrOptions?: number | ApiRequestOptions, options?: ApiRequestOptions): Promise<void>;
345
+ reorderLayers(sceneId: string, layerIds: string[], revisionsOrOptions?: Record<string, number> | ApiRequestOptions, options?: ApiRequestOptions): Promise<void>;
247
346
  listLayerGroups(sceneId: string): Promise<NgisSceneLayerGroup[]>;
248
347
  createLayerGroup(sceneId: string, params: CreateLayerGroupParams): Promise<NgisSceneLayerGroup>;
249
348
  updateLayerGroup(sceneId: string, groupId: string, params: UpdateLayerGroupParams): Promise<NgisSceneLayerGroup>;