@praxisui/dynamic-form 8.0.0-beta.0 → 8.0.0-beta.100

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
@@ -37,7 +37,7 @@ keywords:
37
37
  - "schema-driven"
38
38
  - "layout editor"
39
39
  - "settings integration"
40
- last_updated: "2026-03-22"
40
+ last_updated: "2026-04-12"
41
41
  ---
42
42
 
43
43
  # @praxisui/dynamic-form
@@ -70,10 +70,11 @@ Peer dependencies (Angular v20):
70
70
  - `@angular/core` `^20.0.0`
71
71
  - `@angular/common` `^20.0.0`
72
72
  - `@angular/cdk` `^20.0.0`
73
- - `@praxisui/core` `^0.0.1`
74
- - `@praxisui/visual-builder` `^0.0.1`
75
- - `@praxisui/settings-panel` `^0.0.1`
76
- - `@praxisui/cron-builder` `^0.0.1`
73
+ - `@praxisui/core` `^8.0.0-beta.12`
74
+ - `@praxisui/rich-content` `^8.0.0-beta.10`
75
+ - `@praxisui/visual-builder` `^8.0.0-beta.10`
76
+ - `@praxisui/settings-panel` `^8.0.0-beta.10`
77
+ - `@praxisui/cron-builder` `^8.0.0-beta.10`
77
78
 
78
79
  ## Quick Start
79
80
 
