@ngis/plugin-ui 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 (79) hide show
  1. package/LICENSE +13 -0
  2. package/README.md +25 -0
  3. package/dist/index.cjs +71 -0
  4. package/dist/index.d.ts +114 -0
  5. package/dist/index.mjs +69 -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/api/workflows.d.ts +191 -0
  11. package/dist/src/components/plugin-ui/AttributeTable.d.ts +26 -0
  12. package/dist/src/components/plugin-ui/BatchTable.d.ts +47 -0
  13. package/dist/src/components/plugin-ui/ChartCanvas.d.ts +14 -0
  14. package/dist/src/components/plugin-ui/ExtentPicker.d.ts +22 -0
  15. package/dist/src/components/plugin-ui/FieldSelect.d.ts +23 -0
  16. package/dist/src/components/plugin-ui/Flow.d.ts +256 -0
  17. package/dist/src/components/plugin-ui/ModelSelect.d.ts +36 -0
  18. package/dist/src/components/plugin-ui/ParamForm.d.ts +63 -0
  19. package/dist/src/components/plugin-ui/PickerField.d.ts +52 -0
  20. package/dist/src/components/plugin-ui/ResourceSelect.d.ts +37 -0
  21. package/dist/src/components/plugin-ui/ResultActions.d.ts +48 -0
  22. package/dist/src/components/plugin-ui/RunButton.d.ts +49 -0
  23. package/dist/src/components/plugin-ui/StatChart.d.ts +66 -0
  24. package/dist/src/components/plugin-ui/TaskChrome.d.ts +35 -0
  25. package/dist/src/components/plugin-ui/TaskList.d.ts +29 -0
  26. package/dist/src/components/plugin-ui/TaskStatus.d.ts +55 -0
  27. package/dist/src/components/plugin-ui/batch-model.d.ts +442 -0
  28. package/dist/src/components/plugin-ui/flow-inputs.d.ts +61 -0
  29. package/dist/src/components/plugin-ui/flow-model.d.ts +303 -0
  30. package/dist/src/components/plugin-ui/flow-ports.d.ts +155 -0
  31. package/dist/src/components/plugin-ui/param-form-data.d.ts +122 -0
  32. package/dist/src/components/plugin-ui/param-form-model.d.ts +187 -0
  33. package/dist/src/components/plugin-ui/param-widgets.d.ts +88 -0
  34. package/dist/src/components/plugin-ui/picker-data.d.ts +141 -0
  35. package/dist/src/components/plugin-ui/picker-model.d.ts +150 -0
  36. package/dist/src/components/plugin-ui/recipe-model.d.ts +320 -0
  37. package/dist/src/components/plugin-ui/result-data.d.ts +112 -0
  38. package/dist/src/components/plugin-ui/result-model.d.ts +96 -0
  39. package/dist/src/components/plugin-ui/run-data.d.ts +118 -0
  40. package/dist/src/components/plugin-ui/run-model.d.ts +179 -0
  41. package/dist/src/components/plugin-ui/stat-chart-model.d.ts +168 -0
  42. package/dist/src/components/plugin-ui/validation-messages.d.ts +18 -0
  43. package/dist/src/components/plugin-ui/w2-contracts.d.ts +151 -0
  44. package/dist/src/components/plugin-ui/w3-contracts.d.ts +328 -0
  45. package/dist/src/components/plugin-ui/w4-contracts.d.ts +510 -0
  46. package/dist/src/components/ui/button.d.ts +12 -0
  47. package/dist/src/components/ui/checkbox.d.ts +4 -0
  48. package/dist/src/components/ui/column-resize-handle.d.ts +23 -0
  49. package/dist/src/components/ui/dropdown-menu.d.ts +25 -0
  50. package/dist/src/components/ui/input.d.ts +3 -0
  51. package/dist/src/components/ui/label.d.ts +4 -0
  52. package/dist/src/components/ui/popover.d.ts +10 -0
  53. package/dist/src/components/ui/select.d.ts +15 -0
  54. package/dist/src/components/ui/switch.d.ts +6 -0
  55. package/dist/src/domain/map/types.d.ts +128 -0
  56. package/dist/src/features/gis/components/GisAttributeTable.d.ts +56 -0
  57. package/dist/src/features/gis/hooks/useDatasetFeatureWindow.d.ts +51 -0
  58. package/dist/src/lib/api-error.d.ts +81 -0
  59. package/dist/src/lib/api.d.ts +82 -0
  60. package/dist/src/lib/auth-session.d.ts +22 -0
  61. package/dist/src/lib/hooks/useColumnResize.d.ts +57 -0
  62. package/dist/src/lib/raster-color-layer/RasterColorLayer.d.ts +48 -0
  63. package/dist/src/lib/raster-color-layer/TileGrid.d.ts +31 -0
  64. package/dist/src/lib/raster-color-layer/colorRamp.d.ts +18 -0
  65. package/dist/src/lib/raster-color-layer/shaders/raster-color.frag.d.ts +2 -0
  66. package/dist/src/lib/raster-color-layer/shaders/raster-color.vert.d.ts +2 -0
  67. package/dist/src/lib/raster-color-layer/zoomInterpolate.d.ts +2 -0
  68. package/dist/src/lib/utils.d.ts +2 -0
  69. package/dist/src/sdk/facets/types.d.ts +370 -0
  70. package/dist/src/sdk/host-api.d.ts +256 -0
  71. package/dist/src/sdk/map-api.d.ts +288 -0
  72. package/dist/src/sdk/panels.d.ts +130 -0
  73. package/dist/src/types/analysis.d.ts +205 -0
  74. package/dist/src/types/common.d.ts +17 -0
  75. package/dist/src/types/extensions.d.ts +560 -0
  76. package/dist/src/types/gis.d.ts +242 -0
  77. package/dist/src/types/market-social.d.ts +378 -0
  78. package/dist/src/types/workflow.d.ts +83 -0
  79. package/package.json +59 -0
