@praxisui/list 9.0.0-beta.73 → 9.0.0-beta.74

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
@@ -115,6 +115,7 @@ Remote controls are rendered when `resourcePath` is present. The runtime uses `/
115
115
  - Template expressions use `${item.field}`. Currency/date/number templates can use config i18n defaults.
116
116
  - Selection supports `none`, `single` and `multiple`, with return modes `value`, `item` or `id`.
117
117
  - Item actions can emit local events or delegate to `GlobalActionService`; collection export uses the shared `@praxisui/core` contract for `csv`, `json`, `excel`, `pdf` and `print`.
118
+ - A governed `actions[].recordOpen` resolves the identity declared by the backend against the current-principal resource and contextual surface catalogs before executing `surface.open`; it never derives the target from the displayed label or from `item.id`.
118
119
 
119
120
  ## Known Runtime Boundaries
120
121
 
@@ -126,6 +127,8 @@ Do not document those as active runtime behavior in app-specific guides until th
126
127
 
127
128
  `enableCustomization` opens the canonical list editor and semantic assistant flow. `PRAXIS_LIST_AUTHORING_MANIFEST` is the executable authoring contract for template slots, actions, empty state, selection, layout, data binding, rules, skin, export, localization, accessibility and declared-only warnings.
128
129
 
130
+ `actions[].recordOpen` is runtime materialization evidence produced by governed platform authoring. It round-trips through list config but is intentionally not a free-form field in the visual editor or the list-specific AI manifest. Authors choose business intent; Metadata and Config publish the canonical source field and target identities.
131
+
129
132
  Free JSON patches from AI flows are not the public authoring contract; local apply must be compiled from a manifest-backed `componentEditPlan`.
130
133
 
131
134
  ## Public API Snapshot
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": "1.0.0",
3
- "generatedAt": "2026-07-12T01:34:40.567Z",
3
+ "generatedAt": "2026-07-16T21:05:04.797Z",
4
4
  "packageName": "@praxisui/list",
5
- "packageVersion": "9.0.0-beta.73",
5
+ "packageVersion": "9.0.0-beta.74",
6
6
  "sourceRegistry": "praxis-component-registry-ingestion",
7
7
  "sourceRegistryVersion": "1.0.0",
8
8
  "componentCount": 1,
@@ -7,7 +7,7 @@ import * as i3 from '@angular/material/list';
7
7
  import { MatListModule } from '@angular/material/list';
8
8
  import * as i4 from '@angular/material/icon';
9
9
  import { MatIconModule } from '@angular/material/icon';
10
- import { resolveValuePresentation, LoggerService, GenericCrudService, toTitleCase, PraxisIconDirective, validateGlobalActionRefs, providePraxisI18n, PraxisI18nService, IconPickerService, GLOBAL_ACTION_CATALOG, PRAXIS_GLOBAL_ACTION_CATALOG, getGlobalActionCatalog, SURFACE_OPEN_I18N_NAMESPACE, getGlobalActionUiSchema, SurfaceOpenActionEditorComponent, PRAXIS_I18N_CONFIG, providePraxisI18nConfig, SURFACE_OPEN_I18N_CONFIG, deepMerge, ASYNC_CONFIG_STORAGE, ComponentKeyService, PraxisJsonLogicService, PraxisCollectionExportService, GlobalActionService, GLOBAL_DIALOG_SERVICE, assertPraxisCollectionExportArtifact, normalizePraxisDataQueryContext, ComponentMetadataRegistry, API_URL, LocalStorageAsyncAdapter, LocalStorageConfigService } from '@praxisui/core';
10
+ import { resolveValuePresentation, LoggerService, GenericCrudService, toTitleCase, PraxisIconDirective, validateGlobalActionRefs, providePraxisI18n, PraxisI18nService, IconPickerService, GLOBAL_ACTION_CATALOG, PRAXIS_GLOBAL_ACTION_CATALOG, getGlobalActionCatalog, SURFACE_OPEN_I18N_NAMESPACE, getGlobalActionUiSchema, SurfaceOpenActionEditorComponent, PRAXIS_I18N_CONFIG, providePraxisI18nConfig, SURFACE_OPEN_I18N_CONFIG, deepMerge, ASYNC_CONFIG_STORAGE, ComponentKeyService, PraxisJsonLogicService, PraxisCollectionExportService, ResourceRecordOpenService, GlobalActionService, GLOBAL_DIALOG_SERVICE, ResourceRecordOpenError, assertPraxisCollectionExportArtifact, normalizePraxisDataQueryContext, ComponentMetadataRegistry, API_URL, LocalStorageAsyncAdapter, LocalStorageConfigService } from '@praxisui/core';
11
11
  import * as i5 from '@angular/material/chips';
12
12
  import { MatChipsModule } from '@angular/material/chips';
13
13
  import { MatDividerModule } from '@angular/material/divider';
@@ -3644,6 +3644,7 @@ const PRAXIS_LIST_EN_US = {
3644
3644
  itemDetails: 'Item details',
3645
3645
  emptyState: 'No items available',
3646
3646
  emptyStateConnectResource: 'Connect this list to a resource to load items.',
3647
+ recordOpenUnavailable: 'Unable to open details for this item.',
3647
3648
  'pagination.itemsPerPage': 'Items per page:',
3648
3649
  'pagination.nextPage': 'Next page',
3649
3650
  'pagination.previousPage': 'Previous page',
@@ -4004,6 +4005,7 @@ const PRAXIS_LIST_PT_BR = {
4004
4005
  itemDetails: 'Detalhes do item',
4005
4006
  emptyState: 'Nenhum item disponível',
4006
4007
  emptyStateConnectResource: 'Conecte a lista a um recurso para carregar os itens.',
4008
+ recordOpenUnavailable: 'Não foi possível abrir os detalhes deste item.',
4007
4009
  'pagination.itemsPerPage': 'Itens por página:',
4008
4010
  'pagination.nextPage': 'Próxima página',
4009
4011
  'pagination.previousPage': 'Página anterior',
@@ -4258,7 +4260,7 @@ const PRAXIS_LIST_PT_BR = {
4258
4260
  Width: 'Largura',
4259
4261
  'Min width': 'Largura mínima',
4260
4262
  'Max width': 'Largura máxima',
4261
- Justify: 'Justificar',
4263
+ 'Justify': 'Justificar',
4262
4264
  'Move column left': 'Mover coluna para a esquerda',
4263
4265
  'Move column right': 'Mover coluna para a direita',
4264
4266
  'Remove column': 'Remover coluna',
@@ -12534,6 +12536,7 @@ class PraxisList {
12534
12536
  jsonLogic = inject(PraxisJsonLogicService);
12535
12537
  i18n = inject(PraxisI18nService);
12536
12538
  collectionExport = inject(PraxisCollectionExportService);
12539
+ recordOpenResolver = inject(ResourceRecordOpenService);
12537
12540
  paginatorIntl = inject(MatPaginatorIntl);
12538
12541
  snackBar = inject(MatSnackBar);
12539
12542
  aiApi = inject(AiBackendApiService);
@@ -13522,11 +13525,18 @@ class PraxisList {
13522
13525
  return;
13523
13526
  }
13524
13527
  const globalAction = action?.globalAction;
13528
+ const recordOpenIntent = action?.action === 'surface.open';
13529
+ const recordOpenReference = recordOpenIntent
13530
+ ? action?.recordOpen
13531
+ : undefined;
13525
13532
  const loadingKey = this.buildActionLoadingKey(actionId, item, index);
13526
13533
  if (action?.showLoading)
13527
13534
  this.actionLoadingState[loadingKey] = true;
13528
13535
  try {
13529
- if (globalAction?.actionId) {
13536
+ if (recordOpenIntent) {
13537
+ await this.executeRecordOpenAction(actionId, recordOpenReference, item);
13538
+ }
13539
+ else if (globalAction?.actionId) {
13530
13540
  const hasConfiguredPayload = Object.prototype.hasOwnProperty.call(globalAction, 'payload');
13531
13541
  const shouldUseDefaultPayload = !hasConfiguredPayload && !globalAction.payloadExpr;
13532
13542
  const payload = hasConfiguredPayload
@@ -13553,7 +13563,10 @@ class PraxisList {
13553
13563
  }
13554
13564
  }
13555
13565
  catch (error) {
13556
- if (isDevMode()) {
13566
+ if (recordOpenIntent) {
13567
+ this.reportRecordOpenFailure(actionId, recordOpenReference, error);
13568
+ }
13569
+ else if (isDevMode()) {
13557
13570
  this.logger.warn('[PraxisList] global action execution failed.', this.buildLogOptions({ globalActionId: globalAction?.actionId, actionId, error }, {
13558
13571
  context: { actionId },
13559
13572
  throttleKey: `praxis-list:global-action-execution-failed:${actionId}`,
@@ -13564,11 +13577,54 @@ class PraxisList {
13564
13577
  if (action?.showLoading)
13565
13578
  delete this.actionLoadingState[loadingKey];
13566
13579
  }
13567
- const emitLocal = action?.emitLocal ?? !globalAction?.actionId;
13580
+ const emitLocal = action?.emitLocal ?? (!recordOpenIntent && !globalAction?.actionId);
13568
13581
  if (emitLocal) {
13569
13582
  this.actionClick.emit({ actionId, item, index });
13570
13583
  }
13571
13584
  }
13585
+ async executeRecordOpenAction(actionId, reference, item) {
13586
+ if (!reference) {
13587
+ throw new ResourceRecordOpenError('INVALID_REFERENCE', 'List record-open action is missing its governed reference.');
13588
+ }
13589
+ if (!this.globalActions) {
13590
+ throw new Error('GlobalActionService is unavailable for surface.open.');
13591
+ }
13592
+ const resolution = await firstValueFrom(this.recordOpenResolver.resolve(reference, item));
13593
+ const result = await this.globalActions.executeRef({ actionId: 'surface.open', payload: resolution.payload }, {
13594
+ sourceId: this.listId,
13595
+ output: 'actionClick',
13596
+ payload: resolution.payload,
13597
+ runtime: { item },
13598
+ meta: {
13599
+ actionId,
13600
+ resourceKey: reference.target.resourceKey,
13601
+ resourceId: resolution.resourceId,
13602
+ surfaceId: resolution.surface.id,
13603
+ },
13604
+ });
13605
+ if (result && !result.success) {
13606
+ throw new Error(result.error || 'The surface.open executor rejected the resolved payload.');
13607
+ }
13608
+ }
13609
+ reportRecordOpenFailure(actionId, reference, error) {
13610
+ const failureCode = error instanceof ResourceRecordOpenError
13611
+ ? error.code
13612
+ : 'EXECUTION_FAILED';
13613
+ if (isDevMode()) {
13614
+ this.logger.warn('[PraxisList] contextual record-open failed.', this.buildLogOptions({
13615
+ globalActionId: 'surface.open',
13616
+ actionId,
13617
+ resourceKey: reference?.target.resourceKey,
13618
+ surfaceId: reference?.target.surfaceId,
13619
+ failureCode,
13620
+ error,
13621
+ }, {
13622
+ context: { actionId },
13623
+ throttleKey: `praxis-list:record-open-failed:${actionId}:${failureCode}`,
13624
+ }));
13625
+ }
13626
+ this.snackBar.open(this.t('recordOpenUnavailable', 'Unable to open details for this item.'), undefined, { duration: 4000 });
13627
+ }
13572
13628
  resolveActionPayload(raw, item, index) {
13573
13629
  if (raw == null)
13574
13630
  return { item, index };
package/package.json CHANGED
@@ -1,19 +1,19 @@
1
1
  {
2
2
  "name": "@praxisui/list",
3
- "version": "9.0.0-beta.73",
3
+ "version": "9.0.0-beta.74",
4
4
  "description": "List components and helpers for Praxis UI.",
5
5
  "peerDependencies": {
6
6
  "@angular/common": "^21.0.0",
7
7
  "@angular/core": "^21.0.0",
8
8
  "@angular/material": "^21.0.0",
9
- "@praxisui/dynamic-fields": "^9.0.0-beta.73",
9
+ "@praxisui/dynamic-fields": "^9.0.0-beta.74",
10
10
  "rxjs": ">=7 <9",
11
11
  "@angular/forms": "^21.0.0",
12
12
  "@angular/router": "^21.0.0",
13
- "@praxisui/ai": "^9.0.0-beta.73",
14
- "@praxisui/core": "^9.0.0-beta.73",
15
- "@praxisui/rich-content": "^9.0.0-beta.73",
16
- "@praxisui/settings-panel": "^9.0.0-beta.73"
13
+ "@praxisui/ai": "^9.0.0-beta.74",
14
+ "@praxisui/core": "^9.0.0-beta.74",
15
+ "@praxisui/rich-content": "^9.0.0-beta.74",
16
+ "@praxisui/settings-panel": "^9.0.0-beta.74"
17
17
  },
18
18
  "dependencies": {
19
19
  "tslib": "^2.3.0",
@@ -335,6 +335,7 @@ A integracao com host ocorre por `SettingsValueProvider`:
335
335
  | `actions[].id/icon/label/color/kind/buttonVariant/showIf/placement` | Active | Authoring completo de acoes por item; `showIf` usa Json Logic serializavel e `placement` controla a regiao visual da acao. |
336
336
  | `actions[].globalAction/emitLocal/showLoading/confirmation` | Active | Suporte a acoes globais estruturadas, confirmacao para acoes locais e globais, loading por acao e emissao local opcional. |
337
337
  | `actions[].globalAction.payload/payloadExpr` | Active | `payload` estruturado e `payloadExpr` avancado sao editaveis; ao definir um deles, o editor remove o outro para manter semantica canonica de execucao. |
338
+ | `actions[].action/recordOpen` | Runtime-governed | Preservado no round-trip, mas nao editavel: Metadata/Config publicam a referencia minima e o runtime reconsulta a surface contextual. |
338
339
  | `actions[].emitPayload` | Declared-only | Preservado em JSON/config round-trip; nao aparece no editor visual de Ações porque nao altera o payload nativo da lista. |
339
340
  | `ui.showSearch/searchField/searchPlaceholder` | Active | Bloco de busca. |
340
341
  | `ui.showSort/sortOptions` | Active | Editor visual com deteccao de duplicatas. |
@@ -687,6 +687,8 @@ Notas de UX/WCAG:
687
687
  | `actions[].buttonVariant` | Active | `stroked/raised/flat` |
688
688
  | `actions[].showIf` | Active | condição Json Logic avaliada no contexto `row` |
689
689
  | `actions[].emitPayload` | Declared-only | sem uso no payload de ações nativas; ações `rich-content` hospedadas podem emitir `payload?` próprio |
690
+ | `actions[].action` | Active | `surface.open` identifica a execução contextual governada quando acompanhada de `recordOpen` |
691
+ | `actions[].recordOpen` | Active | referência mínima `{ sourceIdentityField, target: { resourceKey, surfaceId } }`; o runtime reconsulta catálogo, item e `_links.surfaces` antes de materializar |
690
692
  | `actions[].globalAction` | Active | integra com `GlobalActionService.executeRef`; suporta `navigation.openRoute` para navegação interna canônica |
691
693
  | `actions[].globalAction.actionId` | Active | identificador canonico da acao global |
692
694
  | `actions[].globalAction.payload` | Active | template/JSON resolvido recursivamente, inclusive `item.id` em rotas internas |
@@ -700,9 +702,12 @@ Notas de UX/WCAG:
700
702
  Semantica de execucao:
701
703
 
702
704
  1. valida confirmacao (quando configurada);
703
- 2. executa acao global estruturada (se `globalAction` estiver presente), incluindo `navigation.openRoute` para abrir rotas internas com contexto do item;
704
- 3. remove loading;
705
- 4. emite `actionClick` conforme regra `emitLocal`.
705
+ 2. quando `action='surface.open'`, exige `recordOpen`, extrai somente o `sourceIdentityField` declarado, reconsulta o recurso e a surface contextual do principal corrente e falha fechado se qualquer identidade/availability divergir;
706
+ 3. fora desse fluxo, executa acao global estruturada (se `globalAction` estiver presente), incluindo `navigation.openRoute` para abrir rotas internas com contexto do item;
707
+ 4. remove loading;
708
+ 5. emite `actionClick` conforme regra `emitLocal`; uma intencao `recordOpen` nao cai em emissao local por default quando a resolucao falha.
709
+
710
+ `recordOpen` e materializacao governada de runtime e round-trip. O editor visual e o manifesto AI local nao authoram esse campo livremente; a fonte e a projection validada por Metadata/Config.
706
711
 
707
712
  #### UI, i18n, a11y e events
708
713
 
@@ -868,6 +873,8 @@ AI:
868
873
  | `actions[].buttonVariant` | Active | raised/flat/stroked |
869
874
  | `actions[].showIf` | Active | visibilidade |
870
875
  | `actions[].emitPayload` | Declared-only | sem efeito runtime |
876
+ | `actions[].action` | Active | `surface.open` contextual governado |
877
+ | `actions[].recordOpen` | Active | identidade mínima resolvida por catálogo e HATEOAS |
871
878
  | `actions[].globalAction` | Active | acao global estruturada |
872
879
  | `actions[].globalAction.actionId` | Active | identificador canonico da acao global |
873
880
  | `actions[].globalAction.payload` | Active | payload template/json |
@@ -6,7 +6,7 @@ import { MatSelectionListChange } from '@angular/material/list';
6
6
  import { MatPaginatorSelectConfig, PageEvent } from '@angular/material/paginator';
7
7
  import { FormGroup, FormControl } from '@angular/forms';
8
8
  import * as _praxisui_core from '@praxisui/core';
9
- import { PraxisExportFormat, PraxisExportScope, PraxisRuntimeConditionalEffectRule, JsonLogicExpression, RichContentDocument, GlobalActionRef, LocalizationConfig, AiCapability, PraxisDataQueryContext, RichBlockNode, RichBlockHostCapabilities, GlobalActionCatalogEntry, SurfaceOpenPayload, SettingsValueProvider as SettingsValueProvider$1, RichPresenterNode, RichComposeNode, ComponentDocMeta, AiCapabilityCategory, AiCapabilityCatalog, AiValueKind, ComponentAuthoringManifest } from '@praxisui/core';
9
+ import { PraxisExportFormat, PraxisExportScope, PraxisRuntimeConditionalEffectRule, JsonLogicExpression, RichContentDocument, ResourceRecordOpenRef, GlobalActionRef, LocalizationConfig, AiCapability, PraxisDataQueryContext, RichBlockNode, RichBlockHostCapabilities, GlobalActionCatalogEntry, SurfaceOpenPayload, SettingsValueProvider as SettingsValueProvider$1, RichPresenterNode, RichComposeNode, ComponentDocMeta, AiCapabilityCategory, AiCapabilityCatalog, AiValueKind, ComponentAuthoringManifest } from '@praxisui/core';
10
10
  import { BaseAiAdapter, AiResponseCompileResult, PatchResult, PraxisAssistantTurnViewState, PraxisAssistantShellLayout, PraxisAssistantShellLabels, PraxisAssistantSessionSnapshot, PraxisAssistantShellQuickReply, PraxisAssistantShellMessage } from '@praxisui/ai';
11
11
  import { SettingsValueProvider } from '@praxisui/settings-panel';
12
12
  import * as _praxisui_list from '@praxisui/list';
@@ -294,6 +294,8 @@ interface PraxisListConfig {
294
294
  buttonVariant?: 'stroked' | 'raised' | 'flat';
295
295
  showIf?: JsonLogicExpression | null;
296
296
  emitPayload?: 'item' | 'id' | 'value';
297
+ action?: 'surface.open';
298
+ recordOpen?: ResourceRecordOpenRef;
297
299
  globalAction?: GlobalActionRef;
298
300
  emitLocal?: boolean;
299
301
  showLoading?: boolean;
@@ -521,6 +523,7 @@ declare class PraxisList implements OnInit, OnChanges, AfterViewInit, OnDestroy
521
523
  private readonly jsonLogic;
522
524
  private readonly i18n;
523
525
  private readonly collectionExport;
526
+ private readonly recordOpenResolver;
524
527
  private readonly paginatorIntl;
525
528
  private readonly snackBar;
526
529
  private readonly aiApi;
@@ -652,6 +655,8 @@ declare class PraxisList implements OnInit, OnChanges, AfterViewInit, OnDestroy
652
655
  buttonVariant?: "stroked" | "raised" | "flat";
653
656
  showIf?: JsonLogicExpression | null;
654
657
  emitPayload?: "item" | "id" | "value";
658
+ action?: "surface.open";
659
+ recordOpen?: ResourceRecordOpenRef;
655
660
  globalAction?: GlobalActionRef;
656
661
  emitLocal?: boolean;
657
662
  showLoading?: boolean;
@@ -673,6 +678,8 @@ declare class PraxisList implements OnInit, OnChanges, AfterViewInit, OnDestroy
673
678
  emptyStateLabel(): string;
674
679
  isResourceConnectionMissing(): boolean;
675
680
  onActionClick(ev: MouseEvent, actionId: string, item: any, index: number): Promise<void>;
681
+ private executeRecordOpenAction;
682
+ private reportRecordOpenFailure;
676
683
  private resolveActionPayload;
677
684
  private resolveTemplate;
678
685
  private isDeferredTemplateExpressionKey;
@@ -2049,6 +2056,8 @@ declare class PraxisListDocPageComponent {
2049
2056
  buttonVariant?: "stroked" | "raised" | "flat";
2050
2057
  showIf?: _praxisui_core.JsonLogicExpression | null;
2051
2058
  emitPayload?: "item" | "id" | "value";
2059
+ action?: "surface.open";
2060
+ recordOpen?: _praxisui_core.ResourceRecordOpenRef;
2052
2061
  globalAction?: _praxisui_core.GlobalActionRef;
2053
2062
  emitLocal?: boolean;
2054
2063
  showLoading?: boolean;
@@ -2119,6 +2128,7 @@ declare const PRAXIS_LIST_EN_US: {
2119
2128
  readonly itemDetails: "Item details";
2120
2129
  readonly emptyState: "No items available";
2121
2130
  readonly emptyStateConnectResource: "Connect this list to a resource to load items.";
2131
+ readonly recordOpenUnavailable: "Unable to open details for this item.";
2122
2132
  readonly 'pagination.itemsPerPage': "Items per page:";
2123
2133
  readonly 'pagination.nextPage': "Next page";
2124
2134
  readonly 'pagination.previousPage': "Previous page";
@@ -2482,6 +2492,7 @@ declare const PRAXIS_LIST_PT_BR: {
2482
2492
  readonly itemDetails: "Detalhes do item";
2483
2493
  readonly emptyState: "Nenhum item disponível";
2484
2494
  readonly emptyStateConnectResource: "Conecte a lista a um recurso para carregar os itens.";
2495
+ readonly recordOpenUnavailable: "Não foi possível abrir os detalhes deste item.";
2485
2496
  readonly 'pagination.itemsPerPage': "Itens por página:";
2486
2497
  readonly 'pagination.nextPage': "Próxima página";
2487
2498
  readonly 'pagination.previousPage': "Página anterior";