@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,128 @@
1
+ /**
2
+ * Pure data types for the map domain layer. Extracted mechanically from
3
+ * `SceneMapCanvas.tsx` (session C1) — no behavior change. Types re-exported
4
+ * from `SceneMapCanvas.tsx` are part of the frozen compat contract (see
5
+ * `docs/componentization_stage/README.md`); consumer import paths must not
6
+ * change.
7
+ */
8
+ import type { LayerSpecification } from "maplibre-gl";
9
+ import type { RasterColorLayer } from "../../lib/raster-color-layer/RasterColorLayer";
10
+ import type { GisMeasureKind } from "../../types/gis";
11
+ /** Measurement flavour, re-exported so rail consumers import it from one place. */
12
+ export type SceneMeasureKind = GisMeasureKind;
13
+ /** Map camera state — used to restore (`initialView`) and persist (`onViewChange`). */
14
+ export interface SceneMapView {
15
+ center: [number, number];
16
+ zoom: number;
17
+ pitch?: number;
18
+ bearing?: number;
19
+ }
20
+ export type SceneMapCoordinate = number | SceneMapCoordinate[] | number[];
21
+ export interface SceneMapGeometry {
22
+ type: string;
23
+ coordinates?: SceneMapCoordinate;
24
+ geometries?: SceneMapGeometry[];
25
+ }
26
+ export type SceneMapFeatureProperties = Record<string, unknown>;
27
+ export interface SceneMapFeature {
28
+ type: "Feature";
29
+ id?: string | number;
30
+ geometry: SceneMapGeometry;
31
+ properties: SceneMapFeatureProperties;
32
+ }
33
+ export interface SceneMapFeatureCollection {
34
+ type: "FeatureCollection";
35
+ features: SceneMapFeature[];
36
+ }
37
+ export type SceneMapDrawFeatureCollection = SceneMapFeatureCollection;
38
+ export interface SceneMapQueryableLayer {
39
+ sceneLayerId: string;
40
+ mapLayerId: string;
41
+ sourceId: string;
42
+ resourceId: string;
43
+ effectiveFileId: string;
44
+ layerName: string;
45
+ layerType: "fill" | "line" | "circle";
46
+ }
47
+ export interface SceneMapQueriedFeature {
48
+ id?: string | number;
49
+ sceneLayerId: string;
50
+ resourceId: string;
51
+ effectiveFileId: string;
52
+ layerName: string;
53
+ properties: SceneMapFeatureProperties;
54
+ geometry: SceneMapGeometry;
55
+ }
56
+ export interface SceneMapDrawQueryResult {
57
+ drawFeatures: SceneMapDrawFeatureCollection;
58
+ features: SceneMapQueriedFeature[];
59
+ featureCollection: SceneMapFeatureCollection;
60
+ }
61
+ /** A rendered raster layer at an identify click. Numeric samples require a
62
+ * dedicated raster-sampling service; MapLibre image tiles carry no raw cells. */
63
+ export interface SceneMapIdentifyRasterLayer {
64
+ sceneLayerId: string;
65
+ resourceId: string;
66
+ effectiveFileId: string;
67
+ layerName: string;
68
+ }
69
+ /** Result of a one-point map identify operation. */
70
+ export interface SceneMapIdentifyResult {
71
+ coordinate: [number, number];
72
+ features: SceneMapQueriedFeature[];
73
+ rasterLayers: SceneMapIdentifyRasterLayer[];
74
+ }
75
+ /**
76
+ * Live state of the (Terra Draw–backed) measurement overlay. Emitted on every
77
+ * change so the rail's contextual strip can show a running total; the on-map
78
+ * labels with units are rendered by `MaplibreMeasureControl` itself. `features`
79
+ * are the raw measure geometries (guidance vertices filtered out) — the caller
80
+ * derives length/area from them, keeping the geodesic math in one place.
81
+ */
82
+ export interface SceneMeasureState {
83
+ kind: SceneMeasureKind;
84
+ features: SceneMapFeatureCollection;
85
+ finished: boolean;
86
+ }
87
+ export interface SceneMapHighlightFeature {
88
+ layer: SceneMapQueryableLayer;
89
+ properties: SceneMapFeatureProperties;
90
+ }
91
+ export type HighlightFilterExpression = unknown[];
92
+ /** Attribute-table highlight for one scene layer. */
93
+ export interface SceneLayerHighlight {
94
+ /** Scene layer id whose rendered source should be highlighted. */
95
+ layerId: string;
96
+ /** Row ids (== tile `uuid`) to mark as selected. */
97
+ featureIds: string[];
98
+ /** Highlight every feature in the layer, apart from explicit exclusions. */
99
+ allFeatures?: boolean;
100
+ /** Only meaningful with `allFeatures`; rows unchecked after select-all. */
101
+ excludedFeatureIds?: string[];
102
+ }
103
+ export type SceneLayerStyle = Record<string, unknown>;
104
+ export type SceneLayerRenderMode = "maplibre" | "custom-raster-color" | "custom-flow-field";
105
+ export type RenderedSceneLayer = {
106
+ sceneLayerId: string;
107
+ layerId: string;
108
+ sourceId: string;
109
+ sourceKey: string;
110
+ renderMode: SceneLayerRenderMode;
111
+ layerType: LayerSpecification["type"] | "custom";
112
+ paintKeys: string[];
113
+ layoutKeys: string[];
114
+ customLayer?: RasterColorLayer;
115
+ customInstance?: {
116
+ update?(input: SceneLayerStyle): boolean;
117
+ setVisible?(visible: boolean): void;
118
+ destroy?(): void;
119
+ };
120
+ };
121
+ export type ResourceBounds = [number, number, number, number];
122
+ export type SceneTileTarget = {
123
+ mode: "live" | "snapshot";
124
+ id: string;
125
+ };
126
+ export type QueryableRenderedSceneLayer = RenderedSceneLayer & {
127
+ layerType: "fill" | "line" | "circle";
128
+ };
@@ -0,0 +1,56 @@
1
+ import { type DatasetAttributeSelection, type DatasetFeatureRow } from "../../../api/resources";
2
+ /**
3
+ * **Exported for `@ngis/plugin-ui`'s `<AttributeTable>` (Stage 14 Y4b).** It was
4
+ * file-private while this component had exactly one call site
5
+ * (`src/features/gis/panels/registrations.tsx`). The wrapper is a second, and it
6
+ * derives its bridge from this declaration (`Pick<GisAttributeTableProps, …>`)
7
+ * rather than re-typing the callbacks — a hand-copied shape drifts the first
8
+ * time a prop here changes, and `tsc` would not say so. Exporting the interface
9
+ * is additive: no member is added, removed or retyped by that change.
10
+ *
11
+ * That bridge has already earned itself once: dropping `onClose` and adding
12
+ * `active` here (the tabbed bottom dock, `24c471b`) landed as a **type error in
13
+ * the wrapper** on the `main` merge rather than as a dead prop nobody noticed.
14
+ * Changing this interface is expected to break `AttributeTable.tsx` — fix it
15
+ * there, do not re-add a member here to keep it quiet.
16
+ */
17
+ export interface GisAttributeTableProps {
18
+ /** Effective file id of the selected layer's resource. */
19
+ fileId: string;
20
+ layerName: string;
21
+ active: boolean;
22
+ /**
23
+ * Push the enumerated feature ids to highlight on the map (null clears). Only
24
+ * include-mode selections can be enumerated; all-matching highlight is deferred.
25
+ */
26
+ onHighlight: (featureIds: string[] | null) => void;
27
+ /** Mirrors each user-driven row-selection change back to the map. */
28
+ onSelectionChange?: (selection: GisAttributeTableSelectionHighlight | null) => void;
29
+ /** Materialize the current selection into a derived dataset + run-tool flow. */
30
+ onRunTool: (selection: DatasetAttributeSelection) => Promise<void>;
31
+ /** A selection made with the scene's feature-select tool, keyed by stable row id. */
32
+ mapSelection?: {
33
+ featureIds: string[];
34
+ /** Attribute values present in the selected vector-tile features. */
35
+ rows?: DatasetFeatureRow[];
36
+ serial: number;
37
+ } | null;
38
+ }
39
+ /** The table's complete selection meaning, forwarded to the map highlight. */
40
+ export type GisAttributeTableSelectionHighlight = {
41
+ featureIds: string[];
42
+ allFeatures?: false;
43
+ } | {
44
+ featureIds: [];
45
+ allFeatures: true;
46
+ excludedFeatureIds: string[];
47
+ };
48
+ /**
49
+ * Attribute table for the selected vector layer's dataset. Server-driven:
50
+ * columns from `attributes/meta`, rows lazily windowed through `attributes/query`
51
+ * (cursor paging + cache, via {@link useDatasetFeatureWindow}). Supports an
52
+ * infinite-scroll (virtualized) or paged view, server sort on sortable columns,
53
+ * a column picker, a filter builder, and dataset-wide selection keyed by the
54
+ * stable feature `id` (so it spans windows that aren't even loaded).
55
+ */
56
+ export declare function GisAttributeTable({ fileId, layerName, active, onHighlight, onSelectionChange, onRunTool, mapSelection, }: GisAttributeTableProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,51 @@
1
+ import { type DatasetAttributeQueryParams, type DatasetAttributeWhereOp, type DatasetFeatureRow } from "../../../api/resources";
2
+ /** One server-side sort directive (single-column for now). */
3
+ export interface FeatureSort {
4
+ field: string;
5
+ dir: "asc" | "desc";
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
+ export interface DatasetFeatureWindowParams {
14
+ fileId: string;
15
+ /** Active sort (null = server default order). */
16
+ sort: FeatureSort | null;
17
+ /** Active filter conditions (AND-combined). */
18
+ filters: FeatureFilter[];
19
+ /** Columns to request (empty = all). `id` is always returned regardless. */
20
+ select: string[];
21
+ enabled?: boolean;
22
+ }
23
+ export interface DatasetFeatureWindow {
24
+ /** All rows loaded so far, in order (for infinite-scroll rendering). */
25
+ rows: DatasetFeatureRow[];
26
+ /** Loaded rows grouped by the chunk they arrived in (for paged rendering). */
27
+ chunks: DatasetFeatureRow[][];
28
+ /** Another page is available via the cursor. */
29
+ hasMore: boolean;
30
+ /** First page is loading and no rows are visible yet. */
31
+ loading: boolean;
32
+ /** A subsequent page is being appended. */
33
+ loadingMore: boolean;
34
+ error: string | null;
35
+ /** Append the next cursor page (no-op while loading or exhausted). */
36
+ loadMore: () => void;
37
+ /** Force a fresh reload of the current signature (drops its cache entry). */
38
+ reload: () => void;
39
+ }
40
+ /** Attribute-table page size shared by the cursor loader and its pager UI. */
41
+ export declare const DATASET_FEATURE_WINDOW_CHUNK_SIZE = 100;
42
+ export declare function buildFeatureQueryParams(params: DatasetFeatureWindowParams, cursor: string | null): DatasetAttributeQueryParams;
43
+ /**
44
+ * Cursor-windowed loader for a vector dataset's features. Loads fixed-size
45
+ * chunks sequentially via `attributes/query` (cursor paging), keeps them in
46
+ * order, and caches the loaded window per query signature (sort + filter +
47
+ * select) with a small LRU so toggling a filter back is instant. Changing the
48
+ * signature resets the window — sorting/filtering reorders the whole dataset,
49
+ * so prior chunks no longer apply.
50
+ */
51
+ export declare function useDatasetFeatureWindow(params: DatasetFeatureWindowParams): DatasetFeatureWindow;
@@ -0,0 +1,81 @@
1
+ /**
2
+ * The one place a client extracts a server-supplied message from an `ApiError`.
3
+ *
4
+ * **F14.15 froze this function's signature and its resolution order at W0 and no
5
+ * session owned it.** YB2 landed the backend half in W1 — `include-message:
6
+ * always` in `ngis-workflow` and `ngis-extension` — so those two services now put
7
+ * real messages on the wire. Nothing on the client ever read them: a repo-wide
8
+ * grep at the W4 kickoff found no `readApiErrorMessage` anywhere, and the
9
+ * prevailing idiom is `err instanceof Error ? err.message : t("failed")`, which
10
+ * renders the *transport's* message ("HTTP 400") rather than the server's.
11
+ *
12
+ * It is authored at the **W4 kickoff** rather than assigned to a session because
13
+ * both W4 sessions need it and neither owns the other's copy:
14
+ *
15
+ * - **Y6** — every nested-recipe refusal is a `BizException` from
16
+ * `ngis-workflow` whose whole content is its message: depth (400), cycle
17
+ * (409), discriminator ambiguity (400), `"input slot <key> is missing
18
+ * bindInputKey"` (400), `"at least one output slot is required"` (400). F14.6
19
+ * says in as many words that nested recipes are an F14.15-dependent feature,
20
+ * and `ExtensionKindReferenceValidator`'s messages are the reference-version
21
+ * wizard's only diagnostics.
22
+ * - **Y7** — a batch row that fails must show *why* on that row. A failed row
23
+ * does not cancel the rest (F14.21), so the message is the only per-row
24
+ * signal there is.
25
+ *
26
+ * Two copies of this would be the W2 `primaryOutput` lesson repeated at wave
27
+ * scale, and the disagreement would be invisible: both would return *a* string.
28
+ */
29
+ import { ApiError } from "./api";
30
+ /**
31
+ * Extract the server's message from an `ApiError`, or `null` when it carried
32
+ * none. **The caller renders a generic host string on `null` — never an empty
33
+ * toast** (F14.15).
34
+ *
35
+ * Resolution order, first non-blank wins:
36
+ *
37
+ * 1. `body.detail` — FastAPI (`ngis-geoanalysis`, `ngis-geoprocess`, `ngis-ai`,
38
+ * `ngis-tile`, `ngis-mcp`). A `detail` that is an *array* is FastAPI's
39
+ * 422 validation shape and is deliberately **not** joined here — it is
40
+ * structured per-field data, and `asSkillPackageValidationError` is the
41
+ * precedent for narrowing it at the call site.
42
+ * 2. `body.errors[]` — field-level binding errors, joined — **but only when `message` is
43
+ * Spring's generic validation preamble**, which names no field and is the same sentence for
44
+ * every rejected body. See {@link isGenericValidationPreamble}.
45
+ * 3. `body.message` — Spring with `include-message: always` (`ngis-workflow`,
46
+ * `ngis-extension` after YB2).
47
+ * 4. `body.error` — the `BizException` advices (`ngis-workflow`, `ngis-file`,
48
+ * `ngis-geocanvas`, `ngis-scene`, `ngis-codeenv`) — **but only when it does
49
+ * not equal the HTTP reason phrase for the status.** In the advice shape
50
+ * `error` is the human message; in Spring's default shape the same key is
51
+ * literally `"Bad Request"`. Without the guard a client renders the reason
52
+ * phrase as though it were a validation message, which is worse than
53
+ * rendering nothing: it looks like an answer.
54
+ * 5. `body.errors[]` — for a body that carried them beside a *specific* message.
55
+ * 6. `null`.
56
+ *
57
+ * **Step 2 is `V-374`, and it is the whole reason `joinBindingErrors` was reachable
58
+ * from nowhere.** With `include-binding-errors: always`, Spring sends *both* a
59
+ * generic `message` — `"Validation failed for object='createExtensionRequest'.
60
+ * Error count: 2"` — and an `errors[]` array carrying the field names. The generic
61
+ * message is non-blank, so it won step 3 every time and `joinBindingErrors`, which
62
+ * exists and is correct and would have rendered *"version: must not be blank;
63
+ * minNgisVersion: must not be blank"*, was never reached. **The field names reached
64
+ * the wire and the client discarded them one layer above it**, which is F-1's own
65
+ * failure mode repeated inside the fix for F-1. The guard is deliberately narrow:
66
+ * it fires only on Spring's own preamble, so a service that sends a real sentence
67
+ * beside `errors[]` still wins at step 3.
68
+ *
69
+ * `error.message` — the transport's own string — is **never** consulted. It is
70
+ * synthesized by `handleResponse` and saying "HTTP 400" where the server said
71
+ * "at least one output slot is required" is the defect this function exists to
72
+ * remove.
73
+ */
74
+ export declare function readApiErrorMessage(error: ApiError): string | null;
75
+ /**
76
+ * The same read for an `unknown` caught value — the shape a `catch` block
77
+ * actually has. Returns `null` for anything that is not an `ApiError`, so a
78
+ * caller still needs its own fallback; this only saves the `instanceof` dance at
79
+ * the dozens of call sites that would otherwise each write it.
80
+ */
81
+ export declare function readServerMessage(error: unknown): string | null;
@@ -0,0 +1,82 @@
1
+ export declare const GATEWAY_ORIGIN: string;
2
+ export declare const SERVER_GATEWAY_ORIGIN: string;
3
+ export declare const API_BASE_URL: string;
4
+ export declare const WS_BASE_URL: string;
5
+ export interface CommonResult<T = unknown> {
6
+ code: number;
7
+ message: string;
8
+ data: T;
9
+ traceId?: string | null;
10
+ details?: unknown;
11
+ }
12
+ export type ApiResponse<T> = CommonResult<T>;
13
+ export interface ApiRequestOptions {
14
+ signal?: AbortSignal;
15
+ headers?: HeadersInit;
16
+ }
17
+ export declare class ApiError extends Error {
18
+ code: number;
19
+ traceId?: string | null;
20
+ /**
21
+ * The parsed error payload, **verbatim**, or `null` when the body was absent
22
+ * or not JSON.
23
+ *
24
+ * Deliberately **not** named `detail` (Stage 12 W2.2): FastAPI's error body
25
+ * *contains* a `detail` key, while the Java services put a `CommonResult`
26
+ * envelope in exactly the same position. One field carries both, and callers
27
+ * narrow it themselves — see `asSkillPackageValidationError` in
28
+ * `src/api/skills.ts` for the structured-422 case this exists for.
29
+ *
30
+ * `unknown` on purpose: nothing may read a property off it without a guard.
31
+ */
32
+ body?: unknown;
33
+ constructor(message: string, code: number, traceId?: string | null, body?: unknown);
34
+ }
35
+ export declare function getErrorMessage(err: unknown, t: (key: string) => string): string;
36
+ /** Called after login/refresh with the new `{accessToken, csrfToken}` pair. */
37
+ export declare function setSession(accessToken: string, csrfToken: string): void;
38
+ /** Drops the in-memory session (logout / session-expired). Does not touch the server-side cookie. */
39
+ export declare function clearSession(): void;
40
+ /**
41
+ * Silent-refresh bootstrap (SG2): call once on app startup (see `AuthProvider`) to re-derive an
42
+ * in-memory access token from the HttpOnly refresh cookie, since memory doesn't survive a page
43
+ * reload. Returns whether a session was recovered; failure (no cookie / expired) just means
44
+ * "not logged in" and is never surfaced as an error.
45
+ */
46
+ export declare function bootstrapSession(): Promise<boolean>;
47
+ export declare function getAccessToken(): string | null;
48
+ export declare function getAccessTokenSubject(): string | null;
49
+ export declare function getAuthHeaders(token?: string): HeadersInit;
50
+ export declare function fetchWithAuth(input: string, init?: RequestInit, token?: string): Promise<Response>;
51
+ /**
52
+ * Mints a short-lived, single-use WebSocket handshake ticket (SG2) via the authenticated
53
+ * `POST /api/auth/ws-ticket`. Call this immediately before opening the socket — the ticket
54
+ * expires quickly (`expiresInSec`, ~30s) and is consumed on first use, so don't cache the result.
55
+ */
56
+ export declare function getWsTicket(): Promise<string | null>;
57
+ /**
58
+ * Builds an authenticated WS URL (SG2). Replaces the previous `?token=<JWT>` pattern — which
59
+ * leaked the access token into server logs/browser history/referrers and, on the backend side,
60
+ * was never even validated — with a single-use `?ticket=` minted right before connecting. Async:
61
+ * returns `null` if minting the ticket fails (e.g. not authenticated), in which case the caller
62
+ * should not attempt to open the socket.
63
+ */
64
+ export declare function buildAuthorizedWsUrl(path: string, params?: Record<string, string | undefined | null>): Promise<string | null>;
65
+ export declare function buildAuthorizedApiUrl(input: string, params?: Record<string, string | undefined | null>): string;
66
+ /**
67
+ * CSRF-aware POST for the `/api/auth/**` endpoints that require the double-submit header
68
+ * (SG2: `refresh`/`logout`) — kept out of the generic `api` client below since no other
69
+ * endpoint needs it. Auth/token state stays owned by this file per the architecture rule.
70
+ */
71
+ export declare function postAuthCsrf<T>(endpoint: string, body?: unknown, options?: {
72
+ readonly keepalive?: boolean;
73
+ }): Promise<T>;
74
+ export declare const api: {
75
+ get<T>(endpoint: string, params?: Record<string, unknown>, token?: string, signalOrOptions?: AbortSignal | ApiRequestOptions): Promise<T>;
76
+ post<T>(endpoint: string, body?: unknown, token?: string, signalOrOptions?: AbortSignal | ApiRequestOptions): Promise<T>;
77
+ postForm<T>(endpoint: string, formData: FormData, token?: string, signal?: AbortSignal): Promise<T>;
78
+ put<T>(endpoint: string, body: unknown, token?: string, signalOrOptions?: AbortSignal | ApiRequestOptions): Promise<T>;
79
+ patch<T>(endpoint: string, body: unknown, token?: string, signalOrOptions?: AbortSignal | ApiRequestOptions): Promise<T>;
80
+ delete<T>(endpoint: string, token?: string, signalOrOptions?: AbortSignal | ApiRequestOptions): Promise<T>;
81
+ deleteWithBody<T>(endpoint: string, body: unknown, token?: string, signal?: AbortSignal): Promise<T>;
82
+ };
@@ -0,0 +1,22 @@
1
+ export declare const SESSION_EXPIRED_EVENT = "glmw:session-expired";
2
+ export declare const SESSION_EXPIRED_REDIRECT_PATH = "/login";
3
+ /** The minimal user profile we persist alongside the session. Not sensitive — no tokens here. */
4
+ export interface StoredUser {
5
+ id?: string;
6
+ email?: string;
7
+ username?: string;
8
+ avatar?: string;
9
+ role?: string;
10
+ }
11
+ export declare function getStoredDeviceId(): string;
12
+ /**
13
+ * SG2: only the display profile is cached here now — no tokens. `src/lib/api.ts` owns the
14
+ * actual session (in-memory access token + CSRF token, backed by the HttpOnly refresh cookie).
15
+ */
16
+ export declare function storeUser(user: StoredUser | null): void;
17
+ export declare function getStoredUser(): StoredUser | null;
18
+ /** Bumps the cross-tab auth-epoch marker — call on login/logout so other tabs re-hydrate. */
19
+ export declare function bumpAuthEpoch(): void;
20
+ /** True if a `storage` event's key is the auth-epoch marker (vs. some unrelated key). */
21
+ export declare function isAuthEpochKey(key: string | null): boolean;
22
+ export declare function notifySessionExpired(): void;
@@ -0,0 +1,57 @@
1
+ import type { PointerEvent as ReactPointerEvent } from "react";
2
+ /**
3
+ * The attribute in `[data-column-cell]` form. A resize handle finds the box it
4
+ * is sizing by walking up to the nearest element carrying it, so a flexible
5
+ * (`1fr`) column can be measured on screen instead of guessed.
6
+ */
7
+ export declare const COLUMN_CELL_SELECTOR = "[data-column-cell]";
8
+ /** How far one arrow-key press moves a boundary. */
9
+ declare const KEYBOARD_STEP = 16;
10
+ /** One resizable column's sizing contract. */
11
+ export interface ResizableColumn {
12
+ id: string;
13
+ /** Hard floor in px — no drag or nudge may take the column below it. */
14
+ min: number;
15
+ /**
16
+ * Starting width in px. Omit to let the column absorb leftover space
17
+ * (`minmax(min, 1fr)`) until its first drag, after which it is pinned.
18
+ */
19
+ initial?: number;
20
+ /** Ceiling in px (defaults to 960). */
21
+ max?: number;
22
+ }
23
+ export interface ColumnResizeApi {
24
+ /** Pinned width in px, or null while the column is still flexible. */
25
+ widthOf: (id: string) => number | null;
26
+ /** A `grid-template-columns` track list for `ids` (default: every column). */
27
+ template: (ids?: string[]) => string;
28
+ /** Sum of the current widths, flexible columns counted at their minimum. */
29
+ minTotal: number;
30
+ /** The column currently being dragged, for handle affordances. */
31
+ resizingId: string | null;
32
+ startResize: (id: string, event: ReactPointerEvent<HTMLElement>) => void;
33
+ /**
34
+ * Resize the boundary between two adjacent columns while keeping their
35
+ * combined width stable. The first column grows to the right; the second
36
+ * column yields the same amount, subject to both columns' bounds.
37
+ */
38
+ startAdjacentResize: (id: string, nextId: string, event: ReactPointerEvent<HTMLElement>) => void;
39
+ /** Keyboard counterpart of {@link startAdjacentResize}. */
40
+ nudgeAdjacent: (id: string, nextId: string, deltaPx: number, from?: HTMLElement | null) => void;
41
+ /** Keyboard resize; `from` is any node inside the column being sized. */
42
+ nudge: (id: string, deltaPx: number, from?: HTMLElement | null) => void;
43
+ /** Drop one column's pinned width, or every column's when called bare. */
44
+ reset: (id?: string) => void;
45
+ }
46
+ /**
47
+ * Column-width state for a table the user can resize by dragging the boundary
48
+ * in its header. Layout-agnostic: {@link ColumnResizeApi.template} drives a CSS
49
+ * grid, {@link ColumnResizeApi.widthOf} drives a `<colgroup>` under
50
+ * `table-fixed`. Widths live in React state (per mount) — nothing is persisted.
51
+ *
52
+ * A drag listens on `window` rather than capturing the pointer, so releasing
53
+ * outside the viewport still ends it, and the listeners are torn down on
54
+ * unmount. `columns` should be memoized by the caller; only its contents matter.
55
+ */
56
+ export declare function useColumnResize(columns: ResizableColumn[]): ColumnResizeApi;
57
+ export { KEYBOARD_STEP as COLUMN_RESIZE_STEP };
@@ -0,0 +1,48 @@
1
+ import type { CustomLayerInterface, CustomRenderMethodInput, Map as MapLibreMap } from "maplibre-gl";
2
+ import { type TileBounds, type TileRequestInitFactory } from "./TileGrid";
3
+ import { type ColorStop, type RasterColorMode } from "./colorRamp";
4
+ import { type ZoomStop } from "./zoomInterpolate";
5
+ export type RasterColorLayerOptions = {
6
+ urlTemplate: string;
7
+ maxZoom: number;
8
+ mix: [number, number, number, number];
9
+ range: [number, number];
10
+ ramp: ColorStop[];
11
+ colorMode?: RasterColorMode;
12
+ classBreaks?: number[];
13
+ valueRange?: [number, number];
14
+ opacity: number | ZoomStop[];
15
+ visibleZoomRange?: [number, number];
16
+ bounds?: TileBounds;
17
+ getRequestInit?: TileRequestInitFactory;
18
+ };
19
+ export declare function getRasterProjectionMatrix(options: Pick<CustomRenderMethodInput, "defaultProjectionData">): import("maplibre-gl").ProjectionMatrix;
20
+ export declare class RasterColorLayer implements CustomLayerInterface {
21
+ id: string;
22
+ type: "custom";
23
+ renderingMode: "2d";
24
+ private opts;
25
+ private map;
26
+ private gl;
27
+ private program;
28
+ private quadBuffer;
29
+ private tileGrid;
30
+ private rampTexture;
31
+ private rampDirty;
32
+ private uniforms;
33
+ private attribs;
34
+ constructor(id: string, options: RasterColorLayerOptions);
35
+ onAdd(map: MapLibreMap, gl: WebGLRenderingContext | WebGL2RenderingContext): void;
36
+ setColorStyle(options: {
37
+ ramp: ColorStop[];
38
+ colorMode?: RasterColorMode;
39
+ classBreaks?: number[];
40
+ valueRange?: [number, number];
41
+ }): void;
42
+ setRange(range: [number, number]): void;
43
+ setOpacity(opacity: number | ZoomStop[]): void;
44
+ setVisibleZoomRange(range: [number, number]): void;
45
+ render(gl: WebGLRenderingContext | WebGL2RenderingContext, options: CustomRenderMethodInput): void;
46
+ onRemove(): void;
47
+ private ensureRampTexture;
48
+ }
@@ -0,0 +1,31 @@
1
+ export type TileId = {
2
+ z: number;
3
+ x: number;
4
+ y: number;
5
+ };
6
+ export type TileBounds = [number, number, number, number];
7
+ export type TileRequestInitFactory = () => RequestInit | undefined;
8
+ export declare class TileGrid {
9
+ private gl;
10
+ private urlTemplate;
11
+ private maxZoom;
12
+ private onTileLoaded;
13
+ private getRequestInit?;
14
+ private bounds?;
15
+ private cache;
16
+ private queue;
17
+ private activeRequests;
18
+ private destroyed;
19
+ private frame;
20
+ private readonly maxCacheSize;
21
+ constructor(gl: WebGLRenderingContext | WebGL2RenderingContext, urlTemplate: string, maxZoom: number, onTileLoaded: () => void, getRequestInit?: TileRequestInitFactory | undefined, bounds?: TileBounds | undefined);
22
+ coveringTiles(west: number, south: number, east: number, north: number, zoom: number): TileId[];
23
+ ensure(tiles: TileId[]): void;
24
+ getTexture(tile: TileId): WebGLTexture | null;
25
+ private fetchTile;
26
+ private pumpQueue;
27
+ private scheduleRetry;
28
+ private evictIfNeeded;
29
+ private disposeEntry;
30
+ destroy(): void;
31
+ }
@@ -0,0 +1,18 @@
1
+ export type ColorStop = {
2
+ stop: number;
3
+ color: [number, number, number, number];
4
+ };
5
+ export type RasterColorMode = "continuous" | "classified";
6
+ export type RasterClassificationMethod = "equal-interval" | "manual";
7
+ export declare function buildColorRampData(stops: ColorStop[], resolution?: number): Uint8Array<ArrayBuffer>;
8
+ export declare function isValidClassBreaks(value: unknown, range?: [number, number], colorCount?: number): value is number[];
9
+ export declare function buildEqualIntervalBreaks(min: number, max: number, classCount: number): number[] | null;
10
+ export declare function normalizeClassBreaks(classBreaks: unknown, range: [number, number], colorCount?: number): number[] | null;
11
+ export declare function buildClassifiedColorRampData(stops: ColorStop[], classBreaks: unknown, range: [number, number], resolution?: number): Uint8Array<ArrayBuffer> | null;
12
+ export declare function buildRasterColorRampData(stops: ColorStop[], options?: {
13
+ colorMode?: RasterColorMode;
14
+ classBreaks?: unknown;
15
+ valueRange?: [number, number];
16
+ }, resolution?: number): Uint8Array<ArrayBuffer>;
17
+ export declare function computeRasterColorMix(colorRampRes: number, mix: [number, number, number, number], range: [number, number]): [number, number, number, number];
18
+ export declare function computeRasterColorOffset(colorRampRes: number, offset: number, range: [number, number]): number;
@@ -0,0 +1,2 @@
1
+ declare const fragSrc = "\nprecision mediump float;\n\nuniform sampler2D u_tile;\nuniform sampler2D u_color_ramp;\nuniform vec3 u_colorization_mix;\nuniform float u_colorization_offset;\nuniform float u_opacity;\n\nvarying vec2 v_uv;\n\nvoid main() {\n vec4 c = texture2D(u_tile, v_uv);\n\n float value = u_colorization_offset + dot(c.rgb, u_colorization_mix);\n vec4 ramped = texture2D(u_color_ramp, vec2(value, 0.5));\n\n if (ramped.a > 0.0) ramped.rgb /= ramped.a;\n\n float alpha = ramped.a * u_opacity * c.a;\n gl_FragColor = vec4(ramped.rgb * alpha, alpha);\n}\n";
2
+ export default fragSrc;
@@ -0,0 +1,2 @@
1
+ declare const vertSrc = "\nattribute vec2 a_pos;\n\nuniform mat4 u_matrix;\nuniform vec2 u_tile_tl;\nuniform vec2 u_tile_scale;\n\nvarying vec2 v_uv;\n\nvoid main() {\n v_uv = a_pos;\n vec2 mercator = u_tile_tl + a_pos * u_tile_scale;\n gl_Position = u_matrix * vec4(mercator, 0.0, 1.0);\n}\n";
2
+ export default vertSrc;
@@ -0,0 +1,2 @@
1
+ export type ZoomStop = [zoom: number, value: number];
2
+ export declare function resolveZoomStops(value: number | ZoomStop[], zoom: number): number;
@@ -0,0 +1,2 @@
1
+ import { type ClassValue } from "clsx";
2
+ export declare function cn(...inputs: ClassValue[]): string;