@praxisui/core 9.0.41 → 9.0.43

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.
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": "1.0.0",
3
- "generatedAt": "2026-08-30T07:54:09.799Z",
3
+ "generatedAt": "2026-08-30T14:33:21.224Z",
4
4
  "packageName": "@praxisui/core",
5
- "packageVersion": "9.0.41",
5
+ "packageVersion": "9.0.43",
6
6
  "sourceRegistry": "praxis-component-registry-ingestion",
7
7
  "sourceRegistryVersion": "1.0.0",
8
8
  "componentCount": 4,
@@ -4584,9 +4584,9 @@
4584
4584
  {
4585
4585
  "chunkIndex": 159,
4586
4586
  "chunkKind": "recipe",
4587
- "content": "{\"_comment\":\"Recipe para agentes criarem uma Praxis Dynamic Page com tabela rica sobre o quickstart publicado.\",\"_comment2\":\"Use quando a meta for demonstrar renderers, formatters e configuracao de coluna em praxis-table sem mudar o backend.\",\"templateMeta\":{\"registryKey\":\"praxis-dynamic-page\",\"variantId\":\"rich-table-surface\",\"status\":\"public-example\",\"useWhen\":\"Create a metadata-driven page that turns one resource table into a rich operational surface with explicit renderers and formatters.\",\"examplePrompts\":[\"crie uma tabela rica de funcionarios com avatar, badges, salario e datas\",\"gere um exemplo de praxis-table mostrando renderers e formatters\",\"monte uma dynamic page com tabela operacional usando o quickstart publicado\"]},\"platformValue\":{\"canonicalContract\":\"praxis-metadata-starter publishes the structural funcionarios schema through /schemas/filtered.\",\"referenceApi\":\"praxis-api-quickstart serves funcionarios data and options on Render.\",\"angularRuntime\":\"@praxisui/core resolves praxis-dynamic-page and @praxisui/table renders the configured table.\",\"publicExamples\":\"praxis-ui-landing-page exposes the JSON envelope, live runtime preview and capability coverage map.\",\"agentCorpus\":\"praxisui-http-examples provides executable metadata and filter evidence.\"},\"operationalEvidence\":{\"baseUrl\":\"https://praxis-api-quickstart.onrender.com\",\"safeFirstIds\":[\"filtered-schema-response-funcionarios\",\"funcionarios-filter-basic\"],\"httpExamples\":[\"praxisui-http-examples/http/metadata/filtered_schema_response_funcionarios.http\",\"praxisui-http-examples/http/resources/funcionarios_filter_basic.http\"]},\"authoringGuidance\":[\"Keep resourcePath as the base resource path, without /filter.\",\"Let /schemas/filtered provide the canonical field metadata.\",\"Use host-authored columns only to demonstrate renderers, formatting, width, sticky columns and visual density.\",\"Prefer renderer examples that map to real backend fields; do not invent frontend-only fields for a remote example.\",\"Keep status presentation read-only unless a real backend action is configured.\",\"Treat salary as sensitive demonstration data; real hosts should gate visibility and exports through policy.\",\"Emit rowClick and selectionChange so the page can later compose this table with forms, charts or actions.\"],\"teaches\":[\"table.renderers.avatar\",\"table.renderers.badge\",\"table.renderers.chip\",\"table.conditional-renderers.status-badge\",\"table.formatters.currency\",\"table.formatters.date\",\"table.selection-state\"],\"page\":{\"layout\":{\"orientation\":\"columns\",\"columns\":1,\"gap\":\"16px\"},\"widgets\":[{\"key\":\"funcionariosRichTable\",\"shell\":{\"kind\":\"dashboard-card\",\"icon\":\"table\",\"title\":\"Funcionarios rich surface\",\"subtitle\":\"Renderers, formatters and dense business columns\"},\"definition\":{\"id\":\"praxis-table\",\"bindingOrder\":[\"config\",\"resourcePath\"],\"inputs\":{\"resourcePath\":\"human-resources/funcionarios\",\"tableId\":\"human-resources-funcionarios-rich-surface\",\"config\":{\"toolbar\":{\"visible\":true,\"title\":\"Funcionarios\",\"subtitle\":\"Rich metadata-driven employee surface\"},\"columns\":[{\"field\":\"avatarUrl\",\"header\":\"Avatar\",\"type\":\"string\",\"renderer\":{\"type\":\"avatar\",\"avatar\":{\"srcField\":\"avatarUrl\",\"altField\":\"nomeCompleto\",\"initialsField\":\"nomeCompleto\",\"shape\":\"circle\",\"size\":36}},\"width\":\"88px\",\"sticky\":\"start\"},{\"field\":\"nomeCompleto\",\"header\":\"Nome Completo\",\"type\":\"string\",\"sticky\":\"start\",\"width\":\"220px\"},{\"field\":\"email\",\"header\":\"Email\",\"type\":\"string\",\"width\":\"260px\"},{\"field\":\"telefone\",\"header\":\"Telefone\",\"type\":\"string\",\"width\":\"160px\"},{\"field\":\"salario\",\"header\":\"Salario\",\"type\":\"currency\",\"align\":\"end\",\"format\":\"BRL|symbol|2\",\"width\":\"150px\"},{\"field\":\"cargoNome\",\"header\":\"Cargo\",\"type\":\"string\",\"renderer\":{\"type\":\"chip\",\"chip\":{\"textField\":\"cargoNome\",\"variant\":\"outlined\",\"color\":\"primary\"}},\"width\":\"190px\"},{\"field\":\"departamentoNome\",\"header\":\"Departamento\",\"type\":\"string\",\"renderer\":{\"type\":\"badge\",\"badge\":{\"textField\":\"departamentoNome\",\"variant\":\"soft\",\"color\":\"accent\"}},\"width\":\"180px\"},{\"field\":\"estadoCivil\",\"header\":\"Estado Civil\",\"type\":\"string\",\"renderer\":{\"type\":\"badge\",\"badge\":{\"textField\":\"estadoCivil\",\"variant\":\"outlined\",\"color\":\"primary\"}},\"width\":\"150px\"},{\"field\":\"dataAdmissao\",\"header\":\"Data de Admissao\",\"type\":\"date\",\"format\":\"dd/MM/yyyy\",\"width\":\"170px\"},{\"field\":\"dataNascimento\",\"header\":\"Data de Nascimento\",\"type\":\"date\",\"format\":\"dd/MM/yyyy\",\"width\":\"190px\"},{\"field\":\"ativo\",\"header\":\"Status\",\"type\":\"boolean\",\"align\":\"center\",\"format\":\"active-inactive\",\"width\":\"130px\",\"conditionalRenderers\":[{\"condition\":{\"==\":[{\"var\":\"ativo\"},true]},\"renderer\":{\"type\":\"badge\",\"badge\":{\"text\":\"Ativo\",\"color\":\"success\",\"variant\":\"filled\",\"icon\":\"check_circle\"}}},{\"condition\":{\"==\":[{\"var\":\"ativo\"},false]},\"renderer\":{\"type\":\"badge\",\"badge\":{\"text\":\"Inativo\",\"color\":\"warn\",\"variant\":\"outlined\",\"icon\":\"block\"}}}]}],\"behavior\":{\"selection\":{\"enabled\":true,\"type\":\"multiple\"},\"density\":\"compact\"}}},\"outputs\":{\"rowClick\":\"emit\",\"selectionChange\":\"emit\"}}}]}}",
4587
+ "content": "{\"_comment\":\"Recipe para agentes criarem uma Praxis Dynamic Page com tabela rica sobre o quickstart publicado.\",\"_comment2\":\"Use quando a meta for demonstrar renderers, formatters e configuracao de coluna em praxis-table sem mudar o backend.\",\"templateMeta\":{\"registryKey\":\"praxis-dynamic-page\",\"variantId\":\"rich-table-surface\",\"status\":\"public-example\",\"useWhen\":\"Create a metadata-driven page that turns one resource table into a rich operational surface with explicit renderers and formatters.\",\"examplePrompts\":[\"crie uma tabela rica de funcionarios com avatar, badges, salario e datas\",\"gere um exemplo de praxis-table mostrando renderers e formatters\",\"monte uma dynamic page com tabela operacional usando o quickstart publicado\"]},\"platformValue\":{\"canonicalContract\":\"praxis-metadata-starter publishes the structural funcionarios schema through /schemas/filtered.\",\"referenceApi\":\"praxis-api-quickstart serves funcionarios data and options on Render.\",\"angularRuntime\":\"@praxisui/core resolves praxis-dynamic-page and @praxisui/table renders the configured table.\",\"publicExamples\":\"praxis-ui-landing-page exposes the JSON envelope, live runtime preview and capability coverage map.\",\"agentCorpus\":\"praxisui-http-examples provides executable metadata and filter evidence.\"},\"operationalEvidence\":{\"baseUrl\":\"https://praxis-api-quickstart.onrender.com\",\"safeFirstIds\":[\"filtered-schema-response-funcionarios\",\"funcionarios-filter-basic\"],\"httpExamples\":[\"praxisui-http-examples/http/metadata/filtered_schema_response_funcionarios.http\",\"praxisui-http-examples/http/resources/funcionarios_filter_basic.http\"]},\"authoringGuidance\":[\"Keep resourcePath as the base resource path, without /filter.\",\"Let /schemas/filtered provide the canonical field metadata.\",\"Keep columns empty and use columnProjection overrides for renderers, formatting, width, sticky columns and visual density.\",\"Prefer renderer examples that map to real backend fields; do not invent frontend-only fields for a remote example.\",\"Keep status presentation read-only unless a real backend action is configured.\",\"Treat salary as sensitive demonstration data; real hosts should gate visibility and exports through policy.\",\"Emit rowClick and selectionChange so the page can later compose this table with forms, charts or actions.\"],\"teaches\":[\"table.renderers.avatar\",\"table.renderers.compose\",\"table.renderers.badge\",\"table.renderers.chip\",\"table.conditional-renderers.status-badge\",\"table.formatters.currency\",\"table.formatters.date\",\"table.selection-state\"],\"page\":{\"layout\":{\"orientation\":\"columns\",\"columns\":1,\"gap\":\"16px\"},\"widgets\":[{\"key\":\"funcionariosRichTable\",\"shell\":{\"kind\":\"dashboard-card\",\"icon\":\"table\",\"title\":\"Funcionarios rich surface\",\"subtitle\":\"Renderers, formatters and dense business columns\"},\"definition\":{\"id\":\"praxis-table\",\"bindingOrder\":[\"config\",\"resourcePath\"],\"inputs\":{\"resourcePath\":\"human-resources/funcionarios\",\"tableId\":\"human-resources-funcionarios-rich-surface\",\"config\":{\"toolbar\":{\"visible\":true,\"title\":\"Funcionarios\",\"subtitle\":\"Rich metadata-driven employee surface\"},\"columns\":[],\"columnProjection\":{\"source\":\"schema\",\"include\":[\"nomeCompleto\",\"email\",\"telefone\",\"salario\",\"cargoNome\",\"departamentoNome\",\"estadoCivil\",\"dataAdmissao\",\"dataNascimento\",\"ativo\"],\"overrides\":{\"nomeCompleto\":{\"header\":\"Nome Completo\",\"sticky\":\"start\",\"width\":\"260px\",\"renderer\":{\"type\":\"compose\",\"compose\":{\"items\":[{\"type\":\"avatar\",\"avatar\":{\"srcField\":\"avatarUrl\",\"altField\":\"nomeCompleto\",\"initialsField\":\"nomeCompleto\",\"shape\":\"circle\",\"size\":36}},{\"type\":\"value\",\"field\":\"nomeCompleto\"}],\"layout\":{\"direction\":\"row\",\"align\":\"center\",\"gap\":10}}}},\"email\":{\"header\":\"Email\",\"width\":\"260px\"},\"telefone\":{\"header\":\"Telefone\",\"width\":\"160px\"},\"salario\":{\"header\":\"Salario\",\"type\":\"currency\",\"align\":\"end\",\"format\":\"BRL|symbol|2\",\"width\":\"150px\"},\"cargoNome\":{\"header\":\"Cargo\",\"renderer\":{\"type\":\"chip\",\"chip\":{\"textField\":\"cargoNome\",\"variant\":\"outlined\",\"color\":\"primary\"}},\"width\":\"190px\"},\"departamentoNome\":{\"header\":\"Departamento\",\"renderer\":{\"type\":\"badge\",\"badge\":{\"textField\":\"departamentoNome\",\"variant\":\"soft\",\"color\":\"accent\"}},\"width\":\"180px\"},\"estadoCivil\":{\"header\":\"Estado Civil\",\"renderer\":{\"type\":\"badge\",\"badge\":{\"textField\":\"estadoCivil\",\"variant\":\"outlined\",\"color\":\"primary\"}},\"width\":\"150px\"},\"dataAdmissao\":{\"header\":\"Data de Admissao\",\"format\":\"dd/MM/yyyy\",\"width\":\"170px\"},\"dataNascimento\":{\"header\":\"Data de Nascimento\",\"format\":\"dd/MM/yyyy\",\"width\":\"190px\"},\"ativo\":{\"header\":\"Status\",\"align\":\"center\",\"format\":\"active-inactive\",\"width\":\"130px\",\"conditionalRenderers\":[{\"id\":\"status-ativo\",\"condition\":{\"==\":[{\"var\":\"ativo\"},true]},\"renderer\":{\"type\":\"badge\",\"badge\":{\"text\":\"Ativo\",\"color\":\"success\",\"variant\":\"filled\",\"icon\":\"check_circle\"}}},{\"id\":\"status-inativo\",\"condition\":{\"==\":[{\"var\":\"ativo\"},false]},\"renderer\":{\"type\":\"badge\",\"badge\":{\"text\":\"Inativo\",\"color\":\"warn\",\"variant\":\"outlined\",\"icon\":\"block\"}}}]}}},\"behavior\":{\"selection\":{\"enabled\":true,\"type\":\"multiple\"},\"density\":\"compact\"}}},\"outputs\":{\"rowClick\":\"emit\",\"selectionChange\":\"emit\"}}}]}}",
4588
4588
  "sourcePointer": "examples/ai-recipes/praxis-dynamic-page.rich-table-surface.json",
4589
- "contentHash": "ade4a16667fe5bab42d528c14be11994ea993405d0caa62558e4a7e5b7076cd3",
4589
+ "contentHash": "aa3ab19cc0da083075076d3efa34a947b4ec087f09aba04260d6af8ee53282b6",
4590
4590
  "sourceKind": "component_definition",
4591
4591
  "sourceId": "praxis-dynamic-page",
4592
4592
  "corpusVersion": "1.0.0"
@@ -40667,12 +40667,14 @@ class DynamicWidgetPageComponent {
40667
40667
  this.savePage(canonicalPage);
40668
40668
  }
40669
40669
  }
