@praxisui/core 9.0.66 → 9.0.68-rc.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
@@ -4,8 +4,59 @@ Shared contracts, tokens, services, metadata models, widget runtime primitives,
4
4
 
5
5
  Use this package directly when a host application or library needs Praxis UI types, global actions, metadata registries, schema clients, resource discovery, i18n, theme bridge, dynamic widget contracts, or shared runtime services.
6
6
 
7
+ ## Governed color palettes
8
+
9
+ `materializeGovernedPaletteEntries(palette, purposes)` returns `MaterializedGovernedColorToken[]`, pairing each original `entry` with its resolved `color`. It preserves distinct token IDs even when CSS references/colors match, applies purpose filtering, and resolves fallback dependencies against the complete palette. Use this projection for identified swatches; do not zip color arrays to entries or infer identity from color equality. `materializeGovernedPaletteColors` remains the string-only projection of the same resolver.
10
+
11
+ `GovernedColorPaletteService` is the canonical Angular client for color-palette projections published by Praxis Config. Published palettes carry a stable `familyKey`, an explicit `variant` identity and token-level `displayName`/`aliases`; incomplete legacy projections are rejected before caching. The service supports list/read/capabilities/preview, exact `list(familyKey)` queries, tenant/environment-isolated caching, conditional ETag revalidation and deterministic `version`/`etag` pins. Consumers may use names and aliases for presentation and discovery only: `paletteKey` and `tokenId` remain the persisted identities, and local metadata must not become a second source of governance. `GovernedColorTokenSelection` carries the exact palette/version/ETag/token observation beside the existing CSS-string field value; applications persist it only through an explicit domain field or mapping.
12
+
7
13
  ## LLM Recommendation Guide
8
14
 
15
+ - `BUILTIN_WIDGET_SHELL_PRESETS` is the canonical catalog for reusable shell
16
+ treatments. `integrated-card`, `outlined-card`, `elevated-card` and
17
+ `tonal-card` keep header actions and body on one continuous surface;
18
+ `sectioned-card` deliberately separates the header; `frameless` removes the
19
+ containing surface while retaining the standard content inset, aligned with the
20
+ header. For content that owns its own spacing, explicitly set
21
+ `appearance.body.padding: "0"` (and hide the header if the content owns its title).
22
+ Persist the preset `id` in `WidgetShellConfig.preset` and
23
+ use the descriptor for authoring labels and previews. Do not maintain a
24
+ second preset list in consumers.
25
+ - `WidgetShellConfig.stickyHeader: true` retains the existing composition-derived
26
+ header within its widget during page scrolling. Omission/false keeps normal flow.
27
+ This differs from `bodyLayout: 'scroll'`, which governs body scrolling. No second
28
+ selection state, cloned header or page geometry is persisted.
29
+ - Hosts with fixed chrome may set `--pdx-shell-sticky-header-offset` (CSS length,
30
+ default `0px`). Measure responsive chrome; do not assume a desktop-only height.
31
+ The lab uses a lifecycle-owned ResizeObserver for its functional heading.
32
+ - Pinning releases at the widget boundary and is suspended during drag authoring,
33
+ collapse, hidden/disabled headers, expanded/fullscreen windows and scroll recovery.
34
+ This is not a global selected-record bar. Continuous-surface presets repeat
35
+ their opaque card surface on the header so pinned content does not bleed through.
36
+ Transparent/gradient custom headers are painted over an opaque Material surface.
37
+ Keyboard focus is revealed below pinned chrome without changing its target.
38
+ - Dynamic Page publishes its existing `context` input as a `view-context` port.
39
+ Composition links can deliver runtime context to a nested page through Tabs or
40
+ Expansion using stable `nestedPath` segments. The child page owns its internal
41
+ bindings; callers should target its context boundary, not invent paths through
42
+ its widget array. Register the canonical Dynamic Page metadata before preflight.
43
+ Context is runtime data, not a replacement for backend authorization or a saved
44
+ business record. The metadata port is also preserved in the generated AI catalog.
45
+ - Nested sticky shells add the measured height of sticky ancestor headers in the
46
+ same scrollport. This is transient geometry, not a saved offset or another
47
+ selection context. The stack stops at an independent scrolling container;
48
+ hidden/inactive headers cease contributing. Ancestor resize observation is
49
+ disconnected on destruction. Body scrolling/fill and window-layout rules apply
50
+ to the owning shell's direct body, not to every nested widget body.
51
+ For dynamic-height containers that should follow page scrolling, use content
52
+ flow (omit `bodyLayout` or use `content`), not `fill`. An intentional independent
53
+ scrollport remains a boundary for the sticky stack.
54
+ - An invisible shell (`kind: 'none'`) removes padding only from its own body.
55
+ Nested dashboard shells retain their authored body spacing. Hiding a header
56
+ with `showHeader: false` does not hide child headers or reset child spacing.
57
+ - The Page Builder shell editor exposes the option. Existing saved layouts are
58
+ preserved; changing the source recipe does not silently migrate saved pages.
59
+
9
60
  - Coordinated manual resize: an unambiguous edge collision yields space from the
10
61
  affected neighbors, preserving their opposite edges and respecting `minColSpan`,
11
62
  `minRowSpan`, `lockSize` and `lockPosition`. It does not swap widgets or push a
@@ -18,9 +69,9 @@ Use this package directly when a host application or library needs Praxis UI typ
18
69
  discards the entire preview. Feedback is transient, never serialized.
