@praxisui/crud 9.0.4-rc.24 → 9.0.4-rc.26

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
@@ -43,6 +43,7 @@ import { PraxisCrudComponent, CrudMetadata } from '@praxisui/crud';
43
43
  <praxis-crud
44
44
  crudId="employees-crud"
45
45
  [metadata]="metadata"
46
+ tableConfigPersistenceStrategy="input-first"
46
47
  [context]="context"
47
48
  [enableCustomization]="true"
48
49
  (afterSave)="reloadSummary()"
@@ -92,6 +93,7 @@ export class EmployeesCrudComponent {
92
93
  - `metadata`: `CrudMetadata | string`
93
94
  - `crudId`: stable instance id
94
95
  - `componentInstanceId`: optional host-level instance id
96
+ - `tableConfigPersistenceStrategy`: controls persistence precedence for the internal table. Keep `local-first` for user-owned workspaces, use `input-first` for an authoritative governed contract, and use `volatile` for previews that must neither read nor write stored preferences.
95
97
  - `context`: host/runtime context passed into table and form flows
96
98
  - `enableCustomization`: enables the package authoring surface
97
99
  - `authoringCapability`: optionally requires the exact capability from `EnterpriseRuntimeContext.capabilities` before authoring controls, the CRUD editor, and its internal table authoring are available. This is a UX gate; governed persistence must enforce the same policy server-side.
@@ -194,6 +196,12 @@ outside the request schema and are never added to the update payload. Custom
194
196
  drawer adapters receive the same `resourceIdentity` context and retain their own
195
197
  presentation responsibility.
196
198
 
199
+ Related operations also receive `contextIdentity` when the governed surface
200
+ publishes `context.operation.taskScope`. The default host labels it as “Related
201
+ to” and keeps it distinct from the current child record. Both identities stay
202
+ outside dynamic-form inputs and submit payloads; capabilities and HATEOAS remain
203
+ the authority for whether the operation may execute.
204
+
197
205
  Route mode has a different lifecycle because `@praxisui/crud` does not own the
198
206
  destination component. Routed edit hosts must load the entity through the
199
207
  canonical read contract and materialize identity from its response schema. Do
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": "1.0.0",
3
- "generatedAt": "2026-08-04T17:57:31.391Z",
3
+ "generatedAt": "2026-08-04T22:30:51.687Z",
4
4
  "packageName": "@praxisui/crud",
5
- "packageVersion": "9.0.4-rc.24",
5
+ "packageVersion": "9.0.4-rc.26",
6
6
  "sourceRegistry": "praxis-component-registry-ingestion",
7
7
  "sourceRegistryVersion": "1.0.0",
8
8
  "componentCount": 2,
@@ -29,6 +29,13 @@
29
29
  "required": false,
30
30
  "description": "Identificador opcional para múltiplas instâncias na mesma rota."
31
31
  },
32
+ {
33
+ "name": "tableConfigPersistenceStrategy",
34
+ "type": "'local-first' | 'input-first' | 'volatile'",
35
+ "required": false,
36
+ "description": "Política da tabela interna. Use input-first para contratos governados autoritativos e volatile para previews que não devem ler nem gravar preferências locais.",
37
+ "default": "local-first"
38
+ },
32
39
  {
33
40
  "name": "context",
34
41
  "type": "Record<string, unknown>",
@@ -5015,6 +5022,11 @@
5015
5022
  "type": "string",
5016
5023
  "description": "Optional stable host instance id for multiple CRUD widgets on the same route."
5017
5024
  },