40670
- applyLayout(layout, grouping) {
40670
+ applyLayout(layout, grouping, deviceLayoutColumnsSelected = false) {
40671
40671
  this.layout = layout;
40672
40672
  this.canvas = undefined;
40673
40673
  this.grouping = grouping || [];
40674
40674
  const orientation = layout?.orientation || 'vertical';
40675
- const columns = orientation === 'columns' ? this.resolveColumns(layout) : 1;
40675
+ const columns = orientation === 'columns'
40676
+ ? this.resolveColumns(layout, deviceLayoutColumnsSelected)
40677
+ : 1;
40676
40678
  this.pageColumnCount = columns;
40677
40679
  this.gridTemplateColumns = `repeat(${columns}, minmax(0, 1fr))`;
40678
40680
  this.pageGap = layout?.gap || '16px';
@@ -40832,14 +40834,16 @@ class DynamicWidgetPageComponent {
40832
40834
  this.layoutAnnouncement = this.blockedCanvasAnnouncement(interaction.kind);
40833
40835
  this.applyTransientPagePreview(interaction.startPage, this.buildStateRuntime(interaction.startPage.state, interaction.startPage.context));
40834
40836
  }
40835
- resolveColumns(layout) {
40837
+ resolveColumns(layout, deviceLayoutColumnsSelected = false) {
40836
40838
  const base = Math.max(1, Number(layout?.columns) || 2);
40837
- const defaults = {
40838
- sm: 1,
40839
- md: Math.min(2, base),
40840
- lg: Math.min(3, base),
40841
- xl: base,
40842
- };
40839
+ const defaults = deviceLayoutColumnsSelected
40840
+ ? { sm: base, md: base, lg: base, xl: base }
40841
+ : {
40842
+ sm: 1,
40843
+ md: Math.min(2, base),
40844
+ lg: Math.min(3, base),
40845
+ xl: base,
40846
+ };
40843
40847
  const bp = layout?.breakpoints || {};
40844
40848
  const columns = {
40845
40849
  sm: Math.max(1, Number(bp.sm ?? defaults.sm)),
@@ -40928,7 +40932,7 @@ class DynamicWidgetPageComponent {
40928
40932
  this.applyCanvasLayout(effective.canvas, effective.layout, effective.grouping);
40929
40933
  }
40930
40934
  else {
40931
- this.applyLayout(effective.layout, effective.grouping);
40935
+ this.applyLayout(effective.layout, effective.grouping, effective.deviceLayoutColumnsSelected);
40932
40936
  }
40933
40937
  this.syncActiveTabs(effective.groups);
40934
40938
  this.mergedContext = this.mergeContext(pageDefinition?.context, this.context, runtime, {
@@ -40987,6 +40991,7 @@ class DynamicWidgetPageComponent {
40987
40991
  layout,
40988
40992
  canvas,
40989
40993
  grouping,
40994
+ deviceLayoutColumnsSelected: variant?.layout?.columns !== undefined,
40990
40995
  widgets: renderedWidgets,
40991
40996
  groups,
40992
40997
  };
@@ -41905,9 +41910,10 @@ class DynamicWidgetPageComponent {
41905
41910
  if (canvasItem) {
41906
41911
  return `${canvasItem.col} / span ${canvasItem.colSpan}`;
41907
41912
  }
41908
- if (!widget.renderSpan || widget.renderSpan <= 1)
41913
+ const renderSpan = Math.min(this.pageColumnCount, Math.max(1, Math.trunc(Number(widget.renderSpan) || 1)));
41914
+ if (renderSpan <= 1)
41909
41915
  return null;
41910
- return `span ${widget.renderSpan}`;
41916
+ return `span ${renderSpan}`;
41911
41917
  }
41912
41918
  widgetGridRow(widget) {
41913
41919
  const canvasItem = this.resolveRenderedCanvasItem(widget);
@@ -41935,7 +41941,76 @@ class DynamicWidgetPageComponent {
41935
41941
  }
41936
41942
  toPersistedCanonicalPage(page) {
41937
41943
  this.assertNoLegacyConnections(page);
41938
- return writeCompositionLinksToPersistedPage(page, page.composition?.links || []);
41944
+ const canonicalPage = this.stripDerivedPresetDefaults(page);
41945
+ return writeCompositionLinksToPersistedPage(canonicalPage, canonicalPage.composition?.links || []);
41946
+ }
41947
+ /**
41948
+ * Preset defaults are a runtime projection, not authored page state.
41949
+ *
41950
+ * Keeping an identical derived grouping in the persisted document would turn
41951
+ * semantic slot ids such as `master` and `detail-table` into apparent widget
41952
+ * references. The canonical page therefore keeps the preset id and slot
41953
+ * assignments while omitting defaults that the runtime can deterministically
41954
+ * rehydrate. Explicit overrides remain persisted.
41955
+ */
41956
+ stripDerivedPresetDefaults(page) {
41957
+ const canonical = this.clonePageDefinition(page);
41958
+ const preset = this.resolveLayoutPresetDefinition(canonical);
41959
+ if (!preset)
41960
+ return canonical;
41961
+ const layoutDelta = this.jsonObjectDelta(canonical.layout, preset.defaultLayout);
41962
+ if (layoutDelta) {
41963
+ canonical.layout = layoutDelta;
41964
+ }
41965
+ else {
41966
+ delete canonical.layout;
41967
+ }
41968
+ if (this.sameJsonStructure(canonical.grouping, preset.defaultGrouping)) {
41969
+ delete canonical.grouping;
41970
+ }
41971
+ if (canonical.themePreset === preset.defaultThemePreset) {
41972
+ delete canonical.themePreset;
41973
+ }
41974
+ return canonical;
41975
+ }
41976
+ jsonObjectDelta(value, defaults) {
41977
+ if (!value)
41978
+ return undefined;
41979
+ const delta = {};
41980
+ for (const [key, entry] of Object.entries(value)) {
41981
+ const defaultEntry = defaults?.[key];
41982
+ if (this.isJsonObject(entry) && this.isJsonObject(defaultEntry)) {
41983
+ const nestedDelta = this.jsonObjectDelta(entry, defaultEntry);
41984
+ if (nestedDelta)
41985
+ delta[key] = nestedDelta;
41986
+ continue;
41987
+ }
41988
+ if (!this.sameJsonStructure(entry, defaultEntry)) {
41989
+ delta[key] = this.stableJsonValue(entry);
41990
+ }
41991
+ }
41992
+ return Object.keys(delta).length ? delta : undefined;
41993
+ }
41994
+ isJsonObject(value) {
41995
+ return !!value && typeof value === 'object' && !Array.isArray(value);
41996
+ }
41997
+ sameJsonStructure(left, right) {
41998
+ return JSON.stringify(this.stableJsonValue(left)) ===
41999
+ JSON.stringify(this.stableJsonValue(right));
42000
+ }
42001
+ stableJsonValue(value) {
42002
+ if (Array.isArray(value)) {
42003
+ return value.map((entry) => this.stableJsonValue(entry));
42004
+ }
42005
+ if (this.isJsonObject(value)) {
42006
+ return Object.keys(value)
42007
+ .sort()
42008
+ .reduce((result, key) => {
42009
+ result[key] = this.stableJsonValue(value[key]);
42010
+ return result;
42011
+ }, {});
42012
+ }
42013
+ return value;
41939
42014
  }
41940
42015
  savePage(page) {
41941
42016
  const key = this.storageKey();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@praxisui/core",
3
- "version": "9.0.41",
3
+ "version": "9.0.43",
4
4
  "description": "Core library for Praxis UI Workspace: types, tokens, services and utilities shared across @praxisui/* packages.",
5
5
  "peerDependencies": {
6
6
  "@angular/common": "^21.0.0",
@@ -16835,6 +16835,20 @@ declare class DynamicWidgetPageComponent implements OnChanges, OnDestroy {
16835
16835
  private mergeClassNames;
16836
16836
  private resolveRenderedCanvasItem;
16837
16837
  private toPersistedCanonicalPage;
16838
+ /**
16839
+ * Preset defaults are a runtime projection, not authored page state.
16840
+ *
16841
+ * Keeping an identical derived grouping in the persisted document would turn
16842
+ * semantic slot ids such as `master` and `detail-table` into apparent widget
16843
+ * references. The canonical page therefore keeps the preset id and slot
16844
+ * assignments while omitting defaults that the runtime can deterministically
16845
+ * rehydrate. Explicit overrides remain persisted.
16846
+ */
16847
+ private stripDerivedPresetDefaults;
16848
+ private jsonObjectDelta;
16849
+ private isJsonObject;
16850
+ private sameJsonStructure;
16851
+ private stableJsonValue;
16838
16852
  private savePage;
16839
16853
  private loadPersistedPage;
16840
16854
  private storageKey;