@praxisui/table 10.0.0-rc.3 → 10.0.0-rc.31

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
@@ -98,6 +98,97 @@ The host demonstrates this at `/dynamic-page-lab/archetypes/bulk-partial`, using
98
98
  `praxis-dynamic-page.bulk-partial.ui-composition-plan.artifact.json`. The real
99
99
  backend proof creates and removes exclusive payroll-event fixtures.
100
100
 
101
+ ### Analytical drawer discovery
102
+
103
+ The analytical drawer preserves explicit `drawerConfig` and DI-registered layouts. For a remote resource, it queries `/schemas/catalog` for the exact governed `POST {resourcePath}/filter` operation and follows that endpoint's published `schemaLinks.response`. Core `ResourceDiscoveryService` resolves root-relative discovery URLs against the API origin; `API_URL.default` can retain its `/api` prefix for resource requests. No match, multiple matches, a missing response href, or a schema without an explicit drawer layout leaves the remote layout absent. Discovery is cancelled and discarded on resource/context change or destroy; remote layouts are not retained across authorization-context changes.
104
+
105
+ The drawer's existing explicit schema extensions (`x-ui.surfaces.drawer`, `x-ui.drawer`, or `behavior.drawer`) remain layout sources for hosts that publish them. They are not a Metadata surface-catalog contract: `/schemas/surfaces` publishes operation descriptors, never inferred drawer layouts, and a resource path must not be converted into a semantic resource key. The beta `PraxisAnalyticalDrawerComponent.surfaceName` input has been removed: it selected a layout from an unsupported surface-catalog response shape and had no internal consumers. External templates binding that input must remove the binding and supply their explicit layout through `drawerConfig` or the DI registry.
106
+
107
+ ### Governed synchronous update candidate
108
+
109
+ For a remote resource whose collection capabilities explicitly publish an allowed
110
+ `operations['bulk-update']` descriptor with `UNIFORM_UPDATE`, `SYNC`, `EXPLICIT`
111
+ and `PER_ITEM`, the table offers **Atualizar seleção**. It is separate from the
112
+ collection workflow actions above and from the configured one-step `bulkAction`.
113
+ The table takes a detached snapshot of selected wire IDs and their canonical
114
+ `self` links. Missing IDs/links, an unsupported descriptor or more than the
115
+ published `maxTargets` disable the action. Each item is read from its exact
116
+ `self` link for a strong quoted resource ETag before evaluation; a failed or
117
+ changed identity prevents evaluation.
118
+
119
+ The Quickstart candidate's existing uniform update operation is moving to an
120
+ `ASYNC`-only profile, with host adoption still under validation. This BASIC table
121
+ surface accepts `SYNC / EXPLICIT / PER_ITEM` and omits that `ASYNC`-only operation
122
+ without a synchronous fallback. An ASYNC table workflow requires its own consumer
123
+ acceptance; this note does not certify a Render deployment or an ASYNC UI journey.
124
+
125
+ The transient Dynamic Form uses the descriptor's `editableFields.requestSchema`
126
+ as an editor and emits a non-destructive payload preview. Its layout includes
127
+ only the published writable fields; other controls from the full unit PUT schema
128
+ are disabled at form readiness so unrelated required DTO properties do not turn
129
+ the changed-field editor invalid. Its native submit
130
+ action is hidden and implicit Enter submission is prevented. Only valid, dirty,
131
+ published writable fields become `SET`; an explicitly changed `null` becomes
132
+ `CLEAR` only for a published clearable field. Empty defaults do not clear data.
133
+ The table evaluates the frozen selection, shows the server's READY/BLOCKED
134
+ proposal and authorized paged per-item preview. It also shows the exact
135
+ submitted SET/CLEAR fields and values as **requested change**, distinct from
136
+ the server's preview. The current host's `redactedIntent` publishes selection
137
+ summary only; it does not provide field values or old values. The table enables a separate human
138
+ confirmation only after that preview loads. A confirmation retry retains the
139
+ same session, proposal and Idempotency-Key after an uncertain response. HTTP 202
140
+ and nonterminal states remain pending until an explicit status read; terminal
141
+ per-item results are paged. Server authorization, expiry and conflict responses
142
+ remain authoritative.
143
+
144
+ An uncertain confirmation or pending execution keeps the same private table
145
+ session and key while this table instance exists. Closing is disabled until a
146
+ terminal state. A change of principal, tenant, organization, authorities, resource or resolved
147
+ API context hides the panel immediately; returning to the same allowed context
148
+ reattaches only after fresh authorized reads, without evaluating or confirming
149
+ automatically. Destroying the host or reloading the page does not preserve this
150
+ in-memory session. The backend remains the authority for recovery outside it.
151
+
152
+
153
+ The synchronous uniform bulk workflow hides all retained projections, including the
154
+ selection count, intent, totals, item identifiers and cursors, when a protected read
155
+ or confirmation returns 401, 403, 404 or 410. The session, frozen intent and original
156
+ confirmation key remain in table custody; an uncertain 503 never starts a new intent.
157
+ Late responses from a previous authorization cannot restore hidden data or overwrite
158
+ a newer read's loading/error state. Reopening any retained proposal requires fresh
159
+ proposal and preview reads; execution recovery requires a fresh status and, when
160
+ terminal, its result page. An already dispatched session request settles before
161
+ recovery reads and is never cancelled or automatically repeated.
162
+
163
+ Custody includes the canonical active organization as well as actor, tenant and
164
+ operational context. Returning to that context remounts the workflow behind fresh
165
+ authorization. A terminal mixed result presents all nine backend totals independently
166
+ of how many result rows have been loaded. This in-memory custody does not promise
167
+ continuity after a page reload or destruction of the owning table. The backend remains
168
+ the authority for every mutation and protected read.
169
+
170
+
171
+ BASIC selected-item, proposal, preview, execution and result GETs use the existing
172
+ `behavior.loading.requestTimeoutMs` policy, captured for each read (default 30000 ms).
173
+ Explicit 0 disables that deadline only when the host has another governed policy;
174
+ view teardown still cancels the transport. This is the Table loading policy, not a
175
+ new bulk timeout or the server unit deadline. Expiry releases local busy state and
176
+ permits an explicit protected retry with the same proposal/execution and cursor;
177
+ it neither declares a terminal execution nor proves that a mutation failed.
178
+
179
+ The table-owned pending work contains only already dispatched evaluation/confirmation
180
+ POSTs. Destroying a workflow cancels GET subscriptions and ignores stale callbacks;
181
+ remount reads the same retained intent under fresh authorization, without waiting on
182
+ an abandoned GET or repeating a POST. An uncertain pending POST remains in custody
183
+ and is awaited, with its original key. Context denial still hides all protected
184
+ projections, and recovery requires the complete fresh authorized read sequence.
185
+
186
+ This first visual cut does not offer DOMAIN_COMMAND, PER_ITEM_UPDATE, QUERY,
187
+ ASYNC, ATOMIC or durable jobs. The existing `bulk-partial` demo above uses its
188
+ older collection action contract and does not certify this new candidate flow.
189
+ The candidate is a library runtime change, not a live Quickstart acceptance or
190
+ published UI release. Public examples and AI guidance need review before adoption.
191
+
101
192
  ## LLM Recommendation Guide