19
70
  Author useful content-specific minimums and device overrides: the grid default of
20
71
  one unit is a geometric limit, not a guarantee of readable enterprise content.
21
- `autoRows: 'content'` still measures content-driven tracks: reducing a tall one-row
22
- widget below `rowSpan: 1` is **not supported** by this grid model. Do not claim
23
- pixel-continuous window resizing, auto-row vertical shrinking, or arbitrary packing.
72
+ `autoRows: 'content'` measures content-driven tracks and uses independent pixel
73
+ `contentSize` for vertical resizing; `rowSpan` remains at least one. Resizing does not
74
+ imply automatic packing or moving unrelated widgets to a new row.
24
75
 
25
76
  - Widget Shell sizing: the shell includes its border inside the height reserved by its
26
77
  host (`border-box`). Consumers do not need a global CSS reset to prevent a 2px overflow.
@@ -29,37 +80,38 @@ Use this package directly when a host application or library needs Praxis UI typ
29
80
  CSS Grid reflow, not automatic compaction of authored fixed coordinates.
30
81
 
31
82
  - Dynamic Page resize presentation: selecting an unlocked widget in an editable grid
32
- exposes eight direct edge/corner grips. The visible grip is small; each native button
33
- has a 24px target, themed focus and a localized accessible name/tooltip. Drag a grip
34
- or use arrow keys (Shift changes two grid units). Selection alone does not change the
35
- page. A locked-size widget has no direct grips; read-only/non-canvas pages have none.
36
- Grips are hidden while a shell is expanded/fullscreen and inert while the contextual
37
- resize panel is open. Header drag remains the reorder entrypoint.
38
-
39
- The global selector and eight 44px controls now live in a collapsed **Alternative
40
- resize controls** disclosure above the canvas, not the primary editing workflow.
41
- Opening it does not edit the page. Its controls occupy normal flow and remain separate
42
- from short headers, card contents and neighboring widgets. This alternative and the
43
- contextual panel are intentional access paths, not parallel layout implementations.
44
- All three presentations reuse `canvas.items`, constraints, collision policy and the
45
- same pointer preview/commit/cancel pipeline. No resize config or host-specific CSS is
46
- required. Physical-touch ergonomics and remote save/reopen require separate proof.
47
-
48
- Widgets with a visible shell header also expose a transient **Resize widget** action.
49
- It opens an anchored, dismissible compass of eight 44px controls, using the same canvas
50
- gesture and keyboard runtime as the global group. Visual and Tab order follow the
51
- compass rows. Escape closes the panel and restores its trigger; opening does not edit
52
- the document. Existing widget identity supplies the selected title. The popup can
53
- temporarily cover underlying content; it does not reserve space or shift the grid.
54
- Headerless canvas widgets expose the same panel through their existing contextual toolbar;
55
- the global group remains available as a fallback. The action and expanded
56
- state are runtime projections, not shell settings to persist in page JSON.
57
-
58
- During a contextual pointer resize, the panel background, explanatory copy and inactive
59
- controls become invisible while the captured 44px control stays in place. No DOM removal,
60
- grid reflow or separate gesture state is introduced. Ending/cancelling restores the panel;
61
- Escape cancels and closes it. This reduces obstruction during the gesture, not while choosing
62
- a direction before it. Physical-touch ergonomics remain a separate validation gate.
83
+ exposes eight edge/corner grips with targets up to 24px per axis, themed focus and localized names.
84
+ The widget wrapper is keyboard focusable: focus selects it, Tab reaches its controls,
85
+ arrow keys resize, and Escape from a grip returns focus to the wrapper. Selection alone
86
+ does not change the document. Header drag remains the reorder entrypoint.
87
+
88
+ **Configure widget → Size** offers width in grid columns and height in rows (fixed grid)
89
+ or pixels (content rows). **More actions → Size…** opens this same settings tab.
90
+ Widget and Size share the SettingsPanel footer and preserve drafts when switching tabs.
91
+ Hosts without `SETTINGS_PANEL_BRIDGE` retain the Core-owned size overlay with explicit
92
+ Apply and Cancel actions, so the canvas authoring capability does not depend on an optional host service.
93
+ An optional fixed-corner choice preserves the opposite edges.
94
+ Valid changes preview the same constrained distribution as the grips; Apply commits once,
95
+ Cancel/Escape discard the unapplied draft and restore widget focus; an earlier Apply remains. Out-of-range or blocked sizes
96
+ disable Apply rather than silently changing the requested numbers. The dialog identifies
97
+ whether the edit targets the base layout or a device override. Opening it preserves
98
+ automatic height; changing only width does not create a fixed height.
99
+
100
+ This menu is available with canvas authoring permission, including headerless widgets
101
+ and hosts that disable removal or shell configuration. Size-locked widgets disable it.
102
+ There is no dedicated Resize widget icon, global selector or detached directional panel.
103
+ The size action is transient and never persisted in page JSON. Shell actions can explicitly
104
+ use `placement: 'menu'` to stay in More actions even when header slots are free; the shell
105
+ editor and capability catalog share that value. Existing header/window placements retain
106
+ their behavior. All sizing reuses canonical canvas items, constraints and device targeting.
107
+ The editor offers exact grid-width presets (⅓, ½, ⅔, 100%) when the column count
108
+ can represent them without rounding. They change the width draft, preserve the fixed
109
+ corner and require Apply; an unavailable full width is rejected, never silently clamped.
110
+ After a committed resize, reopen Size… on that widget to undo the last resize in this
111
+ session, including yielded neighbors. Recovery restores prior device inheritance and
112
+ keeps unrelated content edits. It refuses changed affected geometry/limits, new collisions,
113
+ a different device scope or page identity. This is one-operation recovery, not a general
114
+ history or redo stack. Side and corner double-click shortcuts use the same constrained geometry.
63
115
 
