@praxisui/dynamic-form 9.0.5-rc.13 → 9.0.5-rc.15

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.
package/README.md CHANGED
@@ -35,11 +35,11 @@ When the exact request schema from `/schemas/filtered` publishes root-level `x-u
35
35
 
36
36
  Triggers, source fields and request/response bindings use JSON Pointer. The runtime supports nested object bindings, accepts at most 64 total input/output bindings and rejects duplicate or hierarchically overlapping pointers. It resolves the capability only against the configured API origin and rejects protocol-relative, encoded-separator or cross-origin targets. Output bindings have one closed meaning: server-derived draft values replace their targets. Metadata cannot select `if-pristine`, `if-empty` or another incidental UI policy.
37
37
 
38
- The runtime waits until hydration finishes and invalidates the previous generation as soon as a newer relevant value or validation status arrives, before the new debounce completes. Every configured response path is resolved before target controls are written; all nested targets are populated before change observers are notified. Chained determinations receive one logical batch signal after the complete commit, rather than one execution per output control. Their dependency order is derived from existing input/output JSON Pointer bindings: downstream waits while upstream is pending and runs only after authoritative outputs or confirmed unchanged inputs. Null/undefined upstream outputs and failed, invalid or cancelled upstream generations settle downstream as skipped instead of invoking it with missing or stale data. If a control rejects a write, the runtime attempts to restore earlier writes and reports a terminal failure. This is a coordinated client-side batch, not a transaction or an ACID guarantee.
38
+ The runtime waits until hydration finishes and invalidates the previous generation as soon as a newer relevant value or validation status arrives, before the new debounce completes. `sourcePaths` is an intentional trigger subset of `inputs`: changing a non-trigger input invalidates the current draft, emits `input-changed-awaiting-trigger`, and blocks submit/navigation until a source trigger recalculates it. Every configured response path is resolved before target controls are written; all nested targets are populated before change observers are notified. Chained determinations receive one logical batch signal after the complete commit, rather than one execution per output control. Their dependency order uses hierarchical input/output JSON Pointer overlap, and the canonical projection rejects cycles and overlapping multi-writers. Downstream waits while upstream is pending and runs only after authoritative outputs or confirmed unchanged inputs. Null/undefined upstream outputs and failed, invalid or cancelled upstream generations settle downstream as skipped instead of invoking it with missing or stale data. If a control rejects a write, the runtime attempts to restore earlier writes and reports a terminal failure. This is a coordinated client-side batch, not a transaction or an ACID guarantee.
39
39
 
40
40
  A determination remains pending through debounce, asynchronous validation and capability execution. Submit attempts wait with a bounded timeout and repeat the stability check after hooks that may mutate controls. Output controls are disabled transiently as server-owned draft values, but remain present in `getRawValue()`, `valueChange` and submit payloads; this state is not persisted in `FormConfig`.
41
41
 
42
- Hosts can observe metadata-only evidence through `reactiveDeterminationExecuted` and aggregate state through `reactiveDeterminationPendingChange`. Before navigating or closing a composed surface, call `hasPendingReactiveDeterminations()` and await `waitForReactiveDeterminations(timeoutMs?)`; a `false` result means timeout and must block the transition. No event or method exposes form or response values.
42
+ Hosts can observe metadata-only evidence through `reactiveDeterminationExecuted` and aggregate state through `reactiveDeterminationPendingChange`. Before navigating or closing a composed surface, inspect `hasPendingReactiveDeterminations()` and `hasUnsatisfiedReactiveDeterminations()` and await `waitForReactiveDeterminations(timeoutMs?)`; `false` means timeout or that the latest generation did not produce a current authoritative draft, and must block the transition. Cancelled pending correlations terminate as `skipped/superseded`. No event or method exposes form or response values.
43
43
 
44
44
  Use `optionSource.dependsOn` for dependent option lists and workflow actions for persistent transitions. Recommendations, side-effect refresh and backend validation are separate capabilities. The final backend command must still authorize, recompute or validate fiscal, pricing, eligibility, compliance and approval outcomes.
45
45
 
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": "1.0.0",
3
- "generatedAt": "2026-08-13T17:03:40.689Z",
3
+ "generatedAt": "2026-08-14T00:04:43.614Z",
4
4
  "packageName": "@praxisui/dynamic-form",
5
- "packageVersion": "9.0.5-rc.13",
5
+ "packageVersion": "9.0.5-rc.15",
6
6
  "sourceRegistry": "praxis-component-registry-ingestion",
7
7
  "sourceRegistryVersion": "1.0.0",
8
8
  "componentCount": 2,
@@ -212,10 +212,14 @@ class ReactiveDeterminationRuntimeService {
212
212
  hasPending(form) {
213
213
  return this.isPending(this.pendingStateByForm.get(form));
214
214
  }
215
+ hasUnsatisfied(form) {
216
+ const state = this.pendingStateByForm.get(form);
217
+ return !!state && [...state.determinations.values()].some((item) => !item.satisfied);
218
+ }
215
219
  waitForStable(form, timeoutMs = DEFAULT_OPERATION_TIMEOUT_MS) {
216
220
  const state = this.pendingStateByForm.get(form);
217
221
  if (!this.isPending(state))
218
- return Promise.resolve(true);
222
+ return Promise.resolve(this.isSatisfied(state));
219
223
  return new Promise((resolve) => {
220
224
  let settled = false;
221
225
  const finish = (stable) => {
@@ -241,12 +245,20 @@ class ReactiveDeterminationRuntimeService {
241
245
  }
242
246
  connect(form, definitions, executor, onEvent, operationTimeoutMs = DEFAULT_OPERATION_TIMEOUT_MS) {
243
247
  const composite = new Subscription();
248
+ const pendingCorrelations = new Set();
249
+ const publishEvent = (event) => {
250
+ if (event.status === 'pending')
251
+ pendingCorrelations.add(event.correlationId);
252
+ else
253
+ pendingCorrelations.delete(event.correlationId);
254
+ onEvent?.(event);
255
+ };
244
256
  const connectionId = ++this.nextConnectionId;
245
257
  const definitionIds = new Set();
246
258
  const determinationKeyFor = (definitionIndex, definition) => `${connectionId}:${definitionIndex}:${definition.id}`;
247
259
  definitions.forEach((definition, definitionIndex) => {
248
260
  if (definitionIds.has(definition.id)) {
249
- onEvent?.(this.event(definition, 'skipped', createCorrelationId(), 0, 'duplicate-determination-id'));
261
+ publishEvent(this.event(definition, 'skipped', createCorrelationId(), 0, 'duplicate-determination-id'));
250
262
  return;
251
263
  }
252
264
  definitionIds.add(definition.id);
@@ -260,9 +272,8 @@ class ReactiveDeterminationRuntimeService {
260
272
  const upstreamDeterminationKeys = definitions.flatMap((candidate, candidateIndex) => {
261
273
  if (candidateIndex === definitionIndex)
262
274
  return [];
263
- const candidateOutputPaths = new Set(candidate.outputs.map((binding) => binding.fieldPath));
264
275
  const feedsCurrent = definition.inputs.some((binding) => {
265
- if (!candidateOutputPaths.has(binding.fieldPath))
276
+ if (!candidate.outputs.some((output) => jsonPointersOverlap(output.fieldPath, binding.fieldPath)))
266
277
  return false;
267
278
  upstreamInputPaths.add(binding.fieldPath);
268
279
  return true;
@@ -272,15 +283,15 @@ class ReactiveDeterminationRuntimeService {
272
283
  : [];
273
284
  });
274
285
  if (triggerControls.length !== definition.trigger.sourcePaths.length) {
275
- onEvent?.(this.event(definition, 'skipped', createCorrelationId(), 0, 'source-control-missing'));
286
+ publishEvent(this.event(definition, 'skipped', createCorrelationId(), 0, 'source-control-missing'));
276
287
  return;
277
288
  }
278
289
  if (inputControls.length !== definition.inputs.length) {
279
- onEvent?.(this.event(definition, 'skipped', createCorrelationId(), 0, 'input-control-missing'));
290
+ publishEvent(this.event(definition, 'skipped', createCorrelationId(), 0, 'input-control-missing'));
280
291
  return;
281
292
  }
282
293
  if (targetControls.length !== definition.outputs.length) {
283
- onEvent?.(this.event(definition, 'skipped', createCorrelationId(), 0, 'target-control-missing'));
294
+ publishEvent(this.event(definition, 'skipped', createCorrelationId(), 0, 'target-control-missing'));
284
295
  return;
285
296
  }
286
297
  composite.add(this.claimTargets(form, targetControls));
@@ -295,9 +306,12 @@ class ReactiveDeterminationRuntimeService {
295
306
  // Publish the new authority before switchMap finalizes the old one.
296
307
  this.beginGeneration(form, determinationKey, currentGeneration);
297
308
  if (shouldExecute) {
298
- onEvent?.(this.event(definition, 'pending', correlationId, 0));
309
+ publishEvent(this.event(definition, 'pending', correlationId, 0));
310
+ }
311
+ else {
312
+ publishEvent(this.event(definition, 'skipped', correlationId, 0, 'input-changed-awaiting-trigger'));
299
313
  }
300
- return { generation: currentGeneration, correlationId, shouldExecute };
314
+ return { generation: currentGeneration, correlationId, shouldExecute, startedAt: Date.now() };
301
315
  }), switchMap((signal) => {
302
316
  const generation$ = signal.shouldExecute
303
317
  ? timer(DEFAULT_DEBOUNCE_MS).pipe(switchMap(() => this.waitForUpstreamDeterminations(form, upstreamDeterminationKeys)), switchMap((upstreamsSatisfied) => {
@@ -305,7 +319,7 @@ class ReactiveDeterminationRuntimeService {
305
319
  return NEVER;
306
320
  }
307
321
  if (triggerControls.some((control) => control.invalid)) {
308
- onEvent?.(this.event(definition, 'skipped', signal.correlationId, 0, 'invalid-source'));
322
+ publishEvent(this.event(definition, 'skipped', signal.correlationId, 0, 'invalid-source'));
309
323
  return EMPTY;
310
324
  }
311
325
  if (!upstreamsSatisfied ||
@@ -315,7 +329,7 @@ class ReactiveDeterminationRuntimeService {
315
329
  const value = form.get(jsonPointerSegments(binding.fieldPath))?.value;
316
330
  return value === null || value === undefined;
317
331
  })) {
318
- onEvent?.(this.event(definition, 'skipped', signal.correlationId, 0, 'upstream-input-unavailable'));
332
+ publishEvent(this.event(definition, 'skipped', signal.correlationId, 0, 'upstream-input-unavailable'));
319
333
  return EMPTY;
320
334
  }
321
335
  let invocation;
@@ -323,12 +337,12 @@ class ReactiveDeterminationRuntimeService {
323
337
  invocation = this.buildInvocation(form, definition);
324
338
  }
325
339
  catch {
326
- onEvent?.(this.event(definition, 'error', signal.correlationId, 0, 'request-binding-invalid'));
340
+ publishEvent(this.event(definition, 'error', signal.correlationId, 0, 'request-binding-invalid'));
327
341
  return EMPTY;
328
342
  }
329
343
  if (invocation.fingerprint === lastSuccessfulFingerprint) {
330
344
  this.satisfyGeneration(form, determinationKey, signal.generation);
331
- onEvent?.(this.event(definition, 'skipped', signal.correlationId, 0, 'unchanged-inputs'));
345
+ publishEvent(this.event(definition, 'skipped', signal.correlationId, 0, 'unchanged-inputs'));
332
346
  return EMPTY;
333
347
  }
334
348
  const startedAt = Date.now();
@@ -338,13 +352,13 @@ class ReactiveDeterminationRuntimeService {
338
352
  }
339
353
  const commit = this.buildCommit(form, definition, response);
340
354
  if (commit === null) {
341
- onEvent?.(this.event(definition, 'error', signal.correlationId, Date.now() - startedAt, 'response-binding-missing'));
355
+ publishEvent(this.event(definition, 'error', signal.correlationId, Date.now() - startedAt, 'response-binding-missing'));
342
356
  return;
343
357
  }
344
358
  this.commit(form, commit);
345
359
  this.satisfyGeneration(form, determinationKey, signal.generation);
346
360
  lastSuccessfulFingerprint = invocation.fingerprint;
347
- onEvent?.(this.event(definition, 'success', signal.correlationId, Date.now() - startedAt, undefined, commit.map((entry) => entry.targetPath)));
361
+ publishEvent(this.event(definition, 'success', signal.correlationId, Date.now() - startedAt, undefined, commit.map((entry) => entry.targetPath)));
348
362
  }), catchError((error) => {
349
363
  if (this.isCurrentGeneration(form, determinationKey, signal.generation)) {
350
364
  const reason = error instanceof TimeoutError
@@ -354,13 +368,18 @@ class ReactiveDeterminationRuntimeService {
354
368
  : error instanceof ReactiveDeterminationCommitError
355
369
  ? 'commit-failed'
356
370
  : 'operation-failed';
357
- onEvent?.(this.event(definition, 'error', signal.correlationId, Date.now() - startedAt, reason));
371
+ publishEvent(this.event(definition, 'error', signal.correlationId, Date.now() - startedAt, reason));
358
372
  }
359
373
  return EMPTY;
360
374
  }), ignoreElements());
361
375
  }))
362
376
  : EMPTY;
363
- return generation$.pipe(finalize(() => this.finishGeneration(form, determinationKey, signal.generation)));
377
+ return generation$.pipe(finalize(() => {
378
+ if (pendingCorrelations.has(signal.correlationId)) {
379
+ publishEvent(this.event(definition, 'skipped', signal.correlationId, Date.now() - signal.startedAt, 'superseded'));
380
+ }
381
+ this.finishGeneration(form, determinationKey, signal.generation);
382
+ }));
364
383
  }))
365
384
  .subscribe();
366
385
  composite.add(determinationSubscription);
@@ -566,12 +585,16 @@ class ReactiveDeterminationRuntimeService {
566
585
  isPending(state) {
567
586
  return !!state && [...state.determinations.values()].some((item) => item.pending);
568
587
  }
588
+ isSatisfied(state) {
589
+ return !state || [...state.determinations.values()].every((item) => item.satisfied);
590
+ }
569
591
  notifyIfStable(state) {
570
592
  if (this.isPending(state))
571
593
  return;
572
594
  const waiters = [...state.waiters];
573
595
  state.waiters.clear();
574
- waiters.forEach((waiter) => waiter(true));
596
+ const satisfied = this.isSatisfied(state);
597
+ waiters.forEach((waiter) => waiter(satisfied));
575
598
  }
576
599
  notifyPendingChange(state) {
577
600
  const pending = this.isPending(state);
@@ -615,6 +638,16 @@ function jsonPointerSegments(pointer) {
615
638
  }
616
639
  return pointer.slice(1).split('/').map((segment) => segment.replace(/~1/g, '/').replace(/~0/g, '~'));
617
640
  }
641
+ function jsonPointersOverlap(left, right) {
642
+ const leftSegments = jsonPointerSegments(left);
643
+ const rightSegments = jsonPointerSegments(right);
644
+ const shorter = Math.min(leftSegments.length, rightSegments.length);
645
+ for (let index = 0; index < shorter; index += 1) {
646
+ if (leftSegments[index] !== rightSegments[index])
647
+ return false;
648
+ }
649
+ return true;
650
+ }
618
651
  function writeJsonPointer(target, pointer, value) {
619
652
  const segments = jsonPointerSegments(pointer);
620
653
  let current = target;
@@ -1119,6 +1152,7 @@ const PRAXIS_DYNAMIC_FORM_I18N_CONFIG = {
1119
1152
  'runtime.reactiveDetermination.error': 'Não foi possível atualizar os campos relacionados. Revise os dados ou tente novamente.',
1120
1153
  'runtime.reactiveDetermination.submitPending': 'Aguarde a atualização dos campos relacionados antes de salvar.',
1121
1154
  'runtime.reactiveDetermination.submitTimeout': 'Os campos relacionados não estabilizaram a tempo. Revise os dados e tente salvar novamente.',
1155
+ 'runtime.reactiveDetermination.submitUnsatisfied': 'Os campos relacionados não foram atualizados com sucesso. Revise os dados e tente novamente.',
1122
1156
  'common.dismiss': 'Fechar',
1123
1157
  'config.tabs.mobileLabel': 'Seção do editor',
1124
1158
  'config.tabs.general': 'Geral',
@@ -1608,6 +1642,7 @@ const PRAXIS_DYNAMIC_FORM_I18N_CONFIG = {
1608
1642
  'runtime.reactiveDetermination.error': 'Related fields could not be updated. Review the data or try again.',
1609
1643
  'runtime.reactiveDetermination.submitPending': 'Wait for related fields to finish updating before saving.',
1610
1644
  'runtime.reactiveDetermination.submitTimeout': 'Related fields did not stabilize in time. Review the data and try saving again.',
1645
+ 'runtime.reactiveDetermination.submitUnsatisfied': 'Related fields were not updated successfully. Review the data and try again.',
1611
1646
  'common.dismiss': 'Dismiss',
1612
1647
  'settings.title': 'Form Configuration',
1613
1648
  'layout.defaultSectionTitle': 'Information',
@@ -11054,6 +11089,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
11054
11089
  args: [{ providedIn: 'root' }]
11055
11090
  }] });
11056
11091
 
11092
+ function exactSchemaOperationId(schema, rootUi) {
11093
+ const records = [rootUi, schema].filter((value) => !!value && typeof value === 'object' && !Array.isArray(value));
11094
+ for (const record of records) {
11095
+ for (const key of ['schemaOperationId', 'operationId', 'x-operation-id']) {
11096
+ const value = record[key];
11097
+ if (typeof value === 'string' && value.trim())
11098
+ return value.trim();
11099
+ }
11100
+ }
11101
+ return null;
11102
+ }
11057
11103
  const FORM_CONFIG_LOAD_TIMEOUT_MS = 1200;
11058
11104
  const FORM_INPUT_PREFS_LOAD_TIMEOUT_MS = 1200;
11059
11105
  const SCHEMA_GLOBAL_CONFIG_READY_TIMEOUT_MS = 1200;
@@ -11486,6 +11532,7 @@ class PraxisDynamicForm {
11486
11532
  formConfigConflictBlockThrough = new Map();
11487
11533
  schemaRootHooks;
11488
11534
  schemaRootReactiveDeterminations = [];
11535
+ schemaRootOperationId = null;
11489
11536
  destroy$ = new Subject();
11490
11537
  formValueChangesSubscription = null;
11491
11538
  reactiveDeterminationSubscription = null;
@@ -13192,7 +13239,9 @@ class PraxisDynamicForm {
13192
13239
  }
13193
13240
  this.reactiveDeterminationSubscription?.unsubscribe();
13194
13241
  const connection = new Subscription();
13195
- connection.add(this.reactiveDeterminationRuntime.connect(this.form, this.schemaRootReactiveDeterminations.filter((definition) => definition.scope.formMode === this.mode), (definition, requestBody, correlationId) => {
13242
+ connection.add(this.reactiveDeterminationRuntime.connect(this.form, this.schemaRootReactiveDeterminations.filter((definition) => definition.scope.formMode === this.mode
13243
+ && (!this.schemaRootOperationId
13244
+ || definition.scope.schemaOperationId === this.schemaRootOperationId)), (definition, requestBody, correlationId) => {
13196
13245
  let headers = composeHeadersWithVersion(this.resolveApiEntry()) ?? new HttpHeaders();
13197
13246
  headers = headers.set('X-Correlation-Id', correlationId);
13198
13247
  return this.http
@@ -13207,7 +13256,11 @@ class PraxisDynamicForm {
13207
13256
  hasPendingReactiveDeterminations() {
13208
13257
  return !!this.form && this.reactiveDeterminationRuntime.hasPending(this.form);
13209
13258
  }
13210
- /** Resolves false on timeout; callers must not navigate, close or submit then. */
13259
+ /** True when the latest settled generation failed or could not derive a current draft. */
13260
+ hasUnsatisfiedReactiveDeterminations() {
13261
+ return !!this.form && this.reactiveDeterminationRuntime.hasUnsatisfied(this.form);
13262
+ }
13263
+ /** Resolves true only when the latest generations settled successfully/current. */
13211
13264
  waitForReactiveDeterminations(timeoutMs) {
13212
13265
  if (!this.form)
13213
13266
  return Promise.resolve(true);
@@ -15658,18 +15711,17 @@ class PraxisDynamicForm {
15658
15711
  });
15659
15712
  }
15660
15713
  async waitForReactiveDeterminationsBeforeSubmit() {
15661
- if (!this.reactiveDeterminationRuntime.hasPending(this.form)) {
15662
- return true;
15663
- }
15664
- const pendingNotice = this.snackBar.open(this.i18n.t('runtime.reactiveDetermination.submitPending', undefined, 'Aguarde a atualização dos campos relacionados antes de salvar.', PRAXIS_DYNAMIC_FORM_I18N_NAMESPACE), this.i18n.t('common.dismiss', undefined, 'Fechar', PRAXIS_DYNAMIC_FORM_I18N_NAMESPACE), { duration: 5000 });
15714
+ const pendingNotice = this.reactiveDeterminationRuntime.hasPending(this.form)
15715
+ ? this.snackBar.open(this.i18n.t('runtime.reactiveDetermination.submitPending', undefined, 'Aguarde a atualização dos campos relacionados antes de salvar.', PRAXIS_DYNAMIC_FORM_I18N_NAMESPACE), this.i18n.t('common.dismiss', undefined, 'Fechar', PRAXIS_DYNAMIC_FORM_I18N_NAMESPACE), { duration: 5000 })
15716
+ : null;
15665
15717
  const stable = await this.reactiveDeterminationRuntime.waitForStable(this.form);
15666
15718
  try {
15667
- pendingNotice.dismiss();
15719
+ pendingNotice?.dismiss();
15668
15720
  }
15669
15721
  catch { }
15670
15722
  if (stable)
15671
15723
  return true;
15672
- this.snackBar.open(this.i18n.t('runtime.reactiveDetermination.submitTimeout', undefined, 'Os campos relacionados não estabilizaram a tempo. Revise os dados e tente salvar novamente.', PRAXIS_DYNAMIC_FORM_I18N_NAMESPACE), this.i18n.t('common.dismiss', undefined, 'Fechar', PRAXIS_DYNAMIC_FORM_I18N_NAMESPACE), { duration: 7000 });
15724
+ this.snackBar.open(this.i18n.t('runtime.reactiveDetermination.submitUnsatisfied', undefined, 'Os campos relacionados não foram atualizados com sucesso. Revise os dados e tente novamente.', PRAXIS_DYNAMIC_FORM_I18N_NAMESPACE), this.i18n.t('common.dismiss', undefined, 'Fechar', PRAXIS_DYNAMIC_FORM_I18N_NAMESPACE), { duration: 7000 });
15673
15725
  return false;
15674
15726
  }
15675
15727
  applySubmitFieldErrors(details) {
@@ -19805,6 +19857,7 @@ class PraxisDynamicForm {
19805
19857
  }
19806
19858
  const ctx = this.buildSchemaContext();
19807
19859
  this.schemaRootReactiveDeterminations = [];
19860
+ this.schemaRootOperationId = null;
19808
19861
  const schemaContext = {
19809
19862
  path: ctx.path,
19810
19863
  operation: ctx.operation,
@@ -19876,15 +19929,18 @@ class PraxisDynamicForm {
19876
19929
  // and are never copied into the persisted/authorable FormConfig.
19877
19930
  try {
19878
19931
  const rootUi = entry?.schema?.['x-ui'];
19932
+ this.schemaRootOperationId = exactSchemaOperationId(entry?.schema, rootUi);
19879
19933
  const hooks = rootUi && typeof rootUi === 'object' ? rootUi.hooks : undefined;
19880
19934
  if (hooks && typeof hooks === 'object') {
19881
19935
  this.schemaRootHooks = hooks;
19882
19936
  }
19883
- this.schemaRootReactiveDeterminations = ctx.schemaType === 'request'
19937
+ const normalizedDeterminations = ctx.schemaType === 'request'
19884
19938
  ? normalizeReactiveDeterminations(rootUi && typeof rootUi === 'object'
19885
19939
  ? rootUi.reactiveDeterminations
19886
19940
  : undefined)
19887
19941
  : [];
19942
+ this.schemaRootReactiveDeterminations = normalizedDeterminations.filter((definition) => !this.schemaRootOperationId
19943
+ || definition.scope.schemaOperationId === this.schemaRootOperationId);
19888
19944
  }
19889
19945
  catch { }
19890
19946
  const schemaId = buildSchemaId(schemaContext);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@praxisui/dynamic-form",
3
- "version": "9.0.5-rc.13",
3
+ "version": "9.0.5-rc.15",
4
4
  "description": "Angular dynamic form engine for Praxis UI: metadata-driven forms, hooks, and services integrating @praxisui/* packages.",
5
5
  "peerDependencies": {
6
6
  "@angular/common": "^21.0.0",
@@ -9,13 +9,13 @@
9
9
  "@angular/forms": "^21.0.0",
10
10
  "@angular/material": "^21.0.0",
11
11
  "@angular/router": "^21.0.0",
12
- "@praxisui/ai": "^9.0.5-rc.13",
13
- "@praxisui/dynamic-fields": "^9.0.5-rc.13",
14
- "@praxisui/metadata-editor": "^9.0.5-rc.13",
15
- "@praxisui/rich-content": "^9.0.5-rc.13",
16
- "@praxisui/settings-panel": "^9.0.5-rc.13",
17
- "@praxisui/visual-builder": "^9.0.5-rc.13",
18
- "@praxisui/core": "^9.0.5-rc.13",
12
+ "@praxisui/ai": "^9.0.5-rc.15",
13
+ "@praxisui/dynamic-fields": "^9.0.5-rc.15",
14
+ "@praxisui/metadata-editor": "^9.0.5-rc.15",
15
+ "@praxisui/rich-content": "^9.0.5-rc.15",
16
+ "@praxisui/settings-panel": "^9.0.5-rc.15",
17
+ "@praxisui/visual-builder": "^9.0.5-rc.15",
18
+ "@praxisui/core": "^9.0.5-rc.15",
19
19
  "rxjs": "^7.8.0"
20
20
  },
21
21
  "dependencies": {
@@ -86,7 +86,7 @@ Este documento e a referencia canonica da API JSON de praxis-dynamic-form.
86
86
  - O estado permanece pending durante debounce, validacao assincrona e execucao. Uma tentativa de submit aguarda estabilizacao com timeout e repete a espera depois de hooks que podem alterar controles.
87
87
  - Controles de output sao desabilitados transitoriamente como valores server-derived, sem mutar/persistir `FormConfig`; seus valores continuam em `getRawValue()`, `valueChange` e no payload de submit.
88
88
  - `reactiveDeterminationExecuted` publica apenas IDs, paths, provenance, estado (`pending`, `success`, `skipped` ou `error`), duracao, correlation ID e motivo. Valores do formulario e da resposta nao participam da evidencia.
89
- - Hosts compostos recebem estado agregado por `reactiveDeterminationPendingChange`. Antes de navegar ou fechar, devem consultar `hasPendingReactiveDeterminations()` e aguardar `waitForReactiveDeterminations(timeoutMs?)`; retorno `false` e timeout e bloqueia a transicao.
89
+ - Hosts compostos recebem estado agregado por `reactiveDeterminationPendingChange`. Antes de navegar ou fechar, devem consultar `hasPendingReactiveDeterminations()` e `hasUnsatisfiedReactiveDeterminations()` e aguardar `waitForReactiveDeterminations(timeoutMs?)`; retorno `false` indica timeout ou geração mais recente sem draft autoritativo e bloqueia a transição. Correlações canceladas recebem terminal `skipped/superseded`.
90
90
  - Cascatas de opcoes continuam em `optionSource.dependsOn`; recommendations, side-effect refresh, backend validation e workflow actions permanecem capacidades separadas.
91
91
  - O comando backend final continua responsavel por autorizacao, recomputacao e validacao de qualquer decisao consequente.
92
92
 
@@ -370,6 +370,7 @@ declare class ReactiveDeterminationRuntimeService {
370
370
  private readonly publishingCommitByForm;
371
371
  private nextConnectionId;
372
372
  hasPending(form: FormGroup): boolean;
373
+ hasUnsatisfied(form: FormGroup): boolean;
373
374
  waitForStable(form: FormGroup, timeoutMs?: number): Promise<boolean>;
374
375
  observePending(form: FormGroup, observer: (pending: boolean) => void): Subscription;
375
376
  isOwnedTarget(form: FormGroup, control: AbstractControl): boolean;
@@ -389,6 +390,7 @@ declare class ReactiveDeterminationRuntimeService {
389
390
  private clearDeterminationState;
390
391
  private isCurrentGeneration;
391
392
  private isPending;
393
+ private isSatisfied;
392
394
  private notifyIfStable;
393
395
  private notifyPendingChange;
394
396
  static ɵfac: i0.ɵɵFactoryDeclaration<ReactiveDeterminationRuntimeService, never>;
@@ -927,6 +929,7 @@ declare class PraxisDynamicForm implements OnInit, OnChanges, OnDestroy {
927
929
  private readonly formConfigConflictBlockThrough;
928
930
  private schemaRootHooks?;
929
931
  private schemaRootReactiveDeterminations;
932
+ private schemaRootOperationId;
930
933
  private destroy$;
931
934
  private formValueChangesSubscription;
932
935
  private reactiveDeterminationSubscription;
@@ -1075,7 +1078,9 @@ declare class PraxisDynamicForm implements OnInit, OnChanges, OnDestroy {
1075
1078
  private scheduleReactiveDeterminationsActivation;
1076
1079
  /** True while at least one backend Reactive Determination is unsettled. */
1077
1080
  hasPendingReactiveDeterminations(): boolean;
1078
- /** Resolves false on timeout; callers must not navigate, close or submit then. */
1081
+ /** True when the latest settled generation failed or could not derive a current draft. */
1082
+ hasUnsatisfiedReactiveDeterminations(): boolean;
1083
+ /** Resolves true only when the latest generations settled successfully/current. */
1079
1084
  waitForReactiveDeterminations(timeoutMs?: number): Promise<boolean>;
1080
1085
  private setReactiveDeterminationPending;
1081
1086
  private handleReactiveDeterminationExecutionEvent;