102
193
 
103
194
  - Recommend when: Angular apps need metadata-driven data grids with TableConfig, filtering, sorting, pagination, row actions, visual rules, detail rows, export, and AI-assisted authoring.
@@ -351,9 +442,11 @@ For row-specific corporate indicators, declare `*Expr` properties inside the vis
351
442
 
352
443
  Table-safe micro visualization kinds are `line`, `area`, `column`, `comparison`, `stackedBar`, `radial`, `harveyBall`, `bullet`, `delta`, and `processFlow`. For enterprise authoring, `delta`, `bullet`, `radial`, `harveyBall`, and `stackedBar` are recommended for rapid scanning in compact cells. `line`, `area`, and `column` are conditional on horizontal space; `comparison` is conditional because it increases row height; and `processFlow` is conditional because its steps must remain recognizable. Use an expanded row or related surface when those trade-offs do not directly support the decision. This is guidance rather than a runtime blocker, so existing governed documents remain faithfully rendered. If a row does not provide the minimum series/step data for a visual kind, the renderer keeps an accessible compact fallback with `role="img"` and the configured fallback or aria text.
353
444
 
445
+ For `microVisualization` kinds that resolve numeric values (`delta`, `bullet`, `radial`, `harveyBall`, `line`, `area`, `column`, and `comparison`), `ColumnsConfigEditorComponent` provides visual authoring for `visualization.thresholds` (`min`, `max`, `tone`, reordering, removal, and a one-click traffic-light preset `0–39 danger`, `40–69 warning`, `70–100 success`) in both primary column renderers and `conditionalRenderers[]` overrides. In `PraxisPresentationVisualizationConfig`, an explicit fixed `visualization.tone` overrides `thresholds` unless `toneExpr` resolves at runtime; choosing **Automatic** (`tone` omitted) or adding/applying threshold bands clears the default fixed tone so `thresholds` take effect immediately, and the editor warns when both a fixed `tone` and `thresholds` are present. Thresholds use the Core condition vocabulary: discrete `equals` matches take priority, then numeric ranges (`min <= value <= max`, with open-ended `min` or `max` allowed) and progressive `value` cutoffs are evaluated in order. Existing `value` and `equals` conditions (including `0`, `false`, and strings) remain valid in primary and conditional renderers and survive tone edits, Apply and reopen; range controls do not convert them. Thresholds without a supported condition or with `min > max` fail renderer validation before Apply. The editor also includes a deterministic numeric sample strip (default `15, 40, 69, 70, 98`) so authors can verify threshold color transitions immediately without depending on generic string sample rows.
446
+
354
447
  A `conditionalRenderers[]` rule can materialize the same `microVisualization` contract when its Json Logic condition is true. Keep `surface: 'table-cell'`, the data property required by the selected kind, and `fallbackText`; first-match precedence still applies. Conditional visuals communicate a row state already defined by the canonical condition—they do not encode a new business rule in the table.