5025
+ {
5026
+ "name": "tableConfigPersistenceStrategy",
5027
+ "type": "'local-first' | 'input-first' | 'volatile'",
5028
+ "description": "Persistence precedence forwarded to the internal Praxis Table. Governed previews should use input-first or volatile explicitly."
5029
+ },
5018
5030
  {
5019
5031
  "name": "context",
5020
5032
  "type": "Record<string, unknown>",
@@ -7206,9 +7218,9 @@
7206
7218
  {
7207
7219
  "chunkIndex": 0,
7208
7220
  "chunkKind": "summary",
7209
- "content": "Component ID: praxis-crud\nSelector: praxis-crud\nFriendly Name: Praxis CRUD\nDescription: Tabela com operações de CRUD via metadados.\nLib/Package: @praxisui/crud\nTags: widget, crud, configurable, hasWizard, stable\nInputs:\n - metadata (CrudMetadata | string)\n - crudId (string)\n - componentInstanceId (string)\n - context (Record<string, unknown>)\n - metadata.queryContext (PraxisDataQueryContext | null)\n - metadata.resource.schemaPath (string | null)\n - metadata.resource.title (string | null)\n - metadata.resource.formTitle (string | null)\n - metadata.filterCriteria (Record<string, unknown> | null)\n - metadata.form (FormConfig)\n - metadata.actions[].form (CrudActionFormContract)\n - enableCustomization (boolean)\n - authoringCapability (string | null)\nOutputs:\n - afterOpen ({ mode: FormOpenMode; action: string })\n - afterClose (void)\n - afterSave ({ id: string | number; data: unknown })\n - afterDelete ({ id: string | number })\n - rowClick ({ row: unknown; index: number })\n - selectionChange (unknown)\n - error (unknown)\n - configureRequested (void)\n - crudAuthoringDocumentApplied (CrudAuthoringWidgetPersistenceEvent)\n - crudAuthoringDocumentSaved (CrudAuthoringWidgetPersistenceEvent)\n",
7221
+ "content": "Component ID: praxis-crud\nSelector: praxis-crud\nFriendly Name: Praxis CRUD\nDescription: Tabela com operações de CRUD via metadados.\nLib/Package: @praxisui/crud\nTags: widget, crud, configurable, hasWizard, stable\nInputs:\n - metadata (CrudMetadata | string)\n - crudId (string)\n - componentInstanceId (string)\n - tableConfigPersistenceStrategy ('local-first' | 'input-first' | 'volatile')\n - context (Record<string, unknown>)\n - metadata.queryContext (PraxisDataQueryContext | null)\n - metadata.resource.schemaPath (string | null)\n - metadata.resource.title (string | null)\n - metadata.resource.formTitle (string | null)\n - metadata.filterCriteria (Record<string, unknown> | null)\n - metadata.form (FormConfig)\n - metadata.actions[].form (CrudActionFormContract)\n - enableCustomization (boolean)\n - authoringCapability (string | null)\nOutputs:\n - afterOpen ({ mode: FormOpenMode; action: string })\n - afterClose (void)\n - afterSave ({ id: string | number; data: unknown })\n - afterDelete ({ id: string | number })\n - rowClick ({ row: unknown; index: number })\n - selectionChange (unknown)\n - error (unknown)\n - configureRequested (void)\n - crudAuthoringDocumentApplied (CrudAuthoringWidgetPersistenceEvent)\n - crudAuthoringDocumentSaved (CrudAuthoringWidgetPersistenceEvent)\n",
7210
7222
  "sourcePointer": "projects/praxis-crud/src/lib/praxis-crud.metadata.ts",
7211
- "contentHash": "bdcc39d2237bf1cea3f5ec621aa5b6fac42f8da6c8467cdbe1682888c722f12d",
7223
+ "contentHash": "a9d744b92ff6f475de4d870e57bc963200a7b68688a8ea5159cc02496a8967be",
7212
7224
  "sourceKind": "component_definition",
7213
7225
  "sourceId": "praxis-crud",
7214
7226
  "corpusVersion": "1.0.0"
@@ -7436,9 +7448,9 @@
7436
7448
  {
7437
7449
  "chunkIndex": 23,
7438
7450
  "chunkKind": "authoring_manifest",
7439
- "content": "{\"schemaVersion\":\"1.0.0\",\"manifestVersion\":\"1.0.0\",\"componentId\":\"praxis-crud\",\"ownerPackage\":\"@praxisui/crud\",\"configSchemaId\":\"CrudMetadata\",\"chunkSection\":\"runtimeInputs\",\"runtimeInputs\":[{\"name\":\"metadata\",\"type\":\"CrudMetadata | string\",\"description\":\"Canonical CRUD metadata or serialized metadata document.\"},{\"name\":\"crudId\",\"type\":\"string\",\"description\":\"Stable CRUD instance id used for table/form identity and persistence.\"},{\"name\":\"componentInstanceId\",\"type\":\"string\",\"description\":\"Optional stable host instance id for multiple CRUD widgets on the same route.\"},{\"name\":\"context\",\"type\":\"Record<string, unknown>\",\"description\":\"Opaque host context used for authoring seeds and launcher inputs.\"},{\"name\":\"enableCustomization\",\"type\":\"boolean\",\"description\":\"Explicit host opt-in for CRUD authoring surfaces.\"},{\"name\":\"authoringCapability\",\"type\":\"string | null\",\"description\":\"Public runtime capability required before CRUD and delegated table authoring can open.\"},{\"name\":\"afterOpen\",\"type\":\"{ mode: FormOpenMode; action: string }\",\"description\":\"Emitted after a CRUD action opens.\"},{\"name\":\"afterSave\",\"type\":\"{ id: string | number; data: unknown }\",\"description\":\"Emitted after save; CRUD refetches the list.\"},{\"name\":\"afterDelete\",\"type\":\"{ id: string | number }\",\"description\":\"Emitted after delete; CRUD refetches the list.\"}]}",
7451
+ "content": "{\"schemaVersion\":\"1.0.0\",\"manifestVersion\":\"1.0.0\",\"componentId\":\"praxis-crud\",\"ownerPackage\":\"@praxisui/crud\",\"configSchemaId\":\"CrudMetadata\",\"chunkSection\":\"runtimeInputs\",\"runtimeInputs\":[{\"name\":\"metadata\",\"type\":\"CrudMetadata | string\",\"description\":\"Canonical CRUD metadata or serialized metadata document.\"},{\"name\":\"crudId\",\"type\":\"string\",\"description\":\"Stable CRUD instance id used for table/form identity and persistence.\"},{\"name\":\"componentInstanceId\",\"type\":\"string\",\"description\":\"Optional stable host instance id for multiple CRUD widgets on the same route.\"},{\"name\":\"tableConfigPersistenceStrategy\",\"type\":\"'local-first' | 'input-first' | 'volatile'\",\"description\":\"Persistence precedence forwarded to the internal Praxis Table. Governed previews should use input-first or volatile explicitly.\"},{\"name\":\"context\",\"type\":\"Record<string, unknown>\",\"description\":\"Opaque host context used for authoring seeds and launcher inputs.\"},{\"name\":\"enableCustomization\",\"type\":\"boolean\",\"description\":\"Explicit host opt-in for CRUD authoring surfaces.\"},{\"name\":\"authoringCapability\",\"type\":\"string | null\",\"description\":\"Public runtime capability required before CRUD and delegated table authoring can open.\"},{\"name\":\"afterOpen\",\"type\":\"{ mode: FormOpenMode; action: string }\",\"description\":\"Emitted after a CRUD action opens.\"},{\"name\":\"afterSave\",\"type\":\"{ id: string | number; data: unknown }\",\"description\":\"Emitted after save; CRUD refetches the list.\"},{\"name\":\"afterDelete\",\"type\":\"{ id: string | number }\",\"description\":\"Emitted after delete; CRUD refetches the list.\"}]}",
7440
7452
  "sourcePointer": "projects/praxis-crud/src/lib/ai/praxis-crud-authoring-manifest.ts",
7441
- "contentHash": "cce406615018b96872557b8ccfe54f7c64d1bd40f8528ac4d62b094fbc27858b",
7453
+ "contentHash": "068b8503236aa70e0543cc63c791a5c9cb192983b3e369285bb44732b3b646d8",
7442
7454
  "sourceKind": "component_definition",
7443
7455
  "sourceId": "praxis-crud",
7444
7456
  "corpusVersion": "1.0.0"
@@ -6,7 +6,7 @@ import { Router, ActivatedRoute, RouterLink } from '@angular/router';
6
6
  import { MatSnackBar } from '@angular/material/snack-bar';
7
7
  import { firstValueFrom, BehaviorSubject, Subscription } from 'rxjs';
8
8
  import * as i2$1 from '@praxisui/core';
9
- import { ASYNC_CONFIG_STORAGE, GlobalConfigService, CrudOperationResolutionService, fillUndefined, SETTINGS_PANEL_DATA, PraxisI18nService, PraxisIconDirective, providePraxisI18nConfig, createDefaultTableConfig, EnterpriseRuntimeContextService, GLOBAL_SURFACE_SERVICE, SurfaceOutletRegistryService, ComponentKeyService, ResourceDiscoveryService, ResourceActionOpenAdapterService, ResourceSurfaceOpenAdapterService, translateUnavailableWorkflowMessage, EmptyStateCardComponent, RESOURCE_DISCOVERY_I18N_CONFIG, PraxisIconButtonComponent, PraxisResourceIdentityComponent, GenericCrudService, ComponentMetadataRegistry } from '@praxisui/core';
9
+ import { ASYNC_CONFIG_STORAGE, GlobalConfigService, CrudOperationResolutionService, fillUndefined, SETTINGS_PANEL_DATA, PraxisI18nService, PraxisIconDirective, providePraxisI18nConfig, createDefaultTableConfig, EnterpriseRuntimeContextService, GLOBAL_SURFACE_SERVICE, SurfaceOutletRegistryService, ComponentKeyService, ResourceDiscoveryService, ResourceActionOpenAdapterService, ResourceSurfaceOpenAdapterService, normalizeSurfaceOperationContext, translateUnavailableWorkflowMessage, EmptyStateCardComponent, RESOURCE_DISCOVERY_I18N_CONFIG, PraxisIconButtonComponent, PraxisResourceIdentityComponent, GenericCrudService, ComponentMetadataRegistry } from '@praxisui/core';
10
10
  import { SettingsPanelService } from '@praxisui/settings-panel';
11
11
  import { PraxisTableInlineAuthoringEditorComponent, PraxisTable } from '@praxisui/table';
12
12
  import { ConfirmDialogComponent } from '@praxisui/dynamic-fields';
@@ -141,15 +141,17 @@ class CrudLauncherService {
141
141
  row[idField] !== undefined) {
142
142
  inputs[idField] = row[idField];
143
143
  }
144
- const resourceIdentity = actionForLaunch.action === 'edit'
144
+ const resourceIdentity = actionForLaunch.action === 'edit' || actionForLaunch.action === 'view'
145
145
  ? runtime?.resourceIdentity ?? null
146
146
  : null;
147
+ const contextIdentity = runtime?.contextIdentity ?? null;
147
148
  if (mode === 'drawer' && this.drawerAdapter) {
148
149
  await Promise.resolve(this.drawerAdapter.open({
149
150
  action: actionForLaunch,
150
151
  metadata: merged.metadata,
151
152
  inputs,
152
153
  resourceIdentity,
154
+ contextIdentity,
153
155
  onClose: drawerCallbacks?.onClose,
154
156
  onResult: drawerCallbacks?.onResult,
155
157
  }));
@@ -212,6 +214,7 @@ class CrudLauncherService {
212
214
  presentation: drawerMode ? 'drawer' : 'modal',
213
215
  dialogPosition,
214
216
  resourceIdentity,
217
+ contextIdentity,
215
218
  },
216
219
  });
217
220
  if (drawerMode) {
@@ -911,6 +914,8 @@ const PRAXIS_CRUD_RUNTIME_I18N_CONFIG = {
911
914
  'crud.actions.opening': 'Abrindo {action}…',
912
915
  'crud.surface.openFailed': 'Não foi possível abrir {title}. Tente novamente ou confirme se esse recurso continua disponível.',
913
916
  'crud.dialog.recordContextLabel': 'Registro em edição',
917
+ 'crud.dialog.viewRecordContextLabel': 'Registro em consulta',
918
+ 'crud.dialog.relatedContextLabel': 'Vinculado a',
914
919
  'crud.delete.confirmMessage': 'Esta ação não pode ser desfeita. Deseja continuar?',
915
920
  'crud.delete.cancel': 'Cancelar',
916
921
  },
@@ -932,6 +937,8 @@ const PRAXIS_CRUD_RUNTIME_I18N_CONFIG = {
932
937
  'crud.actions.opening': 'Opening {action}…',
933
938
  'crud.surface.openFailed': 'Could not open {title}. Try again or confirm that this resource is still available.',
934
939
  'crud.dialog.recordContextLabel': 'Record being edited',
940
+ 'crud.dialog.viewRecordContextLabel': 'Record being viewed',
941
+ 'crud.dialog.relatedContextLabel': 'Related to',
935
942
  'crud.delete.confirmMessage': 'This action cannot be undone. Do you want to continue?',
936
943
  'crud.delete.cancel': 'Cancel',
937
944
  },
@@ -3660,6 +3667,8 @@ class PraxisCrudComponent {
3660
3667
  metadata;
3661
3668
  crudId;
3662
3669
  componentInstanceId;
3670
+ /** Controls whether the CRUD's internal table trusts host input or stored user configuration. */
3671
+ tableConfigPersistenceStrategy = 'local-first';
3663
3672
  context;
3664
3673
  enableCustomization = false;
3665
3674
  /** Capability publica exigida para authoring governado do CRUD e da tabela interna. */
@@ -3863,6 +3872,7 @@ class PraxisCrudComponent {
3863
3872
  action: this.normalizeCrudActionName(actionMeta?.action ?? normalizedAction),
3864
3873
  };
3865
3874
  const resourceIdentity = runtimeEvent?.resourceIdentity ?? null;
3875
+ const contextIdentity = this.resolveTaskScopeIdentity();
3866
3876
  const handledByDuplicateDraft = await this.tryHandleCanonicalDuplicateDraftAction(effectiveAction, contextualRow);
3867
3877
  if (handledByDuplicateDraft) {
3868
3878
  return;
@@ -3911,6 +3921,7 @@ class PraxisCrudComponent {
3911
3921
  : this.collectionCapabilities,
3912
3922
  links: this.resolveCrudRuntimeLinks(effectiveAction.action, contextualRow),
3913
3923
  resourceIdentity,
3924
+ contextIdentity,
3914
3925
  });
3915
3926
  this.afterOpen.emit({ mode, action: effectiveAction.action });
3916
3927
  if (mode === 'drawer') {
@@ -3946,6 +3957,10 @@ class PraxisCrudComponent {
3946
3957
  this.openingActionLabel.set(null);
3947
3958
  }
3948
3959
  }
3960
+ resolveTaskScopeIdentity() {
3961
+ const operation = normalizeSurfaceOperationContext(this.context?.['operation']);
3962
+ return operation?.taskScope?.identity ?? null;
3963
+ }
3949
3964
  getOpeningActionStatus(actionLabel) {
3950
3965
  return this.txWithParams('crud.actions.opening', 'Abrindo {action}…', { action: actionLabel });
3951
3966
  }
@@ -5234,7 +5249,7 @@ class PraxisCrudComponent {
5234
5249
  return Object.entries(params).reduce((current, [name, value]) => current.split(`{${name}}`).join(value), text);
5235
5250
  }
5236
5251
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisCrudComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5237
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: PraxisCrudComponent, isStandalone: true, selector: "praxis-crud", inputs: { metadata: "metadata", crudId: "crudId", componentInstanceId: "componentInstanceId", context: "context", enableCustomization: "enableCustomization", authoringCapability: "authoringCapability" }, outputs: { configureRequested: "configureRequested", afterOpen: "afterOpen", afterClose: "afterClose", afterSave: "afterSave", afterDelete: "afterDelete", error: "error", rowClick: "rowClick", selectionChange: "selectionChange", tableRuntimeConfigChange: "tableRuntimeConfigChange", crudAuthoringDocumentApplied: "crudAuthoringDocumentApplied", crudAuthoringDocumentSaved: "crudAuthoringDocumentSaved" }, providers: [
5252
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: PraxisCrudComponent, isStandalone: true, selector: "praxis-crud", inputs: { metadata: "metadata", crudId: "crudId", componentInstanceId: "componentInstanceId", tableConfigPersistenceStrategy: "tableConfigPersistenceStrategy", context: "context", enableCustomization: "enableCustomization", authoringCapability: "authoringCapability" }, outputs: { configureRequested: "configureRequested", afterOpen: "afterOpen", afterClose: "afterClose", afterSave: "afterSave", afterDelete: "afterDelete", error: "error", rowClick: "rowClick", selectionChange: "selectionChange", tableRuntimeConfigChange: "tableRuntimeConfigChange", crudAuthoringDocumentApplied: "crudAuthoringDocumentApplied", crudAuthoringDocumentSaved: "crudAuthoringDocumentSaved" }, providers: [
5238
5253
  providePraxisI18nConfig(RESOURCE_DISCOVERY_I18N_CONFIG),
5239
5254
  providePraxisI18nConfig(PRAXIS_CRUD_RUNTIME_I18N_CONFIG),
5240
5255
  ], viewQueries: [{ propertyName: "table", first: true, predicate: PraxisTable, descendants: true }], usesOnChanges: true, ngImport: i0, template: `
@@ -5253,6 +5268,7 @@ class PraxisCrudComponent {
5253
5268
  [queryContext]="tableQueryContext"
5254
5269
  [filterCriteria]="tableFilterCriteria"
5255
5270
  [tableId]="crudId || 'default'"
5271
+ [configPersistenceStrategy]="tableConfigPersistenceStrategy"
5256
5272
  [crudContext]="tableCrudContext"
5257
5273
  [enableCustomization]="enableCustomization"
5258
5274
  [authoringCapability]="authoringCapability"
@@ -5300,6 +5316,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
5300
5316
  [queryContext]="tableQueryContext"
5301
5317
  [filterCriteria]="tableFilterCriteria"
5302
5318
  [tableId]="crudId || 'default'"
5319
+ [configPersistenceStrategy]="tableConfigPersistenceStrategy"
5303
5320
  [crudContext]="tableCrudContext"
5304
5321
  [enableCustomization]="enableCustomization"
5305
5322
  [authoringCapability]="authoringCapability"
@@ -5333,6 +5350,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
5333
5350
  args: [{ required: true }]
5334
5351
  }], componentInstanceId: [{
5335
5352
  type: Input
5353
+ }], tableConfigPersistenceStrategy: [{
5354
+ type: Input
5336
5355
  }], context: [{
5337
5356
  type: Input
5338
5357
  }], enableCustomization: [{
@@ -5408,6 +5427,7 @@ class DynamicFormDialogHostComponent {
5408
5427
  resourceId;
5409
5428
  initialValue;
5410
5429
  resourceIdentity = null;
5430
+ contextIdentity = null;
5411
5431
  schemaUrl;
5412
5432
  submitUrl;
5413
5433
  submitMethod;
@@ -5427,6 +5447,8 @@ class DynamicFormDialogHostComponent {
5427
5447
  maximizeLabel: 'Maximizar',
5428
5448
  restoreLabel: 'Restaurar',
5429
5449
  recordContextLabel: translateCrudRuntimeText(this.i18n, 'crud.dialog.recordContextLabel', 'Registro em edição'),
5450
+ viewRecordContextLabel: translateCrudRuntimeText(this.i18n, 'crud.dialog.viewRecordContextLabel', 'Registro em consulta'),
5451
+ relatedContextLabel: translateCrudRuntimeText(this.i18n, 'crud.dialog.relatedContextLabel', 'Vinculado a'),
5430
5452
  discardTitle: 'Descartar alterações?',
5431
5453
  discardMessage: 'Você tem alterações não salvas. Deseja fechar assim mesmo?',
5432
5454
  discardConfirm: 'Descartar',
@@ -5477,9 +5499,12 @@ class DynamicFormDialogHostComponent {
5477
5499
  this.apiUrlEntry = this.data.inputs?.['apiUrlEntry'] ?? null;
5478
5500
  const act = this.data.action?.action;
5479
5501
  this.mode = act === 'edit' ? 'edit' : act === 'view' ? 'view' : 'create';
5480
- this.resourceIdentity = this.mode === 'edit' && this.data.resourceIdentity
5502
+ this.resourceIdentity = this.mode !== 'create' && this.data.resourceIdentity
5481
5503
  ? this.data.resourceIdentity
5482
5504
  : null;
5505
+ this.contextIdentity = this.data.contextIdentity
5506
+ ? this.data.contextIdentity
5507
+ : null;
5483
5508
  this.formConfig = this.resolveFormConfig();
5484
5509
  this.layoutPolicy = this.resolveLayoutPolicy();
5485
5510
  this.formActions = this.resolveFormActions();
@@ -5515,6 +5540,32 @@ class DynamicFormDialogHostComponent {
5515
5540
  .pipe(takeUntilDestroyed(this.destroyRef))
5516
5541
  .subscribe(() => this.saveState());
5517
5542
  }
5543
+ get resourceIdentityLabel() {
5544
+ return this.mode === 'view'
5545
+ ? this.texts['viewRecordContextLabel']
5546
+ : this.texts['recordContextLabel'];
5547
+ }
5548
+ get showContextIdentity() {
5549
+ return !!this.contextIdentity && !this.sameIdentity(this.contextIdentity, this.resourceIdentity);
5550
+ }
5551
+ sameIdentity(left, right) {
5552
+ if (!left || !right)
5553
+ return false;
5554
+ const leftKey = left.key;
5555
+ const rightKey = right.key;
5556
+ const leftLabel = this.identityLabel(left);
5557
+ const rightLabel = this.identityLabel(right);
5558
+ if (leftKey && rightKey) {
5559
+ return leftKey.field === rightKey.field
5560
+ && String(leftKey.value) === String(rightKey.value)
5561
+ && !!leftLabel
5562
+ && leftLabel === rightLabel;
5563
+ }
5564
+ return !!leftLabel && leftLabel === rightLabel;
5565
+ }
5566
+ identityLabel(identity) {
5567
+ return String(identity.displayLabel || identity.title?.value || '').trim();
5568
+ }
5518
5569
  extractInitialValue(inputs) {
5519
5570
  if (!inputs || typeof inputs !== 'object') {
5520
5571
  return null;
@@ -5858,17 +5909,34 @@ class DynamicFormDialogHostComponent {
5858
5909
  aria-labelledby="crudDialogTitle"
5859
5910
  [attr.aria-describedby]="dialogSubtitle ? 'crudDialogDescription' : null"
5860
5911
  >
5861
- @if (resourceIdentity) {
5862
- <div class="crud-resource-identity" data-testid="crud-resource-identity">
5863
- <span class="crud-resource-identity__label" aria-hidden="true">
5864
- {{ texts.recordContextLabel }}
5865
- </span>
5866
- <praxis-resource-identity
5867
- [identity]="resourceIdentity"
5868
- density="compact"
5869
- [showKeyLabel]="true"
5870
- [ariaLabel]="texts.recordContextLabel"
5871
- ></praxis-resource-identity>
5912
+ @if (showContextIdentity || resourceIdentity) {
5913
+ <div class="crud-operation-context" data-testid="crud-operation-context">
5914
+ @if (showContextIdentity) {
5915
+ <div class="crud-resource-identity" data-testid="crud-context-identity">
5916
+ <span class="crud-resource-identity__label" aria-hidden="true">
5917
+ {{ texts.relatedContextLabel }}
5918
+ </span>
5919
+ <praxis-resource-identity
5920
+ [identity]="contextIdentity"
5921
+ density="compact"
5922
+ [showKeyLabel]="true"
5923
+ [ariaLabel]="texts.relatedContextLabel"
5924
+ ></praxis-resource-identity>
5925
+ </div>
5926
+ }
5927
+ @if (resourceIdentity) {
5928
+ <div class="crud-resource-identity" data-testid="crud-resource-identity">
5929
+ <span class="crud-resource-identity__label" aria-hidden="true">
5930
+ {{ resourceIdentityLabel }}
5931
+ </span>
5932
+ <praxis-resource-identity
5933
+ [identity]="resourceIdentity"
5934
+ density="compact"
5935
+ [showKeyLabel]="true"
5936
+ [ariaLabel]="resourceIdentityLabel"
5937
+ ></praxis-resource-identity>
5938
+ </div>
5939
+ }
5872
5940
  </div>
5873
5941
  }
5874
5942
  <praxis-dynamic-form
@@ -5892,7 +5960,7 @@ class DynamicFormDialogHostComponent {
5892
5960
  (formCancel)="onCancel()"
5893
5961
  ></praxis-dynamic-form>
5894
5962
  </mat-dialog-content>
5895
- `, isInline: true, styles: ["praxis-dynamic-form-dialog-host{--dlg-header-h: 56px;--dlg-footer-h: 56px;--dlg-pad: 16px;display:flex;flex-direction:column;height:100%;overflow:hidden}praxis-dynamic-form-dialog-host[data-density=compact]{--dlg-header-h: 44px;--dlg-footer-h: 44px;--dlg-pad: 12px}praxis-dynamic-form-dialog-host .dialog-header{position:sticky;top:0;z-index:1;display:flex;align-items:center;gap:var(--dlg-pad);min-height:var(--dlg-header-h);padding:10px var(--dlg-pad);margin:0;background:var(--md-sys-color-surface-container-high);border-bottom:1px solid var(--md-sys-color-outline-variant);color:var(--md-sys-color-on-surface)}praxis-dynamic-form-dialog-host .dialog-title{margin:0;font:inherit;font-weight:600;color:var(--md-sys-color-on-surface)}praxis-dynamic-form-dialog-host .dialog-heading{display:grid;min-width:0;gap:2px}praxis-dynamic-form-dialog-host .dialog-title-icon{display:inline-grid;flex:0 0 auto;width:36px;height:36px;place-items:center;border-radius:10px;color:var(--md-sys-color-primary);background:color-mix(in srgb,var(--md-sys-color-primary) 12%,transparent)}praxis-dynamic-form-dialog-host .dialog-title-icon mat-icon{width:22px;height:22px;font-size:22px}praxis-dynamic-form-dialog-host .dialog-subtitle{display:-webkit-box;overflow:hidden;color:var(--md-sys-color-on-surface-variant);font-size:.82rem;font-weight:400;line-height:1.35;-webkit-box-orient:vertical;-webkit-line-clamp:2}praxis-dynamic-form-dialog-host .spacer{flex:1}praxis-dynamic-form-dialog-host .dialog-content{flex:1 1 auto;overflow:auto;padding:var(--dlg-pad);max-height:calc(100svh - var(--dlg-header-h) - 32px)}praxis-dynamic-form-dialog-host .crud-resource-identity{position:sticky;top:0;z-index:1;display:grid;gap:.25rem;margin-block-end:var(--dlg-pad);padding-block:.5rem .75rem;background:var(--pfx-form-surface, var(--md-sys-color-surface));border-bottom:1px solid var(--md-sys-color-outline-variant)}praxis-dynamic-form-dialog-host .crud-resource-identity__label{color:var(--md-sys-color-on-surface-variant);font:var(--md-sys-typescale-label-medium, 500 .75rem/1rem inherit)}praxis-dynamic-form-dialog-host.praxis-drawer{--dlg-header-h: 72px;width:100%;min-width:0;max-width:100vw;height:100dvh;background:var(--pfx-form-surface, var(--md-sys-color-surface));color:var(--md-sys-color-on-surface)}praxis-dynamic-form-dialog-host.praxis-drawer .dialog-header{background:color-mix(in srgb,var(--md-sys-color-surface-container-high, var(--md-sys-color-surface)),transparent 4%)}praxis-dynamic-form-dialog-host.praxis-drawer .dialog-content{max-height:none;min-height:0;padding:clamp(12px,2.4vw,24px)}praxis-dynamic-form-dialog-host .dialog-header button.praxis-icon-button{color:var(--md-sys-color-on-surface-variant)}praxis-dynamic-form-dialog-host .dialog-header button.praxis-icon-button:hover{color:var(--md-sys-color-primary)}praxis-dynamic-form-dialog-host .dialog-footer{position:sticky;bottom:0;z-index:1;padding:var(--dlg-pad)}.pfx-blur-backdrop{background-color:var(--pfx-backdrop, rgba(15, 23, 42, .42))!important;backdrop-filter:blur(var(--pfx-backdrop-blur, 10px)) saturate(110%);-webkit-backdrop-filter:blur(var(--pfx-backdrop-blur, 10px)) saturate(110%)}.pfx-transparent-backdrop{background-color:transparent!important}.cdk-overlay-pane.pfx-dialog-pane{overflow:hidden;transition:width .2s ease,height .2s ease,margin .2s ease}.cdk-overlay-pane.pfx-dialog-pane .mat-mdc-dialog-surface,.cdk-overlay-pane.pfx-dialog-pane .mdc-dialog__surface{display:flex;flex-direction:column;width:100%;min-width:0;height:100%;max-height:inherit;overflow:hidden;background:var(--pfx-form-surface, var(--md-sys-color-surface))!important;border:1px solid var(--pfx-form-stroke, var(--md-sys-color-outline-variant))!important}.cdk-overlay-pane.pfx-dialog-pane.pfx-dialog-frosted .mat-mdc-dialog-surface,.cdk-overlay-pane.pfx-dialog-pane.pfx-dialog-frosted .mdc-dialog__surface{backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}.cdk-overlay-pane.pfx-drawer-pane{margin:0!important;border-radius:0!important;box-shadow:var(--md-sys-elevation-level3, 0 24px 80px rgba(15, 23, 42, .28)),0 0 0 1px color-mix(in srgb,var(--md-sys-color-outline, #64748b) 24%,transparent)}.cdk-overlay-pane.pfx-drawer-pane.pfx-drawer-maximized{margin:var(--pfx-drawer-edge-gap, 8px)!important}.cdk-overlay-pane.pfx-drawer-pane .mat-mdc-dialog-surface,.cdk-overlay-pane.pfx-drawer-pane .mdc-dialog__surface{min-width:0!important;border-radius:0!important}\n"], dependencies: [{ kind: "ngmodule", type: MatDialogModule }, { kind: "directive", type: i1.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: i1.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i6.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: PraxisIconButtonComponent, selector: "button[praxisIconButton]", inputs: ["praxisIconButton", "size", "appearance", "presentation", "pressed", "busy"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "component", type: PraxisResourceIdentityComponent, selector: "praxis-resource-identity", inputs: ["identity", "emptyTitle", "density", "showMetadataLabels", "showKeyLabel", "ariaLabel"] }, { kind: "component", type: PraxisDynamicForm, selector: "praxis-dynamic-form", inputs: ["resourcePath", "resourceId", "initialValue", "editorialContext", "mode", "config", "actions", "schemaSource", "schemaUrl", "readUrl", "submitUrl", "submitMethod", "submitIdempotencyKey", "submitCorrelationId", "submitResourceVersion", "responseSchemaUrl", "apiEndpointKey", "apiUrlEntry", "enableCustomization", "showAiAssistant", "formId", "componentInstanceId", "configPersistenceStrategy", "layout", "generatedLayoutPreset", "layoutPolicy", "backConfig", "hooks", "removeEmptyContainersOnSave", "reactiveValidation", "reactiveValidationDebounceMs", "notifyIfOutdated", "snoozeMs", "autoOpenSettingsOnOutdated", "readonlyModeGlobal", "disabledModeGlobal", "presentationModeGlobal", "visibleGlobal", "fieldIconPolicy", "domainRules", "customEndpoints"], outputs: ["formSubmit", "formCancel", "formReset", "configChange", "configPatchChange", "formReady", "valueChange", "syncCompleted", "initializationError", "loadingStateChange", "enableCustomizationChange", "customAction", "actionConfirmation", "schemaStatusChange", "fieldRenderError", "ruleDiagnosticsChange"] }], encapsulation: i0.ViewEncapsulation.None });
5963
+ `, isInline: true, styles: ["praxis-dynamic-form-dialog-host{--dlg-header-h: 56px;--dlg-footer-h: 56px;--dlg-pad: 16px;display:flex;flex-direction:column;height:100%;overflow:hidden}praxis-dynamic-form-dialog-host[data-density=compact]{--dlg-header-h: 44px;--dlg-footer-h: 44px;--dlg-pad: 12px}praxis-dynamic-form-dialog-host .dialog-header{position:sticky;top:0;z-index:1;display:flex;align-items:center;gap:var(--dlg-pad);min-height:var(--dlg-header-h);padding:10px var(--dlg-pad);margin:0;background:var(--md-sys-color-surface-container-high);border-bottom:1px solid var(--md-sys-color-outline-variant);color:var(--md-sys-color-on-surface)}praxis-dynamic-form-dialog-host .dialog-title{margin:0;font:inherit;font-weight:600;color:var(--md-sys-color-on-surface)}praxis-dynamic-form-dialog-host .dialog-heading{display:grid;min-width:0;gap:2px}praxis-dynamic-form-dialog-host .dialog-title-icon{display:inline-grid;flex:0 0 auto;width:36px;height:36px;place-items:center;border-radius:10px;color:var(--md-sys-color-primary);background:color-mix(in srgb,var(--md-sys-color-primary) 12%,transparent)}praxis-dynamic-form-dialog-host .dialog-title-icon mat-icon{width:22px;height:22px;font-size:22px}praxis-dynamic-form-dialog-host .dialog-subtitle{display:-webkit-box;overflow:hidden;color:var(--md-sys-color-on-surface-variant);font-size:.82rem;font-weight:400;line-height:1.35;-webkit-box-orient:vertical;-webkit-line-clamp:2}praxis-dynamic-form-dialog-host .spacer{flex:1}praxis-dynamic-form-dialog-host .dialog-content{flex:1 1 auto;overflow:auto;padding:var(--dlg-pad);max-height:calc(100svh - var(--dlg-header-h) - 32px)}praxis-dynamic-form-dialog-host .crud-operation-context{position:sticky;top:0;z-index:1;display:grid;gap:.75rem;margin-block-end:var(--dlg-pad);padding-block:.5rem .75rem;background:var(--pfx-form-surface, var(--md-sys-color-surface));border-bottom:1px solid var(--md-sys-color-outline-variant)}praxis-dynamic-form-dialog-host .crud-resource-identity{display:grid;gap:.25rem;min-width:0}praxis-dynamic-form-dialog-host .crud-resource-identity__label{color:var(--md-sys-color-on-surface-variant);font:var(--md-sys-typescale-label-medium, 500 .75rem/1rem inherit)}praxis-dynamic-form-dialog-host.praxis-drawer{--dlg-header-h: 72px;width:100%;min-width:0;max-width:100vw;height:100dvh;background:var(--pfx-form-surface, var(--md-sys-color-surface));color:var(--md-sys-color-on-surface)}praxis-dynamic-form-dialog-host.praxis-drawer .dialog-header{background:color-mix(in srgb,var(--md-sys-color-surface-container-high, var(--md-sys-color-surface)),transparent 4%)}praxis-dynamic-form-dialog-host.praxis-drawer .dialog-content{max-height:none;min-height:0;padding:clamp(12px,2.4vw,24px)}praxis-dynamic-form-dialog-host .dialog-header button.praxis-icon-button{color:var(--md-sys-color-on-surface-variant)}praxis-dynamic-form-dialog-host .dialog-header button.praxis-icon-button:hover{color:var(--md-sys-color-primary)}praxis-dynamic-form-dialog-host .dialog-footer{position:sticky;bottom:0;z-index:1;padding:var(--dlg-pad)}.pfx-blur-backdrop{background-color:var(--pfx-backdrop, rgba(15, 23, 42, .42))!important;backdrop-filter:blur(var(--pfx-backdrop-blur, 10px)) saturate(110%);-webkit-backdrop-filter:blur(var(--pfx-backdrop-blur, 10px)) saturate(110%)}.pfx-transparent-backdrop{background-color:transparent!important}.cdk-overlay-pane.pfx-dialog-pane{overflow:hidden;transition:width .2s ease,height .2s ease,margin .2s ease}.cdk-overlay-pane.pfx-dialog-pane .mat-mdc-dialog-surface,.cdk-overlay-pane.pfx-dialog-pane .mdc-dialog__surface{display:flex;flex-direction:column;width:100%;min-width:0;height:100%;max-height:inherit;overflow:hidden;background:var(--pfx-form-surface, var(--md-sys-color-surface))!important;border:1px solid var(--pfx-form-stroke, var(--md-sys-color-outline-variant))!important}.cdk-overlay-pane.pfx-dialog-pane.pfx-dialog-frosted .mat-mdc-dialog-surface,.cdk-overlay-pane.pfx-dialog-pane.pfx-dialog-frosted .mdc-dialog__surface{backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}.cdk-overlay-pane.pfx-drawer-pane{margin:0!important;border-radius:0!important;box-shadow:var(--md-sys-elevation-level3, 0 24px 80px rgba(15, 23, 42, .28)),0 0 0 1px color-mix(in srgb,var(--md-sys-color-outline, #64748b) 24%,transparent)}.cdk-overlay-pane.pfx-drawer-pane.pfx-drawer-maximized{margin:var(--pfx-drawer-edge-gap, 8px)!important}.cdk-overlay-pane.pfx-drawer-pane .mat-mdc-dialog-surface,.cdk-overlay-pane.pfx-drawer-pane .mdc-dialog__surface{min-width:0!important;border-radius:0!important}\n"], dependencies: [{ kind: "ngmodule", type: MatDialogModule }, { kind: "directive", type: i1.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: i1.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i6.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: PraxisIconButtonComponent, selector: "button[praxisIconButton]", inputs: ["praxisIconButton", "size", "appearance", "presentation", "pressed", "busy"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "component", type: PraxisResourceIdentityComponent, selector: "praxis-resource-identity", inputs: ["identity", "emptyTitle", "density", "showMetadataLabels", "showKeyLabel", "ariaLabel"] }, { kind: "component", type: PraxisDynamicForm, selector: "praxis-dynamic-form", inputs: ["resourcePath", "resourceId", "initialValue", "editorialContext", "mode", "config", "actions", "schemaSource", "schemaUrl", "readUrl", "submitUrl", "submitMethod", "submitIdempotencyKey", "submitCorrelationId", "submitResourceVersion", "responseSchemaUrl", "apiEndpointKey", "apiUrlEntry", "enableCustomization", "showAiAssistant", "formId", "componentInstanceId", "configPersistenceStrategy", "layout", "generatedLayoutPreset", "layoutPolicy", "backConfig", "hooks", "removeEmptyContainersOnSave", "reactiveValidation", "reactiveValidationDebounceMs", "notifyIfOutdated", "snoozeMs", "autoOpenSettingsOnOutdated", "readonlyModeGlobal", "disabledModeGlobal", "presentationModeGlobal", "visibleGlobal", "fieldIconPolicy", "domainRules", "customEndpoints"], outputs: ["formSubmit", "formCancel", "formReset", "configChange", "configPatchChange", "formReady", "valueChange", "syncCompleted", "initializationError", "loadingStateChange", "enableCustomizationChange", "customAction", "actionConfirmation", "schemaStatusChange", "fieldRenderError", "ruleDiagnosticsChange"] }], encapsulation: i0.ViewEncapsulation.None });
5896
5964
  }
5897
5965
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DynamicFormDialogHostComponent, decorators: [{
5898
5966
  type: Component,
@@ -5953,17 +6021,34 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
5953
6021
  aria-labelledby="crudDialogTitle"
5954
6022
  [attr.aria-describedby]="dialogSubtitle ? 'crudDialogDescription' : null"
5955
6023
  >
5956
- @if (resourceIdentity) {
5957
- <div class="crud-resource-identity" data-testid="crud-resource-identity">
5958
- <span class="crud-resource-identity__label" aria-hidden="true">
5959
- {{ texts.recordContextLabel }}
5960
- </span>
5961
- <praxis-resource-identity
5962
- [identity]="resourceIdentity"
5963
- density="compact"
5964
- [showKeyLabel]="true"
5965
- [ariaLabel]="texts.recordContextLabel"
5966
- ></praxis-resource-identity>
6024
+ @if (showContextIdentity || resourceIdentity) {
6025
+ <div class="crud-operation-context" data-testid="crud-operation-context">
6026
+ @if (showContextIdentity) {
6027
+ <div class="crud-resource-identity" data-testid="crud-context-identity">
6028
+ <span class="crud-resource-identity__label" aria-hidden="true">
6029
+ {{ texts.relatedContextLabel }}
6030
+ </span>
6031
+ <praxis-resource-identity
6032
+ [identity]="contextIdentity"
6033
+ density="compact"
6034
+ [showKeyLabel]="true"
6035
+ [ariaLabel]="texts.relatedContextLabel"
6036
+ ></praxis-resource-identity>
6037
+ </div>
6038
+ }
6039
+ @if (resourceIdentity) {
6040
+ <div class="crud-resource-identity" data-testid="crud-resource-identity">
6041
+ <span class="crud-resource-identity__label" aria-hidden="true">
6042
+ {{ resourceIdentityLabel }}
6043
+ </span>
6044
+ <praxis-resource-identity
6045
+ [identity]="resourceIdentity"
6046
+ density="compact"
6047
+ [showKeyLabel]="true"
6048
+ [ariaLabel]="resourceIdentityLabel"
6049
+ ></praxis-resource-identity>
6050
+ </div>
6051
+ }
5967
6052
  </div>
5968
6053
  }
5969
6054
  <praxis-dynamic-form
@@ -5987,7 +6072,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
5987
6072
  (formCancel)="onCancel()"
5988
6073
  ></praxis-dynamic-form>
5989
6074
  </mat-dialog-content>
5990
- `, styles: ["praxis-dynamic-form-dialog-host{--dlg-header-h: 56px;--dlg-footer-h: 56px;--dlg-pad: 16px;display:flex;flex-direction:column;height:100%;overflow:hidden}praxis-dynamic-form-dialog-host[data-density=compact]{--dlg-header-h: 44px;--dlg-footer-h: 44px;--dlg-pad: 12px}praxis-dynamic-form-dialog-host .dialog-header{position:sticky;top:0;z-index:1;display:flex;align-items:center;gap:var(--dlg-pad);min-height:var(--dlg-header-h);padding:10px var(--dlg-pad);margin:0;background:var(--md-sys-color-surface-container-high);border-bottom:1px solid var(--md-sys-color-outline-variant);color:var(--md-sys-color-on-surface)}praxis-dynamic-form-dialog-host .dialog-title{margin:0;font:inherit;font-weight:600;color:var(--md-sys-color-on-surface)}praxis-dynamic-form-dialog-host .dialog-heading{display:grid;min-width:0;gap:2px}praxis-dynamic-form-dialog-host .dialog-title-icon{display:inline-grid;flex:0 0 auto;width:36px;height:36px;place-items:center;border-radius:10px;color:var(--md-sys-color-primary);background:color-mix(in srgb,var(--md-sys-color-primary) 12%,transparent)}praxis-dynamic-form-dialog-host .dialog-title-icon mat-icon{width:22px;height:22px;font-size:22px}praxis-dynamic-form-dialog-host .dialog-subtitle{display:-webkit-box;overflow:hidden;color:var(--md-sys-color-on-surface-variant);font-size:.82rem;font-weight:400;line-height:1.35;-webkit-box-orient:vertical;-webkit-line-clamp:2}praxis-dynamic-form-dialog-host .spacer{flex:1}praxis-dynamic-form-dialog-host .dialog-content{flex:1 1 auto;overflow:auto;padding:var(--dlg-pad);max-height:calc(100svh - var(--dlg-header-h) - 32px)}praxis-dynamic-form-dialog-host .crud-resource-identity{position:sticky;top:0;z-index:1;display:grid;gap:.25rem;margin-block-end:var(--dlg-pad);padding-block:.5rem .75rem;background:var(--pfx-form-surface, var(--md-sys-color-surface));border-bottom:1px solid var(--md-sys-color-outline-variant)}praxis-dynamic-form-dialog-host .crud-resource-identity__label{color:var(--md-sys-color-on-surface-variant);font:var(--md-sys-typescale-label-medium, 500 .75rem/1rem inherit)}praxis-dynamic-form-dialog-host.praxis-drawer{--dlg-header-h: 72px;width:100%;min-width:0;max-width:100vw;height:100dvh;background:var(--pfx-form-surface, var(--md-sys-color-surface));color:var(--md-sys-color-on-surface)}praxis-dynamic-form-dialog-host.praxis-drawer .dialog-header{background:color-mix(in srgb,var(--md-sys-color-surface-container-high, var(--md-sys-color-surface)),transparent 4%)}praxis-dynamic-form-dialog-host.praxis-drawer .dialog-content{max-height:none;min-height:0;padding:clamp(12px,2.4vw,24px)}praxis-dynamic-form-dialog-host .dialog-header button.praxis-icon-button{color:var(--md-sys-color-on-surface-variant)}praxis-dynamic-form-dialog-host .dialog-header button.praxis-icon-button:hover{color:var(--md-sys-color-primary)}praxis-dynamic-form-dialog-host .dialog-footer{position:sticky;bottom:0;z-index:1;padding:var(--dlg-pad)}.pfx-blur-backdrop{background-color:var(--pfx-backdrop, rgba(15, 23, 42, .42))!important;backdrop-filter:blur(var(--pfx-backdrop-blur, 10px)) saturate(110%);-webkit-backdrop-filter:blur(var(--pfx-backdrop-blur, 10px)) saturate(110%)}.pfx-transparent-backdrop{background-color:transparent!important}.cdk-overlay-pane.pfx-dialog-pane{overflow:hidden;transition:width .2s ease,height .2s ease,margin .2s ease}.cdk-overlay-pane.pfx-dialog-pane .mat-mdc-dialog-surface,.cdk-overlay-pane.pfx-dialog-pane .mdc-dialog__surface{display:flex;flex-direction:column;width:100%;min-width:0;height:100%;max-height:inherit;overflow:hidden;background:var(--pfx-form-surface, var(--md-sys-color-surface))!important;border:1px solid var(--pfx-form-stroke, var(--md-sys-color-outline-variant))!important}.cdk-overlay-pane.pfx-dialog-pane.pfx-dialog-frosted .mat-mdc-dialog-surface,.cdk-overlay-pane.pfx-dialog-pane.pfx-dialog-frosted .mdc-dialog__surface{backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}.cdk-overlay-pane.pfx-drawer-pane{margin:0!important;border-radius:0!important;box-shadow:var(--md-sys-elevation-level3, 0 24px 80px rgba(15, 23, 42, .28)),0 0 0 1px color-mix(in srgb,var(--md-sys-color-outline, #64748b) 24%,transparent)}.cdk-overlay-pane.pfx-drawer-pane.pfx-drawer-maximized{margin:var(--pfx-drawer-edge-gap, 8px)!important}.cdk-overlay-pane.pfx-drawer-pane .mat-mdc-dialog-surface,.cdk-overlay-pane.pfx-drawer-pane .mdc-dialog__surface{min-width:0!important;border-radius:0!important}\n"] }]
6075
+ `, styles: ["praxis-dynamic-form-dialog-host{--dlg-header-h: 56px;--dlg-footer-h: 56px;--dlg-pad: 16px;display:flex;flex-direction:column;height:100%;overflow:hidden}praxis-dynamic-form-dialog-host[data-density=compact]{--dlg-header-h: 44px;--dlg-footer-h: 44px;--dlg-pad: 12px}praxis-dynamic-form-dialog-host .dialog-header{position:sticky;top:0;z-index:1;display:flex;align-items:center;gap:var(--dlg-pad);min-height:var(--dlg-header-h);padding:10px var(--dlg-pad);margin:0;background:var(--md-sys-color-surface-container-high);border-bottom:1px solid var(--md-sys-color-outline-variant);color:var(--md-sys-color-on-surface)}praxis-dynamic-form-dialog-host .dialog-title{margin:0;font:inherit;font-weight:600;color:var(--md-sys-color-on-surface)}praxis-dynamic-form-dialog-host .dialog-heading{display:grid;min-width:0;gap:2px}praxis-dynamic-form-dialog-host .dialog-title-icon{display:inline-grid;flex:0 0 auto;width:36px;height:36px;place-items:center;border-radius:10px;color:var(--md-sys-color-primary);background:color-mix(in srgb,var(--md-sys-color-primary) 12%,transparent)}praxis-dynamic-form-dialog-host .dialog-title-icon mat-icon{width:22px;height:22px;font-size:22px}praxis-dynamic-form-dialog-host .dialog-subtitle{display:-webkit-box;overflow:hidden;color:var(--md-sys-color-on-surface-variant);font-size:.82rem;font-weight:400;line-height:1.35;-webkit-box-orient:vertical;-webkit-line-clamp:2}praxis-dynamic-form-dialog-host .spacer{flex:1}praxis-dynamic-form-dialog-host .dialog-content{flex:1 1 auto;overflow:auto;padding:var(--dlg-pad);max-height:calc(100svh - var(--dlg-header-h) - 32px)}praxis-dynamic-form-dialog-host .crud-operation-context{position:sticky;top:0;z-index:1;display:grid;gap:.75rem;margin-block-end:var(--dlg-pad);padding-block:.5rem .75rem;background:var(--pfx-form-surface, var(--md-sys-color-surface));border-bottom:1px solid var(--md-sys-color-outline-variant)}praxis-dynamic-form-dialog-host .crud-resource-identity{display:grid;gap:.25rem;min-width:0}praxis-dynamic-form-dialog-host .crud-resource-identity__label{color:var(--md-sys-color-on-surface-variant);font:var(--md-sys-typescale-label-medium, 500 .75rem/1rem inherit)}praxis-dynamic-form-dialog-host.praxis-drawer{--dlg-header-h: 72px;width:100%;min-width:0;max-width:100vw;height:100dvh;background:var(--pfx-form-surface, var(--md-sys-color-surface));color:var(--md-sys-color-on-surface)}praxis-dynamic-form-dialog-host.praxis-drawer .dialog-header{background:color-mix(in srgb,var(--md-sys-color-surface-container-high, var(--md-sys-color-surface)),transparent 4%)}praxis-dynamic-form-dialog-host.praxis-drawer .dialog-content{max-height:none;min-height:0;padding:clamp(12px,2.4vw,24px)}praxis-dynamic-form-dialog-host .dialog-header button.praxis-icon-button{color:var(--md-sys-color-on-surface-variant)}praxis-dynamic-form-dialog-host .dialog-header button.praxis-icon-button:hover{color:var(--md-sys-color-primary)}praxis-dynamic-form-dialog-host .dialog-footer{position:sticky;bottom:0;z-index:1;padding:var(--dlg-pad)}.pfx-blur-backdrop{background-color:var(--pfx-backdrop, rgba(15, 23, 42, .42))!important;backdrop-filter:blur(var(--pfx-backdrop-blur, 10px)) saturate(110%);-webkit-backdrop-filter:blur(var(--pfx-backdrop-blur, 10px)) saturate(110%)}.pfx-transparent-backdrop{background-color:transparent!important}.cdk-overlay-pane.pfx-dialog-pane{overflow:hidden;transition:width .2s ease,height .2s ease,margin .2s ease}.cdk-overlay-pane.pfx-dialog-pane .mat-mdc-dialog-surface,.cdk-overlay-pane.pfx-dialog-pane .mdc-dialog__surface{display:flex;flex-direction:column;width:100%;min-width:0;height:100%;max-height:inherit;overflow:hidden;background:var(--pfx-form-surface, var(--md-sys-color-surface))!important;border:1px solid var(--pfx-form-stroke, var(--md-sys-color-outline-variant))!important}.cdk-overlay-pane.pfx-dialog-pane.pfx-dialog-frosted .mat-mdc-dialog-surface,.cdk-overlay-pane.pfx-dialog-pane.pfx-dialog-frosted .mdc-dialog__surface{backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}.cdk-overlay-pane.pfx-drawer-pane{margin:0!important;border-radius:0!important;box-shadow:var(--md-sys-elevation-level3, 0 24px 80px rgba(15, 23, 42, .28)),0 0 0 1px color-mix(in srgb,var(--md-sys-color-outline, #64748b) 24%,transparent)}.cdk-overlay-pane.pfx-drawer-pane.pfx-drawer-maximized{margin:var(--pfx-drawer-edge-gap, 8px)!important}.cdk-overlay-pane.pfx-drawer-pane .mat-mdc-dialog-surface,.cdk-overlay-pane.pfx-drawer-pane .mdc-dialog__surface{min-width:0!important;border-radius:0!important}\n"] }]
5991
6076
  }], ctorParameters: () => [{ type: undefined, decorators: [{
5992
6077
  type: Inject,
5993
6078
  args: [MatDialogRef]
@@ -6226,6 +6311,12 @@ const PRAXIS_CRUD_COMPONENT_METADATA = {
6226
6311
  type: 'string',
6227
6312
  description: 'Identificador opcional para múltiplas instâncias na mesma rota.',
6228
6313
  },
6314
+ {
6315
+ name: 'tableConfigPersistenceStrategy',
6316
+ type: "'local-first' | 'input-first' | 'volatile'",
6317
+ default: 'local-first',
6318
+ description: 'Política da tabela interna. Use input-first para contratos governados autoritativos e volatile para previews que não devem ler nem gravar preferências locais.',
6319
+ },
6229
6320
  {
6230
6321
  name: 'context',
6231
6322
  type: 'Record<string, unknown>',
@@ -6801,6 +6892,7 @@ const PRAXIS_CRUD_AUTHORING_MANIFEST = {
6801
6892
  { name: 'metadata', type: 'CrudMetadata | string', description: 'Canonical CRUD metadata or serialized metadata document.' },
6802
6893
  { name: 'crudId', type: 'string', description: 'Stable CRUD instance id used for table/form identity and persistence.' },
6803
6894
  { name: 'componentInstanceId', type: 'string', description: 'Optional stable host instance id for multiple CRUD widgets on the same route.' },
6895
+ { name: 'tableConfigPersistenceStrategy', type: "'local-first' | 'input-first' | 'volatile'", description: 'Persistence precedence forwarded to the internal Praxis Table. Governed previews should use input-first or volatile explicitly.' },
6804
6896
  { name: 'context', type: 'Record<string, unknown>', description: 'Opaque host context used for authoring seeds and launcher inputs.' },
6805
6897
  { name: 'enableCustomization', type: 'boolean', description: 'Explicit host opt-in for CRUD authoring surfaces.' },
6806
6898
  { name: 'authoringCapability', type: 'string | null', description: 'Public runtime capability required before CRUD and delegated table authoring can open.' },
package/package.json CHANGED
@@ -1,20 +1,20 @@
1
1
  {
2
2
  "name": "@praxisui/crud",
3
- "version": "9.0.4-rc.24",
3
+ "version": "9.0.4-rc.26",
4
4
  "description": "CRUD building blocks for Praxis UI: integrates dynamic forms and tables with unified configuration and services.",
5
5
  "peerDependencies": {
6
6
  "@angular/common": "^21.0.0",
7
7
  "@angular/core": "^21.0.0",
8
- "@praxisui/dynamic-form": "^9.0.4-rc.24",
9
- "@praxisui/table": "^9.0.4-rc.24",
10
- "@praxisui/core": "^9.0.4-rc.24",
11
- "@praxisui/dynamic-fields": "^9.0.4-rc.24",
12
- "@praxisui/settings-panel": "^9.0.4-rc.24",
8
+ "@praxisui/dynamic-form": "^9.0.4-rc.26",
9
+ "@praxisui/table": "^9.0.4-rc.26",
10
+ "@praxisui/core": "^9.0.4-rc.26",
11
+ "@praxisui/dynamic-fields": "^9.0.4-rc.26",
12
+ "@praxisui/settings-panel": "^9.0.4-rc.26",
13
13
  "@angular/cdk": "^21.0.0",
14
14
  "@angular/forms": "^21.0.0",
15
15
  "@angular/material": "^21.0.0",
16
16
  "@angular/router": "^21.0.0",
17
- "@praxisui/ai": "^9.0.4-rc.24",
17
+ "@praxisui/ai": "^9.0.4-rc.26",
18
18
  "rxjs": "~7.8.0"
19
19
  },
20
20
  "dependencies": {
@@ -55,8 +55,10 @@ interface CrudDrawerOpenConfig {
55
55
  action: CrudAction;
56
56
  metadata: CrudMetadata;
57
57
  inputs: Record<string, unknown>;
58
- /** Read-only record context for edit drawers. It is never part of form inputs or submit payloads. */
58
+ /** Read-only record context for existing-record drawers. It is never part of form inputs or submit payloads. */
59
59
  resourceIdentity?: MaterializedResourceIdentity | null;
60
+ /** Read-only task scope for related operations. It is never part of form inputs or submit payloads. */
61
+ contextIdentity?: MaterializedResourceIdentity | null;
60
62
  /**
61
63
  * Notifica fechamento do drawer (cancelamento/fechamento externo/resultado finalizado).
62
64
  */
@@ -3,6 +3,7 @@ import { NgZone, OnChanges, EventEmitter, SimpleChanges, OnInit, Provider, After
3
3
  import * as _praxisui_core from '@praxisui/core';
4
4
  import { RowAction, ToolbarAction, ApiEndpoint, ApiUrlEntry, DynamicFormLayoutPolicy, BackConfig, TableConfig, PraxisDataQueryContext, FormConfig, ResourceActionCatalogItem, ResourceSurfaceCatalogItem, MaterializedResourceIdentity, LoadingState, RestApiLinks, ResourceCapabilitySnapshot, GenericCrudService, AsyncConfigStorage, ComponentDocMeta, AiCapabilityCatalog, AiCapability, AiCapabilityCategory, AiValueKind, ComponentAuthoringManifest, SettingsValueProvider } from '@praxisui/core';
5
5
  export { BackConfig } from '@praxisui/core';
6
+ import { TableConfigPersistenceStrategy } from '@praxisui/table';
6
7
  import { MatDialogConfig, MatDialogRef, MatDialog } from '@angular/material/dialog';
7
8
  export { MAT_DIALOG_DATA as DIALOG_DATA } from '@angular/material/dialog';
8
9
  import { ComponentType } from '@angular/cdk/portal';
@@ -170,6 +171,8 @@ declare class PraxisCrudComponent implements OnChanges {
170
171
  metadata: CrudMetadata | string;
171
172
  crudId: string;
172
173
  componentInstanceId?: string;
174
+ /** Controls whether the CRUD's internal table trusts host input or stored user configuration. */
175
+ tableConfigPersistenceStrategy: TableConfigPersistenceStrategy;
173
176
  context?: Record<string, unknown>;
174
177
  enableCustomization: boolean;
175
178
  /** Capability publica exigida para authoring governado do CRUD e da tabela interna. */
@@ -245,6 +248,7 @@ declare class PraxisCrudComponent implements OnChanges {
245
248
  onToolbarAction(event: CrudActionRuntimeEvent): Promise<void>;
246
249
  ngOnChanges(changes: SimpleChanges): void;
247
250
  onAction(action: string, row?: Record<string, unknown>, runtimeEvent?: CrudActionRuntimeEvent): Promise<void>;
251
+ private resolveTaskScopeIdentity;
248
252
  getOpeningActionStatus(actionLabel: string): string;
249
253
  private resolveOpeningActionLabel;
250
254
  private hasExplicitOpenBinding;
@@ -332,7 +336,7 @@ declare class PraxisCrudComponent implements OnChanges {
332
336
  private txWithParams;
333
337
  private interpolateTranslationParams;
334
338
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<PraxisCrudComponent, never>;
335
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<PraxisCrudComponent, "praxis-crud", never, { "metadata": { "alias": "metadata"; "required": true; }; "crudId": { "alias": "crudId"; "required": true; }; "componentInstanceId": { "alias": "componentInstanceId"; "required": false; }; "context": { "alias": "context"; "required": false; }; "enableCustomization": { "alias": "enableCustomization"; "required": false; }; "authoringCapability": { "alias": "authoringCapability"; "required": false; }; }, { "configureRequested": "configureRequested"; "afterOpen": "afterOpen"; "afterClose": "afterClose"; "afterSave": "afterSave"; "afterDelete": "afterDelete"; "error": "error"; "rowClick": "rowClick"; "selectionChange": "selectionChange"; "tableRuntimeConfigChange": "tableRuntimeConfigChange"; "crudAuthoringDocumentApplied": "crudAuthoringDocumentApplied"; "crudAuthoringDocumentSaved": "crudAuthoringDocumentSaved"; }, never, never, true, never>;
339
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<PraxisCrudComponent, "praxis-crud", never, { "metadata": { "alias": "metadata"; "required": true; }; "crudId": { "alias": "crudId"; "required": true; }; "componentInstanceId": { "alias": "componentInstanceId"; "required": false; }; "tableConfigPersistenceStrategy": { "alias": "tableConfigPersistenceStrategy"; "required": false; }; "context": { "alias": "context"; "required": false; }; "enableCustomization": { "alias": "enableCustomization"; "required": false; }; "authoringCapability": { "alias": "authoringCapability"; "required": false; }; }, { "configureRequested": "configureRequested"; "afterOpen": "afterOpen"; "afterClose": "afterClose"; "afterSave": "afterSave"; "afterDelete": "afterDelete"; "error": "error"; "rowClick": "rowClick"; "selectionChange": "selectionChange"; "tableRuntimeConfigChange": "tableRuntimeConfigChange"; "crudAuthoringDocumentApplied": "crudAuthoringDocumentApplied"; "crudAuthoringDocumentSaved": "crudAuthoringDocumentSaved"; }, never, never, true, never>;
336
340
  }
337
341
 
338
342
  type CrudDrawerResultType = 'save' | 'delete' | 'close';
@@ -356,6 +360,7 @@ declare class CrudLauncherService {
356
360
  capabilities?: ResourceCapabilitySnapshot | null;
357
361
  links?: RestApiLinks | null;
358
362
  resourceIdentity?: MaterializedResourceIdentity | null;
363
+ contextIdentity?: MaterializedResourceIdentity | null;
359
364
  }): Promise<{
360
365
  mode: FormOpenMode;
361
366
  ref?: DialogRef<any>;
@@ -411,6 +416,7 @@ declare class DynamicFormDialogHostComponent implements OnInit {
411
416
  resourceId?: string | number;
412
417
  initialValue?: Record<string, unknown> | null;
413
418
  resourceIdentity: MaterializedResourceIdentity | null;
419
+ contextIdentity: MaterializedResourceIdentity | null;
414
420
  schemaUrl?: string | null;
415
421
  submitUrl?: string | null;
416
422
  submitMethod?: string | null;
@@ -425,6 +431,10 @@ declare class DynamicFormDialogHostComponent implements OnInit {
425
431
  private idField;
426
432
  texts: Record<string, string>;
427
433
  constructor(dialogRef: DialogRef<DynamicFormDialogHostComponent>, data: any, dialogService: DialogService, crud: GenericCrudService<any>, configStorage: AsyncConfigStorage);
434
+ get resourceIdentityLabel(): string;
435
+ get showContextIdentity(): boolean;
436
+ private sameIdentity;
437
+ private identityLabel;
428
438
  private extractInitialValue;
429
439
  private resolveFormConfig;
430
440
  get dialogTitle(): string;
@@ -685,6 +695,7 @@ interface PraxisCrudWidgetEditorInputs {
685
695
  metadata?: CrudMetadata | string | null;
686
696
  crudId?: string;
687
697
  componentInstanceId?: string;
698
+ tableConfigPersistenceStrategy?: TableConfigPersistenceStrategy;
688
699
  context?: Record<string, unknown>;
689
700
  enableCustomization?: boolean;
690
701
  authoringCapability?: string | null;