@praxisui/dynamic-form 9.0.0-beta.0 → 9.0.0-beta.2

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.
Files changed (2) hide show
  1. package/README.md +135 -812
  2. package/package.json +9 -9
package/README.md CHANGED
@@ -1,127 +1,65 @@
1
- ---
2
- title: "Dynamic Form"
3
- slug: "dynamic-form-overview"
4
- description: "Visao geral do @praxisui/dynamic-form com schema-driven UI, editor embutido, layout runtime e integracao orientada a contrato."
5
- doc_type: "reference"
6
- document_kind: "component-overview"
7
- component: "dynamic-form"
8
- category: "components"
9
- audience:
10
- - "frontend"
11
- - "host"
12
- - "architect"
13
- level: "intermediate"
14
- status: "active"
15
- owner: "praxis-ui"
16
- tags:
17
- - "dynamic-form"
18
- - "schema-driven"
19
- - "runtime"
20
- - "layout"
21
- - "config-editor"
22
- order: 30
23
- icon: "dynamic_form"
24
- toc: true
25
- sidebar: true
26
- search_boost: 1.0
27
- reading_time: 12
28
- estimated_setup_time: 20
29
- version: "1.0"
30
- related_docs:
31
- - "dynamic-form-hot-metadata-updates"
32
- - "praxis-dynamic-form-json-api"
33
- - "host-integration-guide"
34
- - "consumer-integration-quickstart"
35
- keywords:
36
- - "FormConfig"
37
- - "schema-driven"
38
- - "layout editor"
39
- - "settings integration"
40
- last_updated: "2026-04-12"
41
- ---
42
-
43
1
  # @praxisui/dynamic-form
44
2
 
45
- > Standalone dynamic form component with schema-driven UI, native field cascades, settings integration, and a built-in configuration editor.
46
-
47
- ## Documentation
48
-
49
- - Official documentation: https://praxisui.dev
50
- - Quickstart reference app: https://github.com/codexrodrigues/praxis-ui-quickstart
51
- - Recommended for: schema-driven screens that need runtime forms, settings integration and contract-first metadata
3
+ Schema-driven and metadata-driven form runtime for Praxis UI.
52
4
 
53
- ## When to use
5
+ Use this package to render `FormConfig` and `FieldMetadata`, connect forms to backend schema/submit endpoints, host visual configuration, and keep form layout, rules, actions, and submit payloads governed by shared Praxis contracts.
54
6
 
55
- - Build forms from metadata instead of hand-coding each screen
56
- - Keep runtime customization and layout governance inside the host application
57
- - Integrate with other `@praxisui/*` packages through shared contracts and settings flows
7
+ ## Official Links
58
8
 
59
- ## Canonical submit contract
60
-
61
- When the host opens the form from backend `surfaces` or `actions`, the canonical runtime path is `schemaUrl + submitUrl + submitMethod`, and detached hosts may also pass `apiUrlEntry` when the current overlay does not inherit the route-scoped `API_URL`. The legacy `crud.create/update` fallback still exists only as transitional behavior for hosts that have not fully migrated to discovery-driven submit execution.
9
+ - Documentation: https://praxisui.dev/docs/components
10
+ - Live demo: https://praxis-ui-4e602.web.app
11
+ - Quickstart app: https://github.com/codexrodrigues/praxis-ui-quickstart
12
+ - API quickstart: https://github.com/codexrodrigues/praxis-api-quickstart-public
13
+ - Source: https://github.com/codexrodrigues/praxis-ui-angular/tree/main/projects/praxis-dynamic-form
14
+ - Issues: https://github.com/codexrodrigues/praxis-ui-angular/issues
62
15
 
63
16
  ## Install
64
17
 
65
18
  ```bash
66
- npm i @praxisui/dynamic-form@beta
19
+ npm i @praxisui/dynamic-form@latest
67
20
  ```
68
21
 
69
- Peer dependencies (Angular v20):
70
- - `@angular/core` `^20.0.0`
71
- - `@angular/common` `^20.0.0`
72
- - `@angular/cdk` `^20.0.0`
73
- - `@praxisui/core` `^9.0.0-beta.0`
74
- - `@praxisui/rich-content` `^9.0.0-beta.0`
75
- - `@praxisui/visual-builder` `^9.0.0-beta.0`
76
- - `@praxisui/settings-panel` `^9.0.0-beta.0`
77
- - `@praxisui/cron-builder` `^9.0.0-beta.0`
22
+ Peer dependencies:
23
+
24
+ - `@angular/common`, `@angular/core`, `@angular/forms`, `@angular/cdk`, `@angular/material`, `@angular/router` `^21.0.0`
25
+ - `@praxisui/ai`, `@praxisui/core`, `@praxisui/dynamic-fields`, `@praxisui/metadata-editor`, `@praxisui/rich-content`, `@praxisui/settings-panel`, `@praxisui/visual-builder` `^9.0.0-beta.1`
26
+ - `rxjs` `^7.8.0`
78
27
 
79
- ## Quick Start
28
+ ## Minimum Local Runtime
80
29
 
81
- ### 1) PraxisDynamicForm (standalone)
30
+ Use a local `FormConfig` when the host already knows the sections and fields.
82
31
 
83
32
  ```ts
84
33
  import { Component } from '@angular/core';
34
+ import { FormConfig } from '@praxisui/core';
85
35
  import { PraxisDynamicForm } from '@praxisui/dynamic-form';
86
- import type { FormConfig } from '@praxisui/core';
87
36
 
88
37
  @Component({
89
- selector: 'app-form-demo',
90
38
  standalone: true,
39
+ selector: 'app-local-form',
91
40
  imports: [PraxisDynamicForm],
92
41
  template: `