355
448
 
356
- The guided conditional-renderer editor covers icon, image, badge, link, button, chip, progress, avatar, rating, toggle, menu, HTML, compose layout and text items, and micro visualization. Link overrides support fixed or row-derived text and URLs; `_blank` links are rendered with a safe default `rel="noopener noreferrer"` when none is authored. Button and toggle overrides expose the canonical action id, JSON Logic disabled condition, and accessible label. Menu overrides bind the canonical row action collection; HTML overrides expose the existing template, sanitization, and fallback contract. Compose overrides intentionally guide only layout and text items; their other supported item documents remain preserved and continue through governed advanced JSON until dedicated controls exist.
449
+ The guided conditional-renderer editor covers icon, image, badge, link, button, chip, progress, avatar, rating, toggle, menu, HTML, compose layout and text items, and micro visualization (including `thresholds` color bands). Link overrides support fixed or row-derived text and URLs; `_blank` links are rendered with a safe default `rel="noopener noreferrer"` when none is authored. Button and toggle overrides expose the canonical action id, JSON Logic disabled condition, and accessible label. Menu overrides bind the canonical row action collection; HTML overrides expose the existing template, sanitization, and fallback contract. Compose overrides intentionally guide only layout and text items; their other supported item documents remain preserved and continue through governed advanced JSON until dedicated controls exist.
357
450
 
358
451
  ### Enterprise rich-cell evidence lab
359
452
 
@@ -675,7 +768,7 @@ O editor de conteúdo da tabela recebe a configuração efetiva em contexto tran
675
768
 
676
769
  O editor do filtro permite selecionar o recurso e carregar seus campos pelo schema de filtro canônico. `resourcePath` e preferências visuais são persistidos; os metadados carregados permanecem derivados da API.
677
770
 
678
- Ao receber novos `filterCriteria` ou `queryContext` de outro widget, a consulta remota reinicia na página zero. Para um recurso paginado, selecione estratégia de paginação `server` no editor; a estratégia cliente continua sendo uma opção explícita para dados locais.
771
+ Ao receber novos `filterCriteria` ou `queryContext` de outro widget, a consulta remota reinicia na página zero. Com a estratégia omitida, paginação e ordenação são inferidas da origem: `server` para remoto e `client` para local. O editor oferece **Automática (pela fonte de dados)**, que remove a propriedade; escolhas explícitas são preservadas. Consulte [a precedência oficial](docs/local-data-mode-precedence.md) para as regras de round-trip e validação do total remoto.
679
772
 