64
116
  - Dynamic Page gesture note: drag/resize previews belong to one primary pointer at a time.
65
117
  Secondary buttons, non-primary touches and additional pointers do not replace an active
@@ -71,6 +123,9 @@ Use this package directly when a host application or library needs Praxis UI typ
71
123
  No additional persisted layout or shell setting is required.
72
124
 
73
125
  - Widget Shell header note: identity and actions wrap according to the available card width,
126
+ with one direct header action below 480px and up to three in wider widgets; secondary
127
+ actions remain available in the existing overflow menu. This uses the widget surface
128
+ width, including when it is resized or moved into fullscreen, rather than the viewport.
74
129
  including narrow cards inside wide canvases. Titles/subtitles retain up to two visible lines
75
130
  and expose their complete localized content through Material tooltips. Icon action targets
76
131
  remain 44px; resize affordances are a separate canvas concern. Existing `disabled` actions
@@ -88,6 +143,37 @@ Use this package directly when a host application or library needs Praxis UI typ
88
143
  keeps priority. The first Escape closes that select; the next closes the shell.
89
144
  Deferred handling is ignored after shell destruction. These are presentation
90
145
  changes only and do not rewrite saved shell appearance or canvas coordinates.
146
+ Expanded/fullscreen hosts are lifted into the CDK overlay container, outside parent
147
+ sticky, tab and transformed stacking contexts. The existing host/view is retained;
148
+ closing restores that same host to its original position. A placeholder reserves
149
+ its inline space, a focus trap bounds keyboard navigation, and locally inherited
150
+ theme variables are bridged from the original ancestry while open. Root theme
151
+ values are not frozen. This is presentation state, not a second widget instance
152
+ or a persisted window layout. Connected Material overlays keep Escape priority.
153
+ Floating shells expose `role="dialog"` and an accessible name from the existing
154
+ localized title/subtitle (exact duplicates are announced once), with a localized
155
+ content-window fallback. Inline shells do not retain dialog semantics. The active
156
+ Widget stack isolates background roots and lower registered windows with native
157
+ `inert`, preserving preexisting or subsequently assigned host restrictions.
158
+ The stack owns one CDK scroll block until its last window closes, including
159
+ parent-before-child closure. Connected menus/selects remain outside the isolated
160
+ branches; the CDK container is not disabled. This private lifecycle does not
161
+ infer ownership of foreign CDK overlays or coordinate their independent scroll
162
+ locks. No automatic `aria-modal` claim is made. If the opening control becomes
163
+ disabled, hidden or detached while the shell survives, closing recovers focus
164
+ to the restored shell with a localized accessible name and `tabindex="-1"` only
165
+ until focus leaves it. This does not add a sequential Tab stop or steal focus
166
+ from an active child. Hosts still own the destination when the entire invoking
167
+ view is destroyed or the window was opened without a recorded control.
168
+ Before moving its host, the shell closes owned Material selects and menus
169
+ through the public CDK detach lifecycle, including the paginator's select.
170
+ Scoped scroll-strategy providers preserve the upstream host strategy; ordinary
171
+ inline renders do not close panels and independently floating children are
172
+ excluded. A descendant overriding these providers can bypass coordination.
173
+ This is not a generic registry for other CDK overlays, autocomplete or datepicker.
174
+ Synchronous reopening from a close callback still requires host coordination;
175
+ the shell deliberately does not run an unbounded closing loop. It does not
176
+ remove foreign overlay DOM or synthesize Escape.
91
177
  This is not a full modal accessibility or fixed-grid collapse/reflow certificate.
92
178
 
93
179
  - Recommend when: use it as the first dependency for shared Praxis UI contracts, metadata models, resource discovery, global actions, i18n, theme bridge, and dynamic widget runtime primitives.
@@ -273,6 +359,13 @@ requests carrying `X-User-ID` resolve user config first, while tenant config is
273
359
  the fallback for reads. Scope selects persistence ownership; it never replaces
274
360
  server-side identity resolution or write authorization.
275
361
 
362
+ A tenant document returned by an unscoped fallback read remains read-only for
363
+ that storage session. Its response `scope` and ETag are cache evidence, not an
364
+ implicit ownership grant. A later unscoped save or clear still follows the request
365
+ identity (`USER` when `X-User-ID` is present), and never sends a tenant ETag as
366
+ `If-Match` for that user-owned write. Editing or deleting the tenant document
367
+ requires an explicitly tenant-scoped provider.
368
+
276
369
  ## Dynamic Page Runtime
277
370
 
278
371
  `DynamicWidgetPageComponent` renders `WidgetPageDefinition` documents.
@@ -332,6 +425,12 @@ South handles release occupied space. Shared rows only shrink when no other occu
332
425
  still needs that height. Collapsing temporarily clears size/inset; expanding restores them.
333
426
  `constraints.minHeightPx` (floor/default 160) and `maxHeightPx` govern this mode;