93
42
  <praxis-dynamic-form
43
+ formId="employee-local-form"
44
+ mode="create"
94
45
  [config]="config"
95
- [mode]="'create'"
96
- [enableCustomization]="true"
97
- (formSubmit)="onSubmit($event)"
98
- ></praxis-dynamic-form>
46
+ (formSubmit)="save($event.formData)">
47
+ </praxis-dynamic-form>
99
48
  `,
100
49
  })
101
- export class FormDemoComponent {
50
+ export class LocalFormComponent {
102
51
  config: FormConfig = {
103
52
  sections: [
104
53
  {
105
54
  id: 'main',
106
55
  title: 'Employee',
107
- sectionHeader: {
108
- mode: 'auto',
109
- sourceField: 'employeePhoto',
110
- initialsSourceField: 'fullName',
111
- altField: 'fullName',
112
- },
113
56
  rows: [
114
57
  {
115
58
  columns: [
116
59
  {
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'],
60
+ id: 'name-column',
61
+ items: [{ kind: 'field', id: 'name-field', fieldName: 'name' }],
62
+ fields: ['name'],
125
63
  },
126
64
  ],
127
65
  },
@@ -129,775 +67,160 @@ export class FormDemoComponent {
129
67
  },
130
68
  ],
131
69
  fieldMetadata: [
132
- { name: 'employeePhoto', label: 'Photo', controlType: 'input' } as any,
133
- { name: 'fullName', label: 'Full Name', controlType: 'input', required: true } as any,
134
- { name: 'email', label: 'E-mail', controlType: 'email' } as any,
70
+ { name: 'name', label: 'Name', controlType: 'input', required: true },
135
71
  ],
136
72
  };
137
73
 
138
- onSubmit(evt: any) {
139
- console.log('Submitted:', evt);
140
- }
74
+ save(payload: unknown): void {}
141
75
  }
142
76
  ```
143
77
 