680
773
  ## Filter heading and advanced-action presentation
681
774
 
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": "1.0.0",
3
- "generatedAt": "2026-10-07T02:07:06.204Z",
3
+ "generatedAt": "2026-10-16T17:45:27.774Z",
4
4
  "packageName": "@praxisui/table",
5
- "packageVersion": "10.0.0-rc.3",
5
+ "packageVersion": "10.0.0-rc.31",
6
6
  "sourceRegistry": "praxis-component-registry-ingestion",
7
7
  "sourceRegistryVersion": "1.0.0",
8
8
  "componentCount": 3,
@@ -7621,6 +7621,20 @@
7621
7621
  "type": "LoadingState",
7622
7622
  "required": false,
7623
7623
  "description": "Emitido quando a tabela muda de fase de carregamento no runtime"
7624
+ },
7625
+ {
7626
+ "name": "queryContextClear",
7627
+ "type": "PraxisTableQueryContextClearEvent",
7628
+ "required": false,
7629
+ "description": "Emitido quando um filtro cruzado ou queryContext ativo é removido na toolbar da tabela",
7630
+ "label": "Filtro cruzado limpo"
7631
+ },
7632
+ {
7633
+ "name": "queryContextChange",
7634
+ "type": "PraxisDataQueryContext",
7635
+ "required": false,
7636
+ "description": "Emitido quando o queryContext interno da tabela é modificado por interações locais ou remoção de filtros",
7637
+ "label": "Query Context alterado"
7624
7638
  }
7625
7639
  ],
7626
7640
  "configSchemaId": "TableConfig",
@@ -8006,6 +8020,40 @@
8006
8020
  "public": true,
8007
8021
  "group": "status"
8008
8022
  }
8023
+ },
8024
+ {
8025
+ "id": "queryContextClear",
8026
+ "label": "Query Context Clear",
8027
+ "direction": "output",
8028
+ "semanticKind": "event",
8029
+ "schema": {
8030
+ "id": "PraxisTableQueryContextClearEvent",
8031
+ "kind": "structural-shape",
8032
+ "ref": "{ tableId?: string; clearedFields: string[]; remainingFilterCount: number }"
8033
+ },
8034
+ "cardinality": "stream",
8035
+ "description": "Evento emitido quando filtros externos ou cruzados vinculados ao queryContext sao removidos da toolbar.",
8036
+ "exposure": {
8037
+ "public": true,
8038
+ "group": "data"
8039
+ }
8040
+ },
8041
+ {
8042
+ "id": "queryContextChange",
8043
+ "label": "Query Context Change",
8044
+ "direction": "output",
8045
+ "semanticKind": "query-context",
8046
+ "schema": {
8047
+ "id": "PraxisDataQueryContext",
8048
+ "kind": "ts-type",
8049
+ "ref": "PraxisDataQueryContext"
8050
+ },
8051
+ "cardinality": "stream",
8052
+ "description": "Contexto de consulta atualizado apos reconciliacao ou limpeza de filtros editaveis na regiao unica de filtros da tabela.",
8053
+ "exposure": {
8054
+ "public": true,
8055
+ "group": "data"
8056
+ }
8009
8057
  }
8010
8058
  ],
