@honua/app-platform 0.1.7-beta.0 → 0.1.9-beta.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 (181) hide show
  1. package/control-plane/admin-client.d.ts +87 -0
  2. package/control-plane/admin-client.d.ts.map +1 -0
  3. package/control-plane/admin-client.js +169 -0
  4. package/control-plane/client.d.ts.map +1 -1
  5. package/control-plane/client.js +21 -3
  6. package/control-plane/commands/catalog.d.ts +133 -0
  7. package/control-plane/commands/catalog.d.ts.map +1 -0
  8. package/control-plane/commands/catalog.js +353 -0
  9. package/control-plane/commands/errors.d.ts +96 -0
  10. package/control-plane/commands/errors.d.ts.map +1 -0
  11. package/control-plane/commands/errors.js +130 -0
  12. package/control-plane/commands/identity.d.ts +71 -0
  13. package/control-plane/commands/identity.d.ts.map +1 -0
  14. package/control-plane/commands/identity.js +0 -0
  15. package/control-plane/commands/index.d.ts +39 -0
  16. package/control-plane/commands/index.d.ts.map +1 -0
  17. package/control-plane/commands/index.js +34 -0
  18. package/control-plane/commands/runtime.d.ts +146 -0
  19. package/control-plane/commands/runtime.d.ts.map +1 -0
  20. package/control-plane/commands/runtime.js +318 -0
  21. package/control-plane/commands/types.d.ts +226 -0
  22. package/control-plane/commands/types.d.ts.map +1 -0
  23. package/control-plane/commands/types.js +30 -0
  24. package/control-plane/commands/validate.d.ts +27 -0
  25. package/control-plane/commands/validate.d.ts.map +1 -0
  26. package/control-plane/commands/validate.js +132 -0
  27. package/control-plane/generated/admin-api.d.ts +30263 -0
  28. package/control-plane/generated/admin-api.d.ts.map +1 -0
  29. package/control-plane/generated/admin-api.js +5 -0
  30. package/control-plane/generated/admin-operations.d.ts +4053 -0
  31. package/control-plane/generated/admin-operations.d.ts.map +1 -0
  32. package/control-plane/generated/admin-operations.js +820 -0
  33. package/control-plane/index.d.ts +11 -0
  34. package/control-plane/index.d.ts.map +1 -1
  35. package/control-plane/index.js +8 -0
  36. package/control-plane/types.d.ts +14 -0
  37. package/control-plane/types.d.ts.map +1 -1
  38. package/controls/catalog.d.ts.map +1 -1
  39. package/controls/catalog.js +12 -1
  40. package/controls/qualification.d.ts.map +1 -1
  41. package/controls/qualification.js +61 -2
  42. package/core/client.js +1 -1
  43. package/core/credential-redaction.d.ts +163 -0
  44. package/core/credential-redaction.d.ts.map +1 -0
  45. package/core/credential-redaction.js +389 -0
  46. package/core/error-classifications.d.ts +15 -0
  47. package/core/error-classifications.d.ts.map +1 -1
  48. package/core/error-classifications.js +15 -0
  49. package/core/error-code-registry.d.ts +15 -0
  50. package/core/error-code-registry.d.ts.map +1 -1
  51. package/core/error-code-registry.js +15 -0
  52. package/core/grpc-adapter.d.ts +2 -2
  53. package/core/grpc-adapter.d.ts.map +1 -1
  54. package/core/grpc-adapter.js +30 -6
  55. package/core/job-run-errors.d.ts +10 -0
  56. package/core/job-run-errors.d.ts.map +1 -0
  57. package/core/job-run-errors.js +14 -0
  58. package/core/job-run-lifecycle.d.ts +34 -0
  59. package/core/job-run-lifecycle.d.ts.map +1 -0
  60. package/core/job-run-lifecycle.js +193 -0
  61. package/core/ogc-processes.d.ts +38 -31
  62. package/core/ogc-processes.d.ts.map +1 -1
  63. package/core/ogc-processes.js +123 -173
  64. package/core/process-runner.js +15 -100
  65. package/core/surfaces.d.ts +2 -8
  66. package/core/surfaces.d.ts.map +1 -1
  67. package/core/surfaces.js +21 -112
  68. package/core/types.d.ts +7 -6
  69. package/core/types.d.ts.map +1 -1
  70. package/gen/geospatial/v1/common_pb.d.ts +514 -0
  71. package/gen/geospatial/v1/common_pb.d.ts.map +1 -0
  72. package/gen/geospatial/v1/common_pb.js +335 -0
  73. package/gen/geospatial/v1/execution_types_pb.d.ts +1186 -0
  74. package/gen/geospatial/v1/execution_types_pb.d.ts.map +1 -0
  75. package/gen/geospatial/v1/execution_types_pb.js +396 -0
  76. package/gen/geospatial/v1/feature_service_pb.d.ts +403 -0
  77. package/gen/geospatial/v1/feature_service_pb.d.ts.map +1 -0
  78. package/gen/geospatial/v1/feature_service_pb.js +47 -0
  79. package/gen/geospatial/v1/index.d.ts +5 -0
  80. package/gen/geospatial/v1/index.d.ts.map +1 -0
  81. package/gen/geospatial/v1/index.js +9 -0
  82. package/gen/geospatial/v1/spatial_types_pb.d.ts +285 -0
  83. package/gen/geospatial/v1/spatial_types_pb.d.ts.map +1 -0
  84. package/gen/geospatial/v1/spatial_types_pb.js +58 -0
  85. package/gen/geospatial/v1/workspace_artifact_types_pb.d.ts +379 -0
  86. package/gen/geospatial/v1/workspace_artifact_types_pb.d.ts.map +1 -0
  87. package/gen/geospatial/v1/workspace_artifact_types_pb.js +151 -0
  88. package/generated-app/projection.d.ts.map +1 -1
  89. package/generated-app/projection.js +10 -1
  90. package/operate/client.d.ts +11 -1
  91. package/operate/client.d.ts.map +1 -1
  92. package/operate/client.js +39 -2
  93. package/operate/index.d.ts +1 -1
  94. package/operate/index.d.ts.map +1 -1
  95. package/operate/types.d.ts +40 -6
  96. package/operate/types.d.ts.map +1 -1
  97. package/package.json +2 -2
  98. package/query-planner/grpc.d.ts +4 -4
  99. package/query-planner/grpc.d.ts.map +1 -1
  100. package/query-planner/grpc.js +11 -11
  101. package/query-planner/types.d.ts +4 -4
  102. package/query-planner/types.d.ts.map +1 -1
  103. package/runtime/errors.d.ts +1 -1
  104. package/runtime/errors.d.ts.map +1 -1
  105. package/runtime/errors.js +2 -0
  106. package/runtime/generated/map-package-schema-meta.d.ts +11 -0
  107. package/runtime/generated/map-package-schema-meta.d.ts.map +1 -0
  108. package/runtime/generated/map-package-schema-meta.js +12 -0
  109. package/runtime/generated/map-package-schema-validator.d.ts +16 -0
  110. package/runtime/generated/map-package-schema-validator.d.ts.map +1 -0
  111. package/runtime/generated/map-package-schema-validator.js +1422 -0
  112. package/runtime/index.d.ts +5 -1
  113. package/runtime/index.d.ts.map +1 -1
  114. package/runtime/index.js +6 -0
  115. package/runtime/map-package-export.d.ts +151 -0
  116. package/runtime/map-package-export.d.ts.map +1 -0
  117. package/runtime/map-package-export.js +636 -0
  118. package/runtime/map-package-validation.d.ts +21 -1
  119. package/runtime/map-package-validation.d.ts.map +1 -1
  120. package/runtime/map-package-validation.js +132 -95
  121. package/runtime/map-package.d.ts +80 -0
  122. package/runtime/map-package.d.ts.map +1 -1
  123. package/scene-workspace/cesium-adapter.d.ts +8 -8
  124. package/scene-workspace/cesium-adapter.d.ts.map +1 -1
  125. package/scene-workspace/cesium-adapter.js +31 -18
  126. package/scene-workspace/primitives.d.ts.map +1 -1
  127. package/scene-workspace/primitives.js +9 -6
  128. package/scene-workspace/source-to-cesium.js +1 -1
  129. package/studio/chart-spec.d.ts +6 -6
  130. package/studio/chart-spec.d.ts.map +1 -1
  131. package/studio/chart-spec.js +6 -6
  132. package/studio/index.d.ts +17 -7
  133. package/studio/index.d.ts.map +1 -1
  134. package/studio/index.js +15 -5
  135. package/studio/lifecycle-client.d.ts +262 -22
  136. package/studio/lifecycle-client.d.ts.map +1 -1
  137. package/studio/lifecycle-client.js +524 -24
  138. package/studio/lifecycle-errors.d.ts +8 -2
  139. package/studio/lifecycle-errors.d.ts.map +1 -1
  140. package/studio/lifecycle-errors.js +6 -1
  141. package/studio/lifecycle-types.d.ts +228 -8
  142. package/studio/lifecycle-types.d.ts.map +1 -1
  143. package/studio/publication-status.d.ts +55 -0
  144. package/studio/publication-status.d.ts.map +1 -0
  145. package/studio/publication-status.js +103 -0
  146. package/web-components/application-context.d.ts +186 -0
  147. package/web-components/application-context.d.ts.map +1 -0
  148. package/web-components/application-context.js +429 -0
  149. package/web-components/elements.d.ts +10 -1
  150. package/web-components/elements.d.ts.map +1 -1
  151. package/web-components/elements.js +187 -7
  152. package/web-components/export-redaction.d.ts +11 -102
  153. package/web-components/export-redaction.d.ts.map +1 -1
  154. package/web-components/export-redaction.js +7 -260
  155. package/web-components/feature-editor-offline.d.ts +32 -0
  156. package/web-components/feature-editor-offline.d.ts.map +1 -0
  157. package/web-components/feature-editor-offline.js +115 -0
  158. package/web-components/feature-editor-workflow.d.ts +85 -22
  159. package/web-components/feature-editor-workflow.d.ts.map +1 -1
  160. package/web-components/feature-editor-workflow.js +240 -0
  161. package/web-components/feature-editor.d.ts +20 -6
  162. package/web-components/feature-editor.d.ts.map +1 -1
  163. package/web-components/feature-editor.js +159 -7
  164. package/web-components/feature-inspection.d.ts +287 -0
  165. package/web-components/feature-inspection.d.ts.map +1 -0
  166. package/web-components/feature-inspection.js +1464 -0
  167. package/web-components/feature-mutation.d.ts +77 -0
  168. package/web-components/feature-mutation.d.ts.map +1 -0
  169. package/web-components/feature-mutation.js +137 -0
  170. package/web-components/feature-table-engine.d.ts +94 -4
  171. package/web-components/feature-table-engine.d.ts.map +1 -1
  172. package/web-components/feature-table-engine.js +365 -18
  173. package/web-components/feature-table-view.d.ts +35 -1
  174. package/web-components/feature-table-view.d.ts.map +1 -1
  175. package/web-components/feature-table-view.js +128 -12
  176. package/web-components/index.d.ts +14 -6
  177. package/web-components/index.d.ts.map +1 -1
  178. package/web-components/index.js +8 -2
  179. package/gen/honua/v1/feature_service_pb.d.ts +0 -918
  180. package/gen/honua/v1/feature_service_pb.d.ts.map +0 -1
  181. package/gen/honua/v1/feature_service_pb.js +0 -375