144
- Tip: connect to a backend resource by setting `resourcePath`/`resourceId`. The component can fetch schemas and reconcile local layout with server metadata when `enableCustomization` is true.
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.
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.
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
- ```
78
+ ## Minimum Backend Runtime
200
79
 
201
- For a submitted value like:
80
+ For schema-driven backend surfaces, prefer explicit `schemaUrl`, `submitUrl`, and `submitMethod`.
202
81
 
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
-
216
- Presentation/read-only note:
217
- - `praxis-dynamic-form` hospeda `FieldMetadata`, mas não é o resolvedor direto de `valuePresentation`.
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`.
219
-
220
- ## Canonical editorial resolution for field editors
221
-
222
- Para nome, descricao e icone do tipo de campo no configurador/layout editor e no titulo do metadata editor:
223
-
224
- - `@praxisui/dynamic-form` nao mantem mapa editorial local por `controlType`;
225
- - a resolucao canonica vem de `ComponentMetadataRegistry.resolveEditorial(..., { namespace: 'dynamicFields' })`;
226
- - aliases e normalizacao de `controlType` sao apenas ponte tecnica de lookup, nao fonte de copy;
227
- - quando nao existe descriptor canonico para aquele tipo, o fallback aceito e tecnico/neutro.
228
-
229
- Fronteira operacional:
230
-
231
- - package-owned fields de `@praxisui/dynamic-fields`: consomem descriptor editorial canonico da lib;
232
- - `ComponentDocMeta` desses fields e derivado, nao a origem semantica;
233
- - catalogo/showcase continua derivado;
234
- - custom types do host seguem suportados via `ComponentMetadataRegistry.register(ComponentDocMeta)`, sem exigir mapa local no form.
235
-
236
- ### Canonical DI contract for hosts
237
-
238
- `praxis-dynamic-form` does not register `GenericCrudService` internally.
239
- When the runtime uses `resourcePath`, `resourceId`, schema fetch, CRUD submit, or custom endpoints, the host must provide the service in the effective host scope.
240
-
241
- Canonical rule:
242
- - `praxis-table` is self-hosted for `GenericCrudService`.
243
- - `praxis-dynamic-form` is host-driven for `GenericCrudService`.
244
- - If the host needs a specific `endpointKey` or pre-configuration, it must provide and configure the same `GenericCrudService` instance before rendering the form.
245
-
246
- Minimal host example:
247
-
248
- ```ts
249
- import { Component } from '@angular/core';
250
- import { GenericCrudService } from '@praxisui/core';
251
- import { PraxisDynamicForm } from '@praxisui/dynamic-form';
252
-
253
- @Component({
254
- selector: 'app-form-host',
255
- standalone: true,
256
- imports: [PraxisDynamicForm],
257
- providers: [GenericCrudService],
258
- template: `
259
- <praxis-dynamic-form
260
- [formId]="'employees-form'"
261
- [resourcePath]="'employees'"
262
- [mode]="'edit'"
263
- ></praxis-dynamic-form>
264
- `,
265
- })
266
- export class FormHostComponent {}
267
- ```
268
-
269
- If the host must force a non-default API endpoint, configure the service in the same host scope:
270
-
271
- ```ts
272
- import { Component } from '@angular/core';
273
- import { ApiEndpoint, GenericCrudService } from '@praxisui/core';
274
- import { PraxisDynamicForm } from '@praxisui/dynamic-form';
275
-
276
- @Component({
277
- selector: 'app-form-host',
278
- standalone: true,
279
- imports: [PraxisDynamicForm],
280
- providers: [GenericCrudService],
281
- template: `
282
- <praxis-dynamic-form
283
- [formId]="'employees-form'"
284
- [resourcePath]="'human-resources/employees'"
285
- [mode]="'edit'"
286
- ></praxis-dynamic-form>
287
- `,
288
- })
289
- export class FormHostComponent {
290
- constructor(private readonly crud: GenericCrudService<any>) {
291
- this.crud.configure('human-resources/employees', ApiEndpoint.HumanResources);
292
- }
293
- }
294
- ```
295
-
296
- Without that provider, a standalone host can fail with `NG0201: No provider found for _GenericCrudService`.
297
-
298
- ### 2) Config Editor component
299
-
300
- Use the standalone editor directly to edit and reconcile the form configuration.
301
-
302
- ```ts
303
- import { Component } from '@angular/core';
304
- import { MatDialog } from '@angular/material/dialog';
305
- import { PraxisDynamicFormConfigEditor } from '@praxisui/dynamic-form';
306
- import type { FormConfig } from '@praxisui/core';
307
-
308
- @Component({
309
- selector: 'app-form-editor-launcher',
310
- standalone: true,
311
- template: `<button (click)="openEditor()">Open Config Editor</button>`,
312
- })
313
- export class FormEditorLauncherComponent {
314
- constructor(private dialog: MatDialog) {}
315
-
316
- openEditor() {
317
- const initial: FormConfig = { sections: [] } as any;
318
- this.dialog.open(PraxisDynamicFormConfigEditor, {
319
- width: '1024px',
320
- data: { formConfig: initial, formId: 'employees-form', mode: 'edit' },
321
- });
322
- }
323
- }
82
+ ```html
83
+ <praxis-dynamic-form
84
+ formId="employee-edit"
85
+ mode="edit"
86
+ resourcePath="/api/employees"
87
+ [resourceId]="employeeId"
88
+ schemaUrl="/api/employees/schemas/edit"
89
+ readUrl="/api/employees/42"
90
+ submitUrl="/api/employees/42"
91
+ submitMethod="PUT"
92
+ (formSubmit)="onSubmitted($event)">
93
+ </praxis-dynamic-form>
324
94
  ```
325
95
 
326
- Alternatively, when `enableCustomization` is true, `praxis-dynamic-form` renders a gear button that opens the editor internally.
327
-
328
- ## Agentic Authoring & Manifest
96
+ `resourcePath` can connect the form to backend discovery, but it is not the only or always preferred minimum. Detached dialog/drawer hosts may also need `apiEndpointKey` or `apiUrlEntry` so relative runtime URLs resolve against the intended backend.
329
97
 
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.
98
+ When using resource, schema, read, or submit flows, the host must provide the effective API/CRUD service wiring for that scope.
331
99
 
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.
100
+ ## Runtime Inputs And Outputs
338
101
 
339
- For more details on authoring manifests, see `docs/ai/agentic-authoring/component-authoring-contracts/README.md`.
102
+ Common inputs:
340
103
 
341
- ## API Surface
104
+ - `config`, `formId`, `componentInstanceId`
105
+ - `mode`: `create | edit | view`
106
+ - `resourcePath`, `resourceId`, `initialValue`
107
+ - `schemaUrl`, `readUrl`, `submitUrl`, `submitMethod`, `responseSchemaUrl`
108
+ - `apiEndpointKey`, `apiUrlEntry`
109
+ - `actions`, `layout`, `backConfig`, `hooks`
110
+ - `enableCustomization`
111
+ - `readonlyModeGlobal`, `disabledModeGlobal`, `presentationModeGlobal`, `visibleGlobal`
112
+ - `notifyIfOutdated`, `snoozeMs`, `autoOpenSettingsOnOutdated`
113
+ - `domainRules`
114
+ - `editorialContext`
342
115
 
343
- - Components: `PraxisDynamicForm`, `PraxisDynamicFormConfigEditor`, `JsonConfigEditorComponent`, `LayoutEditorComponent`
344
- - Services: `FormConfigService`, `FormLayoutService`, `DynamicFormLayoutService`, `FormContextService`
345
- - Utilities: form rule converters, normalize date arrays, **FormRulesService** (aplica regras de propriedades)
346
- - Metadata helpers: `providePraxisDynamicFormMetadata`
116
+ Common outputs:
347
117
 
348
- ## Editorial Hosting (Foundation)
118
+ - `formSubmit`, `formCancel`, `formReset`
119
+ - `formReady`, `valueChange`
120
+ - `configChange`, `configPatchChange`
121
+ - `syncCompleted`, `schemaStatusChange`
122
+ - `loadingStateChange`, `initializationError`
123
+ - `customAction`, `actionConfirmation`
124
+ - `fieldRenderError`, `ruleDiagnosticsChange`
349
125
 
350
- O `@praxisui/dynamic-form` agora aceita uma surface fundacional para hospedar blocos editoriais ao redor do formulario, sem misturar esses blocos com `fieldMetadata` nem com `formData`.
126
+ ## Submit Contract
351
127
 
352
- Inputs relevantes:
353
- - `config.formBlocksBefore?: RichContentDocument | null`
354
- - `config.formBlocksBeforeActions?: RichContentDocument | null`
355
- - `config.formBlocksAfter?: RichContentDocument | null`
356
- - `config.editorialContext?: Record<string, unknown>`
357
- - `[editorialContext]?: Record<string, unknown>`
128
+ `formSubmit` exposes both persistence data and full UI state:
358
129
 
359
- Precedencia de contexto editorial:
360
- 1. runtime base do form
361
- 2. `config.editorialContext`
362
- 3. input host `[editorialContext]`
130
+ - `formData`: filtered backend payload after submit policies are applied
131
+ - `rawFormData`: complete form value bag, including UI-only values
363
132
 
364
- As camadas posteriores sobrescrevem as anteriores.
365
-
366
- Exemplo minimo:
133
+ Host-owned fields that should participate in UI state but not be persisted by default belong in `FormConfig.fieldMetadata` with `source: 'local'` or `transient: true`.
367
134
 
368
135
  ```ts
