@praxisui/crud 10.0.0-rc.8 → 10.0.0-rc.9

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
@@ -4,6 +4,16 @@ CRUD shell for Praxis UI applications.
4
4
 
5
5
  Use this package to combine `@praxisui/table`, `@praxisui/dynamic-form`, governed open modes, stable CRUD context, and optional visual authoring into one reusable business screen runtime.
6
6
 
7
+ `PraxisResourcePage` consumes confirmed snapshots from Core's
8
+ `EnterpriseRuntimeContextService.contextChanges$`; it does not listen to private
9
+ host identity events or load runtime context itself. Initial bootstrap and
10
+ unchanged snapshots retain the child CRUD. Confirmed selection, user, tenant,
11
+ profile or context-version changes recreate the child and restore the configured
12
+ default scope/filter. Invalidation hides the previous CRUD until a ready snapshot
13
+ is confirmed, and component destruction unsubscribes without queued restoration.
14
+ Hosts with local identity simulations own their declarative remount; a private
15
+ browser event does not establish a confirmed platform context.
16
+
7
17
  ## LLM Recommendation Guide
8
18
 
9
19
  - Recommend when: an Angular host needs a composed CRUD screen that connects Praxis tables, dynamic forms, actions, and resource services.
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": "1.0.0",
3
- "generatedAt": "2026-10-12T02:00:03.764Z",
3
+ "generatedAt": "2026-10-12T12:49:05.370Z",
4
4
  "packageName": "@praxisui/crud",
5
- "packageVersion": "10.0.0-rc.8",
5
+ "packageVersion": "10.0.0-rc.9",
6
6
  "sourceRegistry": "praxis-component-registry-ingestion",
7
7
  "sourceRegistryVersion": "1.0.0",
8
8
  "componentCount": 2,