@@ -0,0 +1,242 @@
1
+ /**
2
+ * Shared UI types for the GIS workspace (`type=GIS` scratch scene).
3
+ * Kept separate from `analysis.ts` / `map-styling.ts` so the GIS surface can
4
+ * evolve without coupling to the other workspaces.
5
+ */
6
+ /** Left dock tab: the layer stack vs. the scene's data (resources). */
7
+ export type GisLeftTab = "layers" | "data";
8
+ /**
9
+ * Lifecycle state of the scratch workspace:
10
+ * - `opening` — calling `GET /gis/workspace`
11
+ * - `prompt` — prior session has content; awaiting Restore vs. Start empty
12
+ * - `ready` — workspace scene loaded and rendered
13
+ * - `error` — open/load failed
14
+ */
15
+ export type GisWorkspaceStatus = "opening" | "prompt" | "ready" | "error";
16
+ /** Camera state persisted to / restored from `scene.configJson`. */
17
+ export interface GisViewport {
18
+ center: [number, number];
19
+ zoom: number;
20
+ pitch?: number;
21
+ bearing?: number;
22
+ }
23
+ /**
24
+ * Protomaps hosted-style theme that backs the basemap switcher. Mapped straight
25
+ * to the `SceneMapCanvas` `basemap` prop (the Protomaps v5 theme segment).
26
+ */
27
+ export type GisBasemap = "light" | "dark" | "white" | "grayscale" | "black";
28
+ /** Basemap options shown in the switcher, in display order. */
29
+ export declare const GIS_BASEMAPS: readonly GisBasemap[];
30
+ export declare const GIS_DEFAULT_BASEMAP: GisBasemap;
31
+ /**
32
+ * Terra Draw modes the rail drives directly (a subset of the library's modes).
33
+ * Each maps 1:1 to `TerraDraw.setMode(mode)`.
34
+ */
35
+ export type GisDrawMode = "point" | "linestring" | "polygon" | "rectangle" | "circle" | "text" | "select";
36
+ /**
37
+ * The measurement flavours the rail can start. Distinct from `GisDrawMode`:
38
+ * measuring is an ephemeral overlay (its own isolated Terra Draw instance via
39
+ * `MaplibreMeasureControl`), never a drawn feature.
40
+ */
41
+ export type GisMeasureKind = "distance" | "area";
42
+ /**
43
+ * What the cursor snaps to while drawing lines/polygons or measuring:
44
+ * `node` = existing vertices only, `edge` = nearest point on a segment only,
45
+ * `both` = vertices win over edges when both are in range.
46
+ */
47
+ export type GisSnapTarget = "node" | "edge" | "both";
48
+ /**
49
+ * Shared snapping configuration applied to BOTH the draw and measure Terra Draw
50
+ * controls (via their `modeOptions` → `snapping.toCustom`). One config drives
51
+ * both tool sets, matching the reason measuring was migrated onto the library
52
+ * control. Note: Terra Draw only supports snapping on line/polygon modes, so
53
+ * rectangle/circle/point/text draw tools are unaffected.
54
+ */
55
+ export interface GisSnapConfig {
56
+ /** Master on/off. When false, `toCustom` returns undefined (no snap). */
57
+ enabled: boolean;
58
+ /** Snap to vertices, edges, or both (node-priority). */
59
+ target: GisSnapTarget;
60
+ /** Snap radius in screen pixels. */
61
+ tolerancePx: number;
62
+ /**
63
+ * Scene-layer ids to snap to. `null` means "all currently visible scene
64
+ * layers"; a list narrows snapping to just those layers. Runtime-only (layer
65
+ * ids are scene-specific, so this is never persisted).
66
+ */
67
+ layerIds: string[] | null;
68
+ /**
69
+ * Also snap across controls — drawing snaps to measured geometry and vice
70
+ * versa (each control reads the other's Terra Draw snapshot).
71
+ */
72
+ includeDrawnFeatures: boolean;
73
+ }
74
+ /** Sensible starting snapping config: off, both targets, 10px, all layers. */
75
+ export declare const GIS_DEFAULT_SNAP_CONFIG: GisSnapConfig;
76
+ /**
77
+ * Stable ids for every catalog entry. Persisted inside the saved layout — treat
78
+ * this as an append-only enum (never renumber or repurpose an existing id).
79
+ */
80
+ export type GisToolId = "draw-point" | "draw-line" | "draw-polygon" | "draw-rectangle" | "draw-circle" | "draw-text" | "select" | "delete-selection" | "delete-all" | "undo" | "redo" | "export" | "attribute-table" | "basemap" | "zoom-in" | "zoom-out" | "home" | "measure-distance" | "measure-area" | "snapping" | "code" | "workflow" | "tools" | "style" | "ai" | "plugins" | "measure" | "identify" | "select-by-attribute";
81
+ /**
82
+ * Items persisted in the customizable rail. `GisToolId` remains the frozen
83
+ * command catalog; host-only composite controls can ride on the rail without
84
+ * becoming invocable map tools.
85
+ */
86
+ export type GisRailItemId = GisToolId | "multi-view" | "flow-field";
87
+ /** How a tool behaves when invoked from the rail. */
88
+ export type GisToolKind = "mode" | "measure" | "action" | "dropdown";
89
+ /** One-shot actions the rail fires on the map via the imperative handle
90
+ * (plus `code-panel`, which toggles the code dock instead of the map). */
91
+ export type GisToolAction = "delete-selection" | "delete-all" | "undo" | "redo" | "export" | "attribute-table" | "code-panel" | "workflow-panel" | "tools-panel" | "style-panel" | "ai-panel" | "zoom-in" | "zoom-out" | "home";
92
+ /** Catalog grouping used by the customize dialog. */
93
+ export type GisToolCategory = "draw" | "measure" | "edit" | "history" | "data" | "navigation" | "analysis";
94
+ /** Minimal slice of `WorkspacePanelRegistry` a rail tool's `invoke` needs. */
95
+ export interface GisRailPanelControl {
96
+ open: (slot: "right" | "bottom", id: string) => void;
97
+ toggle: (slot: "right" | "bottom", id: string) => void;
98
+ setActive: (slot: "right" | "bottom", id: string | null) => void;
99
+ getActive: (slot: "right" | "bottom") => string | null;
100
+ }
101
+ /**
102
+ * Minimal slice of the `NgisMapAPI` facade (`sdk/map-api.ts`) a rail tool's
103
+ * `invoke` needs — draw / measure / camera commands plus the panel registry.
104
+ * The concrete facade has a far larger surface; this is just what the rail
105
+ * exercises, so the real `NgisMapAPI` satisfies it structurally.
106
+ */
107
+ export interface GisRailApi {
108
+ draw: {
109
+ setMode: (mode: GisDrawMode) => void;
110
+ cancelMode: () => void;
111
+ clear: () => void;
112
+ deleteSelection: () => void;
113
+ undo: () => void;
114
+ redo: () => void;
115
+ export: () => void;
116
+ getActiveMode: () => string | null;
117
+ };
118
+ measure: {
119
+ start: (kind: GisMeasureKind) => void;
120
+ clear: () => void;
121
+ };
122
+ camera: {
123
+ zoomIn: () => void;
124
+ zoomOut: () => void;
125
+ zoomHome: () => void;
126
+ /** Structural mirror of `NgisCameraFacet.getView` (additive widening,
127
+ * session P5 — the dynamic rail-tool path reads the current center).
128
+ * `null` until the facade's raw map is bound. */
129
+ getView: () => {
130
+ center: [number, number];
131
+ zoom: number;
132
+ pitch?: number;
133
+ bearing?: number;
134
+ } | null;
135
+ };
136
+ panels: GisRailPanelControl;
137
+ }
138
+ /** Minimal slice of `useGisRailStore` a rail tool's `invoke` needs. */
139
+ export interface GisRailStoreHandle {
140
+ getState: () => {
141
+ activeToolId: GisToolId | null;
142
+ actions: {
143
+ setActiveMode: (mode: GisDrawMode | null, toolId: GisToolId | null) => void;
144
+ setActiveMeasure: (kind: GisMeasureKind | null, toolId: GisToolId | null) => void;
145
+ clearActive: () => void;
146
+ };
147
+ };
148
+ }
149
+ /**
150
+ * ctx passed to `GisToolDef.invoke`. v1 (C8) exposes the `NgisMapAPI` facade
151
+ * (replacing C7's raw `{ mapHandle }` ref — the facade subsumes it and adds the
152
+ * panel registry the old `onToggleAttributeTable`/`onToggleWorkflow` callbacks
153
+ * used to cover) and the rail store (for the active-tool lit-state bookkeeping
154
+ * a toggle needs). Both fields are structural, so future widening stays additive.
155
+ */
156
+ export interface GisRailInvokeContext {
157
+ api: GisRailApi;
158
+ railStore: GisRailStoreHandle;
159
+ }
160
+ /** A single catalog entry. `icon` is a lucide name resolved in-component. */
161
+ export interface GisToolDef {
162
+ id: GisToolId;
163
+ icon: string;
164
+ /** i18n key suffix under `GIS.rail.tools.*`. */
165
+ labelKey: string;
166
+ category: GisToolCategory;
167
+ kind: GisToolKind;
168
+ /** For `kind: "mode"` — the Terra Draw mode to activate. */
169
+ drawMode?: GisDrawMode;
170
+ /** For `kind: "measure"` — the measurement flavour to start. */
171
+ measureKind?: GisMeasureKind;
172
+ /** For `kind: "action"` — the imperative handle method to call. */
173
+ action?: GisToolAction;
174
+ /** Whether the tool unfolds the contextual readout strip while active. */
175
+ hasContextStrip?: boolean;
176
+ /** Disabled "coming soon" catalog entry — rendered greyed, not invocable. */
177
+ disabled?: boolean;
178
+ /**
179
+ * Hide this tool whenever its backing panel is not registered. The tool id
180
+ * remains in persisted layouts so disabling a plugin is reversible: when
181
+ * the panel registers again, its button returns to the user's saved place.
182
+ */
183
+ requiredPanel?: {
184
+ slot: "right" | "bottom";
185
+ id: string;
186
+ };
187
+ /** Invoked by `GisCommandRail`'s dispatcher in place of the old hardcoded
188
+ * switch. Absent for `kind: "dropdown"` entries (`basemap`/`snapping`),
189
+ * which render their own inline UI and never go through the dispatcher. */
190
+ invoke?: (ctx: GisRailInvokeContext) => void;
191
+ }
192
+ export interface GisHostRailItemDef {
193
+ id: Exclude<GisRailItemId, GisToolId>;
194
+ icon: string;
195
+ /** i18n key suffix under `GIS.rail.tools.*`. */
196
+ labelKey: string;
197
+ category: GisToolCategory;
198
+ kind: "host";
199
+ }
200
+ export type GisRailItemDef = GisToolDef | GisHostRailItemDef;
201
+ /** One user-named cluster of tools on the rail. */
202
+ export interface GisRailGroup {
203
+ id: string;
204
+ /** Custom name; when null, render the default i18n label for `nameKey`. */
205
+ name: string | null;
206
+ /** Default-group i18n key under `GIS.rail.groups.*` (seeded groups only). */
207
+ nameKey?: string;
208
+ /** Ordered, visible tools in this group. */
209
+ toolIds: GisRailItemId[];
210
+ }
211
+ /**
212
+ * Per-user persisted rail layout. A tool is "pinned to the bar" exactly when it
213
+ * lives in a group; every other catalog tool stays reachable from the "More
214
+ * tools" overflow menu. Deleting a group simply returns its tools to overflow —
215
+ * nothing is ever lost.
216
+ */
217
+ export interface GisRailLayout {
218
+ version: number;
219
+ groups: GisRailGroup[];
220
+ }
221
+ /** Live measurement readout shown in the rail's contextual strip. */
222
+ export interface GisMeasureSummary {
223
+ /** Vertex count of the measured path / polygon ring. */
224
+ points: number;
225
+ /** Total path length (distance) or polygon perimeter (area), in meters. */
226
+ lengthMeters: number;
227
+ /** Polygon area in square meters (0 for a distance measurement). */
228
+ areaSqMeters: number;
229
+ }
230
+ /** Live geometry readout shown in the rail's contextual strip. */
231
+ export interface GisDrawSummary {
232
+ /** Number of user-drawn features currently on the map. */
233
+ features: number;
234
+ /** Total vertex count across those features. */
235
+ vertices: number;
236
+ /** Total line length in meters (linestrings). */
237
+ lengthMeters: number;
238
+ /** Total polygon area in square meters. */
239
+ areaSqMeters: number;
240
+ /** Scene features intersected by the drawn geometry (draw-to-query). */
241
+ matched: number;
242
+ }
@@ -0,0 +1,378 @@
1
+ /**
2
+ * DTOs for the Stage 15 **community market** — ratings, comments, abuse reports,
3
+ * install counts, the recipe flywheel counter, extension packs, and the
4
+ * verified-publisher flag.
5
+ *
6
+ * **Authored at the W1 kickoff and read-only to the wave** (the YD48 rule: a
7
+ * signature is not a seam — shared shapes are implemented once, by the kickoff,
8
+ * and no session re-declares one). ZB1 owns the ratings/comments/reports/
9
+ * install-count/stats wire; ZB2 owns packs; ZB3 owns the verified-publisher
10
+ * flag. Z4/Z5/Z6 consume these types and the byte-identical fixtures beside
11
+ * them (`src/types/__fixtures__/market-social/`) — never invented shapes.
12
+ *
13
+ * Field names are the **wire** names; nothing is renamed to taste. Java
14
+ * `Instant` serializes as an ISO-8601 `string`. The paging envelope is the one
15
+ * that already exists — {@link PageResult} — never a second one.
16
+ *
17
+ * Contract: `docs/open_ecosystem_stage/README.md` → **F15.5** (ratings/comments/
18
+ * reports), **F15.6** (install counts + flywheel), **F15.7** (packs), **F15.11**
19
+ * (verified publisher), **F15.18** (this module and its fixture set).
20
+ *
21
+ * Every surface here is **G-B**: authenticated, LAN-only, no anonymous row. Every
22
+ * record carries the authenticated user id.
23
+ */
24
+ import type { PageResult } from "./common";
25
+ /**
26
+ * Integer 1–5. Modelled as a union rather than `number` so a client cannot post
27
+ * a 0 or a 6 past the type checker — the server rejects them either way, but the
28
+ * refusal should not need a round trip to discover.
29
+ */
30
+ export type RatingValue = 1 | 2 | 3 | 4 | 5;
31
+ /** Server-side cap on {@link RatingDto.text}, measured on the serialized UTF-8. */
32
+ export declare const RATING_TEXT_MAX_LENGTH = 2000;
33
+ /**
34
+ * One user's rating of one extension.
35
+ *
36
+ * **Installers only at creation** — the server requires an install row for
37
+ * (user, extension) at write time. It is *not* required afterward: a rating
38
+ * **survives uninstall**, because the recorded experience was real (F15.5).
39
+ * One per (user, extension), a UNIQUE constraint rather than a convention, and
40
+ * **editable in place** — an opinion is not an immutable artifact, so there is
41
+ * no version here.
42
+ */
43
+ export interface RatingDto {
44
+ extId: string;
45
+ /** The rating's author. Always present — there is no anonymous surface. */
46
+ userId: string;
47
+ value: RatingValue;
48
+ /** `null` when the rater left a score and no words. */
49
+ text: string | null;
50
+ createdAt: string;
51
+ /** Equal to {@link createdAt} until the first in-place edit. */
52
+ updatedAt: string;
53
+ }
54
+ /** `POST`/`PUT /api/extensions/{extId}/rating` — the caller is the authenticated user. */
55
+ export interface RatingUpsertRequest {
56
+ value: RatingValue;
57
+ /** Omit or `null` to score without text; over {@link RATING_TEXT_MAX_LENGTH} is a `400`. */
58
+ text?: string | null;
59
+ }
60
+ /**
61
+ * Aggregate shown on the market card and detail page.
62
+ *
63
+ * **Shown from the first rating** — no display threshold. A LAN community is too
64
+ * small for one to buy anything, and every surface is authenticated anyway
65
+ * (F15.5). `mean` is rounded to one decimal server-side; `count` 0 means nothing
66
+ * has been rated and `mean` is `null`, never `0` (a 0 would render as a real
67
+ * score).
68
+ */
69
+ export interface RatingAggregateDto {
70
+ extId: string;
71
+ count: number;
72
+ mean: number | null;
73
+ }
74
+ /**
75
+ * **Flat v1 — no threads.** There is deliberately no `parentId`: adding one
76
+ * later is additive, while shipping a tree that nothing renders is not.
77
+ *
78
+ * Author may edit and delete their own; **ADMIN may hard-delete any**, which
79
+ * writes an audit event recording actor and target. Moderation is
80
+ * report-driven, not pre-moderated — a comment posts immediately under G-B.
81
+ */
82
+ export interface CommentDto {
83
+ id: string;
84
+ extId: string;
85
+ authorId: string;
86
+ body: string;
87
+ createdAt: string;
88
+ updatedAt: string;
89
+ }
90
+ /** `POST /api/extensions/{extId}/comments` and the author's in-place edit. */
91
+ export interface CommentUpsertRequest {
92
+ body: string;
93
+ }
94
+ /** What a report points at. An extension, or one social row on it. */
95
+ export type ReportTargetType = "EXTENSION" | "COMMENT" | "RATING";
96
+ /**
97
+ * `OPEN → RESOLVED | DISMISSED`. Transitions are **ADMIN-only**, and the two
98
+ * terminal states are terminal: a fresh concern about the same target is a new
99
+ * report, never a reopen.
100
+ */
101
+ export type ReportStatus = "OPEN" | "RESOLVED" | "DISMISSED";
102
+ /**
103
+ * One abuse report.
104
+ *
105
+ * **One OPEN report per (reporter, target)** — a duplicate submission returns
106
+ * the existing row rather than minting queue noise, so a client may treat
107
+ * submit as idempotent while the first report is still open.
108
+ */
109
+ export interface ReportDto {
110
+ id: string;
111
+ targetType: ReportTargetType;
112
+ targetId: string;
113
+ /** The extension the target belongs to — present even when the target is a comment
114
+ * or rating, so the ADMIN queue can group without a second lookup. */
115
+ extId: string;
116
+ reporterId: string;
117
+ /** Required and non-blank at submission. */
118
+ reason: string;
119
+ status: ReportStatus;
120
+ /** ADMIN's optional note, written at the transition. `null` while `OPEN`. */
121
+ resolutionNote: string | null;
122
+ /** The ADMIN who moved it out of `OPEN`. `null` while `OPEN`. */
123
+ resolvedBy: string | null;
124
+ resolvedAt: string | null;
125
+ createdAt: string;
126
+ }
127
+ /** `POST /api/extensions/{extId}/reports`. */
128
+ export interface ReportCreateRequest {
129
+ targetType: ReportTargetType;
130
+ targetId: string;
131
+ reason: string;
132
+ }
133
+ /** `POST /api/extensions/reports/{id}/resolve` \| `/dismiss` — ADMIN only. */
134
+ export interface ReportTransitionRequest {
135
+ resolutionNote?: string | null;
136
+ }
137
+ /**
138
+ * The ADMIN report queue — **paged from day one**, the lesson of the review
139
+ * queue that shipped as a plain unpaged array (Track B substrate, item 4).
140
+ */
141
+ export type ReportQueuePage = PageResult<ReportDto>;
142
+ /**
143
+ * `GET /api/extensions/stats/recipes` — **the exit criterion's "community-recipe
144
+ * counting exists"**, open to any authenticated caller (it exposes no ownership
145
+ * detail, only cohort totals).
146
+ *
147
+ * Counts **PUBLISHED recipe-bearing versions**: a `tool`-kind reference version
148
+ * whose referenced tool has `provider = "workflow"`. Both granularities ship
149
+ * because they answer different questions — `*Versions` measures activity,
150
+ * `*Extensions` measures breadth.
151
+ *
152
+ * **Cohort is the owner's role at query time**, which is documented rather than
153
+ * denormalized: an owner promoted to ADMIN reclassifies their history. Accepted
154
+ * at LAN scale. The flywheel *signal* (community > 1× first-party) is measured
155
+ * at +6 months; this stage owes only the counting.
156
+ */
157
+ export interface RecipeStatsDto {
158
+ adminVersions: number;
159
+ communityVersions: number;
160
+ adminExtensions: number;
161
+ communityExtensions: number;
162
+ /** When the counts were taken, ISO-8601. */
163
+ asOf: string;
164
+ }
165
+ /**
166
+ * The additive fields Stage 15 lands on the **existing** `ExtensionSummaryDto` /
167
+ * `ExtensionDetailDto`. Declared as a separate interface here so ZB1's additive
168
+ * change and Z4's consumption agree before either is written; the shipped
169
+ * `ExtensionSummary` in `./extensions` gains these members, it is not replaced.
170
+ *
171
+ * `installCount` is `COUNT(ngis_extension_install WHERE enabled = true)` — a
172
+ * disabled install is not adoption, and uninstall already deletes the row.
173
+ * Shown from **1**, no threshold.
174
+ */
175
+ export interface ExtensionSocialFields {
176
+ installCount: number;
177
+ /** `null` until the first rating — never a synthetic 0-star aggregate. */
178
+ rating: RatingAggregateDto | null;
179
+ /** F15.11 — an operator attestation about the *publisher*, carried on the extension
180
+ * because that is where the market renders it. Confers zero capability. */
181
+ publisherVerified: boolean;
182
+ }
183
+ /**
184
+ * What the badge attests on a LAN deployment: **the operator knows who this
185
+ * publisher is.** Identity attestation by the deployment's operator, nothing
186
+ * more — not code review, not an endorsement, and it grants no extra capability.
187
+ *
188
+ * ADMIN-granted and revocable; there is **no self-serve path this stage**
189
+ * (DNS-TXT-style self-verification is a G-C-era design, deliberately out).
190
+ * Stored in `ngis-extension` riding migration **V5** — never in `ngis-auth`: no
191
+ * cross-service migration is declared for this stage, and this is a market
192
+ * concept, not an account one.
193
+ */
194
+ export interface VerifiedPublisherDto {
195
+ userId: string;
196
+ verified: boolean;
197
+ /** The ADMIN who last granted or revoked. */
198
+ grantedBy: string;
199
+ grantedAt: string;
200
+ /** Free-text operator note recording *how* identity was established. */
201
+ note: string | null;
202
+ }
203
+ /** `PUT /api/extensions/publishers/{userId}/verified` — ADMIN only. */
204
+ export interface VerifiedPublisherRequest {
205
+ verified: boolean;
206
+ note?: string | null;
207
+ }
208
+ /**
209
+ * Packs are **new tables** (migration V6), never a fourth `ExtensionKind`.
210
+ * A kind would ripple through every wire enum, the frozen
211
+ * `PackageManifestValidator`, the G-D `kind === "ui-plugin"` filters and every
212
+ * client narrowing on the three-kind union — for a thing that is a *grouping*,
213
+ * not an artifact.
214
+ *
215
+ * The ACL vocabulary is the extension one **minus `GROUP`** (F15.16: no
216
+ * group-membership service exists platform-wide to resolve against).
217
+ */
218
+ export type PackVisibility = "PRIVATE" | "PUBLIC";
219
+ /** One curated member. Order is the pack author's; it is presentation, not dependency. */
220
+ export interface PackMemberDto {
221
+ extId: string;
222
+ /** 0-based position in the curated list. */
223
+ position: number;
224
+ /** Denormalized for rendering the pack without N detail fetches. */
225
+ displayName: string;
226
+ kind: string;
227
+ }
228
+ /**
229
+ * A pack **curates identity, not versions**: it pins no member version. Each
230
+ * member installs under the user's normal version policy (`LATEST` by default,
231
+ * pin-able per install afterward) exactly as a direct install would — versions
232
+ * stay the registry's business.
233
+ */
234
+ export interface PackDto {
235
+ packId: string;
236
+ ownerId: string;
237
+ displayName: string;
238
+ summary: string | null;
239
+ visibility: PackVisibility;
240
+ members: PackMemberDto[];
241
+ createdAt: string;
242
+ updatedAt: string;
243
+ }
244
+ /** Per-member result of a pack install. */
245
+ export type PackInstallStatus = "INSTALLED" | "ALREADY_INSTALLED" | "FAILED";
246
+ /**
247
+ * One member's outcome.
248
+ *
249
+ * **A member the caller cannot see FAILs with a reason — a pack is never an ACL
250
+ * bypass.** That is the standing install-is-adoption-never-bypass rule extended
251
+ * to packs, and it is why this is a per-member report rather than a single
252
+ * status code.
253
+ */
254
+ export interface PackInstallMemberResultDto {
255
+ extId: string;
256
+ status: PackInstallStatus;
257
+ /** Present iff `status === "FAILED"`. */
258
+ reason: string | null;
259
+ }
260
+ /**
261
+ * **No rollback.** Members that installed stay installed when a sibling fails;
262
+ * existing installs are left untouched (`ALREADY_INSTALLED`). Partial success is
263
+ * the designed outcome, not an error path — the UI reports per member.
264
+ */
265
+ export interface PackInstallResultDto {
266
+ packId: string;
267
+ results: PackInstallMemberResultDto[];
268
+ }
269
+ /**
270
+ * `POST /api/extensions/scene/{sceneId}/template/install` (F15.8) — adopting a
271
+ * scene template's composition into the caller's own library.
272
+ *
273
+ * **The same per-member report as a pack, deliberately.** The Java record reuses
274
+ * `PackInstallMemberResultDto` verbatim (`SceneTemplateInstallResultDto`), because
275
+ * the act is identical — adopt a curated set, per member, never bypassing an ACL —
276
+ * and only the curation channel differs: a pack is a curated `extId` list, a scene
277
+ * template is a scene's E6 composition. A member the caller cannot see FAILs.
278
+ *
279
+ * Authored at the **W4 kickoff**, not by a session: `V15-095` asked for this pair
280
+ * at the W1 close and `V15-287` recorded at the W3 close that neither the Z5
281
+ * kickoff nor the orchestrator had made it, so the leg had shipped in W1 and gone
282
+ * two full waves with no frontend type, no fixture and no client. There is still
283
+ * **no UI caller** — that is a market-detail surface and no W4 session owns one.
284
+ */
285
+ export interface SceneTemplateInstallResultDto {
286
+ sceneId: string;
287
+ results: PackInstallMemberResultDto[];
288
+ }
289
+ /** `POST`/`PUT /api/extensions/packs` — members given as an ordered `extId` list. */
290
+ export interface PackUpsertRequest {
291
+ displayName: string;
292
+ summary?: string | null;
293
+ visibility: PackVisibility;
294
+ extIds: string[];
295
+ }
296
+ /**
297
+ * Which of F15.10's three static checks produced a finding.
298
+ *
299
+ * The list is closed on purpose: F15.10 names exactly three checks, and a
300
+ * reviewer UI that renders per-check sections needs to know the sections before
301
+ * a scan runs. A fourth check is a contract change, not a new string.
302
+ */
303
+ export type ScanCheckId = "imports" | "secrets" | "permissions";
304
+ /**
305
+ * **There is no blocking severity, and that is the contract, not an oversight.**
306
+ * F15.10 makes all three checks advisory — E4 human review stays the
307
+ * decision-maker, because a heuristic that blocks an upload gives a false
308
+ * positive no appeal path. The existing hard refusals (malformed manifest,
309
+ * oversized/malformed bundle) are unchanged and live nowhere near this type.
310
+ */
311
+ export type ScanSeverity = "INFO" | "WARN";
312
+ /**
313
+ * One scanner finding.
314
+ *
315
+ * `code` is the stable machine identity a UI keys its copy off; `message` is the
316
+ * server's English fallback for a code the client does not know. Render `code`
317
+ * when you have a translation, `message` when you do not — never concatenate.
318
+ */
319
+ export interface ScanFindingDto {
320
+ check: ScanCheckId;
321
+ severity: ScanSeverity;
322
+ /**
323
+ * Stable, screaming-snake. The set ZB3 mints at W2:
324
+ * `IMPORT_NOT_ALLOWLISTED` · `EVAL_CALL` · `NEW_FUNCTION_CALL` ·
325
+ * `SECRET_PATTERN` · `SECRET_ENTROPY`. A client must tolerate an unknown code
326
+ * (render `message`) rather than narrow on this being exhaustive.
327
+ */
328
+ code: string;
329
+ message: string;
330
+ /** 1-based line in `bundle.mjs` when the finding is positional; `null` otherwise. */
331
+ line: number | null;
332
+ /**
333
+ * The offending specifier or a **redacted** excerpt — never the matched secret
334
+ * itself, and truncated server-side. A scan result is readable by every ADMIN
335
+ * reviewer, so it must not become a second place credentials live.
336
+ */
337
+ evidence: string | null;
338
+ }
339
+ /**
340
+ * Scope delta against the previous PUBLISHED version of the same extension —
341
+ * the reviewer blocker F15.10 exists to remove (today a scope-widening bump has
342
+ * to be caught by diffing two JSON bodies by hand).
343
+ *
344
+ * Computed server-side against **PUBLISHED** only: a DRAFT or REJECTED
345
+ * predecessor is not what users are running, so diffing against it would show a
346
+ * widening that never reached anyone.
347
+ */
348
+ export interface ScanPermissionDiffDto {
349
+ /** `null` when no PUBLISHED predecessor exists — a first publish adds everything. */
350
+ previousVersion: string | null;
351
+ /** Scopes present now and not before. **The reviewer's actual signal.** */
352
+ added: string[];
353
+ removed: string[];
354
+ unchanged: string[];
355
+ }
356
+ /**
357
+ * The persisted scan payload: a JSONB column on `ngis_extension_version`,
358
+ * surfaced on {@link import("./extensions").ExtensionReviewQueueEntryDto}.
359
+ *
360
+ * **`completed: false` is not "clean".** The scanner runs inside the upload
361
+ * request and must never fail an upload it could not analyse, so a scanner error
362
+ * persists as `completed: false` with empty findings. A UI that renders that as
363
+ * a green check is asserting a result nobody obtained — show *not scanned*.
364
+ */
365
+ export interface VersionScanResultDto {
366
+ /**
367
+ * Payload schema version. Present because these bytes are stored, not just
368
+ * transported: a row written by today's build is read by every later build,
369
+ * and a stored blob with no version is unmigratable.
370
+ */
371
+ schema: 1;
372
+ scannedAt: string;
373
+ completed: boolean;
374
+ /** Empty means the checks ran and found nothing — iff `completed` is `true`. */
375
+ findings: ScanFindingDto[];
376
+ /** `null` when the permission check itself did not run. */
377
+ permissionDiff: ScanPermissionDiffDto | null;
378
+ }