369
- import type { FormConfig } from '@praxisui/core';
370
-
371
- const formConfig: FormConfig = {
372
- editorialContext: {
373
- accountName: 'Helena Costa',
374
- accountRole: 'Gestora financeira',
375
- accountContext: {
376
- user: {
377
- name: 'Helena Costa',
378
- email: 'helena.costa@praxis.demo',
379
- role: 'Gestora financeira',
380
- },
381
- tenant: {
382
- name: 'Praxis Holding',
383
- },
384
- },
385
- },
386
- formBlocksBefore: {
387
- kind: 'praxis.rich-content',
388
- version: '1.0.0',
389
- nodes: [
390
- {
391
- type: 'mediaBlock',
392
- title: 'Praxis Summit 2026',
393
- subtitle: 'Inscricao institucional',
394
- description:
395
- 'Experiencia institucional com composicao editorial hospedada antes do formulario.',
396
- icon: 'event',
397
- },
398
- {
399
- type: 'card',
400
- content: [
401
- { type: 'text', text: 'Antes de comecar', variant: 'title' },
402
- {
403
- type: 'text',
404
- text: 'Confirme os dados abaixo antes do envio e consulte a documentacao se precisar de suporte.',
405
- },
406
- ],
407
- },
408
- ],
409
- },
410
- formBlocksBeforeActions: {
411
- kind: 'praxis.rich-content',
412
- version: '1.0.0',
413
- nodes: [
414
- {
415
- type: 'card',
416
- content: [
417
- { type: 'text', text: 'Conta atual', variant: 'title' },
418
- { type: 'text', textExpr: '${accountContext.user.name}' },
419
- { type: 'text', textExpr: '${accountContext.user.email}' },
420
- ],
421
- },
422
- ],
423
- },
424
- formBlocksAfter: {
425
- kind: 'praxis.rich-content',
426
- version: '1.0.0',
427
- nodes: [
428
- {
429
- type: 'card',
430
- content: [
431
- { type: 'text', text: 'Praxis', variant: 'title' },
432
- { type: 'text', text: 'Todos os direitos reservados.' },
433
- ],
434
- },
435
- ],
436
- },
437
- sections: [
136
+ const config: FormConfig = {
137
+ sections: [],
138
+ fieldMetadata: [
139
+ { name: 'fullName', label: 'Full name', controlType: 'input' },
438
140
  {
439
- id: 'registration',
440
- title: 'Dados da inscricao',
441
- rows: [
442
- {
443
- columns: [
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
- },
454
- ],
455
- },
456
- ],
141
+ name: 'approvalComment',
142
+ label: 'Approval comment',
143
+ controlType: 'textarea',
144
+ source: 'local',
145
+ submitPolicy: 'omit',
457
146
  },
458
- ],
459
- fieldMetadata: [
460
- { name: 'fullName', label: 'Nome completo', controlType: 'input', required: true },
461
- { name: 'workEmail', label: 'E-mail corporativo', controlType: 'email', required: true },
462
- ],
463
- };
464
- ```
465
-
466
- Uso no host:
467
-
468
- ```html
469
- <praxis-dynamic-form
470
- [config]="formConfig"
471
- [editorialContext]="{ accountName: 'Helena Costa', accountRole: 'Host override' }"
472
- ></praxis-dynamic-form>
473
- ```
474
-
475
- Notas:
476
- - `formBlocksBefore`, `formBlocksBeforeActions` e `formBlocksAfter` nao entram em `formData`.
477
- - `editorialContext` deve ser tratado como imutavel; mutacoes in-place nao invalidam o cache do host.
478
- - A surface editorial agora usa `RichContentDocument`, delegando os nodes para `@praxisui/rich-content`.
479
- - `formBlocksBeforeActions` renderiza depois das secoes e antes da area principal de acoes, sendo o slot recomendado para blocos contextuais como `form:user-context-summary`.
480
- - `formBlocksAfter` permanece como slot de fechamento e continua renderizando depois do formulario inteiro, incluindo os CTAs.
481
- - secoes agora suportam `appearance` (`card|plain|step`) e `stepLabel` para cabecalhos mais estruturados.
482
-
483
- See public exports: `projects/praxis-dynamic-form/src/public-api.ts`.
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
147
  {
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
- }
148
+ name: 'manualOverrideReason',
149
+ label: 'Manual override reason',
150
+ controlType: 'textarea',
151
+ source: 'local',
152
+ submitPolicy: 'includeWhenDirty',
535
153
  },
536
- { "kind": "field", "id": "field-nome", "fieldName": "nome" },
537
- { "kind": "field", "id": "field-email", "fieldName": "email" }
538
154
  ],
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
-
551
- ## Documentacao Tecnica da Lib
552
-
553
- - `projects/praxis-dynamic-form/src/lib/praxis-dynamic-form.json-api.md`
554
- - `projects/praxis-dynamic-form/docs/hot-metadata-updates.md`
555
- - `projects/praxis-dynamic-form/docs/layout-items-visual-blocks.md`
556
-
557
- ## Header de seção com avatar dinâmico
558
-
559
- - `FormSection.icon` continua suportado como identidade visual estática do header.
560
- - `FormSection.sectionHeader` amplia o contrato para permitir avatar resolvido a partir do `formData`, sem perder compatibilidade com `icon`.
561
- - `FormSection.headerActions` permite ações iconográficas no canto direito do header da seção, reutilizando o mesmo output `customAction` com `source: 'section-header'` e `sectionId`.
562
- - Modos suportados:
563
- - `icon`: usa o ícone estático da seção e, se ele estiver vazio, pode reutilizar `fallbackIcon` como identidade visual explícita do header.
564
- - `avatar-image`: tenta resolver uma imagem a partir de `sourceField`.
565
- - `avatar-initials`: deriva iniciais a partir de `initialsSourceField` e, na ausência dele, usa `altField`.
566
- - `auto`: tenta imagem, depois iniciais e por fim aplica o fallback configurado.
567
- - Campos principais de `sectionHeader`:
568
- - `sourceField`: field cujo valor atual representa foto/avatar.
569
- - `initialsSourceField`: field textual usado para derivar iniciais.
570
- - `altField`: field usado como texto acessível do avatar e fallback textual para iniciais.
571
- - `fallbackIcon`: ícone usado quando a fonte dinâmica estiver vazia ou inválida, e também como fallback explícito do modo `icon` quando `FormSection.icon` não estiver definido.
572
- - `emptyState`: `fallback-icon`, `placeholder-avatar` ou `none`, útil sobretudo em fluxos `create`; o padrão efetivo é `placeholder-avatar`, neutro para qualquer domínio. Quando `fallback-icon` é usado, o ícone permanece dentro da moldura/avatar e continua obedecendo `size`.
573
- - `size`: tamanho semântico do avatar (`sm`, `md` ou `lg`), materializado pelo runtime por meio dos tokens `--pfx-form-section-avatar-size*`.
574
- - `initialsMaxLength`: máximo de letras no avatar textual, normalizado para `1..4`.
575
- - Campos principais de `headerActions[]`:
576
- - `id`, `label`, `icon`
577
- - `action` (opcional; se vazio, usa `id`; quando informado, ele vira o nome lógico emitido e a base de rules/mensagens)
578
- - `tooltip` (opcional; se vazio, usa `label`)
579
- - `color`, `visible`, `disabled`, `loading`
580
- - `className` (classe CSS opcional aplicada ao botão do header)
581
- - `style` (objeto de estilo inline opcional; no editor é informado como JSON)
582
- - Formatos de imagem aceitos pelo runtime:
583
- - URL
584
- - data URL base64
585
- - `File`/`Blob`
586
- - objetos com `url`, `src`, `dataUrl`, `base64` ou `bytes`
587
- - Comportamento típico em cadastro:
588
- - formulário vazio: mostra placeholder neutro por padrão, ou `fallbackIcon` se `emptyState` pedir isso
589
- - nome preenchido, sem foto: `auto` pode mostrar iniciais
590
- - foto preenchida: mostra a imagem
591
-
592
- ## IA — catálogo de capacidades (composição)
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
-
596
- O assistente usa um catálogo agregado de capabilities para gerar patches seguros:
597
-
598
- - **Macro (FormConfig)**: layout, regras, ações, hooks e mensagens (`form-ai-capabilities`).
599
- - **Base (FieldMetadata)**: propriedades comuns de campos (`field-metadata-ai-capabilities`).
600
- - **Micro (por controlType)**: overlay específico de cada input (`@praxisui/dynamic-fields`).
601
-
602
- Os paths micro são normalizados para `fieldMetadata[].<prop>` para garantir que os patches apontem para a raiz correta do formulário.
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
-
606
- ## Regras de formulário (novo contrato)
607
-
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.
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>`.
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.
612
- - Whitelist por tipo (somente propriedades a seguir são aplicadas; demais são descartadas e logadas em dev):
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).
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`).
615
- - `action`: `visible`, `disabled`, `loading`, `label`, `icon`, `tooltip`, `color` (`primary|accent|warn|basic`), `variant` (`raised|stroked|flat|fab`), `size` (`small|medium|large`), `className`, `style`.
616
- - `row`: `visible`, `gap`, `rowGap`, `className`, `style`.
617
- - `column`: `visible`, `span`, `offset`, `order`, `hidden`, `align` (`start|center|end|stretch`), `padding`, `className`, `style`.
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`.
629
-
630
- ### Builder integrado
631
-
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`.
635
- - A aba de Propriedades usa inputs tipados (enum/number/boolean/string/JSON) conforme o schema injetado; valores inválidos são ignorados.
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`).
637
-
638
- ## Layout padrão (sem FormConfig)
639
-
640
- Quando o componente não recebe uma `FormConfig` prévia (primeira execução), ele gera um layout padrão a partir do metadata do backend:
641
-
642
- - Linhas/colunas: 2 campos por linha (padrão). Em telas pequenas (xs/sm) os campos empilham (1 por linha), e a partir de md ficam lado a lado.
643
- - Responsividade do grid (12 colunas):
644
- - xs: 12, sm: 12, md: 6, lg: 6, xl: 6 (para 2 por linha). Para outros valores, a regra é `base = floor(12 / fieldsPerRow)`.
645
- - Largura dos campos: `mat-form-field { width: 100% }` e um **allow‑list** de tipos “input‑like” em `data-field-type` recebem `width: 100%`. Controles compactos (ex.: checkbox/radio/toggle/rating/slider) **não** são forçados a preencher a coluna.
646
- - Editor de Configuração: ao abrir a aba “Layout”, o editor reflete esse layout padrão; ao aplicar/salvar, persiste a `FormConfig` no storage do host.
647
- - Personalização: você pode ajustar o layout pelo Editor (arrastar/seções/linhas/colunas, alterar spans) ou fornecer uma `FormConfig` completa via `[config]`.
648
-
649
- ## Form Actions — Layout & Styling
650
-
651
- A barra de ações (onde ficam "ENVIAR", "Cancelar", etc.) é configurável via `config.actions` e pelo Editor (aba "Ações").
652
-
653
- Defaults
654
- - Rótulo do botão principal: `ENVIAR`.
655
- - Posição estrutural: `afterSections` (renderiza abaixo da última seção).
656
- - Alinhamento: `right`.
657
- - Orientação: `horizontal`.
658
- - Espaçamento: `normal`.
659
- - Background: sem cor por padrão (herda da superfície). Configure via `containerStyles` ou `containerClassName` se desejar uma superfície própria.
660
-
661
- Estrutura (parcial)
662
- ```ts
663
- interface FormActionsLayout {
664
- submit: FormActionButton; // id, label, color, type, variant, shortcut, etc.
665
- cancel: FormActionButton;
666
- reset: FormActionButton;
667
- custom?: FormActionButton[]; // botões extras
668
-
669
- // Layout/posicionamento
670
- placement?: 'afterSections' | 'insideLastSection' | 'top';
671
- position?: 'left' | 'center' | 'right' | 'justified' | 'split';
672
- orientation?: 'horizontal' | 'vertical';
673
- spacing?: 'compact' | 'normal' | 'spacious';
674
- sticky?: boolean; // fixa a barra (bottom)
675
-
676
- // Estilização do container
677
- containerClassName?: string; // adiciona classe ao container
678
- containerStyles?: { [k: string]: any }; // estilos inline (camelCase)
679
-
680
- // Mobile
681
- mobile?: { position?: 'left'|'center'|'right'|'justified'; orientation?: 'horizontal'|'vertical'; collapseToMenu?: boolean };
682
- }
683
- ```
684
-
685
- Exemplo (config)
686
- ```ts
687
- config.actions = {
688
- submit: { visible: true, label: 'ENVIAR', type: 'submit', color: 'primary', variant: 'raised', shortcut: 'ctrl+s' },
689
- cancel: { visible: true, label: 'Cancelar', type: 'button', color: 'basic' },
690
- reset: { visible: false, label: 'Reset' },
691
- placement: 'afterSections',
692
- position: 'right',
693
- orientation: 'horizontal',
694
- spacing: 'normal',
695
- sticky: false,
696
- containerClassName: 'my-form-actions',
697
- containerStyles: {
698
- background: 'var(--md-sys-color-surface-container)',
699
- border: '1px solid var(--md-sys-color-outline-variant)',
700
- borderRadius: '12px',
701
- padding: '12px 16px'
702
- },
703
- mobile: { collapseToMenu: true }
704
155
  };
705
156
  ```