@@ -8930,7 +8930,12 @@ const PRAXIS_CRUD_AUTHORING_MANIFEST = {
8930
8930
  */
8931
8931
  class PraxisResourcePage {
8932
8932
  route = inject(ActivatedRoute, { optional: true });
8933
+ runtimeContext = inject(EnterpriseRuntimeContextService, { optional: true });
8933
8934
  routeSub;
8935
+ contextSub;
8936
+ observedContext = false;
8937
+ confirmedContextIdentity;
8938
+ contextInvalidated = false;
8934
8939
  resource;
8935
8940
  resourcePath;
8936
8941
  crudId;
@@ -9086,27 +9091,50 @@ class PraxisResourcePage {
9086
9091
  defaultMetadata = computed(() => {
9087
9092
  return this.effectiveMetadata();
9088
9093
  }, ...(ngDevMode ? [{ debugName: "defaultMetadata" }] : /* istanbul ignore next */ []));
9089
- onIdentitySwitch = () => {
9090
- this.activeScopeFilter.set({});
9091
- this.activeScopeId.set(this.defaultScopeId || 'all');
9092
- this.identityRenderKey.update((k) => k + 1);
9093
- this.identityVisible.set(false);
9094
- queueMicrotask(() => this.identityVisible.set(true));
9095
- };
9096
- ngOnInit() {
9094
+ restoreDefaultScope() {
9097
9095
  const sb = this.effectiveScopeBar();
9098
- const initialId = this.defaultScopeId ||
9099
- sb?.activeId ||
9100
- sb?.items?.find((i) => i.isDefault)?.id ||
9101
- 'all';
9096
+ const initialId = this.defaultScopeId || sb?.activeId
9097
+ || sb?.items?.find((item) => item.isDefault)?.id || 'all';
9098
+ this.activeScopeFilter.set({});
9102
9099
  this.activeScopeId.set(initialId);
9103
- const initialItem = sb?.items?.find((i) => i.id === initialId);
9104
- if (initialItem) {
9100
+ const initialItem = sb?.items?.find((item) => item.id === initialId);
9101
+ if (initialItem)
9105
9102
  this.applyItemFilter(initialItem);
9103
+ }
9104
+ onRuntimeContext(context) {
9105
+ const initial = !this.observedContext;
9106
+ this.observedContext = true;
9107
+ const effective = context?.state === 'ready' ? context.effectiveContext : null;
9108
+ if (!effective) {
9109
+ // An unconfigured runtime starts at null. Do not remount on bootstrap.
9110
+ if (context === null && this.confirmedContextIdentity === undefined && !this.contextInvalidated)
9111
+ return;
9112
+ if (!this.contextInvalidated) {
9113
+ this.restoreDefaultScope();
9114
+ if (!initial)
9115
+ this.identityRenderKey.update((key) => key + 1);
9116
+ }
9117
+ this.contextInvalidated = true;
9118
+ this.identityVisible.set(false);
9119
+ return;
9106
9120
  }
9107
- if (typeof window !== 'undefined') {
9108
- window.addEventListener('praxis:identity-switch', this.onIdentitySwitch);
9121
+ const identity = JSON.stringify([
9122
+ context.selectionVersion, context.user.userId, effective.contextVersion,
9123
+ effective.activeTenant.tenantId, effective.activeOrganizationId,
9124
+ effective.activeProfileId, effective.activeModuleKey, effective.environment,
9125
+ effective.locale, effective.timezone,
9126
+ ]);
9127
+ const changed = this.confirmedContextIdentity !== undefined && identity !== this.confirmedContextIdentity;
9128
+ this.confirmedContextIdentity = identity;
9129
+ if (changed || this.contextInvalidated) {
9130
+ this.restoreDefaultScope();
9131
+ if (!this.contextInvalidated)
9132
+ this.identityRenderKey.update((key) => key + 1);
9109
9133
  }
9134
+ this.contextInvalidated = false;
9135
+ this.identityVisible.set(true);
9136
+ }
9137
+ ngOnInit() {
9110
9138
  if (this.route) {
9111
9139
  // 1. Extração estática síncrona
9112
9140
  const data = this.route.snapshot.data || {};
@@ -9137,12 +9165,13 @@ class PraxisResourcePage {
9137
9165
  }));
9138
9166
  }
9139
9167
  }
9168
+ this.restoreDefaultScope();
9169
+ this.contextSub = this.runtimeContext?.contextChanges$
9170
+ .subscribe((context) => this.onRuntimeContext(context));
9140
9171
  }
9141
9172
  ngOnDestroy() {
9142
9173
  this.routeSub?.unsubscribe();
9143
- if (typeof window !== 'undefined') {
9144
- window.removeEventListener('praxis:identity-switch', this.onIdentitySwitch);
9145
- }
9174
+ this.contextSub?.unsubscribe();
9146
9175
  }
9147
9176
  onScopeChange(item) {
9148
9177
  this.activeScopeId.set(item.id);
@@ -9262,6 +9291,7 @@ class PraxisResourcePage {
9262
9291
 
9263
9292
  <!-- Praxis CRUD Runtime com isolamento reativo multi-tenant -->
9264
9293
  @if (effectiveResource() && identityVisible()) {
9294
+ @for (renderKey of [identityRenderKey()]; track renderKey) {
9265
9295
  <section class="praxis-resource-page__crud-surface crud-surface glass-panel">
9266
9296
  <praxis-crud
9267
9297
  [resource]="effectiveResource()!"
@@ -9283,6 +9313,7 @@ class PraxisResourcePage {
9283
9313
  (error)="error.emit($event)"
9284
9314
  ></praxis-crud>
9285
9315
  </section>
9316
+ }
9286
9317
  }
9287
9318
  </div>
9288
9319
  `, isInline: true, styles: [":host{display:block;width:100%;min-height:100%;box-sizing:border-box}.praxis-resource-page{width:100%;display:flex;flex-direction:column;gap:20px;padding:var(--praxis-page-padding, 24px);box-sizing:border-box;max-width:1540px;margin:0 auto}.praxis-resource-page__header{display:flex;justify-content:space-between;align-items:flex-start;gap:16px;flex-wrap:wrap}.praxis-resource-page__header-main{min-width:0;display:flex;flex-direction:column}.praxis-resource-page__domain-badge{display:inline-flex;align-items:center;gap:6px;padding:4px 10px;border-radius:9999px;font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;margin-bottom:8px;align-self:flex-start;background:var(--praxis-domain-badge-bg, rgba(255, 255, 255, .08));color:var(--praxis-domain-badge-color, inherit);border:1px solid var(--praxis-domain-badge-border, rgba(255, 255, 255, .1))}.praxis-resource-page__domain-badge span.material-symbols-outlined{font-size:14px}.praxis-resource-page__title{margin:0 0 6px;font-size:var(--praxis-page-title-size, 2rem);font-weight:800;letter-spacing:-.02em;color:var(--praxis-page-title-color, var(--foreground, inherit));line-height:1.2}.praxis-resource-page__subtitle{margin:0;font-size:var(--praxis-page-subtitle-size, .9rem);color:var(--muted-foreground, #94a3b8);max-width:800px;line-height:1.5}.praxis-resource-page__crud-surface{width:100%;border-radius:var(--praxis-surface-radius, 16px);overflow:hidden}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: PraxisCrudComponent, selector: "praxis-crud", inputs: ["metadata", "crudId", "resource", "componentInstanceId", "tableConfigPersistenceStrategy", "context", "enableCustomization", "showResourceIdentityKey", "authoringCapability", "filterCriteria", "filterTeleport", "disableAutoDrawer"], outputs: ["configureRequested", "afterOpen", "afterClose", "afterSave", "afterDelete", "reactiveDeterminationExecuted", "reactiveDeterminationPendingChange", "reactiveDeterminationStabilityChange", "error", "rowClick", "rowAction", "selectionChange", "resourceEvent", "tableRuntimeConfigChange", "drawerOpen", "drawerClose", "crudAuthoringDocumentApplied", "crudAuthoringDocumentSaved", "kpiCardClick"] }, { kind: "component", type: PraxisScopeBarComponent, selector: "praxis-scope-bar", inputs: ["items", "activeId", "showOmnibox", "omniboxPlaceholder", "omniboxValue", "omniboxDebounceMs", "leadIcon", "leadLabel", "showClearButton", "clearLabel", "target", "targetWidgetId", "scopeBarId", "searchField"], outputs: ["activeIdChange", "omniboxValueChange", "scopeChange", "searchChange", "filterCriteriaChange", "clear"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
@@ -9336,6 +9367,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
9336
9367
 
9337
9368
  <!-- Praxis CRUD Runtime com isolamento reativo multi-tenant -->
9338
9369
  @if (effectiveResource() && identityVisible()) {
9370
+ @for (renderKey of [identityRenderKey()]; track renderKey) {
9339
9371
  <section class="praxis-resource-page__crud-surface crud-surface glass-panel">
9340
9372
  <praxis-crud
9341
9373
  [resource]="effectiveResource()!"
@@ -9357,6 +9389,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
9357
9389
  (error)="error.emit($event)"
9358
9390
  ></praxis-crud>
9359
9391
  </section>
9392
+ }
9360
9393
  }
9361
9394
  </div>
9362
9395
  `, styles: [":host{display:block;width:100%;min-height:100%;box-sizing:border-box}.praxis-resource-page{width:100%;display:flex;flex-direction:column;gap:20px;padding:var(--praxis-page-padding, 24px);box-sizing:border-box;max-width:1540px;margin:0 auto}.praxis-resource-page__header{display:flex;justify-content:space-between;align-items:flex-start;gap:16px;flex-wrap:wrap}.praxis-resource-page__header-main{min-width:0;display:flex;flex-direction:column}.praxis-resource-page__domain-badge{display:inline-flex;align-items:center;gap:6px;padding:4px 10px;border-radius:9999px;font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;margin-bottom:8px;align-self:flex-start;background:var(--praxis-domain-badge-bg, rgba(255, 255, 255, .08));color:var(--praxis-domain-badge-color, inherit);border:1px solid var(--praxis-domain-badge-border, rgba(255, 255, 255, .1))}.praxis-resource-page__domain-badge span.material-symbols-outlined{font-size:14px}.praxis-resource-page__title{margin:0 0 6px;font-size:var(--praxis-page-title-size, 2rem);font-weight:800;letter-spacing:-.02em;color:var(--praxis-page-title-color, var(--foreground, inherit));line-height:1.2}.praxis-resource-page__subtitle{margin:0;font-size:var(--praxis-page-subtitle-size, .9rem);color:var(--muted-foreground, #94a3b8);max-width:800px;line-height:1.5}.praxis-resource-page__crud-surface{width:100%;border-radius:var(--praxis-surface-radius, 16px);overflow:hidden}\n"] }]
package/package.json CHANGED
@@ -1,20 +1,20 @@
1
1
  {
2
2
  "name": "@praxisui/crud",
3
- "version": "10.0.0-rc.8",
3
+ "version": "10.0.0-rc.9",
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": "^10.0.0-rc.8",
9
- "@praxisui/table": "^10.0.0-rc.8",
10
- "@praxisui/core": "^10.0.0-rc.8",
11
- "@praxisui/dynamic-fields": "^10.0.0-rc.8",
12
- "@praxisui/settings-panel": "^10.0.0-rc.8",
8
+ "@praxisui/dynamic-form": "^10.0.0-rc.9",
9
+ "@praxisui/table": "^10.0.0-rc.9",
10
+ "@praxisui/core": "^10.0.0-rc.9",
11
+ "@praxisui/dynamic-fields": "^10.0.0-rc.9",
12
+ "@praxisui/settings-panel": "^10.0.0-rc.9",
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": "^10.0.0-rc.8",
17
+ "@praxisui/ai": "^10.0.0-rc.9",
18
18
  "rxjs": "~7.8.0"
19
19
  },
20
20
  "dependencies": {
@@ -1039,7 +1039,12 @@ interface PraxisResourceScopeBarConfig {
1039
1039
  */
1040
1040
  declare class PraxisResourcePage implements OnInit, OnDestroy {
1041
1041
  private readonly route;
1042
+ private readonly runtimeContext;
1042
1043
  private routeSub?;
1044
+ private contextSub?;
1045
+ private observedContext;
1046
+ private confirmedContextIdentity?;
1047
+ private contextInvalidated;
1043
1048
  resource?: string;
1044
1049
  resourcePath?: string;
1045
1050
  crudId?: string;
@@ -1099,7 +1104,8 @@ declare class PraxisResourcePage implements OnInit, OnDestroy {
1099
1104
  readonly resolvedCrudId: _angular_core.Signal<string>;
1100
1105
  readonly effectiveMetadata: _angular_core.Signal<CrudMetadata>;
1101
1106
  readonly defaultMetadata: _angular_core.Signal<CrudMetadata>;
1102
- private readonly onIdentitySwitch;
1107
+ private restoreDefaultScope;
1108
+ private onRuntimeContext;
1103
1109
  ngOnInit(): void;
1104
1110
  ngOnDestroy(): void;
1105
1111
  onScopeChange(item: PraxisScopeBarItem): void;