@praxisui/dynamic-form 1.0.0-beta.8 → 2.0.0-beta.0

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
@@ -1,7 +1,61 @@
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-03-22"
41
+ ---
42
+
1
43
  # @praxisui/dynamic-form
2
44
 
3
45
  > Standalone dynamic form component with schema-driven UI, native field cascades, settings integration, and a built-in configuration editor.
4
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
52
+
53
+ ## When to use
54
+
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
58
+
5
59
  ## Install
6
60
 
7
61
  ```bash
@@ -36,7 +90,7 @@ import type { FormConfig } from '@praxisui/core';
36
90
  <praxis-dynamic-form
37
91
  [config]="config"
38
92
  [mode]="'create'"
39
- [editModeEnabled]="true"
93
+ [enableCustomization]="true"
40
94
  (formSubmit)="onSubmit($event)"
41
95
  ></praxis-dynamic-form>
42
96
  `,
@@ -46,18 +100,29 @@ export class FormDemoComponent {
46
100
  sections: [
47
101
  {
48
102
  id: 'main',
49
- label: 'Employee',
103
+ title: 'Employee',
104
+ sectionHeader: {
105
+ mode: 'auto',
106
+ sourceField: 'employeePhoto',
107
+ initialsSourceField: 'fullName',
108
+ altField: 'fullName',
109
+ },
50
110
  rows: [
51
111
  {
52
112
  columns: [
53
- { fields: [{ name: 'fullName', label: 'Full Name', controlType: 'text' }] },
54
- { fields: [{ name: 'email', label: 'E-mail', controlType: 'email' }] },
113
+ { id: 'col-fullName', fields: ['fullName'] },
114
+ { id: 'col-email', fields: ['email'] },
55
115
  ],
56
116
  },
57
117
  ],
58
118
  },
59
119
  ],
60
- } as any;
120
+ fieldMetadata: [
121
+ { name: 'employeePhoto', label: 'Photo', controlType: 'input' } as any,
122
+ { name: 'fullName', label: 'Full Name', controlType: 'input', required: true } as any,
123
+ { name: 'email', label: 'E-mail', controlType: 'email' } as any,
124
+ ],
125
+ };
61
126
 