706
157
 
707
- CSS por classe (opcional)
708
- ```scss
709
- .my-form-actions {
710
- background: var(--md-sys-color-surface-container);
711
- border: 1px solid var(--md-sys-color-outline-variant);
712
- border-radius: 12px;
713
- padding: 12px 16px;
714
- }
715
- ```
716
-
717
- Dicas
718
- - Cores do botão: use `color` = `primary|accent|warn|basic` (respeitam o tema Material).
719
- - Tokens M3: prefira `--md-sys-*` para cores/superfícies.
720
- - Mobile: ative `collapseToMenu` para colapsar botões extras em menu nas telas pequenas.
721
- - A classe de tema é decisão do host (`.dark-theme` ou `.theme-dark`/`.theme-light`); mantenha tokens e componentes no mesmo escopo.
722
- - Editor integrado:
723
- - `Ações > Botões Customizados` permite editar `label`, `icon`, `color`, `variant`, `size`, `tooltip`, `shortcut`, `className`, `disabled`, `loading` e `style` (JSON).
724
- - `Seção > Cabeçalho` permite editar `headerActions[]` sem JSON manual, incluindo adicionar, remover e reordenar ações do header.
725
- - No campo `action`, o editor sugere o catálogo global canônico do core e mantém fallback para ação customizada livre; o valor salvo continua compatível com `action` textual legado.
726
- - Ações globais no header seguem o mesmo princípio operacional do restante da plataforma: dependem de executor no app (`ActionResolver`) e validam parâmetro obrigatório quando o catálogo exigir.
727
- - Para `surface.open`, o editor expõe o authoring especializado da surface em vez de reduzir tudo a texto cru.
728
- - A mesma aba permite reordenar `actions.custom[]` pelos controles de mover para cima/baixo; a ordem salva é a mesma ordem renderizada no runtime, sempre depois de `submit/cancel/reset`.
729
-
730
- ### Tokens M3 obrigatórios (host)
731
-
732
- Para que o builder e os editores respeitem o tema do app host:
733
-
734
- - Superfícies: `--md-sys-color-surface`, `--md-sys-color-surface-variant`, `--md-sys-color-surface-container-*`
735
- - Texto/contorno: `--md-sys-color-on-surface`, `--md-sys-color-on-surface-variant`, `--md-sys-color-outline`, `--md-sys-color-outline-variant`
736
- - Semânticos: `--md-sys-color-primary`, `--md-sys-color-secondary`, `--md-sys-color-tertiary`, `--md-sys-color-error`
737
- - Containers: `--md-sys-color-primary-container`, `--md-sys-color-secondary-container`, `--md-sys-color-tertiary-container`, `--md-sys-color-error-container`
738
- - Elevação: `--md-sys-elevation-level1`–`--md-sys-elevation-level3`
739
-
740
-
741
- ## Section titles — espaçamento global
742
-
743
- O título de seção usa por padrão `margin: 0 0 6px 0`. Você pode ajustar globalmente via CSS var:
744
-
745
- ```scss
746
- /* Global (app host) */
747
- :root { --pfx-section-title-mb: 10px; } // ex.: 10px abaixo do título
748
- ```
749
-
750
- Ou por seção, via metadado `titleGapBottom` (em pixels), que aplica inline somente naquela seção.
158
+ Do not model backend DTO fields as local/transient fields. If a field belongs to the public backend schema, fix the canonical schema or metadata mapping.
751
159
 