@@ -36,6 +36,7 @@
36
36
  import { createEditSession, } from "../contract/edit-session.js";
37
37
  import { createEditSketchWorkflow, } from "../contract/edit-sketch.js";
38
38
  import { buildEditorFormModel, coerceEditorFieldValue, editorFieldsFromSchema, editorOperationAvailability, redactEditorAttachment, resolveActiveEditorSubtype, resolveEditorFields, resolveEditorOperations, } from "./feature-editor-model.js";
39
+ import { createHonuaFeatureMutationReceipt } from "./feature-mutation.js";
39
40
  const EMPTY_UNDO = { canUndo: false, canRedo: false, undoDepth: 0, redoDepth: 0 };
40
41
  const CONFLICT_CHOICES = ["keep-mine", "discard-mine", "reload"];
41
42
  export class HonuaFeatureEditorWorkflow {
@@ -55,6 +56,9 @@ export class HonuaFeatureEditorWorkflow {
55
56
  #conflict;
56
57
  #message = "";
57
58
  #committedFeatureId;
59
+ #mutationReceipt;
60
+ #mutationId;
61
+ #offline;
58
62
  #abort;
59
63
  /**
60
64
  * The model whose in-flight submit was cancelled. Identity, not a boolean:
@@ -120,6 +124,40 @@ export class HonuaFeatureEditorWorkflow {
120
124
  const errors = dedupeValidationErrors([...sessionErrors, ...derivedErrors]);
121
125
  return { valid: errors.length === 0, errors };
122
126
  }
127
+ /** Review the exact current attempt without calling an adapter or changing workflow state. */
128
+ preflight() {
129
+ const model = this.#model;
130
+ const operation = this.#operation;
131
+ const validation = this.validate();
132
+ const blockers = [];
133
+ if (!model || !operation)
134
+ blockers.push("no-draft");
135
+ if (this.#conflict)
136
+ blockers.push("conflict");
137
+ if (this.#status === "submitting")
138
+ blockers.push("submitting");
139
+ if (this.#status === "queued")
140
+ blockers.push("queued");
141
+ const errors = validation.errors.map(({ code, fieldName, message }) => ({
142
+ code,
143
+ ...(fieldName ? { fieldName } : {}),
144
+ message,
145
+ }));
146
+ return Object.freeze({
147
+ ready: Boolean(model && operation && validation.valid && blockers.length === 0),
148
+ transported: false,
149
+ ...(model && this.#mutationId ? { mutationId: this.#mutationId } : {}),
150
+ source: Object.freeze({
151
+ sourceId: this.source.descriptor.id,
152
+ protocol: this.source.descriptor.protocol,
153
+ }),
154
+ ...(operation ? { operation } : {}),
155
+ ...(model && this.#identity ? { identity: Object.freeze({ ...this.#identity }) } : {}),
156
+ ...(model ? { draft: this.#draftSummary(model) } : {}),
157
+ validation: Object.freeze({ valid: validation.valid, errors: Object.freeze(errors) }),
158
+ blockers: Object.freeze(blockers),
159
+ });
160
+ }
123
161
  /** Current UI state. Never contains an attachment payload. */
124
162
  snapshot() {
125
163
  const model = this.#model;
@@ -128,6 +166,7 @@ export class HonuaFeatureEditorWorkflow {
128
166
  status: this.#status,
129
167
  ...(this.#operation ? { operation: this.#operation } : {}),
130
168
  ...(this.#identity ? { identity: this.#identity } : {}),
169
+ ...(model && this.#mutationId ? { mutationId: this.#mutationId } : {}),
131
170
  operations: this.operations(),
132
171
  ...(model && this.#operation
133
172
  ? {
@@ -160,6 +199,8 @@ export class HonuaFeatureEditorWorkflow {
160
199
  busy: this.#status === "submitting",
161
200
  message: this.#message,
162
201
  ...(this.#committedFeatureId !== undefined ? { committedFeatureId: this.#committedFeatureId } : {}),
202
+ ...(this.#mutationReceipt ? { mutationReceipt: this.#mutationReceipt } : {}),
203
+ ...(this.#offline ? { offline: this.#offline } : {}),
163
204
  };
164
205
  }
165
206
  // ── draft lifecycle ────────────────────────────────────────────────────
@@ -198,6 +239,8 @@ export class HonuaFeatureEditorWorkflow {
198
239
  * recorded — never silently downgraded.
199
240
  */
200
241
  begin(operation, feature) {
242
+ if (this.#status === "queued")
243
+ return this.snapshot();
201
244
  const target = operation === "create" ? feature : (feature ?? this.#selection);
202
245
  this.#abort?.abort();
203
246
  // Any in-flight submit now belongs to a superseded draft, cancelled or not.
@@ -206,6 +249,9 @@ export class HonuaFeatureEditorWorkflow {
206
249
  this.#failures = [];
207
250
  this.#attachments = [];
208
251
  this.#committedFeatureId = undefined;
252
+ this.#mutationReceipt = undefined;
253
+ this.#mutationId = undefined;
254
+ this.#offline = undefined;
209
255
  this.#identity = this.#identityFor(target);
210
256
  // Resolve availability against the feature this draft would open on, not
211
257
  // the standing selection — an explicit `feature` argument may carry a
@@ -228,6 +274,7 @@ export class HonuaFeatureEditorWorkflow {
228
274
  this.#operation = operation;
229
275
  this.#baseline = target ? cloneFeature(target) : undefined;
230
276
  this.#model = this.#createModel(operation, target);
277
+ this.#mutationId = this.#allocateMutationId();
231
278
  if (operation === "create")
232
279
  this.#seedCreateDefaults();
233
280
  this.#status = "draft";
@@ -236,6 +283,8 @@ export class HonuaFeatureEditorWorkflow {
236
283
  }
237
284
  /** Discards the draft and aborts any in-flight submit. */
238
285
  cancel() {
286
+ if (this.#status === "queued")
287
+ return this.snapshot();
239
288
  if (this.#status === "submitting")
240
289
  this.#cancelledModel = this.#model;
241
290
  this.#abort?.abort();
@@ -246,14 +295,21 @@ export class HonuaFeatureEditorWorkflow {
246
295
  this.#conflict = undefined;
247
296
  this.#failures = [];
248
297
  this.#attachments = [];
298
+ this.#mutationReceipt = undefined;
299
+ this.#mutationId = undefined;
300
+ this.#offline = undefined;
249
301
  this.#status = "cancelled";
250
302
  this.#message = "Edit cancelled. Nothing was sent to the service.";
251
303
  return this.#notify();
252
304
  }
253
305
  /** Reverts the draft to the feature it opened on, keeping the draft open. */
254
306
  discardChanges() {
307
+ if (this.#status === "queued")
308
+ return this.snapshot();
255
309
  this.#model?.discard();
256
310
  this.#attachments = [];
311
+ this.#mutationReceipt = undefined;
312
+ this.#offline = undefined;
257
313
  this.#failures = [];
258
314
  this.#conflict = undefined;
259
315
  if (this.#model)
@@ -268,6 +324,8 @@ export class HonuaFeatureEditorWorkflow {
268
324
  * never reaches transport as a string).
269
325
  */
270
326
  setValue(name, raw) {
327
+ if (this.#status === "queued")
328
+ return this.snapshot();
271
329
  const model = this.#model;
272
330
  if (!model)
273
331
  return this.snapshot();
@@ -278,6 +336,8 @@ export class HonuaFeatureEditorWorkflow {
278
336
  }
279
337
  /** Records several already-typed attribute values at once. */
280
338
  setValues(values) {
339
+ if (this.#status === "queued")
340
+ return this.snapshot();
281
341
  const model = this.#model;
282
342
  if (!model)
283
343
  return this.snapshot();
@@ -288,6 +348,9 @@ export class HonuaFeatureEditorWorkflow {
288
348
  // ── geometry ───────────────────────────────────────────────────────────
289
349
  /** Arms a sketch tool. Unsupported tools report their reason rather than throwing. */
290
350
  startSketch(tool) {
351
+ if (this.#status === "queued") {
352
+ return { tool, state: "unsupported", reason: "The draft is locked while its offline edit is queued." };
353
+ }
291
354
  const model = this.#model;
292
355
  if (!model)
293
356
  return { tool, state: "unsupported", reason: "No edit draft is open." };
@@ -301,6 +364,8 @@ export class HonuaFeatureEditorWorkflow {
301
364
  }
302
365
  /** Applies a geometry through the sketch workflow (undoable). */
303
366
  setGeometry(tool, geometry) {
367
+ if (this.#status === "queued")
368
+ return this.snapshot();
304
369
  const model = this.#model;
305
370
  if (!model)
306
371
  return this.snapshot();
@@ -309,6 +374,8 @@ export class HonuaFeatureEditorWorkflow {
309
374
  return this.#notify();
310
375
  }
311
376
  undo() {
377
+ if (this.#status === "queued")
378
+ return false;
312
379
  const undone = this.#model?.undo() ?? false;
313
380
  if (undone) {
314
381
  this.#afterEdit();
@@ -317,6 +384,8 @@ export class HonuaFeatureEditorWorkflow {
317
384
  return undone;
318
385
  }
319
386
  redo() {
387
+ if (this.#status === "queued")
388
+ return false;
320
389
  const redone = this.#model?.redo() ?? false;
321
390
  if (redone) {
322
391
  this.#afterEdit();
@@ -330,6 +399,8 @@ export class HonuaFeatureEditorWorkflow {
330
399
  * only redacted metadata enters the snapshot.
331
400
  */
332
401
  stageAttachment(attachment, options = {}) {
402
+ if (this.#status === "queued")
403
+ return this.snapshot();
333
404
  const model = this.#model;
334
405
  if (!model)
335
406
  return this.snapshot();
@@ -340,6 +411,8 @@ export class HonuaFeatureEditorWorkflow {
340
411
  }
341
412
  /** Stages an attachment deletion. */
342
413
  stageAttachmentDelete(attachmentIds, options = {}) {
414
+ if (this.#status === "queued")
415
+ return this.snapshot();
343
416
  const model = this.#model;
344
417
  if (!model)
345
418
  return this.snapshot();
@@ -356,6 +429,8 @@ export class HonuaFeatureEditorWorkflow {
356
429
  * attachments; undo history restarts because tool support is a new baseline.
357
430
  */
358
431
  configureSketchTools(tools) {
432
+ if (this.#status === "queued")
433
+ return this.snapshot();
359
434
  // Idempotent: re-declaring the same tool support must not rebuild an open
360
435
  // draft (a sketch adapter may re-announce it on every attach).
361
436
  if (JSON.stringify(this.#sketchTools ?? null) === JSON.stringify(tools))
@@ -387,6 +462,16 @@ export class HonuaFeatureEditorWorkflow {
387
462
  if (!model || !operation) {
388
463
  return this.#preflightRejection("create", "No edit draft is open.", "validation");
389
464
  }
465
+ if (this.#status === "queued") {
466
+ return {
467
+ status: "queued",
468
+ operation,
469
+ transported: false,
470
+ failures: this.#failures,
471
+ attachments: this.#attachments,
472
+ snapshot: this.snapshot(),
473
+ };
474
+ }
390
475
  if (this.#conflict) {
391
476
  return this.#preflightRejection(operation, "Resolve the conflicting change before submitting again.", "conflict", "conflict");
392
477
  }
@@ -401,8 +486,12 @@ export class HonuaFeatureEditorWorkflow {
401
486
  this.#message = "The draft was not sent: fix the highlighted problems first.";
402
487
  return this.#commit(operation, false);
403
488
  }
489
+ const mutationId = this.#mutationId ?? this.#allocateMutationId();
490
+ this.#mutationId = mutationId;
404
491
  this.#status = "submitting";
405
492
  this.#failures = [];
493
+ this.#mutationReceipt = undefined;
494
+ this.#offline = undefined;
406
495
  this.#message = `Applying the ${operation}…`;
407
496
  this.#notify();
408
497
  const result = await model.submit();
@@ -426,6 +515,25 @@ export class HonuaFeatureEditorWorkflow {
426
515
  }
427
516
  this.#failures = result.failures.map(normalizeFailure);
428
517
  this.#attachments = attachmentDraftsFromResult(model, result.attachmentResults);
518
+ if (result.status === "succeeded" || result.status === "partial") {
519
+ const committedFeatureId = result.committedFeatureId ?? this.#identity?.featureId;
520
+ const feature = operation === "delete" ? undefined : featureAfterCommit(model.snapshot().feature, committedFeatureId);
521
+ this.#mutationReceipt = createHonuaFeatureMutationReceipt({
522
+ mutationId,
523
+ acceptedAt: this.#options.now?.() ?? new Date().toISOString(),
524
+ sourceId: this.source.descriptor.id,
525
+ protocol: this.source.descriptor.protocol,
526
+ operation,
527
+ status: result.status === "succeeded" ? "accepted" : "partially-accepted",
528
+ ...(committedFeatureId !== undefined ? { featureId: committedFeatureId } : {}),
529
+ ...(feature ? { feature } : {}),
530
+ attachmentMutations: result.attachmentResults.length,
531
+ optimistic: result.optimistic,
532
+ });
533
+ }
534
+ else {
535
+ this.#mutationReceipt = undefined;
536
+ }
429
537
  const conflictFailure = result.failures.find((failure) => failure.kind === "conflict");
430
538
  if (result.status === "succeeded") {
431
539
  this.#status = "committed";
@@ -459,10 +567,89 @@ export class HonuaFeatureEditorWorkflow {
459
567
  if (this.#status === "submitting") {
460
568
  return this.#preflightRejection(this.#operation ?? "create", "An edit is already in flight.", "validation");
461
569
  }
570
+ if (this.#status === "queued")
571
+ return this.submit();
462
572
  if (this.#status !== "conflict")
463
573
  this.#status = "draft";
464
574
  return this.submit();
465
575
  }
576
+ /**
577
+ * Project a durable offline queue transition into the online workflow's
578
+ * recoverable states. The queue remains authoritative; only identities and
579
+ * reason codes enter the component, never credentials or persisted payloads.
580
+ */
581
+ applyOfflineState(state) {
582
+ if (state.sourceId !== this.source.descriptor.id || !this.#model || !this.#operation)
583
+ return this.snapshot();
584
+ this.#mutationId = state.idempotencyKey;
585
+ this.#offline = Object.freeze({ ...state });
586
+ this.#failures = [];
587
+ if (state.state === "pending" || state.state === "retryable") {
588
+ this.#conflict = undefined;
589
+ this.#status = "queued";
590
+ this.#message =
591
+ state.state === "retryable"
592
+ ? `The edit remains offline and will retry${state.retryAt ? ` after ${state.retryAt}` : ""}.`
593
+ : "The edit is saved in the offline queue and will apply after reconnect.";
594
+ return this.#notify();
595
+ }
596
+ if (state.state === "conflicted") {
597
+ this.#status = "conflict";
598
+ this.#conflict = {
599
+ reason: state.conflictId
600
+ ? `Offline replay conflict ${state.conflictId} requires a decision.`
601
+ : "The offline edit conflicts with the current server feature.",
602
+ ...(this.#identity?.version !== undefined ? { mine: this.#identity.version } : {}),
603
+ ...(state.serverGeneration !== undefined ? { theirs: state.serverGeneration } : {}),
604
+ choices: CONFLICT_CHOICES,
605
+ };
606
+ this.#message = "The offline edit conflicted after reconnect. Choose how to resolve it.";
607
+ return this.#notify();
608
+ }
609
+ if (state.state === "cancelled") {
610
+ this.#conflict = undefined;
611
+ this.#status = "rejected";
612
+ this.#failures = [
613
+ {
614
+ kind: "transport",
615
+ operation: "session",
616
+ description: state.reasonCode
617
+ ? `The offline edit was cancelled: ${state.reasonCode}.`
618
+ : "The offline edit was cancelled before it reached the service.",
619
+ },
620
+ ];
621
+ this.#message = this.#failures[0]?.description ?? "The offline edit was cancelled.";
622
+ return this.#notify();
623
+ }
624
+ if (state.state === "discarded") {
625
+ this.#conflict = undefined;
626
+ this.#model = undefined;
627
+ this.#operation = undefined;
628
+ this.#baseline = undefined;
629
+ this.#status = "idle";
630
+ this.#message = "The offline edit was discarded; the server version remains authoritative.";
631
+ return this.#notify();
632
+ }
633
+ const operation = this.#operation;
634
+ this.#conflict = undefined;
635
+ const featureId = this.#identity?.featureId;
636
+ const feature = operation === "delete" ? undefined : featureAfterCommit(this.#model.snapshot().feature, featureId);
637
+ this.#mutationReceipt = createHonuaFeatureMutationReceipt({
638
+ mutationId: state.idempotencyKey,
639
+ acceptedAt: state.appliedAt ?? this.#options.now?.() ?? new Date().toISOString(),
640
+ sourceId: this.source.descriptor.id,
641
+ protocol: this.source.descriptor.protocol,
642
+ operation,
643
+ status: "accepted",
644
+ ...(featureId !== undefined ? { featureId } : {}),
645
+ ...(feature ? { feature } : {}),
646
+ });
647
+ this.#status = "committed";
648
+ this.#committedFeatureId = featureId;
649
+ this.#baseline = cloneFeature(this.#model.snapshot().feature);
650
+ this.#message = "The offline edit was accepted after reconnect.";
651
+ return this.#notify();
652
+ }
466
653
  // ── conflict resolution ────────────────────────────────────────────────
467
654
  /**
468
655
  * Records an explicit decision for a parked conflict.
@@ -478,6 +665,10 @@ export class HonuaFeatureEditorWorkflow {
478
665
  const conflict = this.#conflict;
479
666
  if (!conflict)
480
667
  return this.snapshot();
668
+ if (this.#offline?.state === "conflicted") {
669
+ this.#message = "Resolve this conflict through the offline queue so the durable edit and the UI stay consistent.";
670
+ return this.#notify();
671
+ }
481
672
  if (choice === "reload") {
482
673
  this.#conflict = undefined;
483
674
  this.#model = undefined;
@@ -559,6 +750,8 @@ export class HonuaFeatureEditorWorkflow {
559
750
  * untouched and parked until {@link resolveConflict} records a decision.
560
751
  */
561
752
  applyExternalChange(feature) {
753
+ if (this.#status === "queued")
754
+ return "ignored";
562
755
  const model = this.#model;
563
756
  const identity = this.#identity;
564
757
  if (!model || identity === undefined || identity.featureId === undefined)
@@ -681,10 +874,43 @@ export class HonuaFeatureEditorWorkflow {
681
874
  ...(token !== undefined ? { version: token } : {}),
682
875
  };
683
876
  }
877
+ #allocateMutationId() {
878
+ const value = this.#options.mutationId?.() ?? defaultMutationId(this.source.descriptor.id);
879
+ const normalized = value.trim();
880
+ if (!normalized)
881
+ throw new TypeError("mutationId must be a non-empty string.");
882
+ return normalized;
883
+ }
884
+ #draftSummary(model) {
885
+ const feature = model.snapshot().feature;
886
+ const current = feature.attributes;
887
+ const baseline = (this.#baseline?.attributes ?? {});
888
+ const changedFields = [...new Set([...Object.keys(baseline), ...Object.keys(current)])]
889
+ .filter((field) => !sameScalar(baseline[field], current[field]))
890
+ .sort();
891
+ const geometry = sameScalar(this.#baseline?.geometry, feature.geometry)
892
+ ? "unchanged"
893
+ : feature.geometry === null || feature.geometry === undefined
894
+ ? "cleared"
895
+ : "set";
896
+ const attachments = { add: 0, update: 0, delete: 0 };
897
+ for (const attachment of this.#attachments)
898
+ attachments[attachment.operation] += 1;
899
+ return Object.freeze({
900
+ dirty: model.dirty(),
901
+ changedFields: Object.freeze(changedFields),
902
+ geometry,
903
+ attachments: Object.freeze(attachments),
904
+ });
905
+ }
684
906
  #afterEdit() {
685
907
  if (this.#status === "committed" || this.#status === "rejected" || this.#status === "cancelled") {
908
+ if (this.#status === "committed")
909
+ this.#mutationId = this.#allocateMutationId();
686
910
  this.#status = "draft";
687
911
  this.#failures = [];
912
+ this.#mutationReceipt = undefined;
913
+ this.#offline = undefined;
688
914
  this.#message = draftMessage(this.#operation ?? "update");
689
915
  }
690
916
  }
@@ -708,6 +934,7 @@ export class HonuaFeatureEditorWorkflow {
708
934
  failures: this.#failures,
709
935
  attachments: this.#attachments,
710
936
  snapshot: this.snapshot(),
937
+ ...(this.#mutationReceipt ? { mutationReceipt: this.#mutationReceipt } : {}),
711
938
  };
712
939
  }
713
940
  /**
@@ -738,6 +965,7 @@ export class HonuaFeatureEditorWorkflow {
738
965
  failures: this.#failures,
739
966
  attachments: this.#attachments,
740
967
  snapshot,
968
+ ...(this.#mutationReceipt ? { mutationReceipt: this.#mutationReceipt } : {}),
741
969
  };
742
970
  }
743
971
  #notify() {
@@ -887,3 +1115,15 @@ function sameScalar(left, right) {
887
1115
  }
888
1116
  return String(left) === String(right);
889
1117
  }
1118
+ let mutationSequence = 0;
1119
+ function defaultMutationId(sourceId) {
1120
+ const randomUuid = globalThis.crypto?.randomUUID;
1121
+ if (randomUuid)
1122
+ return randomUuid.call(globalThis.crypto);
1123
+ mutationSequence += 1;
1124
+ return `${String(sourceId)}:${Date.now()}:${mutationSequence}`;
1125
+ }
1126
+ function featureAfterCommit(feature, featureId) {
1127
+ const cloned = cloneFeature(feature);
1128
+ return featureId === undefined ? cloned : { ...cloned, id: featureId };
1129
+ }
@@ -41,16 +41,22 @@
41
41
  * @module
42
42
  */
43
43
  import type { CanonicalFeature } from "../contract/types.js";
44
- import type { HonuaFeatureEditorCommit, HonuaFeatureEditorSnapshot, HonuaFeatureEditorWorkflow } from "./feature-editor-workflow.js";
44
+ import type { HonuaApplicationContext, HonuaApplicationContextChangeEvent } from "./application-context.js";
45
+ import type { HonuaFeatureEditorCommit, HonuaFeatureEditorConflictChoice, HonuaFeatureEditorOfflineState, HonuaFeatureEditorPreflight, HonuaFeatureEditorSnapshot, HonuaFeatureEditorWorkflow } from "./feature-editor-workflow.js";
45
46
  import type { HonuaFeatureEditorMessages } from "./types.js";
46
47
  declare const HTMLElementBase: typeof HTMLElement;
47
48
  /** Detail of the `honua-feature-edit-change` event (redacted snapshot). */
48
- export interface HonuaFeatureEditChangeDetail {
49
- readonly snapshot: HonuaFeatureEditorSnapshot;
49
+ export interface HonuaFeatureEditChangeDetail<T = Record<string, unknown>> {
50
+ readonly snapshot: HonuaFeatureEditorSnapshot<T>;
50
51
  }
51
52
  /** Detail of the `honua-feature-edit-commit` event (submit outcome). */
52
- export interface HonuaFeatureEditCommitDetail {
53
- readonly commit: HonuaFeatureEditorCommit;
53
+ export interface HonuaFeatureEditCommitDetail<T = Record<string, unknown>> {
54
+ readonly commit: HonuaFeatureEditorCommit<T>;
55
+ }
56
+ /** Durable offline conflict choice requested by the editor presentation. */
57
+ export interface HonuaFeatureEditOfflineConflictDetail {
58
+ readonly choice: HonuaFeatureEditorConflictChoice;
59
+ readonly offline: HonuaFeatureEditorOfflineState;
54
60
  }
55
61
  export declare class HonuaFeatureEditorElement<T = Record<string, unknown>> extends HTMLElementBase {
56
62
  #private;
@@ -60,6 +66,12 @@ export declare class HonuaFeatureEditorElement<T = Record<string, unknown>> exte
60
66
  /** The workflow this editor renders. Setting it re-subscribes and re-renders. */
61
67
  get workflow(): HonuaFeatureEditorWorkflow<T> | undefined;
62
68
  set workflow(workflow: HonuaFeatureEditorWorkflow<T> | undefined);
69
+ /** Application shell context assigned and driven by `mountHonuaApplication()`. */
70
+ get applicationContext(): HonuaApplicationContext<T> | undefined;
71
+ set applicationContext(context: HonuaApplicationContext<T> | undefined);
72
+ honuaApplicationContextConnected(context: HonuaApplicationContext<T>): void;
73
+ honuaApplicationContextChanged(event: HonuaApplicationContextChangeEvent<T>): void;
74
+ honuaApplicationContextDisconnected(context: HonuaApplicationContext<T>): void;
63
75
  /** Equivalent to setting `.workflow`. */
64
76
  connect(workflow: HonuaFeatureEditorWorkflow<T>): void;
65
77
  /**
@@ -71,7 +83,9 @@ export declare class HonuaFeatureEditorElement<T = Record<string, unknown>> exte
71
83
  get selectedFeature(): CanonicalFeature<T> | undefined;
72
84
  set selectedFeature(feature: CanonicalFeature<T> | undefined);
73
85
  /** The last snapshot rendered. */
74
- get snapshot(): HonuaFeatureEditorSnapshot | undefined;
86
+ get snapshot(): HonuaFeatureEditorSnapshot<T> | undefined;
87
+ /** Credential-free, transport-free review of the current draft. */
88
+ preflight(): HonuaFeatureEditorPreflight | undefined;
75
89
  attributeChangedCallback(): void;
76
90
  connectedCallback(): void;
77
91
  disconnectedCallback(): void;
@@ -1 +1 @@
1
- {"version":3,"file":"feature-editor.d.ts","sourceRoot":"","sources":["../../../src/web-components/feature-editor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAG7D,OAAO,KAAK,EACV,wBAAwB,EAExB,0BAA0B,EAC1B,0BAA0B,EAC3B,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,YAAY,CAAC;AAQ7D,QAAA,MAAM,eAAe,EAAE,OAAO,WAAkF,CAAC;AAEjH,2EAA2E;AAC3E,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,QAAQ,EAAE,0BAA0B,CAAC;CAC/C;AAED,wEAAwE;AACxE,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,MAAM,EAAE,wBAAwB,CAAC;CAC3C;AAQD,qBAAa,yBAAyB,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAE,SAAQ,eAAe;;IACzF,WAAkB,kBAAkB,IAAI,MAAM,EAAE,CAE/C;IAiBD,IAAW,QAAQ,IAAI,0BAA0B,CAEhD;IACD,IAAW,QAAQ,CAAC,QAAQ,EAAE,0BAA0B,GAAG,SAAS,EAGnE;IAED,iFAAiF;IACjF,IAAW,QAAQ,IAAI,0BAA0B,CAAC,CAAC,CAAC,GAAG,SAAS,CAE/D;IAED,IAAW,QAAQ,CAAC,QAAQ,EAAE,0BAA0B,CAAC,CAAC,CAAC,GAAG,SAAS,EAYtE;IAeD,yCAAyC;IAClC,OAAO,CAAC,QAAQ,EAAE,0BAA0B,CAAC,CAAC,CAAC,GAAG,IAAI;IAI7D;;;;;OAKG;IACH,IAAW,eAAe,IAAI,gBAAgB,CAAC,CAAC,CAAC,GAAG,SAAS,CAE5D;IAED,IAAW,eAAe,CAAC,OAAO,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAAG,SAAS,EAElE;IAED,kCAAkC;IAClC,IAAW,QAAQ,IAAI,0BAA0B,GAAG,SAAS,CAE5D;IAEM,wBAAwB,IAAI,IAAI;IAIhC,iBAAiB,IAAI,IAAI;IAQzB,oBAAoB,IAAI,IAAI;IAgDnC,SAAS,CAAC,MAAM,IAAI,IAAI;CAwZzB;AAED,mFAAmF;AACnF,wBAAgB,wBAAwB,CAAC,QAAQ,wBAA2B,GAAG,IAAI,CAKlF"}
1
+ {"version":3,"file":"feature-editor.d.ts","sourceRoot":"","sources":["../../../src/web-components/feature-editor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,KAAK,EACV,uBAAuB,EACvB,kCAAkC,EAEnC,MAAM,0BAA0B,CAAC;AAGlC,OAAO,KAAK,EACV,wBAAwB,EACxB,gCAAgC,EAChC,8BAA8B,EAC9B,2BAA2B,EAC3B,0BAA0B,EAC1B,0BAA0B,EAC3B,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,YAAY,CAAC;AAQ7D,QAAA,MAAM,eAAe,EAAE,OAAO,WAAkF,CAAC;AAEjH,2EAA2E;AAC3E,MAAM,WAAW,4BAA4B,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACvE,QAAQ,CAAC,QAAQ,EAAE,0BAA0B,CAAC,CAAC,CAAC,CAAC;CAClD;AAED,wEAAwE;AACxE,MAAM,WAAW,4BAA4B,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACvE,QAAQ,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAAC,CAAC,CAAC;CAC9C;AAED,4EAA4E;AAC5E,MAAM,WAAW,qCAAqC;IACpD,QAAQ,CAAC,MAAM,EAAE,gCAAgC,CAAC;IAClD,QAAQ,CAAC,OAAO,EAAE,8BAA8B,CAAC;CAClD;AAQD,qBAAa,yBAAyB,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAE,SAAQ,eAAe;;IACzF,WAAkB,kBAAkB,IAAI,MAAM,EAAE,CAE/C;IAkBD,IAAW,QAAQ,IAAI,0BAA0B,CAEhD;IACD,IAAW,QAAQ,CAAC,QAAQ,EAAE,0BAA0B,GAAG,SAAS,EAGnE;IAED,iFAAiF;IACjF,IAAW,QAAQ,IAAI,0BAA0B,CAAC,CAAC,CAAC,GAAG,SAAS,CAE/D;IAED,IAAW,QAAQ,CAAC,QAAQ,EAAE,0BAA0B,CAAC,CAAC,CAAC,GAAG,SAAS,EAYtE;IAgBD,kFAAkF;IAClF,IAAW,kBAAkB,IAAI,uBAAuB,CAAC,CAAC,CAAC,GAAG,SAAS,CAEtE;IAED,IAAW,kBAAkB,CAAC,OAAO,EAAE,uBAAuB,CAAC,CAAC,CAAC,GAAG,SAAS,EAI5E;IAEM,gCAAgC,CAAC,OAAO,EAAE,uBAAuB,CAAC,CAAC,CAAC,GAAG,IAAI;IAI3E,8BAA8B,CAAC,KAAK,EAAE,kCAAkC,CAAC,CAAC,CAAC,GAAG,IAAI;IAKlF,mCAAmC,CAAC,OAAO,EAAE,uBAAuB,CAAC,CAAC,CAAC,GAAG,IAAI;IAIrF,yCAAyC;IAClC,OAAO,CAAC,QAAQ,EAAE,0BAA0B,CAAC,CAAC,CAAC,GAAG,IAAI;IAI7D;;;;;OAKG;IACH,IAAW,eAAe,IAAI,gBAAgB,CAAC,CAAC,CAAC,GAAG,SAAS,CAE5D;IAED,IAAW,eAAe,CAAC,OAAO,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAAG,SAAS,EAOlE;IAED,kCAAkC;IAClC,IAAW,QAAQ,IAAI,0BAA0B,CAAC,CAAC,CAAC,GAAG,SAAS,CAE/D;IAED,mEAAmE;IAC5D,SAAS,IAAI,2BAA2B,GAAG,SAAS;IAIpD,wBAAwB,IAAI,IAAI;IAIhC,iBAAiB,IAAI,IAAI;IAQzB,oBAAoB,IAAI,IAAI;IAgDnC,SAAS,CAAC,MAAM,IAAI,IAAI;CA0fzB;AAED,mFAAmF;AACnF,wBAAgB,wBAAwB,CAAC,QAAQ,wBAA2B,GAAG,IAAI,CAKlF"}