62
127
  onSubmit(evt: any) {
63
128
  console.log('Submitted:', evt);
@@ -65,7 +130,70 @@ export class FormDemoComponent {
65
130
  }
66
131
  ```
67
132
 
68
- Tip: connect to a backend resource by setting `resourcePath`/`resourceId`. The component can fetch schemas and reconcile local layout with server metadata when `editModeEnabled` is true.
133
+ 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.
134
+ 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.
135
+
136
+ ### Canonical DI contract for hosts
137
+
138
+ `praxis-dynamic-form` does not register `GenericCrudService` internally.
139
+ When the runtime uses `resourcePath`, `resourceId`, schema fetch, CRUD submit, or custom endpoints, the host must provide the service in the effective host scope.
140
+
141
+ Canonical rule:
142
+ - `praxis-table` is self-hosted for `GenericCrudService`.
143
+ - `praxis-dynamic-form` is host-driven for `GenericCrudService`.
144
+ - If the host needs a specific `endpointKey` or pre-configuration, it must provide and configure the same `GenericCrudService` instance before rendering the form.
145
+
146
+ Minimal host example:
147
+
148
+ ```ts
149
+ import { Component } from '@angular/core';
150
+ import { GenericCrudService } from '@praxisui/core';
151
+ import { PraxisDynamicForm } from '@praxisui/dynamic-form';
152
+
153
+ @Component({
154
+ selector: 'app-form-host',
155
+ standalone: true,
156
+ imports: [PraxisDynamicForm],
157
+ providers: [GenericCrudService],
158
+ template: `
159
+ <praxis-dynamic-form
160
+ [formId]="'employees-form'"
161
+ [resourcePath]="'employees'"
162
+ [mode]="'edit'"
163
+ ></praxis-dynamic-form>
164
+ `,
165
+ })
166
+ export class FormHostComponent {}
167
+ ```
168
+
169
+ If the host must force a non-default API endpoint, configure the service in the same host scope:
170
+
171
+ ```ts
172
+ import { Component } from '@angular/core';
173
+ import { ApiEndpoint, GenericCrudService } from '@praxisui/core';
174
+ import { PraxisDynamicForm } from '@praxisui/dynamic-form';
175
+
176
+ @Component({
177
+ selector: 'app-form-host',
178
+ standalone: true,
179
+ imports: [PraxisDynamicForm],
180
+ providers: [GenericCrudService],
181
+ template: `
182
+ <praxis-dynamic-form
183
+ [formId]="'employees-form'"
184
+ [resourcePath]="'human-resources/employees'"
185
+ [mode]="'edit'"
186
+ ></praxis-dynamic-form>
187
+ `,
188
+ })
189
+ export class FormHostComponent {
190
+ constructor(private readonly crud: GenericCrudService<any>) {
191
+ this.crud.configure('human-resources/employees', ApiEndpoint.HumanResources);
192
+ }
193
+ }
194
+ ```
195
+
196
+ Without that provider, a standalone host can fail with `NG0201: No provider found for _GenericCrudService`.
69
197
 
70
198
  ### 2) Config Editor component
71
199
 
@@ -95,22 +223,373 @@ export class FormEditorLauncherComponent {
95
223
  }
96
224
  ```
97
225
 
98
- Alternatively, when `editModeEnabled` is true, `praxis-dynamic-form` renders a gear button that opens the editor internally.
226
+ Alternatively, when `enableCustomization` is true, `praxis-dynamic-form` renders a gear button that opens the editor internally.
99
227
 
100
228
  ## API Surface
101
229
 
102
230
  - Components: `PraxisDynamicForm`, `PraxisDynamicFormConfigEditor`, `JsonConfigEditorComponent`, `LayoutEditorComponent`
103
231
  - Services: `FormConfigService`, `FormLayoutService`, `DynamicFormLayoutService`, `FormContextService`
104
- - Utilities: form rule converters, normalize date arrays
232
+ - Utilities: form rule converters, normalize date arrays, **FormRulesService** (aplica regras de propriedades)
105
233
  - Metadata helpers: `providePraxisDynamicFormMetadata`
106
234
 
235
+ ## Editorial Hosting (Foundation)
236
+
237
+ 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`.
238
+
239
+ Inputs relevantes:
240
+ - `config.formBlocksBefore?: WidgetDefinition[]`
241
+ - `config.formBlocksBeforeActions?: WidgetDefinition[]`
242
+ - `config.formBlocksAfter?: WidgetDefinition[]`
243
+ - `config.editorialContext?: Record<string, unknown>`
244
+ - `[editorialContext]?: Record<string, unknown>`
245
+
246
+ Precedencia de contexto editorial:
247
+ 1. runtime base do form
248
+ 2. `config.editorialContext`
249
+ 3. input host `[editorialContext]`
250
+
251
+ As camadas posteriores sobrescrevem as anteriores.
252
+
253
+ Exemplo minimo:
254
+
255
+ ```ts
256
+ import type { FormConfig } from '@praxisui/core';
257
+
258
+ const formConfig: FormConfig = {
259
+ editorialContext: {
260
+ accountName: 'Helena Costa',
261
+ accountRole: 'Gestora financeira',
262
+ accountContext: {
263
+ user: {
264
+ name: 'Helena Costa',
265
+ email: 'helena.costa@praxis.demo',
266
+ role: 'Gestora financeira',
267
+ },
268
+ tenant: {
269
+ name: 'Praxis Holding',
270
+ },
271
+ },
272
+ },
273
+ formBlocksBefore: [
274
+ {
275
+ id: 'widget:hero-banner',
276
+ inputs: {
277
+ instanceId: 'editorial:before:hero:1',
278
+ title: 'Praxis Summit 2026',
279
+ subtitle: 'Inscricao institucional',
280
+ description: 'Experiencia institucional com composicao editorial hospedada antes do formulario.',
281
+ variant: 'event',
282
+ },
283
+ },
284
+ {
285
+ id: 'widget:rich-text-block',
286
+ inputs: {
287
+ instanceId: 'editorial:before:rich-text:1',
288
+ title: 'Antes de comecar',
289
+ subtitle: 'Contexto editorial',
290
+ icon: 'info',
291
+ contentFormat: 'markdown',
292
+ content: 'Confirme os dados abaixo antes do envio e consulte a [documentacao](https://example.com/docs) se precisar de suporte.',
293
+ },
294
+ },
295
+ {
296
+ id: 'widget:legal-notice',
297
+ inputs: {
298
+ instanceId: 'editorial:before:legal:1',
299
+ title: 'Uso de dados',
300
+ contentFormat: 'plain',
301
+ content: 'Ao participar, voce reconhece a politica institucional aplicavel.',
302
+ severity: 'info',
303
+ },
304
+ },
305
+ ],
306
+ formBlocksBeforeActions: [
307
+ {
308
+ id: 'form:user-context-summary',
309
+ inputs: {
310
+ instanceId: 'editorial:before-actions:user-context:1',
311
+ title: 'Conta atual',
312
+ source: 'context',
313
+ context: '${accountContext}',
314
+ fields: [
315
+ { label: 'Nome', valuePath: 'user.name', fallback: '-' },
316
+ { label: 'E-mail', valuePath: 'user.email', fallback: '-' },
317
+ ],
318
+ },
319
+ },
320
+ ],
321
+ formBlocksAfter: [
322
+ {
323
+ id: 'widget:footer-links',
324
+ inputs: {
325
+ instanceId: 'editorial:after:footer-links:1',
326
+ brandText: 'Praxis',
327
+ secondaryText: 'Todos os direitos reservados.',
328
+ links: [
329
+ { label: 'Docs', href: '/components/dynamic-form/docs/overview' },
330
+ { label: 'API', href: '/components/dynamic-form/api' },
331
+ ],
332
+ },
333
+ },
334
+ ],
335
+ sections: [
336
+ {
337
+ id: 'registration',
338
+ title: 'Dados da inscricao',
339
+ rows: [
340
+ {
341
+ columns: [
342
+ { fields: ['fullName'] },
343
+ { fields: ['workEmail'] },
344
+ ],
345
+ },
346
+ ],
347
+ },
348
+ ],
349
+ fieldMetadata: [
350
+ { name: 'fullName', label: 'Nome completo', controlType: 'input', required: true },
351
+ { name: 'workEmail', label: 'E-mail corporativo', controlType: 'email', required: true },
352
+ ],
353
+ };
354
+ ```
355
+
356
+ Uso no host:
357
+
358
+ ```html
359
+ <praxis-dynamic-form
360
+ [config]="formConfig"
361
+ [editorialContext]="{ accountName: 'Helena Costa', accountRole: 'Host override' }"
362
+ (widgetEvent)="onWidgetEvent($event)"
363
+ ></praxis-dynamic-form>
364
+ ```
365
+
366
+ Notas:
367
+ - `formBlocksBefore`, `formBlocksBeforeActions` e `formBlocksAfter` nao entram em `formData`.
368
+ - `widgetEvent` reemite eventos dos widgets hospedados com `placement` (`before`, `beforeActions` ou `after`).
369
+ - `editorialContext` deve ser tratado como imutavel; mutacoes in-place nao invalidam o cache do host.
370
+ - A surface editorial ja suporta widgets reais compartilhados para hero, rich text, legal notice, user context summary e footer links.
371
+ - `formBlocksBeforeActions` renderiza depois das secoes e antes da area principal de acoes, sendo o slot recomendado para blocos contextuais como `form:user-context-summary`.
372
+ - `formBlocksAfter` permanece como slot de fechamento e continua renderizando depois do formulario inteiro, incluindo os CTAs.
373
+ - secoes agora suportam `appearance` (`card|plain|step`) e `stepLabel` para cabecalhos mais estruturados.
374
+
107
375
  See public exports: `projects/praxis-dynamic-form/src/public-api.ts`.
108
376
 
377
+ ## Documentacao Tecnica da Lib
378
+
379
+ - `projects/praxis-dynamic-form/src/lib/praxis-dynamic-form.json-api.md`
380
+ - `projects/praxis-dynamic-form/docs/hot-metadata-updates.md`
381
+
382
+ ## Header de seção com avatar dinâmico
383
+
384
+ - `FormSection.icon` continua suportado como identidade visual estática do header.
385
+ - `FormSection.sectionHeader` amplia o contrato para permitir avatar resolvido a partir do `formData`, sem perder compatibilidade com `icon`.
386
+ - Modos suportados:
387
+ - `icon`: usa o ícone estático da seção.
388
+ - `avatar-image`: tenta resolver uma imagem a partir de `sourceField`.
389
+ - `avatar-initials`: deriva iniciais a partir de `initialsSourceField` e, na ausência dele, usa `altField`.
390
+ - `auto`: tenta imagem, depois iniciais e por fim aplica o fallback configurado.
391
+ - Campos principais de `sectionHeader`:
392
+ - `sourceField`: field cujo valor atual representa foto/avatar.
393
+ - `initialsSourceField`: field textual usado para derivar iniciais.
394
+ - `altField`: field usado como texto acessível do avatar e fallback textual para iniciais.
395
+ - `fallbackIcon`: ícone usado quando a fonte dinâmica estiver vazia ou inválida.
396
+ - `emptyState`: `fallback-icon`, `placeholder-avatar` ou `none`, útil sobretudo em fluxos `create`; o padrão efetivo é `placeholder-avatar`, neutro para qualquer domínio.
397
+ - `initialsMaxLength`: máximo de letras no avatar textual, normalizado para `1..4`.
398
+ - Formatos de imagem aceitos pelo runtime:
399
+ - URL
400
+ - data URL base64
401
+ - `File`/`Blob`
402
+ - objetos com `url`, `src`, `dataUrl`, `base64` ou `bytes`
403
+ - Comportamento típico em cadastro:
404
+ - formulário vazio: mostra placeholder neutro por padrão, ou `fallbackIcon` se `emptyState` pedir isso
405
+ - nome preenchido, sem foto: `auto` pode mostrar iniciais
406
+ - foto preenchida: mostra a imagem
407
+
408
+ ## IA — catálogo de capacidades (composição)
409
+
410
+ O assistente usa um catálogo agregado de capabilities para gerar patches seguros:
411
+
412
+ - **Macro (FormConfig)**: layout, regras, ações, hooks e mensagens (`form-ai-capabilities`).
413
+ - **Base (FieldMetadata)**: propriedades comuns de campos (`field-metadata-ai-capabilities`).
414
+ - **Micro (por controlType)**: overlay específico de cada input (`@praxisui/dynamic-fields`).
415
+
416
+ Os paths micro são normalizados para `fieldMetadata[].<prop>` para garantir que os patches apontem para a raiz correta do formulário.
417
+
418
+ ## Regras de formulário (novo contrato)
419
+
420
+ - Formato: cada regra tem `targetType` (`field | section | action | row | column`), `targets: string[]` (IDs canônicos, sem prefixo), e `effect` com `condition` (DSL ou Specification), `properties` e `propertiesWhenFalse`.
421
+ - Compatibilidade: regras antigas (`context/targetField`) são migradas para `properties/targets` automaticamente; prefixos `section:/action:/row:/column:` são removidos e `targetType` é inferido se não vier explicitado.
422
+ - 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.
423
+ - Whitelist por tipo (somente propriedades a seguir são aplicadas; demais são descartadas e logadas em dev):
424
+ - `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).
425
+ - `section`: `visible`, `title`, `description`, `icon`, `sectionHeader` (objeto rico) e tambem subpropriedades tipadas como `sectionHeader.mode`, `sectionHeader.sourceField`, `sectionHeader.initialsSourceField`, `sectionHeader.altField`, `sectionHeader.fallbackIcon`, `sectionHeader.emptyState`, `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`).
426
+ - `action`: `visible`, `disabled`, `loading`, `label`, `icon`, `tooltip`, `color` (`primary|accent|warn|basic`), `variant` (`raised|stroked|flat|fab`), `size` (`small|medium|large`), `className`, `style`.
427
+ - `row`: `visible`, `gap`, `rowGap`, `className`, `style`.
428
+ - `column`: `visible`, `span`, `offset`, `order`, `hidden`, `align` (`start|center|end|stretch`), `padding`, `className`, `style`.
429
+ - 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`.
430
+ - 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.
431
+
432
+ ### Builder integrado
433
+
434
+ - 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`).
435
+ - A aba de Propriedades usa inputs tipados (enum/number/boolean/string/JSON) conforme o schema injetado; valores inválidos são ignorados.
436
+ - 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`).
437
+
438
+ ## Layout padrão (sem FormConfig)
439
+
440
+ 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:
441
+
442
+ - 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.
443
+ - Responsividade do grid (12 colunas):
444
+ - xs: 12, sm: 12, md: 6, lg: 6, xl: 6 (para 2 por linha). Para outros valores, a regra é `base = floor(12 / fieldsPerRow)`.
445
+ - 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.
446
+ - 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.
447
+ - Personalização: você pode ajustar o layout pelo Editor (arrastar/seções/linhas/colunas, alterar spans) ou fornecer uma `FormConfig` completa via `[config]`.
448
+
449
+ ## Form Actions — Layout & Styling
450
+
451
+ A barra de ações (onde ficam "ENVIAR", "Cancelar", etc.) é configurável via `config.actions` e pelo Editor (aba "Ações").
452
+
453
+ Defaults
454
+ - Rótulo do botão principal: `ENVIAR`.
455
+ - Posição estrutural: `afterSections` (renderiza abaixo da última seção).
456
+ - Alinhamento: `right`.
457
+ - Orientação: `horizontal`.
458
+ - Espaçamento: `normal`.
459
+ - Background: sem cor por padrão (herda da superfície). Configure via `containerStyles` ou `containerClassName` se desejar uma superfície própria.
460
+
461
+ Estrutura (parcial)
462
+ ```ts
463
+ interface FormActionsLayout {
464
+ submit: FormActionButton; // id, label, color, type, variant, shortcut, etc.
465
+ cancel: FormActionButton;
466
+ reset: FormActionButton;
467
+ custom?: FormActionButton[]; // botões extras
468
+
469
+ // Layout/posicionamento
470
+ placement?: 'afterSections' | 'insideLastSection' | 'top';
471
+ position?: 'left' | 'center' | 'right' | 'justified' | 'split';
472
+ orientation?: 'horizontal' | 'vertical';
473
+ spacing?: 'compact' | 'normal' | 'spacious';
474
+ sticky?: boolean; // fixa a barra (bottom)
475
+
476
+ // Estilização do container
477
+ containerClassName?: string; // adiciona classe ao container
478
+ containerStyles?: { [k: string]: any }; // estilos inline (camelCase)
479
+
480
+ // Mobile
481
+ mobile?: { position?: 'left'|'center'|'right'|'justified'; orientation?: 'horizontal'|'vertical'; collapseToMenu?: boolean };
482
+ }
483
+ ```
484
+
485
+ Exemplo (config)
486
+ ```ts
487
+ config.actions = {
488
+ submit: { visible: true, label: 'ENVIAR', type: 'submit', color: 'primary', variant: 'raised', shortcut: 'ctrl+s' },
489
+ cancel: { visible: true, label: 'Cancelar', type: 'button', color: 'basic' },
490
+ reset: { visible: false, label: 'Reset' },
491
+ placement: 'afterSections',
492
+ position: 'right',
493
+ orientation: 'horizontal',
494
+ spacing: 'normal',
495
+ sticky: false,
496
+ containerClassName: 'my-form-actions',
497
+ containerStyles: {
498
+ background: 'var(--md-sys-color-surface-container)',
499
+ border: '1px solid var(--md-sys-color-outline-variant)',
500
+ borderRadius: '12px',
501
+ padding: '12px 16px'
502
+ },
503
+ mobile: { collapseToMenu: true }
504
+ };
505
+ ```
506
+
507
+ CSS por classe (opcional)
508
+ ```scss
509
+ .my-form-actions {
510
+ background: var(--md-sys-color-surface-container);
511
+ border: 1px solid var(--md-sys-color-outline-variant);
512
+ border-radius: 12px;
513
+ padding: 12px 16px;
514
+ }
515
+ ```
516
+
517
+ Dicas
518
+ - Cores do botão: use `color` = `primary|accent|warn|basic` (respeitam o tema Material).
519
+ - Tokens M3: prefira `--md-sys-*` para cores/superfícies.
520
+ - Mobile: ative `collapseToMenu` para colapsar botões extras em menu nas telas pequenas.
521
+ - A classe de tema é decisão do host (`.dark-theme` ou `.theme-dark`/`.theme-light`); mantenha tokens e componentes no mesmo escopo.
522
+
523
+ ### Tokens M3 obrigatórios (host)
524
+
525
+ Para que o builder e os editores respeitem o tema do app host:
526
+
527
+ - Superfícies: `--md-sys-color-surface`, `--md-sys-color-surface-variant`, `--md-sys-color-surface-container-*`
528
+ - Texto/contorno: `--md-sys-color-on-surface`, `--md-sys-color-on-surface-variant`, `--md-sys-color-outline`, `--md-sys-color-outline-variant`
529
+ - Semânticos: `--md-sys-color-primary`, `--md-sys-color-secondary`, `--md-sys-color-tertiary`, `--md-sys-color-error`
530
+ - Containers: `--md-sys-color-primary-container`, `--md-sys-color-secondary-container`, `--md-sys-color-tertiary-container`, `--md-sys-color-error-container`
531
+ - Elevação: `--md-sys-elevation-level1`–`--md-sys-elevation-level3`
532
+
533
+
534
+ ## Section titles — espaçamento global
535
+
536
+ O título de seção usa por padrão `margin: 0 0 6px 0`. Você pode ajustar globalmente via CSS var:
537
+
538
+ ```scss
539
+ /* Global (app host) */
540
+ :root { --pfx-section-title-mb: 10px; } // ex.: 10px abaixo do título
541
+ ```
542
+
543
+ Ou por seção, via metadado `titleGapBottom` (em pixels), que aplica inline somente naquela seção.
544
+
109
545
  ## Compatibility
110
546
 
111
547
  - `@praxisui/dynamic-form` `0.0.x` → Angular `20.x`
112
548
  - Module format: `ESM2022`
113
549
 
550
+ ## Crash Recovery Checkpoint
551
+
552
+ Estado registrado em `2026-03-15` para retomada rapida do trabalho de E2E do editor `/form-config-editor`.
553
+
554
+ Suites Playwright ja adicionadas e validadas:
555
+ - `projects/praxis-dynamic-form/test-dev/e2e/form-config-editor-smoke.playwright.spec.ts`
556
+ - `projects/praxis-dynamic-form/test-dev/e2e/form-config-editor-layout.playwright.spec.ts`
557
+ - `projects/praxis-dynamic-form/test-dev/e2e/form-config-editor-rules.playwright.spec.ts`
558
+ - `projects/praxis-dynamic-form/test-dev/e2e/form-config-editor-cascades.playwright.spec.ts`
559
+ - `projects/praxis-dynamic-form/test-dev/e2e/form-config-editor-json.playwright.spec.ts`
560
+ - `projects/praxis-dynamic-form/test-dev/e2e/form-config-editor-hooks.playwright.spec.ts`
561
+ - `projects/praxis-dynamic-form/test-dev/e2e/form-config-editor-actions.playwright.spec.ts`
562
+ - `projects/praxis-dynamic-form/test-dev/e2e/form-config-editor-actions-custom.playwright.spec.ts`
563
+
564
+ Cobertura ja estabilizada:
565
+ - smoke das tabs principais do editor integrado
566
+ - `Layout`
567
+ - `Regras`
568
+ - `Cascatas`
569
+ - `JSON` com edicao real e bloqueio de payload invalido
570
+ - `Hooks`
571
+ - `Acoes` (`Botoes Padrao` e `Layout`)
572
+ - `Acoes` customizadas profundas (`showAlert` simples e estruturado)
573
+ - `Comportamento`
574
+ - `Mensagens`
575
+ - `Dicas e Tooltips`
576
+
577
+ Ajuste tecnico aplicado durante essa fase:
578
+ - [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`.
579
+ - [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.
580
+ - [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`.
581
+
582
+ Comando base usado para rerodar suites isoladas:
583
+
584
+ ```bash
585
+ node ./node_modules/playwright/cli.js test --config tools/e2e/playwright/praxis-dynamic-form-config-editor.playwright.config.ts <spec>
586
+ ```
587
+
588
+ Se houver crash, retomar daqui:
589
+ 1. garantir que o app esteja no ar
590
+ 2. rerodar a ultima suite alterada
591
+ 3. proximo alvo pendente: consolidar a bateria completa ou seguir para outra area residual do editor
592
+
114
593
  ## License
115
594
 
116
595
  Apache-2.0 – see the `LICENSE` packaged with this library or the repository root.
@@ -119,9 +598,9 @@ Apache-2.0 – see the `LICENSE` packaged with this library or the repository ro
119
598
 
120
599
  ### Concept Usage
121
600
 
122
- - [Data Driven Forms](../../../../docs/concepts/data-driven-forms.md)
123
- - [Declarative UI](../../../../docs/concepts/declarative-ui.md)
124
- - [Schemadriven UI](../../../../docs/concepts/schema-driven-ui.md)
601
+ - Data-driven forms
602
+ - Declarative UI
603
+ - Schema-driven UI
125
604
 
126
605
  ## Quando usar cada mecanismo
127
606
 
@@ -142,13 +621,13 @@ Apache-2.0 – see the `LICENSE` packaged with this library or the repository ro
142
621
 
143
622
  ## Links úteis
144
623
 
145
- - Fluxo de Schema (ETag/304, schemaId, reconciliação): `docs/schemas/fluxo-schema.md`
624
+ - 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)
146
625
  - Guia de implementação e metadados da cascata: `docs/CASCADE-NATIVA.md`
147
- - Padrões de endpoints (Options vs Filter) para selects: `docs/DEVS-GENERIC-CRUD-SERVICE.md`
626
+ - 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)
148
627
 
149
628
  ## Verificação de Schema (ETag/If-None-Match)
150
629
 
151
- - Inputs (opcionais; ativos apenas com `editModeEnabled=true`):
630
+ - Inputs (opcionais; ativos apenas com `enableCustomization=true`):
152
631
  - `notifyIfOutdated: 'inline' | 'snackbar' | 'both' | 'none' = 'both'`
153
632
  - `snoozeMs: number = 86400000`
154
633
  - `autoOpenSettingsOnOutdated: boolean = false`
@@ -161,6 +640,12 @@ Apache-2.0 – see the `LICENSE` packaged with this library or the repository ro
161
640
  - Comportamento:
162
641
  - Quando já existe base local (ex.: `config.sections.length > 0`), o componente faz uma verificação leve via `/schemas/filtered` com `If-None-Match`.
163
642
  - 304 → apenas atualiza `lastVerifiedAt` e emite `schemaStatusChange(outdated=false)`.
164
- - 200 → atualiza `serverHash/lastVerifiedAt`, define `schemaOutdated = editModeEnabled && hadBase`, emite `schemaStatusChange`. Não aplica schema automaticamente.
643
+ - 200 → atualiza `serverHash/lastVerifiedAt`, define `schemaOutdated = enableCustomization && hadBase`, emite `schemaStatusChange`. Não aplica schema automaticamente.
165
644
  - Primeira vez (sem base): baixa o corpo do schema para gerar o layout; persiste `form-schema-meta:{formId}`.
166
645
  - 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.
646
+
647
+ ### URL da API (absoluto vs relativo)
648
+
649
+ - 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`).
650
+ - 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`.
651
+ - O `GenericCrudService.getSchemasFilteredBaseUrl()` retorna sempre uma URL absoluta; o `SchemaMetadataClient` também aceita `baseUrl` relativo quando há origin disponível.