752
- ## Compatibility
160
+ ## Layout, Rules, And Rich Content
753
161
 
754
- - `@praxisui/dynamic-form` `0.0.x` Angular `20.x`
755
- - Module format: `ESM2022`
162
+ `FormConfig` owns sections, rows, columns, field placement, visual blocks, rules, hooks, and actions.
756
163
 
757
- ## Crash Recovery Checkpoint
164
+ - `sections[].rows[].columns[].items[]` is the ordered layout contract.
165
+ - `kind: "field"` references `fieldMetadata[].name`.
166
+ - `kind: "richContent"` hosts a `RichContentDocument` and does not enter `fieldMetadata`, `formData`, or HTTP payloads.
167
+ - `formRules` govern visibility, required state, values, labels, styles, and other whitelisted runtime properties.
168
+ - `formCommandRules` are used for governed side effects such as global actions.
758
169
 
759
- Estado registrado em `2026-03-15` para retomada rapida do trabalho de E2E do editor `/form-config-editor`.
170
+ Presentation formatting of field values is resolved by `@praxisui/dynamic-fields` from field metadata such as `valuePresentation`.
760
171
 
761
- Suites Playwright ja adicionadas e validadas:
762
- - `projects/praxis-dynamic-form/test-dev/e2e/form-config-editor-smoke.playwright.spec.ts`
763
- - `projects/praxis-dynamic-form/test-dev/e2e/form-config-editor-layout.playwright.spec.ts`
764
- - `projects/praxis-dynamic-form/test-dev/e2e/form-config-editor-rules.playwright.spec.ts`
765
- - `projects/praxis-dynamic-form/test-dev/e2e/form-config-editor-cascades.playwright.spec.ts`
766
- - `projects/praxis-dynamic-form/test-dev/e2e/form-config-editor-json.playwright.spec.ts`
767
- - `projects/praxis-dynamic-form/test-dev/e2e/form-config-editor-hooks.playwright.spec.ts`
768
- - `projects/praxis-dynamic-form/test-dev/e2e/form-config-editor-actions.playwright.spec.ts`
769
- - `projects/praxis-dynamic-form/test-dev/e2e/form-config-editor-actions-custom.playwright.spec.ts`
172
+ ## Visual Editor
770
173
 