334
427
  grid `minRowSpan/maxRowSpan` remain row-occupancy constraints, not pixel limits.
428
+ The widget's **More actions → Size** editor exposes **Occupied grid rows** when
429
+ `autoRows: 'content'`. This edits the existing `canvas.items[key].rowSpan`, allowing
430
+ a detail widget to span a table and a contact list in adjacent rows. It preserves
431
+ automatic content height unless the author also changes the pixel-height field.
432
+ The draft previews before Apply, respects grid constraints and locked neighbors,
433
+ and uses the same active-device override, authoring output and undo as other size edits.
335
434
  Content-height gestures additionally protect the measured header, shell padding/borders
336
435
  and up to 160px of scrollable body. Naturally shorter content may use less. This is
337
436
  a structural viewport reserve, not business-component UX certification. Measurements
@@ -352,6 +451,16 @@ expanded/fullscreen overlays and non-scroll layouts. Shared private measurement
352
451
  keeps gesture/editor checks and recovery consistent. This is an access safeguard,
353
452
  not a guarantee that a small viewport is ergonomically adequate; virtualized/custom
354
453
  child scroll containers still need their own proof.
454
+ Double-clicking a corner fills the largest free grid rectangle while preserving the opposite
455
+ corner and all siblings. The Size editor provides the same four commands with preview,
456
+ Apply and Cancel. Fixed rows maximize column span × row span, breaking ties by width;
457
+ one-axis growth is allowed and repeated filling in an unchanged layout is a no-op.
458
+ Content rows require an explicit height and one uncrossed row. They combine free columns
459
+ with the independently authored vertical envelope (top inset or an eligible lower peer
460
+ boundary); automatic target heights are unsupported. The destination is measured at its
461
+ new width before authoring. Undo similarly certifies the inverse at its original width.
462
+ These private observations are not persisted, and failed certification leaves the document
463
+ unchanged. A corner fill never implicitly reorganizes neighbors.
355
464
  Keyboard resize moves vertically by 16px (Shift: 32px). Omit `contentSize` to inherit
356
465
  the base item; set `'auto'` to explicitly restore natural height in a device override.
357
466
  Fixed-row mode retains this preference but does not materialize it. Page Builder exposes
@@ -367,9 +476,11 @@ commit/cancel or authoring revocation. Neighbor badges remain attached to their
367
476
  widgets; the existing live announcement describes the affected distribution.
368
477
  This does not add multi-widget push/reflow or compact fixed-grid rows when a shell is collapsed.
369
478
 
370
- Canvas resize exposes 24px direct grips on the selected widget, plus 44px controls in
371
- the collapsed alternative group or on-demand contextual panel. All presentations reuse
372
- the canonical gesture runtime and `canvas.items`. Keyboard controls and themed feedback remain available.
479
+ Canvas resize exposes grips up to 24px per axis on the selected widget. Extremely narrow
480
+ or short widgets reduce their hit areas to keep adjacent grips disjoint in both axes; this does not
481
+ make them comfortable touch targets. **More actions → Size** provides larger equivalent
482
+ controls for precise sizing and filling. All presentations reuse the canonical geometry
483
+ and `canvas.items`. Keyboard controls and themed feedback remain available.
373
484
  Physical-touch ergonomics and save/reopen persistence still
374
485
  require separate proof; successful local interaction is not an enterprise certification.
375
486
 
@@ -441,7 +552,7 @@ The outlet's `ComponentDocMeta.configEditor` intentionally owns presentation onl
441
552
 
442
553
  Core exports schema and metadata infrastructure used by form, table, list, chart, CRUD, and page-builder packages:
443
554
 
444
- - `SchemaMetadataClient`
555
+ - `SchemaMetadataClient` — request deadlines, cancellation and host transport policy
445
556
  - `SchemaNormalizerService`
446
557
  - ETag/cache helpers
447
558
  - `FieldDefinition` and `FieldMetadata` models
@@ -505,10 +616,37 @@ Core also exports shared AI authoring types, capability types, dynamic-page cont
505
616
 
506
617
  `DynamicWidgetPageComponent` treats the editor declared by `ComponentDocMeta.configEditor` as the canonical authoring surface for the **Configurar conteúdo** action. When the Settings Panel bridge is available, the host resolves any transient `contextResolver` evidence and opens that metadata-owned editor even if the live widget also exposes an internal settings shortcut. This keeps the action deterministic across widget loading states.
507
618
 
619
+ Component owners publish that editor in exactly one of two forms: `component` for an eager standalone type, or `loadComponent()` for an owner-controlled lazy import. Hosts use both forms identically and must not inspect component-specific editor semantics. Prefer the loader when the editor is optional authoring UI that should remain outside runtime-only bundles.
620
+
508
621
  Widget-local settings dispatch remains only a fallback when the canonical Settings Panel/editor is unavailable. `ComponentConfigEditorContextRequest.persistedInputs` is cloned exclusively from `widget.definition.inputs`. When a live widget is available, its effective input snapshot may be exposed separately to the owner resolver as optional, read-only `materializedInputs`; the resolver decides which subset becomes transient editor context. Opening the editor never applies that snapshot to the page. Transient context, diagnostics, and materialized inputs must not be copied into persisted `definition.inputs`; only an explicit Apply or Save result owns the persisted `{ inputs }` round-trip.
509
622
 