@@ -112,8 +113,16 @@ export class FormDemoComponent {
112
113
  rows: [
113
114
  {
114
115
  columns: [
115
- { id: 'col-fullName', fields: ['fullName'] },
116
- { id: 'col-email', fields: ['email'] },
116
+ {
117
+ id: 'col-fullName',
118
+ items: [{ kind: 'field', id: 'field-fullName', fieldName: 'fullName' }],
119
+ fields: ['fullName'],
120
+ },
121
+ {
122
+ id: 'col-email',
123
+ items: [{ kind: 'field', id: 'field-email', fieldName: 'email' }],
124
+ fields: ['email'],
125
+ },
117
126
  ],
118
127
  },
119
128
  ],
@@ -136,6 +145,74 @@ Tip: connect to a backend resource by setting `resourcePath`/`resourceId`. The c
136
145
  For metadata-driven surfaces published by the backend, prefer passing `schemaUrl`, `submitUrl` and `submitMethod` explicitly. In dialog/drawer hosts detached from the route injector, pass `apiUrlEntry` in addition to `apiEndpointKey` so relative runtime URLs still resolve against the remote backend instead of the local shell origin.
137
146
  When a late `config` hydration rebuilds the surface, the runtime preserves the current form values already mounted in memory only when the logical context remains the same (`resourcePath`, `resourceId`, and mode). If the form was preloaded from `resourceId`, that entity hydration remains visible after the rebuild only for the same entity context; entity switches and create-mode transitions do not restore the previous record snapshot.
138
147
 
148
+ ## Local fields, transient state, and submit payloads
149
+
150
+ `praxis-dynamic-form` supports host-owned fields that participate in the form runtime without becoming part of the backend DTO. This is the right model for confirmation controls, wizard state, client-side calculated previews, host notes, and other UI context that should be available to rules and hooks but should not be persisted by default.
151
+
152
+ The submit contract exposes two values:
153
+
154
+ | Property | Meaning | Typical consumer |
155
+ | --- | --- | --- |
156
+ | `formSubmit.formData` | filtered persistence payload after submit policy is applied | HTTP submit, persistence hooks, backend DTO integration |
157
+ | `formSubmit.rawFormData` | full form value before filtering | host hooks, UI audit context, diagnostics, calculations |
158
+
159
+ Default behavior:
160
+
161
+ - schema-backed fields are included in `formData`.
162
+ - fields with `source: 'local'` are omitted from `formData` and kept in `rawFormData`.
163
+ - fields with `transient: true` are omitted from `formData` and kept in `rawFormData`.
164
+ - `submitPolicy` has priority over `source` and `transient`.
165
+
166
+ | `submitPolicy` | Effect |
167
+ | --- | --- |
168
+ | `include` | always include the field in `formData` |
169
+ | `omit` | always omit the field from `formData` |
170
+ | `includeWhenDirty` | include the field in `formData` only when the control is dirty |
171
+
172
+ Example:
173
+
174
+ ```ts
175
+ fieldMetadata: [
176
+ { name: 'fullName', label: 'Full name', controlType: 'input' },
177
+ {
178
+ name: 'approvalComment',
179
+ label: 'Approval comment',
180
+ controlType: 'textarea',
181
+ source: 'local',
182
+ submitPolicy: 'omit',
183
+ },
184
+ {
185
+ name: 'calculatedRiskScore',
186
+ label: 'Risk score',
187
+ controlType: 'numericTextBox',
188
+ transient: true,
189
+ readOnly: true,
190
+ },
191
+ {
192
+ name: 'manualOverrideReason',
193
+ label: 'Manual override reason',
194
+ controlType: 'textarea',
195
+ source: 'local',
196
+ submitPolicy: 'includeWhenDirty',
197
+ },
198
+ ];
199
+ ```
200
+
201
+ For a submitted value like:
202
+
203
+ ```json
204
+ {
205
+ "fullName": "Ana Souza",
206
+ "approvalComment": "Checked with HR",
207
+ "calculatedRiskScore": 72,
208
+ "manualOverrideReason": "Approved exception"
209
+ }
210
+ ```
211
+
212
+ `rawFormData` keeps all four values. `formData` keeps `fullName` and, when dirty, `manualOverrideReason`; it omits `approvalComment` and `calculatedRiskScore`.
213
+
214
+ Do not use local/transient fields to hide a backend contract problem. If a field exists in the backend schema, keep it server-backed and fix the canonical schema or metadata mapping instead of creating a parallel local convention.
215
+
139
216
  Presentation/read-only note:
140
217
  - `praxis-dynamic-form` hospeda `FieldMetadata`, mas não é o resolvedor direto de `valuePresentation`.
141
218
  - quando campos entram em modo view/presentation, a semântica de apresentação vem do metadata do campo e é resolvida por `@praxisui/dynamic-fields`.
@@ -248,6 +325,19 @@ export class FormEditorLauncherComponent {
248
325
 
249
326
  Alternatively, when `enableCustomization` is true, `praxis-dynamic-form` renders a gear button that opens the editor internally.
250
327
 
328
+ ## Agentic Authoring & Manifest
329
+
330
+ `praxis-dynamic-form` supports executable authoring contracts through the `ComponentAuthoringManifest`. This allows AI agents to perform structured, safe, and deterministic edits to the form configuration.
331
+
332
+ - **Manifest:** `PRAXIS_DYNAMIC_FORM_AUTHORING_MANIFEST`
333
+ - **Editable targets:** `field`, `section`, `row`, `column`, `visualBlock`, `formAction`, `formRule`, `message`, `layoutPlacement`, `localField`, `schemaBackedField`.
334
+ - **Operations:** Covers field property updates, local field lifecycle, layout item manipulation, visual block authoring, rule management, and action configuration declared in `PRAXIS_DYNAMIC_FORM_AUTHORING_MANIFEST`.
335
+ - **Validation:** Deterministic validators cover ID uniqueness, layout integrity, rich content document validity, local/schema-backed separation, destructive confirmation, and editor round-trip preservation.
336
+ - **Examples/evals:** Fixtures cover local transient fields, schema-backed relabeling, layout moves, visual block add/move, visibility rules, unknown target rejection, destructive confirmation, and save/reopen without drift.
337
+ - **Round-trip:** Preserves editor state and ensures that AI-generated patches are compatible with the visual editor.
338
+
339
+ For more details on authoring manifests, see `docs/ai/agentic-authoring/component-authoring-contracts/README.md`.
340
+
251
341
  ## API Surface
252
342
 
253
343
  - Components: `PraxisDynamicForm`, `PraxisDynamicFormConfigEditor`, `JsonConfigEditorComponent`, `LayoutEditorComponent`
@@ -351,8 +441,16 @@ const formConfig: FormConfig = {
351
441
  rows: [
352
442
  {
353
443
  columns: [
354
- { fields: ['fullName'] },
355
- { fields: ['workEmail'] },
444
+ {
445
+ id: 'registration-full-name',
446
+ items: [{ kind: 'field', id: 'field-fullName', fieldName: 'fullName' }],
447
+ fields: ['fullName'],
448
+ },
449
+ {
450
+ id: 'registration-work-email',
451
+ items: [{ kind: 'field', id: 'field-workEmail', fieldName: 'workEmail' }],
452
+ fields: ['workEmail'],
453
+ },
356
454
  ],
357
455
  },
358
456
  ],
@@ -384,10 +482,77 @@ Notas:
384
482
 
385
483
  See public exports: `projects/praxis-dynamic-form/src/public-api.ts`.
386
484
 
485
+ ## Blocos visuais em colunas
486
+
487
+ O layout de coluna usa `sections[].rows[].columns[].items[]` como contrato
488
+ canônico ordenado. Itens `kind: 'field'` referenciam
489
+ `fieldMetadata[].name`; itens `kind: 'richContent'` hospedam um
490
+ `RichContentDocument` dentro da coluna e não entram em `fieldMetadata`,
491
+ `formData` ou payload HTTP.
492
+
493
+ `sections[].rows[].columns[].fields[]` permanece aceito como entrada de
494
+ migração e como projeção derivada dos itens `kind: 'field'`, mas não deve ser
495
+ usado para criar blocos visuais.
496
+
497
+ No editor visual, **Campos da API** lista apenas campos existentes em
498
+ `fieldMetadata[]` que ainda não aparecem em `columns[].items[]` como
499
+ `kind: 'field'`. Ao adicionar um campo, o editor reinsere o item em `items[]`
500
+ e mantém `fields[]` sincronizado como projeção; `fieldMetadata[]` não é
501
+ alterado.
502
+
503
+ **Adicionar bloco visual** oferece presets internos como texto, aviso,
504
+ separador, card informativo, `callout`, `keyValueList`, `recordSummary`,
505
+ `disclosure` e `emptyState`. Esses presets são apenas atalhos de authoring que
506
+ geram documentos `praxis.rich-content` válidos para itens `kind: 'richContent'`;
507
+ eles não criam novo `kind`, não criam `FieldMetadata` e não participam do
508
+ payload de submit.
509
+
510
+ O runtime do formulário também passa `hostCapabilities` canônicos para
511
+ `@praxisui/rich-content`, permitindo:
512
+ - `actionButton` despachar ações via a mesma trilha de `customAction`/`globalAction`
513
+ já usada pelo formulário;
514
+ - `requiresCapabilities` resolver capacidades como `form.mode.edit`,
515
+ `form.mode.view`, `form.customization.enabled`, `form.resource.connected` e
516
+ `form.actions.submit`.
517
+
518
+ Exemplo mínimo de uma coluna com campo, bloco visual e projeção `fields[]`:
519
+
520
+ ```json
521
+ {
522
+ "items": [
523
+ {
524
+ "kind": "richContent",
525
+ "id": "identity-guidance-block",
526
+ "document": {
527
+ "schemaVersion": 1,
528
+ "nodes": [
529
+ {
530
+ "type": "paragraph",
531
+ "text": "Revise os dados principais antes de salvar."
532
+ }
533
+ ]
534
+ }
535
+ },
536
+ { "kind": "field", "id": "field-nome", "fieldName": "nome" },
537
+ { "kind": "field", "id": "field-email", "fieldName": "email" }
538
+ ],
539
+ "fields": ["nome", "email"]
540
+ }
541
+ ```
542
+
543
+ Guia completo: `projects/praxis-dynamic-form/docs/layout-items-visual-blocks.md`.
544
+
545
+ ## Authoring e Persistência em Hosts
546
+
547
+ - `configChange` preserva o contrato legado e continua emitindo `FormConfig` para consumidores diretos.
548
+ - `configPatchChange` é o output recomendado para hosts genéricos, como Page Builder e páginas dinâmicas, persistirem autoria assistida: o payload segue `{ inputPatch: { config: FormConfig } }`.
549
+ - Fluxos de IA e editores devem materializar alterações no `FormConfig` canônico do componente; o host decide onde gravar `inputs.config`.
550
+
387
551
  ## Documentacao Tecnica da Lib
388
552
 
389
553
  - `projects/praxis-dynamic-form/src/lib/praxis-dynamic-form.json-api.md`
390
554
  - `projects/praxis-dynamic-form/docs/hot-metadata-updates.md`
555
+ - `projects/praxis-dynamic-form/docs/layout-items-visual-blocks.md`
391
556
 
392
557
  ## Header de seção com avatar dinâmico
393
558
 
@@ -426,6 +591,8 @@ See public exports: `projects/praxis-dynamic-form/src/public-api.ts`.
426
591
 
427
592
  ## IA — catálogo de capacidades (composição)
428
593
 
594
+ O copiloto semântico do formulário usa o shell global de `@praxisui/ai` e registra uma sessão segura no `PraxisAssistantSessionRegistryService`. O botão local apenas abre o contexto do formulário; minimizar/reabrir deve preservar a sessão no cockpit global, sem tratar o formulário como fonte primária de regra de negócio.
595
+
429
596
  O assistente usa um catálogo agregado de capabilities para gerar patches seguros:
430
597
 
431
598
  - **Macro (FormConfig)**: layout, regras, ações, hooks e mensagens (`form-ai-capabilities`).
@@ -434,27 +601,39 @@ O assistente usa um catálogo agregado de capabilities para gerar patches seguro
434
601
 
435
602
  Os paths micro são normalizados para `fieldMetadata[].<prop>` para garantir que os patches apontem para a raiz correta do formulário.
436
603
 
604
+ O fluxo agentic-authoring do formulário exige `componentEditPlan` validado pelo `PRAXIS_DYNAMIC_FORM_AUTHORING_MANIFEST` antes de aplicar patch local. Prompts de regra de negócio, política, elegibilidade, compliance, LGPD, aprovação/publicação ou decisão compartilhada devem seguir para `domain-rules`/`shared_rule_authoring`; em `view` sem customização ou readonly, o assistente opera apenas como ajuda/diagnóstico e não aplica mutação local.
605
+
437
606
  ## Regras de formulário (novo contrato)
438
607
 
439
- - Formato: cada regra tem `targetType` (`field | section | action | row | column`), `targets: string[]` (IDs canônicos do alvo), e `effect` com `condition` (`JsonLogicExpression | null`), `properties` e `propertiesWhenFalse`.
608
+ - Formato: cada regra tem `targetType` (`field | section | action | row | column | visualBlock`), `targets: string[]` (IDs canônicos do alvo), e `effect` com `condition` (`JsonLogicExpression | null`), `properties` e `propertiesWhenFalse`.
609
+ - Regra de negócio, política, elegibilidade, validação, compliance ou decisão compartilhada deve ser authorada no fluxo governado de `domain-rules`/`shared_rule_authoring`. `visualBlockGuidance` fica restrito a projeção visual opcional: quando uma decisão já governada precisar explicar impacto na UI, a materialização `form_config` pode gerar `type: "visualBlockGuidance"`, `targetType: "visualBlock"` e `metadata.origin: "llm"` com `metadata.reviewStatus: "pending"`; o editor aceita a projeção, marca como `accepted` e materializa `formRulesState` internamente para round-trip visual.
440
610
  - Compatibilidade: regras antigas (`context/targetField`) são migradas para `properties/targets` automaticamente; prefixes legados `section:/action:/row:/column:` continuam sendo normalizados quando representarem alvos tradicionais. Para header actions de seção, o ID canônico é preservado como `section:<sectionId>:header-action:<actionLogicalId>`.
441
611
  - Semântica de limpeza: valores `null` em `properties/propertiesWhenFalse` removem o override e retornam ao valor base do layout; ausência mantém o valor base.
442
612
  - Whitelist por tipo (somente propriedades a seguir são aplicadas; demais são descartadas e logadas em dev):
443
- - `field`: `visible`, `required`, `readonly`, `disabled`, `className`, `style`, `label`, `description`, `placeholder`, `hint`, `tooltip`, `prefixIcon`, `suffixIcon`, `prefixText`, `suffixText`, `defaultValue`, `options` (array `{label,value,disabled?}`), `appearance` (`fill|outline`), `color` (`primary|accent|warn`), `floatLabel` (`auto|always|never`), `hintPosition` (`start|end`), `validators` (primitivos por chave).
613
+ - `field`: `visible`, `required`, `readonly`, `disabled`, `className`, `style`, `label`, `description`, `placeholder`, `hint`, `tooltip`, `prefixIcon`, `suffixIcon`, `prefixText`, `suffixText`, `defaultValue`, `value`, `options` (array `{label,value,disabled?}`), `appearance` (`fill|outline`), `color` (`primary|accent|warn`), `floatLabel` (`auto|always|never`), `hintPosition` (`start|end`), `validators` (primitivos por chave).
444
614
  - `section`: `visible`, `title`, `description`, `icon`, `sectionHeader` (objeto rico), `headerActions` (ações contextuais do cabeçalho) e tambem subpropriedades tipadas como `sectionHeader.mode`, `sectionHeader.sourceField`, `sectionHeader.initialsSourceField`, `sectionHeader.altField`, `sectionHeader.fallbackIcon`, `sectionHeader.emptyState`, `sectionHeader.size`, `sectionHeader.initialsMaxLength`, `className`, `style`, `collapsible`, `collapsed`, `headerTooltip`, `headerAlign` (`start|center`), `appearance` (`card|plain|step`), `stepLabel`, gaps (`gapBottom`, `titleGapBottom`, `descriptionGapBottom`), cores/tipografia (`titleColor`, `descriptionColor`, `titleStyle`, `descriptionStyle`).
445
615
  - `action`: `visible`, `disabled`, `loading`, `label`, `icon`, `tooltip`, `color` (`primary|accent|warn|basic`), `variant` (`raised|stroked|flat|fab`), `size` (`small|medium|large`), `className`, `style`.
446
- - IDs canônicos de regras para actions do header de seção usam o formato `section:<sectionId>:header-action:<actionLogicalId>`, evitando colisão com botões globais e com ações repetidas em seções diferentes.
447
- - Se a seção ainda não tiver `id`, o runtime aceita o fallback `header-action:<actionLogicalId>` por compatibilidade; para contratos persistidos, prefira sempre materializar `section.id`.
448
616
  - `row`: `visible`, `gap`, `rowGap`, `className`, `style`.
449
617
  - `column`: `visible`, `span`, `offset`, `order`, `hidden`, `align` (`start|center|end|stretch`), `padding`, `className`, `style`.
450
- - Runtime (FormRulesService): filtra por whitelist, converte tipos (enum/number/boolean/string), saneia objetos (`options/validators/style`), aplica remoção de chaves com `null` e retorna mapas `fieldProps/sectionProps/actionProps/rowProps/columnProps`.
451
- - Renderização: `PraxisDynamicForm` aplica overrides em campos/seções/ações/linhas/colunas (visibilidade, gaps, padding, classes, estilos, labels, etc.); colunas respeitam `align/span/offset/order/hidden/padding` vindo das regras.
618
+ - `visualBlock`: `visible`, `hidden`, `layout`, `className`, `rootClassName`, `style`, `text`, `title`, `message`; para documentos compostos, use `textNodeId`, `titleNodeId` ou `messageNodeId` para apontar o node textual seguro.
619
+ - IDs canônicos de regras para actions do header de seção usam o formato `section:<sectionId>:header-action:<actionLogicalId>`, evitando colisão com botões globais e com ações repetidas em seções diferentes.
620
+ - Se a seção ainda não tiver `id`, o runtime aceita o fallback `header-action:<actionLogicalId>` por compatibilidade; para contratos persistidos, prefira sempre materializar `section.id`.
621
+ - Valores calculados: regras de `targetType: "field"` podem escrever `effect.properties.value` ou `effect.propertiesWhenFalse.value`. Use envelope fechado e exclusivo: exatamente `{ "expression": <JsonLogic> }` para calcular ou exatamente `{ "literal": <valor> }` para valor estruturado. Primitivos, `null` e arrays continuam aceitos como literais; objetos literais devem usar `{ "literal": ... }` para não serem tratados como Json Logic legado. Envelopes com chaves extras ou com `literal` e `expression` juntos são inválidos.
622
+ - Ordem de regras: valores calculados são resolvidos em fase anterior, com iteração limitada até estabilizar, antes da aplicação das demais propriedades. Assim uma regra pode calcular `age` e outra regra pode depender de `age` sem depender da posição no array.
623
+ - Comandos condicionais: `formCommandRules` é a superfície aditiva para side effects governados, avaliada depois que `formRules` estabilizam valores calculados. O primeiro corte suporta `effects[].kind: "global-action"` com `globalAction: GlobalActionRef`, policy operacional (`trigger`, `distinct`, `distinctBy`, `debounceMs`, `errorPolicy`, `runOnInitialEvaluation`) e default `trigger: "on-condition-enter"`. Comandos não devem ser colocados em `formRules[].effect.properties`. No editor visual, `payloadExpr` permanece escape hatch de JSON avancado: ele e preservado quando ja existe e nenhum payload estruturado e definido, mas novos campos visuais authoram `payload` literal.
624
+ - Runtime (FormRulesService): filtra por whitelist, converte tipos (enum/number/boolean/string), saneia objetos (`options/validators/style`), aplica remoção de chaves com `null`, resolve `fieldValues` calculados e retorna mapas `fieldProps/sectionProps/actionProps/rowProps/columnProps/visualBlockProps`.
625
+ - Renderização: `PraxisDynamicForm` aplica overrides em campos/seções/ações/linhas/colunas/blocos visuais (visibilidade, gaps, padding, classes, estilos, labels e texto seguro, conforme o tipo); colunas respeitam `align/span/offset/order/hidden/padding` vindo das regras.
626
+ - Regras de `visualBlock` são visual-only: elas não criam `FieldMetadata`, `FormControl`, valor em `formData` ou payload HTTP. Use campo local com `submitPolicy` quando a experiência precisar de valor persistível ou leitura em `rawFormData`.
627
+
628
+ Politica de erro dos comandos condicionais: falhas de `GlobalActionService.executeRef(...)` geram diagnostico sem propagar excecao para o ciclo do form, e a memoria `distinct` evita repeticao enquanto a condicao segue verdadeira. Sem `GlobalActionService`, actions mutaveis (`api.post`, `api.put`, `api.patch`, `api.delete`) falham fechado e nao sao emitidas via `customAction`.
452
629
 
453
630
  ### Builder integrado
454
631
 
455
- - No editor visual, use a aba “Propriedades” (integrada ao builder) para selecionar o alvo (`targetType` + autocomplete de IDs de campos/seções/ações/linhas/colunas), escolher propriedades whitelisted e definir valores para `properties` (branch true) e `propertiesWhenFalse` (branch false). Botão “Limpar override” remove a propriedade (equivalente a `null`).
632
+ - No editor visual, use a aba “Propriedades” (integrada ao builder) para selecionar o alvo (`targetType` + autocomplete de IDs de campos/seções/ações/linhas/colunas/blocos visuais), escolher propriedades whitelisted e definir valores para `properties` (branch true) e `propertiesWhenFalse` (branch false). Botão “Limpar override” remove a propriedade (equivalente a `null`).
633
+ - Para `visualBlock`, o painel de propriedades exibe um seletor de node quando a propriedade é `text`, `title` ou `message`; o editor persiste `textNodeId`, `titleNodeId` ou `messageNodeId` automaticamente para evitar sobrescrever o node errado em documentos compostos.
634
+ - No round-trip do builder, `config.type` continua sendo reservado para o tipo visual do node; semântica de regra como `visualBlockGuidance` é preservada internamente em `config.ruleType` e volta para `formRules[].type`.
456
635
  - A aba de Propriedades usa inputs tipados (enum/number/boolean/string/JSON) conforme o schema injetado; valores inválidos são ignorados.
457
- - O config editor fornece `targetSchemas` (campos/seções/ações/linhas/colunas) e `targetPropertySchemas` para o builder; `formRules` são salvas no formato canônico (sem `context/targetField`).
636
+ - O config editor fornece `targetSchemas` (campos/seções/ações/linhas/colunas/blocos visuais) e `targetPropertySchemas` para o builder; `formRules` são salvas no formato canônico (sem `context/targetField`).
458
637
 
459
638
  ## Layout padrão (sem FormConfig)
460
639
 
@@ -597,14 +776,14 @@ Cobertura ja estabilizada:
597
776
  - `JSON` com edicao real e bloqueio de payload invalido
598
777
  - `Hooks`
599
778
  - `Acoes` (`Botoes Padrao` e `Layout`)
600
- - `Acoes` customizadas profundas (`showAlert` simples e estruturado)
779
+ - `Acoes` customizadas profundas (`dialog.alert` com `globalAction` simples e estruturado)
601
780
  - `Comportamento`
602
781
  - `Mensagens`
603
782
  - `Dicas e Tooltips`
604
783
 
605
784
  Ajuste tecnico aplicado durante essa fase:
606
785
  - [praxis-dynamic-form-config-editor.ts](/mnt/d/Developer/praxis-plataform/praxis-ui-angular/projects/praxis-dynamic-form/src/lib/config-editor/praxis-dynamic-form-config-editor.ts) agora chama `this.jsonEditor?.updateJsonFromConfig(this.editedConfig)` em `onConfigChange(...)` para manter o editor JSON sincronizado com mudancas vindas de abas como `Hooks`.
607
- - [actions-editor.component.ts](/mnt/d/Developer/praxis-plataform/praxis-ui-angular/projects/praxis-dynamic-form/src/lib/actions-editor/actions-editor.component.ts) agora mescla catalogo legado + catalogo global do app para o editor de acoes customizadas.
786
+ - [actions-editor.component.ts](/mnt/d/Developer/praxis-plataform/praxis-ui-angular/projects/praxis-dynamic-form/src/lib/actions-editor/actions-editor.component.ts) agora usa o catalogo global canonico do app para o editor de acoes customizadas.
608
787
  - [actions-editor.component.ts](/mnt/d/Developer/praxis-plataform/praxis-ui-angular/projects/praxis-dynamic-form/src/lib/actions-editor/actions-editor.component.ts) agora preserva draft local dos campos globais e usa `track` estavel em `actions.custom`.
609
788
 
610
789
  Comando base usado para rerodar suites isoladas:
@@ -652,6 +831,8 @@ Apache-2.0 – see the `LICENSE` packaged with this library or the repository ro
652
831
  - Fluxo de Schema (ETag/304, schemaId, reconciliação): `projects/praxis-core/docs/schema-flow.md` (canônico) e `docs/schemas/fluxo-schema.md` (resumo operacional)
653
832
  - Guia de implementação e metadados da cascata: `docs/CASCADE-NATIVA.md`
654
833
  - Padrões de endpoints (Options vs Filter) para selects: `projects/praxis-dynamic-fields/docs/generic-crud-service.md` (canônico) e `docs/DEVS-GENERIC-CRUD-SERVICE.md` (resumo operacional)
834
+ - Recipe oficial de Entity Lookup corporativo com `RESOURCE_ENTITY`, `dependsOn`, `dependencyFilterMap`, reidratação por `by-ids` e política de seleção: `examples/ai-recipes/praxis-dynamic-form.entity-lookup-procurement.json`
835
+ - Recipe oficial de comandos condicionais governados com `formCommandRules`, `GlobalActionRef`, policy operacional, valor calculado e date range confirmado: `examples/ai-recipes/praxis-dynamic-form.command-rules.json`
655
836
 
656
837
  ## Verificação de Schema (ETag/If-None-Match)
657
838
 
@@ -666,9 +847,11 @@ Apache-2.0 – see the `LICENSE` packaged with this library or the repository ro
666
847
  - `form-schema-prefs:{formId}` → `{ notifyIfOutdated?, snoozeMs?, autoOpenSettingsOnOutdated? }`
667
848
  - Por hash (suppress): `schemaIgnore:{formId}:{hash}`, `schemaSnooze:{formId}:{hash}`, `schemaNotified:{formId}:{hash}`
668
849
  - Comportamento:
669
- - Quando existe base local (ex.: `config.sections.length > 0`), o componente faz uma verificação leve via `/schemas/filtered` com `If-None-Match`.
670
- - 304 apenas atualiza `lastVerifiedAt` e emite `schemaStatusChange(outdated=false)`.
671
- - 200 atualiza `serverHash/lastVerifiedAt`, define `schemaOutdated = enableCustomization && hadBase`, emite `schemaStatusChange`. Não aplica schema automaticamente.
850
+ - Quando existe uma config persistida para o `formId`, o componente normaliza a config local, baixa o schema estrutural canônico e reconcilia `fieldMetadata` antes de montar o formulário.
851
+ - Essa reconciliação preserva customizações locais intencionais, incluindo campos com `source: "local"`, `transient: true` e `submitPolicy`, mas atualiza semântica server-backed publicada pelo schema.
852
+ - Campos de select publicados com `x-ui.endpoint` em `/options/filter` preservam `endpoint` como operação concreta e `resourcePath` como recurso dono. Isso evita usar `/filter` ou consultar schema do endpoint de options por engano.
853
+ - 304 em verificações posteriores apenas atualiza `lastVerifiedAt` e emite `schemaStatusChange(outdated=false)`.
854
+ - 200 em verificações posteriores atualiza `serverHash/lastVerifiedAt`, define `schemaOutdated = enableCustomization && hadBase` e emite `schemaStatusChange`.
672
855
  - Primeira vez (sem base): baixa o corpo do schema para gerar o layout; persiste `form-schema-meta:{formId}`.
673
856
  - Notificações respeitam preferências e são one‑shot por hash; o banner/snackbar oferecem ações para Reconciliar, Lembrar depois (snooze) e Ignorar.
674
857
 
@@ -677,3 +860,44 @@ Apache-2.0 – see the `LICENSE` packaged with this library or the repository ro
677
860
  - Quando `API_URL.default.baseUrl` for relativo (ex.: `'/api'`), a lib resolve a origem a partir de `location.origin` no browser. Isso cobre o cenário comum com proxy de dev (`/api`, `/schemas`).
678
861
  - Em SSR (sem `location.origin`), configure `baseUrl` absoluto (ex.: `https://api.acme.com/api`) para evitar erros do tipo “Invalid URL” ao construir chamadas de `/schemas/filtered`.
679
862
  - O `GenericCrudService.getSchemasFilteredBaseUrl()` retorna sempre uma URL absoluta; o `SchemaMetadataClient` também aceita `baseUrl` relativo quando há origin disponível.
863
+
864
+ ## Regras de Domínio Compartilhadas
865
+
866
+ `praxis-dynamic-form` pode consumir regras materializadas pelo backend sem gravá-las dentro de `FormConfig`. O input `domainRules` é opt-in e combina as regras remotas com `config.formRules` no mesmo `FormRulesService`.
867
+
868
+ ```html
869
+ <praxis-dynamic-form
870
+ formId="funcionarios-form-demo"
871
+ [config]="formConfig"
872
+ [domainRules]="{
873
+ enabled: true,
874
+ targetArtifactKey: 'funcionarios-form-demo',
875
+ targetLayer: 'form_config',
876
+ targetArtifactType: 'praxis-dynamic-form',
877
+ status: 'applied'
878
+ }">
879
+ </praxis-dynamic-form>
880
+ ```
881
+
882
+ - `targetArtifactKey` usa `formId`/`componentInstanceId` quando omitido.
883
+ - `targetLayer` default: `form_config`.
884
+ - `targetArtifactType` default: `praxis-dynamic-form`.
885
+ - Para consumo runtime, prefira `status: 'applied'`; `pending_review` pertence a etapas de governança antes da aplicação.
886
+ - `materializedPayload` pode ser um `FormLayoutRule` direto ou uma operação reconhecida, como `rule.visualBlockGuidance.add`, desde que essa operação seja tratada como projeção visual derivada e não como fonte primária da regra de negócio.
887
+ - A rastreabilidade da regra compartilhada fica em `metadata.domainRule`.
888
+ - Quando o backend enviar `decisionDiagnostics`, o runtime preserva esse envelope em `metadata.domainRule.decisionDiagnostics`, mantendo a explicação canônica da decisão semântica governada junto da regra derivada.
889
+
890
+ E2E vivo com API real:
891
+
892
+ ```bash
893
+ PAX_PROXY_TARGET=https://praxis-api-quickstart.onrender.com \
894
+ node scripts/run-playwright-with-dev-host.js \
895
+ --port 4003 \
896
+ --path /funcionarios-form-demo \
897
+ --spec projects/praxis-dynamic-form/test-dev/e2e/funcionarios-form-demo-domain-rules.playwright.spec.ts
898
+ ```
899
+
900
+ Validado em 2026-04-23 contra `praxis-api-quickstart` publicado no Render com
901
+ `praxis-config-starter:0.1.0-rc.22`: o spec confirmou resposta 200 de
902
+ `/api/praxis/config/domain-rules/materializations` e presença de materialização
903
+ `form_config` com `operation: "rule.visualBlockGuidance.add"`.
@@ -0,0 +1,80 @@
1
+ ---
2
+ title: "Dynamic Form Authoring Document Semantics"
3
+ doc_type: "adr"
4
+ component: "praxis-dynamic-form"
5
+ status: "accepted"
6
+ owner: "praxis-ui"
7
+ last_updated: "2026-04-15"
8
+ ---
9
+
10
+ # Dynamic Form Authoring Document Semantics
11
+
12
+ ## Decision
13
+
14
+ `DynamicFormAuthoringDocument` e o snapshot canônico completo de autoria de `praxis-dynamic-form`.
15
+
16
+ Ao aplicar um documento canônico:
17
+
18
+ - `config` substitui integralmente a configuração anterior.
19
+ - `bindings` substitui integralmente os bindings persistíveis anteriores.
20
+ - ausência de `bindings.mode` implica limpeza do binding persistido e retorno ao modo default efetivo do host.
21
+ - `contextSnapshot` substitui integralmente o contexto autoral persistido anterior.
22
+ - ausência de `backConfig`, `presentation` ou `schemaPrefs` dentro de `contextSnapshot` significa remoção explícita do bloco ausente.
23
+
24
+ Payloads legados ou parciais recebidos por APIs de compatibilidade, como `applyConfigFromAdapter(...)`, permanecem com semântica `merge-compat`: campos não informados devem ser preservados até migração completa para o contrato canônico.
25
+
26
+ Valores de runtime/discovery, como `schemaUrl`, `submitUrl` e `submitMethod`, não fazem parte do `DynamicFormAuthoringDocument`. O editor pode exibi-los apenas como diagnóstico read-only do host atual, mas nunca os persiste em `bindings` ou `contextSnapshot`.
27
+
28
+ ## Field Metadata Editor Patches
29
+
30
+ Patches vindos de `@praxisui/metadata-editor` para `fieldMetadata` seguem semantica de JSON Merge Patch no campo editado.
31
+
32
+ Para `entityLookup`, o bloco `fieldMetadata[].optionSource` e aninhado e deve ser mesclado em profundidade. O dynamic-form deve preservar campos existentes, como `key`, `resourcePath`, `valuePropertyPath` e `capabilities.byIds`, quando o editor alterar apenas partes do contrato, como `dependsOn`, `selectionPolicy`, `detail` ou `capabilities.auditSnapshot`.
33
+
34
+ Essa regra evita perda silenciosa do contrato canonico de Entity Lookup durante o fluxo: abrir editor -> aplicar/salvar -> reabrir editor -> runtime consumir.
35
+
36
+ ## Consequences
37
+
38
+ - `backConfig`, `presentation` e `schemaPrefs` permanecem fora de `FormConfig`, mas fazem parte oficialmente do documento autoral persistível em `contextSnapshot`.
39
+ - `Apply` e `Save` vindos do editor operam em modo `replace-all`.
40
+ - `reset` do editor limpa todos os modos persistidos do artefato.
41
+ - adapters legados continuam suportados sem limpeza destrutiva de contexto ausente.
42
+
43
+ ## Visual Blocks Inside Layout
44
+
45
+ Blocos visuais dentro de seção, linha ou coluna devem seguir a decisão
46
+ canônica registrada em:
47
+
48
+ - `../../../docs/2026-04-dynamic-form-visual-blocks-layout-items-plan.md`
49
+ - `../../../../docs/adr/ADR-0006-dynamic-form-layout-items-visual-blocks.md`
50
+ - `./layout-items-visual-blocks.md`
51
+ - `./dynamic-form-rules-authoring-plan.md`
52
+
53
+ A semântica alvo é `FormColumn.items`, com uma union discriminada entre campo e
54
+ rich content. `FormColumn.fields` pode ser aceito como entrada de migração para
55
+ configurações antigas, mas não deve seguir como segunda fonte de verdade.
56
+
57
+ O editor deve produzir e persistir o documento canônico, e o runtime deve
58
+ consumi-lo sem adaptador oculto. O round-trip esperado é:
59
+
60
+ `abrir editor -> adicionar bloco visual -> aplicar/salvar -> reabrir -> renderizar`
61
+
62
+ Guardrail permanente:
63
+
64
+ - bloco visual não entra em `fieldMetadata`;
65
+ - bloco visual não cria `FormControl`;
66
+ - bloco visual não entra no payload de submit;
67
+ - bloco visual não usa `source`, `transient` ou `submitPolicy`.
68
+ - operações AI para bloco visual devem escrever apenas em
69
+ `sections[].rows[].columns[].items[]`, usando itens `kind: 'richContent'`;
70
+ - `sections[].rows[].columns[].fields[]` é apenas projeção/migração de campos
71
+ e não pode ser usado para representar bloco visual.
72
+ - presets de bloco visual no editor são atalhos internos para criar
73
+ `RichContentDocument` com nodes já suportados por `@praxisui/rich-content`;
74
+ presets não introduzem novo `kind` nem contrato paralelo.
75
+ - "Adicionar campo da API" reinsere somente campos já existentes em
76
+ `fieldMetadata[]` e ausentes de `columns[].items[]`; a operação escreve o
77
+ item `kind: 'field'` em `items[]` e preserva `fieldMetadata[]`.
78
+
79
+ Em fase beta, não vamos deixar legado para trás sem migração, mas também não
80
+ vamos manter legado como contrato paralelo permanente.