771
- Cobertura ja estabilizada:
772
- - smoke das tabs principais do editor integrado
773
- - `Layout`
774
- - `Regras`
775
- - `Cascatas`
776
- - `JSON` com edicao real e bloqueio de payload invalido
777
- - `Hooks`
778
- - `Acoes` (`Botoes Padrao` e `Layout`)
779
- - `Acoes` customizadas profundas (`dialog.alert` com `globalAction` simples e estruturado)
780
- - `Comportamento`
781
- - `Mensagens`
782
- - `Dicas e Tooltips`
174
+ Use `PraxisDynamicFormConfigEditor` directly or enable customization on the runtime.
783
175
 
784
- Ajuste tecnico aplicado durante essa fase:
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`.
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.
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`.
788
-
789
- Comando base usado para rerodar suites isoladas:
176
+ ```ts
177
+ import { PraxisDynamicFormConfigEditor } from '@praxisui/dynamic-form';
790
178
 
791
- ```bash
792
- node ./node_modules/playwright/cli.js test --config tools/e2e/playwright/praxis-dynamic-form-config-editor.playwright.config.ts <spec>
179
+ dialog.open(PraxisDynamicFormConfigEditor, {
180
+ width: '1024px',
181
+ data: {
182
+ formConfig: config,
183
+ formId: 'employee-edit',
184
+ mode: 'edit',
185
+ },
186
+ });
793
187
  ```
794
188
 
795
- Se houver crash, retomar daqui:
796
- 1. garantir que o app esteja no ar
797
- 2. rerodar a ultima suite alterada
798
- 3. proximo alvo pendente: consolidar a bateria completa ou seguir para outra area residual do editor
189
+ The editor preserves the same `FormConfig` contract used by the runtime. Field type names, descriptions, and icons come from the dynamic-fields editorial metadata chain, not from a local map inside Dynamic Form.
799
190
 
800
- ## License
191
+ ## AI Authoring
801
192
 
802
- Apache-2.0 – see the `LICENSE` packaged with this library or the repository root.
193
+ `PRAXIS_DYNAMIC_FORM_AUTHORING_MANIFEST` declares executable operations for governed edits to:
803
194
 
804
- # Praxis Dynamic Form — Cascata vs Connections
195
+ - fields and local fields
196
+ - sections, rows, columns, and layout placement
197
+ - visual blocks
198
+ - form actions
199
+ - form rules and messages
200
+ - schema-backed field edits
805
201
 
806
- ### Concept Usage
202
+ The manifest is meant to keep AI-generated patches compatible with the visual editor and the runtime `FormConfig` contract.
807
203
 
808
- - Data-driven forms
809
- - Declarative UI
810
- - Schema-driven UI
204
+ ## Public API
811
205
 
812
- ## Quando usar cada mecanismo
206
+ Main exports:
813
207
 
814
- - Connections (dot‑path)
815
- - Para orquestração “externa” (entre widgets, tabs, tabelas ou fora do formulário).
816
- - Escrevem em `inputs.config.*` do widget do formulário, inclusive metadados de campos (ex.: `inputs.config.fieldMetadata[i].filterCriteria`).
817
- - Escala bem para cenários de página e integração cross‑widget.
208
+ - `PraxisDynamicForm`
209
+ - `PraxisFilterForm`
210
+ - `PraxisDynamicFormConfigEditor`
211
+ - `JsonConfigEditorComponent`
212
+ - `LayoutEditorComponent`
213
+ - `PraxisFormActionsComponent`
214
+ - `FormConfigService`, `FormLayoutService`, `DynamicFormLayoutService`, `FormContextService`, `FormRulesService`, `DomainRuleFormRulesService`
215
+ - widget config editors for dynamic form and filter form
216
+ - metadata providers such as `providePraxisDynamicFormMetadata`
217
+ - rule utilities, date normalization, settings-panel providers
218
+ - `FORM_AI_CAPABILITIES`, `FORM_COMPONENT_AI_CAPABILITIES`
219
+ - `PRAXIS_DYNAMIC_FORM_AUTHORING_MANIFEST`
818
220
 
819
- - Cascata nativa (campo→campo, intra‑form)
820
- - Declarativa via metadata (`dependencyFields`, `resetOnDependentChange`, etc.).
821
- - Observa `FormControl.valueChanges` dos campos dependentes, aplica `filterCriteria` e recarrega conforme estratégia (`loadOn`, `dependencyLoadOnChange`).
822
- - Elimina código ad hoc na página.
823
-
824
- ## Convivência e fonte de verdade
825
-
826
- - Se Connections atualizam `filterCriteria` de um campo, considere desativar a cascata nativa nesse campo (`enableDependencyCascade: false`) ou usar `dependencyLoadOnChange: 'manual'` (cascata só atualiza filtros e Connections disparam o load).
827
- - Estratégia de merge recomendada: `dependencyMergeStrategy: 'merge'` para preservar chaves vindas de Connections.
828
-
829
- ## Links úteis
830
-
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)
832
- - Guia de implementação e metadados da cascata: `docs/CASCADE-NATIVA.md`
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`
836
-
837
- ## Verificação de Schema (ETag/If-None-Match)
838
-
839
- - Inputs (opcionais; ativos apenas com `enableCustomization=true`):
840
- - `notifyIfOutdated: 'inline' | 'snackbar' | 'both' | 'none' = 'both'`
841
- - `snoozeMs: number = 86400000`
842
- - `autoOpenSettingsOnOutdated: boolean = false`
843
- - Output:
844
- - `schemaStatusChange: { outdated: boolean; serverHash?: string; lastVerifiedAt?: string; formId?: string }`
845
- - Persistência (ConfigStorage):
846
- - `form-schema-meta:{formId}` → `{ serverHash?: string; lastVerifiedAt?: string }`
847
- - `form-schema-prefs:{formId}` → `{ notifyIfOutdated?, snoozeMs?, autoOpenSettingsOnOutdated? }`
848
- - Por hash (suppress): `schemaIgnore:{formId}:{hash}`, `schemaSnooze:{formId}:{hash}`, `schemaNotified:{formId}:{hash}`
849
- - Comportamento:
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`.
855
- - Primeira vez (sem base): baixa o corpo do schema para gerar o layout; persiste `form-schema-meta:{formId}`.
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.
857
-
858
- ### URL da API (absoluto vs relativo)
859
-
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`).
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`.
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
- ```
221
+ ## Notes
899
222
 
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"`.
223
+ - Separate local-only, backend schema-driven, and authored/customizable usage before deciding the minimum host setup.
224
+ - Keep backend schema fields server-backed; use local/transient metadata only for UI-owned state.
225
+ - Prefer explicit `schemaUrl + submitUrl + submitMethod` for backend-published surfaces and actions.
226
+ - Use the official documentation for full recipes on cascades, schema reconciliation, domain rules, action layout, and visual editor workflows.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@praxisui/dynamic-form",
3
- "version": "9.0.0-beta.0",
3
+ "version": "9.0.0-beta.2",
4
4
  "description": "Angular dynamic form engine for Praxis UI: metadata-driven forms, hooks, and services integrating @praxisui/* packages.",