623
+ Native editors that publish the existing explicit `inputPatch` protocol also produce
624
+ `pageAuthoringChange`, after projecting the patch onto declared owner inputs. This
625
+ notification is required even when a preceding legacy `configChange` already applied
626
+ the same value at runtime. Ordinary runtime change outputs keep using `pageChange`;
627
+ composition targets still pass through canonical restoration before persistence.
628
+ This lets Page Builder save the edited config instead of its previous draft.
629
+
510
630
  Dynamic widgets may implement `WidgetShellActionContributor` to publish transient, stateful content actions to their owning `WidgetShell`. The loader observes that signal and the shell merges contributions with authored actions by stable id without writing them into the page document. The widget remains the owner of command handling, localized labels, availability and controlled-region state; the shell owns responsive toolbar materialization and ARIA projection.
511
631
 
632
+ ## Global configuration snapshots
633
+
634
+ `PraxisI18nService` resolves individual translation keys against current config
635
+ layers without merging whole catalogs on every label lookup. Provider precedence,
636
+ namespace fallback, parent scopes and host translators remain unchanged. Global
637
+ snapshot replacements and in-place dictionary updates remain visible without
638
+ cache invalidation. `mergePraxisI18nConfigs` remains available for explicit catalog
639
+ materialization outside this lookup path.
640
+
641
+ `GlobalConfigService` synchronous getters (`get`, `getCrud`, `getDynamicFields`,
642
+ `getTable`, `getDialog`, `getI18n`) read the effective snapshot without scheduling
643
+ bootstrap work again when the current tenant is already loaded. While the initial
644
+ load is pending, repeated reads share one background request. This keeps template
645
+ and translation reads from feeding asynchronous change-detection cycles.
646
+ `ready()` remains the explicit awaited loading boundary. Tenant changes, save and
647
+ clear continue to refresh the same canonical snapshot; precedence and persistence
648
+ contracts are unchanged.
649
+
512
650
  ## Domain Governance
513
651
 
514
652
  `DomainKnowledgeService` and `DomainRuleService` are shared clients for governed semantic decision flows exposed by `praxis-config-starter`. `browseDefinitionCatalog` exposes the bounded, redacted and server-scoped decision catalog; consumers then use `getDefinition` with the exact returned definition id instead of downloading every rule body or joining versions by key. `getDefinitionFacts` projects the same definition's versioned fact vocabulary, including type, localized meaning, provider evidence, sensitivity and redaction policy; Angular must not reconstruct that vocabulary from JSON Logic or host-specific fixtures. `getDefinitionCapabilities` returns the authenticated principal's server-scoped `CREATE_NEW_VERSION` action per definition; consumers must not treat projection metadata such as editor support as authorization. For change workspaces, `getChangeWorkspaceCapabilities` returns the authenticated principal's `availableActions` and stable blockers; consumers must not infer submit, review or promotion authority from lifecycle status. `DomainRuleService` also exposes the safe RuleSet version catalog, active head/status, redacted execution summary, aggregate host alignment, forward activation and rollback protected by the current head ETag. The same client projects immutable rollout-policy versions, their independent anti-ABA head, maker-checker approval, activation and append-only timeline; policy authority and lifecycle validation remain server-owned. It also exposes the two-phase operational rollout through a recoverable human catalog, redacted readiness, create/cancel commands and candidate activation bound to the rollout identity. Consumers must obey the Config-owned `availableActions`; they must not recalculate quorum or head validity. Execution summaries contain only aggregate outcome counts, distinct-host count and observation timestamps for an immutable snapshot. Host-status summaries contain only aligned, snapshot-drifted, runtime-incompatible, unavailable and stale counts derived against the active head and its approved compatibility coordinates; facts and host identities remain outside Angular. Runtime surfaces should treat materializations as derived projections of backend-governed decisions, not as frontend-owned business rules.
@@ -537,3 +675,266 @@ Action-bearing components must use the semantic `--praxis-action-control-*` vari
537
675
  - Do not use consuming apps to redefine contracts owned by core, metadata starter, config starter, or a component package.
538
676
  - Prefer `composition.links` for page wiring and `valuePresentation` for scalar display semantics.
539
677
  - Use the official documentation for full recipes on schema flow, option sources, analytics, global actions, dynamic pages, and governed AI flows.