8011
8059
  "runtimeProfiles": [
@@ -31301,9 +31349,9 @@
31301
31349
  {
31302
31350
  "chunkIndex": 0,
31303
31351
  "chunkKind": "summary",
31304
- "content": "Component ID: praxis-table\nSelector: praxis-table\nFriendly Name: Praxis Table\nDescription: Tabela interativa com paginação, ordenação e ações em massa.\nCategory: table\nLib/Package: @praxisui/table\nTags: widget, table, configurable, hasWizard, stable\nInputs:\n - config (TableConfig)\n - resourcePath (string)\n - data (any[])\n - configPersistenceStrategy ('local-first' | 'input-first' | 'volatile')\n - queryContext (PraxisDataQueryContext | null)\n - aiContext (TableAiContext | null)\n - aiAssistantVoiceInputMode ('disabled' | 'browser-speech')\n - aiAssistantVoiceLanguage (string)\n - filterCriteria (Record<string, any>)\n - autoDelete (boolean)\n - enableCustomization (boolean)\n - authoringCapability (string)\n - dense (boolean)\n - horizontalScroll ('auto' | 'wrap' | 'none')\n - tableId (string)\n - componentInstanceId (string)\n - title (string)\n - subtitle (string)\n - icon (string)\n - notifyIfOutdated ('inline' | 'snackbar' | 'both' | 'none')\n - snoozeMs (number)\n - autoOpenSettingsOnOutdated (boolean)\n - crudContext (any)\nOutputs:\n - rowClick ({ row: any; index: number })\n - widgetEvent (WidgetEventEnvelope)\n - resourceEvent (PraxisResourceEvent)\n - rowDoubleClick ({ action: string; row: any })\n - rowExpansionChange (RowExpansionChangeEvent)\n - selectionChange ({ trigger: string; row?: any; selectedRows: any[]; selectedCount: number; tableId?: string })\n - rowAction ({ action: string; row: any })\n - toolbarAction ({ action: string })\n - bulkAction ({ action: string; rows: any[] })\n - columnReorder ({ action: string; trigger: \"drag\" | \"keyboard\" | \"undo\"; operationId: number; tableId: string; sourceField: string; targetField: string; previousIndex: number; currentIndex: number; sourceZone: \"start\" | \"center\" | \"end\"; targetZone: \"start\" | \"center\" | \"end\"; previousOrder: string[]; currentOrder: string[]; persisted: boolean })\n - columnReorderAttempt ({ action: \"columnReorderAttempt\"; trigger: \"drag\" | \"keyboard\"; operationId: number; tableId: string; sourceField: string; targetField: string; previousIndex: number; currentIndex: number; sourceZone: \"start\" | \"center\" | \"end\"; targetZone: \"start\" | \"center\" | \"end\"; result: \"blocked\"; reasonCode: \"drop-zone-policy-blocked\"; configuredColumnDropZones: string[] })\n - beforeDelete (any)\n - afterDelete (any)\n - deleteError ({ row: any; error: unknown })\n - beforeBulkDelete (any[])\n - afterBulkDelete (any[])\n - bulkDeleteError ({ rows: any[]; error: unknown })\n - schemaStatusChange ({ outdated: boolean; serverHash?: string; lastVerifiedAt?: string; trigger?: string; tableId?: string })\n - metadataChange ({ trigger: string; meta: any; tableId?: string })\n - configChange (TableConfig)\n - loadingStateChange (LoadingState)\nInsertion Presets:\n - page-table: Tabela da página - Configuração autorada no Page Builder, com prioridade sobre preferências anteriores da tabela.\n",
31352
+ "content": "Component ID: praxis-table\nSelector: praxis-table\nFriendly Name: Praxis Table\nDescription: Tabela interativa com paginação, ordenação e ações em massa.\nCategory: table\nLib/Package: @praxisui/table\nTags: widget, table, configurable, hasWizard, stable\nInputs:\n - config (TableConfig)\n - resourcePath (string)\n - data (any[])\n - configPersistenceStrategy ('local-first' | 'input-first' | 'volatile')\n - queryContext (PraxisDataQueryContext | null)\n - aiContext (TableAiContext | null)\n - aiAssistantVoiceInputMode ('disabled' | 'browser-speech')\n - aiAssistantVoiceLanguage (string)\n - filterCriteria (Record<string, any>)\n - autoDelete (boolean)\n - enableCustomization (boolean)\n - authoringCapability (string)\n - dense (boolean)\n - horizontalScroll ('auto' | 'wrap' | 'none')\n - tableId (string)\n - componentInstanceId (string)\n - title (string)\n - subtitle (string)\n - icon (string)\n - notifyIfOutdated ('inline' | 'snackbar' | 'both' | 'none')\n - snoozeMs (number)\n - autoOpenSettingsOnOutdated (boolean)\n - crudContext (any)\nOutputs:\n - rowClick ({ row: any; index: number })\n - widgetEvent (WidgetEventEnvelope)\n - resourceEvent (PraxisResourceEvent)\n - rowDoubleClick ({ action: string; row: any })\n - rowExpansionChange (RowExpansionChangeEvent)\n - selectionChange ({ trigger: string; row?: any; selectedRows: any[]; selectedCount: number; tableId?: string })\n - rowAction ({ action: string; row: any })\n - toolbarAction ({ action: string })\n - bulkAction ({ action: string; rows: any[] })\n - columnReorder ({ action: string; trigger: \"drag\" | \"keyboard\" | \"undo\"; operationId: number; tableId: string; sourceField: string; targetField: string; previousIndex: number; currentIndex: number; sourceZone: \"start\" | \"center\" | \"end\"; targetZone: \"start\" | \"center\" | \"end\"; previousOrder: string[]; currentOrder: string[]; persisted: boolean })\n - columnReorderAttempt ({ action: \"columnReorderAttempt\"; trigger: \"drag\" | \"keyboard\"; operationId: number; tableId: string; sourceField: string; targetField: string; previousIndex: number; currentIndex: number; sourceZone: \"start\" | \"center\" | \"end\"; targetZone: \"start\" | \"center\" | \"end\"; result: \"blocked\"; reasonCode: \"drop-zone-policy-blocked\"; configuredColumnDropZones: string[] })\n - beforeDelete (any)\n - afterDelete (any)\n - deleteError ({ row: any; error: unknown })\n - beforeBulkDelete (any[])\n - afterBulkDelete (any[])\n - bulkDeleteError ({ rows: any[]; error: unknown })\n - schemaStatusChange ({ outdated: boolean; serverHash?: string; lastVerifiedAt?: string; trigger?: string; tableId?: string })\n - metadataChange ({ trigger: string; meta: any; tableId?: string })\n - configChange (TableConfig)\n - loadingStateChange (LoadingState)\n - queryContextClear (PraxisTableQueryContextClearEvent)\n - queryContextChange (PraxisDataQueryContext)\nInsertion Presets:\n - page-table: Tabela da página - Configuração autorada no Page Builder, com prioridade sobre preferências anteriores da tabela.\n",
31305
31353
  "sourcePointer": "projects/praxis-table/src/lib/praxis-table.metadata.ts",
31306
- "contentHash": "64d2a59402198b1eccbeaa937ba155379b3409b1d3f1d695e2f17b73b66f7447",
31354
+ "contentHash": "190f781b06cc93cae5292f354b0a1a09ae4fd0ee9007180e6f1e21ceb82a45ad",
31307
31355
  "sourceKind": "component_definition",
31308
31356
  "sourceId": "praxis-table",
31309
31357
  "corpusVersion": "1.0.0"
@@ -11,6 +11,27 @@ Regras de conflito:
11
11
  - `resourcePath` + `data` ao mesmo tempo resolve para `remote`.
12
12
  - Limpar `document.bindings.resourcePath` no documento autorado é a forma explícita de remover a conexão remota via editor.
13
13
 
14
+ ## Estratégia contextual de paginação e ordenação
15
+
16
+ `pagination.strategy` e `sorting.strategy` são opcionais. Quando ausentes, o
17
+ runtime usa `server` para uma origem remota e `client` para dados locais. Os
18
+ defaults compartilhados e o reset dos editores preservam essa ausência; não
19
+ transformam uma configuração parcial em uma escolha explícita de cliente.
20
+
21
+ Os editores apresentam **Automática (pela fonte de dados)**. Essa opção
22
+ remove a propriedade do documento. As únicas estratégias públicas continuam
23
+ sendo `client` e `server`; não existe um valor serializado `auto`. Uma escolha
24
+ explícita é preservada, respeitando as restrições e diagnostics do modo local.
25
+ Uma operação parcial de authoring que omite a estratégia mantém uma escolha
26
+ anterior; omissão em um patch não significa limpar essa escolha.
27
+
28
+ Na paginação remota, o total vem da resposta do backend. O paginator e o sort
29
+ cliente do `MatTableDataSource` não devem ser anexados nessa estratégia, pois
30
+ seus callbacks assíncronos podem substituir o total pela quantidade de registros
31
+ da página e voltar o índice para zero. A prova deve aguardar esses callbacks e
32
+ as materializações de capabilities, navegar à segunda página e confirmar que o
33
+ total e o intervalo continuam corretos. Uma captura intermediária não basta.
34
+
14
35
  ## Ciclo da primeira consulta remota
15
36
 
16
37
  Quando `resourcePath`, `config` e `queryContext` chegam no primeiro ciclo de inputs, o