5
5
  "peerDependencies": {
6
6
  "@angular/common": "^21.0.0",
@@ -9,13 +9,13 @@
9
9
  "@angular/forms": "^21.0.0",
10
10
  "@angular/material": "^21.0.0",
11
11
  "@angular/router": "^21.0.0",
12
- "@praxisui/ai": "^9.0.0-beta.0",
13
- "@praxisui/dynamic-fields": "^9.0.0-beta.0",
14
- "@praxisui/metadata-editor": "^9.0.0-beta.0",
15
- "@praxisui/rich-content": "^9.0.0-beta.0",
16
- "@praxisui/settings-panel": "^9.0.0-beta.0",
17
- "@praxisui/visual-builder": "^9.0.0-beta.0",
18
- "@praxisui/core": "^9.0.0-beta.0",
12
+ "@praxisui/ai": "^9.0.0-beta.2",
13
+ "@praxisui/dynamic-fields": "^9.0.0-beta.2",
14
+ "@praxisui/metadata-editor": "^9.0.0-beta.2",
15
+ "@praxisui/rich-content": "^9.0.0-beta.2",
16
+ "@praxisui/settings-panel": "^9.0.0-beta.2",
17
+ "@praxisui/visual-builder": "^9.0.0-beta.2",
18
+ "@praxisui/core": "^9.0.0-beta.2",
19
19
  "rxjs": "^7.8.0"
20
20
  },
21
21
  "dependencies": {
@@ -28,7 +28,7 @@
28
28
  },
29
29
  "repository": {
30
30
  "type": "git",
31
- "url": "https://github.com/codexrodrigues/praxis-ui-angular"
31
+ "url": "git+https://github.com/codexrodrigues/praxis-ui-angular.git"
32
32
  },
33
33
  "homepage": "https://praxisui.dev",
34
34
  "bugs": {