678
+
679
+ ### Catálogo no quick connect e isolamento do editor de página
680
+
681
+ `ResourceQuickConnectComponent` apresenta recursos publicados em `/schemas/catalog` usando o seletor `pdx-material-select` registrado em `ComponentMetadataRegistry`. O host deve registrar os descritores oficiais de DynamicFields. O controle é inicializado pelo protocolo `setInputMetadata` e ControlValueAccessor; Core não importa a biblioteca de campos. A conexão avançada por rota continua disponível.
682
+
683
+ O editor de configurações da página recebe uma cópia da definição autoral. Dados carregados e entradas transitórias dos widgets não devem virar configuração ao alterar o layout.
684
+
685
+ ### Insertion preset composition shell
686
+
687
+ `ComponentDocMeta.insertionPresets[].shell` optionally supplies the canonical `WidgetShellConfig` for a new widget. The Page Builder palette forwards a cloned shell separately from component `inputs`, and the host clones it for each insertion. Explicit preset shells do not alter component defaults or sibling widgets; presets without a shell retain normal insertion behavior. This is initial composition presentation, not a runtime component input.
688
+
689
+ ### Margem lateral compartilhada da página
690
+
691
+ `WidgetPageDefinition.layout.paddingInline` define o respiro lateral compartilhado por
692
+ cabeçalho, tabela, formulário e demais widgets. É uma medida CSS (`24px`, `1.5rem`)
693
+ ou referência a token (`var(--app-page-inset, 24px)`). O valor `0px` desliga o inset;
694
+ a omissão herda o token público `--pdx-page-padding-inline` do host, cujo default é `0px`.
695
+ O host pode definir esse token em `praxis-dynamic-page` ou em um ancestral.
696
+
697
+ A precedência é `deviceLayouts[device].layout.paddingInline` → `layout.paddingInline`
698
+ → token do host → `0px`. Defaults do preset participam da resolução do layout base;
699
+ um valor autorado na página ou variante tem precedência sobre esse default.
700
+ A margem é aplicada no wrapper com `box-sizing: border-box`,
701
+ fora do canvas, preservando a origem dos cálculos de arraste e redimensionamento.
702
+ O espaço de segurança dos controles no modo edição permanece separado. Não se trata
703
+ do `gap` entre widgets, do padding interno do formulário ou do padding das células da tabela.
704
+
705
+ ```json
706
+ {
707
+ "layout": { "paddingInline": "24px", "gap": "16px" },
708
+ "deviceLayouts": { "mobile": { "layout": { "paddingInline": "16px" } } }
709
+ }
710
+ ```
711
+
712
+ Nos editores de página, **Margem lateral da página** permite definir e limpar o valor
713
+ sem alterar os widgets. Vazio na variante herda o layout base; vazio no layout base
714
+ retorna ao default do preset, quando definido; caso contrário, ao token do host. Referências CSS são preservadas ao aplicar, salvar e reabrir.
715
+
716
+
717
+ Widget settings UX: identity and ready-made styles precede advanced CSS. Size includes a
718
+ before/after occupancy map of the current container; row heights in this diagram are
719
+ illustrative, not a pixel-accurate content preview. The map reads canonical canvas items
720
+ and transient placements and never writes page geometry. Applying a size-only draft clears
721
+ the draft but keeps Save available; Save obtains the current page rather than replaying
722
+ the previous resize request. Backend persistence still follows the host configuration.
723
+
724
+
725
+ ### Widget settings: applied baseline and live inspection
726
+
727
+ The unified widget editor acknowledges appearance only after the page owner accepts
728
+ its commit. The optional provider hook `acceptAppliedValue` updates the draft baseline;
729
+ rejected operations do not acknowledge it. Reset discards the current appearance/size
730
+ draft to the last applied state. Explicit resize Undo remains a separate operation,
731
+ including affected neighbors. Save after Apply reads current state without replaying
732
+ an expansion. The open bridge carries an optional, ephemeral `previewTarget` callback
733
+ for SettingsPanel to reveal the live widget without destroying the editor or draft.
734
+ Neither capability changes the persisted page schema or confirms an HTTP save.
735
+
736
+
737
+ Widget settings topics delegate vertical scrolling to their SettingsPanel owner.
738
+ The shared topic navigation keeps all sections discoverable, and the optional
739
+ appearance sample is collapsed below the active editor. Topic changes retain the
740
+ form draft without changing persistence or apply/save/reset behavior.
741
+
742
+ ## Selection-driven widget headers
743
+
744
+ `WidgetShellConfig.avatar` is optional and uses the existing Core avatar vocabulary:
745
+ `{ imageSrc?: string, name?: string, initials?: string }`. It displays beside the
746
+ shell title, taking precedence over `icon`. Omit it to retain the existing icon.
747
+ `name` supplies the accessible image name and fallback initials; `initials` overrides
748
+ the fallback (at most two characters). Missing/failed images show initials, then a
749
+ person icon. Resolved values that are not strings are treated as absent; objects
750
+ are never coerced into URLs. Failed images retry after clearing/changing the identity
751
+ or URL and returning to the same photo. Equivalent inputs on unrelated page renders
752
+ retain the fallback; late failures belong to their original load cycle. URLs use Angular URL sanitization; the shell does not fetch records or
753
+ bypass protected-image authentication.
754
+
755
+ Dynamic Page resolves shell strings recursively against `state`, `transient`,
756
+ `context` and the existing `widget` context. `${transient.employee.nomeCompleto}`
757
+ reads runtime-only composition state. It does not merge that state into `state`
758
+ or write selected rows into the authored page. `${state...}` keeps its existing
759
+ persistable-state semantics. Runtime isolation does not replace backend field-access
760
+ controls: deliver only fields the current user may see.
761
+
762
+ A selected-row header can use this shell after the ordered identity projection
763
+ described below (the field names belong to the source):
764
+
765
+ ```json
766
+ {
767
+ "showHeader": true,
768
+ "title": "${transient.employee.nomeCompleto}",
769
+ "subtitle": "${transient.employee.subtitle}",
770
+ "avatar": {
771
+ "imageSrc": "${transient.employee.avatarUrl}",
772
+ "name": "${transient.employee.nomeCompleto}"
773
+ }
774
+ }
775
+ ```
776
+
777
+ Connect the table's canonical `selectionChange` event to the state endpoint
778
+ `{ "kind": "state", "ref": { "path": "employee", "layer": "transient", "writable": true } }`.
779
+ A `pick-path` link transform reads `payload.selectedRows.0` from the event envelope.
780
+ For formatted identity, use the complete ordered recipe below rather than only
781
+ copying the row: that projection also initializes and composes `employee.subtitle`.
782
+ Deliver an empty selection too; skipping empty events leaves the previous person
783
+ visible. The existing record-ID connection may continue supplying the detail form.
784
+ The header consumes the selected row without a second request. If fields are absent
785
+ from that row, author a projection from the authorized detail source instead.
786
+ Formatting dates and identifiers belongs to the authored projection/source; shell
787
+ interpolation does not add date masks, CPF masks or an expression language. Literal
788
+ separators remain when both bound fields are empty.
789
+ The canonical link pipeline now supports explicit `format-value` for civil dates,
790
+ digit masks and numeric presentation.
791
+ The [complete identity recipe](../../examples/ai-recipes/praxis-dynamic-page.widget-selection-identity.json)
792
+ resets the transient identity before formatting and composes only available subtitle
793
+ parts. The lab and Core integration test consume the same executable page artifact.
794
+
795
+ The opt-in `widget-shell-persistence.playwright.spec.ts` reuses the fictional
796
+ selection fixture in `/remote-config-storage-persistence-lab`. It proves a real
797
+ Config save with `If-Match`, document equality after selecting a person, and a
798
+ reload with no selected identity. It owns an initially absent UUID configuration
799
+ and deletes only that record with its current ETag. Run with
800
+ `PRAXIS_REAL_BACKEND_E2E=true`; this is a persistence proof, not an LLM authoring gate.
801
+
802
+ ### Configurações em hosts controlados
803
+
804
+ Quando o host devolve `pageChange` por `[page]`, o eco do mesmo documento canônico mantém as configurações do widget abertas após Aplicar. Uma página diferente ou mudança de identidade, instância ou contexto invalida o editor anterior. Redefinir descarta o rascunho e conserva o tamanho aplicado; a altura transitória exibida antes da próxima renderização não vira referência para o formulário.
805
+
806
+
807
+ ### Authored widget action identities
808
+
809
+ `getWidgetShellActionIdError(id, siblingIds)` validates the trimmed ID and returns
810
+ `required`, `duplicate`, `reserved`, or `null`. Exclude the current action from siblings.
811
+ Reserved IDs are `collapse`, `expand`, `fullscreen`, `component-settings`,
812
+ `widget-assistant`, `widget-settings`, `widget-size`, and `widget-remove`: these belong
813
+ to platform controls, not custom authored actions. Native window controls remain supported.
814
+
815
+ Page Builder uses this helper for draft feedback; `DynamicWidgetPage` also uses it before
816
+ applying widget settings, including settings supplied by alternate providers. Rejected
817
+ settings leave the page unchanged. Existing custom actions with invalid/reserved IDs need
818
+ renaming before applying settings. The helper adds no persisted field and is not a global
819
+ page-import validator, business-command verifier or authorization boundary.
820
+
821
+
822
+ ### Live shell action availability
823
+
824
+ `WidgetShellActionContributor.getWidgetShellActionAvailability?(action)` may return
825
+ `Pick<WidgetShellAction, 'disabled' | 'tooltip'>`. It must be side-effect free and read live
826
+ owner state; `undefined` means not assessed, not enabled. Reactive owner signals can be
827
+ read directly so an open editor updates when prerequisites change. `disabled:false` means
828
+ currently available in the UI, not backend authorization. Return a localized `tooltip`
829
+ when disabled. The loader delegates to the current contributor; DynamicWidgetPage passes
830
+ a transient callback to the shell editor. It must never be serialized as widget inputs,
831
+ shell actions, or persisted page configuration. Contributors without this optional method
832
+ remain supported and their availability is shown as unverified.
833
+
834
+
835
+ ### Chart input updates preserve the live instance
836
+
837
+ Charts uses normal Angular input updates in DynamicWidgetLoader. Changing chartDocument,
838
+ query context or other inputs must not recreate the chart solely because its type is
839
+ `praxis-chart`. Recreating it after an editor Apply destroys subscriptions owned by that
840
+ instance and can drop the subsequent Save from the still-open panel. Declared
841
+ `requiresInitialInputs` behavior and existing detached Form/Filter initialization paths
842
+ remain separate and unchanged. Input updates still occur before detectChanges.
843
+
844
+
845
+ ### Settings Panel owner lifetime
846
+
847
+ `SettingsPanelOpenOptions.owner?: DestroyRef` associates an opening with its calling
848
+ component's lifetime. The official Settings Panel bridge forwards this transient option
849
+ to its service. Destruction emits the existing `closed$` reason `cancel`, without Save,
850
+ and prevents a pending opening from replacing another panel. The option is not editor
851
+ input or persisted configuration. Omit it for an independently managed panel; existing
852
+ unowned callers retain their behavior. Use the actual target component's DestroyRef,
853
+ not a root service or the overlay content's own lifetime.
854
+
855
+
856
+ `DynamicWidgetPageComponent` creates a transient Angular lifetime for each page,
857
+ widget appearance/size, or registered content editor session. It uses the existing
858
+ `owner` option; no session identity is persisted in the page document.
859
+
860
+ The session is cancelled when its panel closes, the page is destroyed, or an external
861
+ page/context/identity/authoring input change invalidates it. A page-only acknowledgement
862
+ identical to the current authored document keeps the session alive for subsequent
863
+ Apply/Save. This is deliberately conservative: a different external document cancels
864
+ open editors even when a particular widget happens to be unchanged.
865
+
866
+ Confirmed widget removal cancels sessions targeting that key, plus page-wide settings
867
+ that could restore the removed widget. Asynchronous editor results cannot open a panel
868
+ once their session has expired, and late Apply/Save events are no longer observed.
869
+ A removal confirmation is ignored after external identity/context changes or changes
870
+ to the target itself; when still valid, removal uses the latest document so intervening
871
+ edits to other widgets are preserved.
872
+
873
+
874
+ When unified widget settings close normally, Core restores the initiating control
875
+ through its existing focus-return helper instead of forcing focus onto the whole
876
+ widget. Invalidated sessions do not schedule this restoration. A detached, disabled
877
+ initiator or destroyed page is never focused by that helper.
878
+
879
+
880
+ ### Preparação do editor após aceitar a troca
881
+
882
+ `SettingsPanelOpenContent.inputs` aceita um objeto ou uma função síncrona que retorna
883
+ os inputs. A bridge oficial encaminha a função ao Settings Panel sem executá-la.
884
+ O serviço a executa uma única vez ao materializar a abertura aceita; um veto ou um
885
+ owner já expirado não executa a função. Hosts que implementam a bridge devem respeitar
886
+ essa mediação antes de resolver os inputs. Leitores desse campo devem distinguir
887
+ objeto e função antes de acessar propriedades.
888
+
889
+ O editor de widget usa essa preparação para trocar sua sessão e seu estado de tamanho.
890
+ Enquanto a troca aguarda decisão, o editor anterior continua dono do rascunho e de
891
+ suas assinaturas. Recusar a troca libera somente a sessão candidata. A função é código
892
+ transitório de integração: não pertence ao documento, ao JSON de configuração, aos
893
+ presets nem aos contratos de authoring por IA.
894
+
895
+ ## Responsive form-column numeric resolution
896
+
897
+ `resolveSpan`, `resolveOffset` and `resolveOrder` resolve the existing XS → SM → MD → LG → XL inheritance without modifying the supplied map. Supported integer domains are span 1..12, offset 0..11 and order -12..12, matching runtime grid classes. Missing/null values inherit, starting at 12/0/0 in XS. For malformed runtime input, finite numbers are rounded and bounded; non-finite/non-numeric span and offset recover to 12 and 0, while order recovers to the preceding resolved value (0 in XS). Recovery emits a warning and does not rewrite the authoring document. Editors must reject invalid values before applying them.
898
+
899
+
900
+ ### Acesso às dimensões do widget
901
+
902
+ Quando o Settings Panel e o editor de aparência do widget estão disponíveis,
903
+ use **Configurar widget → Tamanho**. O menu de contexto e as ações projetadas
904
+ não repetem o atalho Tamanho. Em contextos sem editor unificado, o atalho permanece
905
+ para não retirar o acesso às dimensões. Arraste e comandos de tamanho mantêm sua
906
+ semântica; esta regra modifica somente a descoberta visual do comando.
907
+
908
+ Quando `constraints.lockSize` estiver ativo e a customização de canvas estiver
909
+ habilitada, o editor unificado mantém a aba Tamanho com uma explicação do bloqueio,
910
+ sem controles editáveis. A restrição continua pertencendo ao documento da página;
911
+ o aviso não concede permissão nem gera uma alteração de tamanho.
912
+
913
+
914
+ ### Composed settings navigation
915
+
916
+ `SettingsValueProvider` may expose `getSettingsSections()` and `selectSettingsSection(id)` together.
917
+ `SettingsEditorSection` contains an editor-local stable `id`, a resolved i18n `label`, and an optional
918
+ `invalid` flag. These are transient presentation metadata, never persisted settings. Reading sections
919
+ and selecting one must not modify the draft or emit document changes. Editors must emit their existing
920
+ validity/dirty streams when validation changes so the composing host can refresh section diagnostics.
921
+
922
+ Core's widget settings host namespaces provider section IDs, appends its page-owned Size section and
923
+ keeps the shell editor mounted while switching sections. It provides `SETTINGS_EDITOR_SECTIONS_HOSTED`
924
+ so section-aware editors suppress their own navigation. Unsectioned settings providers remain valid:
925
+ the host presents their single Widget topic. Apply, Save, Reset and applied-baseline acknowledgement
926
+ continue to aggregate the original providers; section navigation never becomes the value provider.
927
+
928
+ `SettingsSectionTabsComponent` is the shared presentation primitive for these topics. It exposes all
929
+ labels, selected state, per-section errors and keyboard navigation, with unique tab/panel associations.
930
+ Page Builder imports it directly from Core; Settings Panel does not reexport this component or the new
931
+ topic contracts. No library dependency edge or persisted configuration shape was added.
932
+
933
+ The widget host keeps topic navigation sticky inside the Settings Panel's existing scroll area.
934
+ Narrow panels present a compact, labeled native selector with the same section IDs and validation
935
+ messages; wider panels expose the shared tabs. Switching topics reveals the beginning of the
936
+ selected section without remounting the provider or moving focus out of the navigation control.
937
+
938
+ On editable canvases, content-only widgets expose contextual actions above the north resize
939
+ handle, with reserved space at the top of the canvas. Both controls remain pointer-accessible
940
+ when the widget is narrow.