@praxisui/page-builder 9.0.35 → 9.0.36
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 +10 -0
- package/ai/component-registry.json +74 -34
- package/fesm2022/praxisui-page-builder.mjs +484 -132
- package/package.json +4 -4
- package/types/praxisui-page-builder.d.ts +18 -3
|
@@ -7,7 +7,7 @@ import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
|
|
|
7
7
|
import * as i2$1 from '@angular/material/icon';
|
|
8
8
|
import { MatIconModule } from '@angular/material/icon';
|
|
9
9
|
import * as i2$2 from '@praxisui/core';
|
|
10
|
-
import { PraxisIconDirective, providePraxisI18n, BUILTIN_SHELL_PRESETS, PraxisIconButtonComponent, PraxisI18nService, GLOBAL_ACTION_CATALOG, getGlobalActionCatalog, PRAXIS_GLOBAL_ACTION_CATALOG, SurfaceOpenActionEditorComponent, BUILTIN_PAGE_LAYOUT_PRESETS, BUILTIN_PAGE_THEME_PRESETS, SETTINGS_PANEL_DATA as SETTINGS_PANEL_DATA$1, NestedWidgetConfigAccessor, deepMerge, generateId, ComponentMetadataRegistry, ObservabilityDashboardService, PraxisRuntimeComponentObservationRegistryService, domainKnowledgeTimelineToRichContentDocument, DomainRuleService, DomainKnowledgeService, SETTINGS_PANEL_BRIDGE, DynamicWidgetPageComponent, DYNAMIC_PAGE_SHELL_EDITOR } from '@praxisui/core';
|
|
10
|
+
import { PraxisIconDirective, providePraxisI18n, BUILTIN_SHELL_PRESETS, PraxisIconButtonComponent, PraxisI18nService, GLOBAL_ACTION_CATALOG, getGlobalActionCatalog, PRAXIS_GLOBAL_ACTION_CATALOG, SurfaceOpenActionEditorComponent, BUILTIN_PAGE_LAYOUT_PRESETS, BUILTIN_PAGE_THEME_PRESETS, SETTINGS_PANEL_DATA as SETTINGS_PANEL_DATA$1, NestedWidgetConfigAccessor, CompositionValidatorService, deepMerge, generateId, ComponentMetadataRegistry, ObservabilityDashboardService, PraxisRuntimeComponentObservationRegistryService, domainKnowledgeTimelineToRichContentDocument, DomainRuleService, DomainKnowledgeService, SETTINGS_PANEL_BRIDGE, DynamicWidgetPageComponent, DYNAMIC_PAGE_SHELL_EDITOR } from '@praxisui/core';
|
|
11
11
|
import * as i3 from '@angular/material/tooltip';
|
|
12
12
|
import { MatTooltipModule } from '@angular/material/tooltip';
|
|
13
13
|
import * as i3$1 from '@angular/material/form-field';
|
|
@@ -4043,10 +4043,10 @@ function buildConnectionsViewerModel(page) {
|
|
|
4043
4043
|
const fromWidgetId = link.from.kind === 'component-port' ? link.from.ref.widget : null;
|
|
4044
4044
|
const toWidgetId = link.to.kind === 'component-port' ? link.to.ref.widget : null;
|
|
4045
4045
|
const fromNestedPath = link.from.kind === 'component-port' && link.from.ref.nestedPath?.length
|
|
4046
|
-
? clone$
|
|
4046
|
+
? clone$3(link.from.ref.nestedPath)
|
|
4047
4047
|
: null;
|
|
4048
4048
|
const toNestedPath = link.to.kind === 'component-port' && link.to.ref.nestedPath?.length
|
|
4049
|
-
? clone$
|
|
4049
|
+
? clone$3(link.to.ref.nestedPath)
|
|
4050
4050
|
: null;
|
|
4051
4051
|
const toStatePath = link.to.kind === 'state' ? link.to.ref.path : null;
|
|
4052
4052
|
const diagnosticReasons = [];
|
|
@@ -4131,7 +4131,7 @@ function formatPathSegment$1(segment) {
|
|
|
4131
4131
|
segment.id || segment.key || segment.index,
|
|
4132
4132
|
].filter((part) => part !== undefined && part !== '').join(':');
|
|
4133
4133
|
}
|
|
4134
|
-
function clone$
|
|
4134
|
+
function clone$3(value) {
|
|
4135
4135
|
if (value == null || typeof value !== 'object')
|
|
4136
4136
|
return value;
|
|
4137
4137
|
return JSON.parse(JSON.stringify(value));
|
|
@@ -4443,8 +4443,8 @@ function buildCompositionLinkFromPorts(source, target, links) {
|
|
|
4443
4443
|
const transform = inferCanonicalTransform(source, target);
|
|
4444
4444
|
return {
|
|
4445
4445
|
id: uniqueConnectionLinkId(source, target, links),
|
|
4446
|
-
from: clone$
|
|
4447
|
-
to: clone$
|
|
4446
|
+
from: clone$2(source.endpoint),
|
|
4447
|
+
to: clone$2(target.endpoint),
|
|
4448
4448
|
intent: inferConnectionIntent(source, target),
|
|
4449
4449
|
...(transform ? { transform } : {}),
|
|
4450
4450
|
metadata: {
|
|
@@ -4585,7 +4585,7 @@ function componentPort(widgetId, port, direction, componentType, nestedPath, lab
|
|
|
4585
4585
|
port,
|
|
4586
4586
|
direction,
|
|
4587
4587
|
...(componentType ? { componentType } : {}),
|
|
4588
|
-
...(nestedPath?.length ? { nestedPath: clone$
|
|
4588
|
+
...(nestedPath?.length ? { nestedPath: clone$2(nestedPath) } : {}),
|
|
4589
4589
|
},
|
|
4590
4590
|
};
|
|
4591
4591
|
return {
|
|
@@ -4599,7 +4599,7 @@ function componentPort(widgetId, port, direction, componentType, nestedPath, lab
|
|
|
4599
4599
|
label,
|
|
4600
4600
|
kind: direction,
|
|
4601
4601
|
endpoint,
|
|
4602
|
-
...(nestedPath?.length ? { nestedPath: clone$
|
|
4602
|
+
...(nestedPath?.length ? { nestedPath: clone$2(nestedPath) } : {}),
|
|
4603
4603
|
};
|
|
4604
4604
|
}
|
|
4605
4605
|
function statePort(path) {
|
|
@@ -4867,7 +4867,7 @@ function readArray(value) {
|
|
|
4867
4867
|
function readString(value) {
|
|
4868
4868
|
return typeof value === 'string' && value.trim() ? value : undefined;
|
|
4869
4869
|
}
|
|
4870
|
-
function clone$
|
|
4870
|
+
function clone$2(value) {
|
|
4871
4871
|
if (value == null || typeof value !== 'object')
|
|
4872
4872
|
return value;
|
|
4873
4873
|
return JSON.parse(JSON.stringify(value));
|
|
@@ -7343,7 +7343,7 @@ class ConnectionEditorComponent {
|
|
|
7343
7343
|
...(current ?? {}),
|
|
7344
7344
|
debounceMs: 150,
|
|
7345
7345
|
distinct: true,
|
|
7346
|
-
delivery: '
|
|
7346
|
+
delivery: 'sync',
|
|
7347
7347
|
};
|
|
7348
7348
|
}
|
|
7349
7349
|
applyNodeRotation(node) {
|
|
@@ -11382,9 +11382,10 @@ class PageConfigEditorComponent {
|
|
|
11382
11382
|
this.patchDeviceLayoutForm(this.deviceMobileForm, deviceLayouts?.mobile);
|
|
11383
11383
|
}
|
|
11384
11384
|
buildDeviceLayouts() {
|
|
11385
|
-
const
|
|
11386
|
-
const
|
|
11387
|
-
const
|
|
11385
|
+
const current = this.currentPageInput()?.deviceLayouts;
|
|
11386
|
+
const desktop = this.buildDeviceLayoutVariant(this.deviceDesktopForm, current?.desktop);
|
|
11387
|
+
const tablet = this.buildDeviceLayoutVariant(this.deviceTabletForm, current?.tablet);
|
|
11388
|
+
const mobile = this.buildDeviceLayoutVariant(this.deviceMobileForm, current?.mobile);
|
|
11388
11389
|
if (desktop === null || tablet === null || mobile === null)
|
|
11389
11390
|
return null;
|
|
11390
11391
|
const deviceLayouts = {};
|
|
@@ -11419,7 +11420,7 @@ class PageConfigEditorComponent {
|
|
|
11419
11420
|
widgetOverridesJson: variant?.widgetOverrides ? JSON.stringify(variant.widgetOverrides, null, 2) : '',
|
|
11420
11421
|
}, { emitEvent: false });
|
|
11421
11422
|
}
|
|
11422
|
-
buildDeviceLayoutVariant(form) {
|
|
11423
|
+
buildDeviceLayoutVariant(form, current) {
|
|
11423
11424
|
const breakpoints = this.parseJson(form.controls.breakpointsJson.value, {});
|
|
11424
11425
|
const groupingOverrides = this.parseJson(form.controls.groupingOverridesJson.value, []);
|
|
11425
11426
|
const widgetOverrides = this.parseJson(form.controls.widgetOverridesJson.value, {});
|
|
@@ -11434,13 +11435,19 @@ class PageConfigEditorComponent {
|
|
|
11434
11435
|
layout.gap = form.controls.gap.value.trim();
|
|
11435
11436
|
if (Object.keys(breakpoints).length)
|
|
11436
11437
|
layout.breakpoints = breakpoints;
|
|
11437
|
-
const variant = {};
|
|
11438
|
+
const variant = this.clone(current || {});
|
|
11438
11439
|
if (Object.keys(layout).length)
|
|
11439
11440
|
variant.layout = layout;
|
|
11441
|
+
else
|
|
11442
|
+
delete variant.layout;
|
|
11440
11443
|
if (groupingOverrides.length)
|
|
11441
11444
|
variant.groupingOverrides = groupingOverrides;
|
|
11445
|
+
else
|
|
11446
|
+
delete variant.groupingOverrides;
|
|
11442
11447
|
if (Object.keys(widgetOverrides).length)
|
|
11443
11448
|
variant.widgetOverrides = widgetOverrides;
|
|
11449
|
+
else
|
|
11450
|
+
delete variant.widgetOverrides;
|
|
11444
11451
|
return variant;
|
|
11445
11452
|
}
|
|
11446
11453
|
hydrateStateEditor(state) {
|
|
@@ -13077,6 +13084,7 @@ const PAGE_BUILDER_AI_CAPABILITIES = {
|
|
|
13077
13084
|
{ path: 'uiCompositionPlan.widgets', category: 'widgets', valueKind: 'array', description: 'Widgets planejados antes da compilacao para page.widgets.' },
|
|
13078
13085
|
{ path: 'uiCompositionPlan.widgets[].key', category: 'widgets', valueKind: 'string', description: 'ID estavel do widget planejado.', critical: true },
|
|
13079
13086
|
{ path: 'uiCompositionPlan.widgets[].componentId', category: 'widgets', valueKind: 'string', description: 'Componente registrado que sera compilado para definition.id.', critical: true },
|
|
13087
|
+
{ path: 'uiCompositionPlan.widgets[].role', category: 'layout', valueKind: 'string', description: 'Papel semantico explicito usado na materializacao de layout; master-detail aceita master, detail, filters, actions, supporting e os slots detail-* do preset oficial.' },
|
|
13080
13088
|
{ path: 'uiCompositionPlan.widgets[].shell', category: 'widgets', valueKind: 'object', description: 'Moldura governada do widget, compilada para page.widgets[].shell sem enriquecimento do host.' },
|
|
13081
13089
|
{ path: 'uiCompositionPlan.widgets[].inputs', category: 'widgets', valueKind: 'object', description: 'Inputs canonicos do componente, compilados para definition.inputs.' },
|
|
13082
13090
|
{ path: 'uiCompositionPlan.widgets[].bindingOrder', category: 'widgets', valueKind: 'array', description: 'Ordem declarativa de binding, compilada para definition.bindingOrder.' },
|
|
@@ -14310,14 +14318,14 @@ const PRAXIS_PAGE_BUILDER_AUTHORING_MANIFEST = {
|
|
|
14310
14318
|
target: { kind: 'agenticPreview', resolver: 'agentic-preview-result', ambiguityPolicy: 'fail', required: true },
|
|
14311
14319
|
inputSchema: { type: 'object', required: ['previewId'], properties: { previewId: { type: 'string' }, compiledFormPatch: { type: 'object' } } },
|
|
14312
14320
|
effects: [{ kind: 'compile-domain-patch', handler: 'page-builder-preview-apply', handlerContract: {
|
|
14313
|
-
reads: ['PageBuilderAiAdapter.
|
|
14321
|
+
reads: ['UiCompositionPlan', 'PageBuilderAiAdapter.applyUiCompositionPlan', 'ComponentMetadataRegistry', 'CompositionValidatorService.validatePage', 'compiledFormPatch.patch.page', 'PraxisAssistantTurnViewState'],
|
|
14314
14322
|
writes: ['DynamicPageBuilderComponent.page', 'pageChange'],
|
|
14315
14323
|
identityKeys: ['previewId'],
|
|
14316
|
-
failureModes: ['preview-result-invalid', 'patch-page-missing', 'runtime-page-invalid', 'patch-envelope-persistence-attempted'],
|
|
14317
|
-
description: '
|
|
14324
|
+
failureModes: ['preview-result-invalid', 'target-registry-missing', 'component-not-registered', 'component-not-materializable', 'port-not-registered', 'port-contract-incompatible', 'patch-page-missing', 'runtime-page-invalid', 'patch-envelope-persistence-attempted'],
|
|
14325
|
+
description: 'Preflights UiCompositionPlan against the target runtime registry and canonical Core composition validator before applying the attested WidgetPageDefinition locally.',
|
|
14318
14326
|
} }],
|
|
14319
|
-
validators: ['preview-result-valid', 'compiled-page-patch-present', 'no-envelope-runtime-persistence', 'runtime-page-valid'],
|
|
14320
|
-
affectedPaths: ['compiledFormPatch.patch.page', 'page'],
|
|
14327
|
+
validators: ['preview-result-valid', 'ui-composition-target-registry-available', 'ui-composition-components-materializable', 'composition-endpoints-resolve', 'compiled-page-patch-present', 'no-envelope-runtime-persistence', 'runtime-page-valid'],
|
|
14328
|
+
affectedPaths: ['uiCompositionPlan', 'compiledFormPatch.patch.page', 'page'],
|
|
14321
14329
|
submissionImpact: 'visual-only',
|
|
14322
14330
|
preconditions: ['agentic-preview-loaded'],
|
|
14323
14331
|
},
|
|
@@ -14398,6 +14406,8 @@ const PRAXIS_PAGE_BUILDER_AUTHORING_MANIFEST = {
|
|
|
14398
14406
|
{ validatorId: 'state-json-valid', level: 'error', code: 'PAGE_BUILDER_STATE_JSON', description: 'State values must be JSON-serializable.' },
|
|
14399
14407
|
{ validatorId: 'composition-state-links-still-valid', level: 'error', code: 'PAGE_BUILDER_STATE_LINKS', description: 'State edits must not break composition links.' },
|
|
14400
14408
|
{ validatorId: 'preview-result-valid', level: 'error', code: 'PAGE_BUILDER_PREVIEW_VALID', description: 'Agentic preview result must be structurally valid.' },
|
|
14409
|
+
{ validatorId: 'ui-composition-target-registry-available', level: 'error', code: 'PAGE_BUILDER_UI_PLAN_TARGET_REGISTRY', description: 'UiCompositionPlan apply requires the ComponentMetadataRegistry of the target host.' },
|
|
14410
|
+
{ validatorId: 'ui-composition-components-materializable', level: 'error', code: 'PAGE_BUILDER_UI_PLAN_COMPONENTS_MATERIALIZABLE', description: 'Every planned component must resolve materializable metadata in the target registry.' },
|
|
14401
14411
|
{ validatorId: 'compiled-page-patch-present', level: 'error', code: 'PAGE_BUILDER_PATCH_PAGE_PRESENT', description: 'Preview envelope must include compiledFormPatch.patch.page.' },
|
|
14402
14412
|
{ validatorId: 'no-envelope-runtime-persistence', level: 'error', code: 'PAGE_BUILDER_NO_ENVELOPE_PERSISTENCE', description: 'Runtime persistence cannot save the full preview envelope.' },
|
|
14403
14413
|
{ validatorId: 'runtime-page-valid', level: 'error', code: 'PAGE_BUILDER_RUNTIME_PAGE_VALID', description: 'Runtime page must remain renderable by praxis-dynamic-page.' },
|
|
@@ -14418,7 +14428,7 @@ const PRAXIS_PAGE_BUILDER_AUTHORING_MANIFEST = {
|
|
|
14418
14428
|
'Persisted wiring uses page.composition.links and must not write legacy connections.',
|
|
14419
14429
|
'Nested component ports use component-port refs with nestedPath ending in a stable widget key.',
|
|
14420
14430
|
'Child widget definition.inputs edits delegate to the child manifest or ComponentDocMeta.configEditor.',
|
|
14421
|
-
'Agentic preview applies
|
|
14431
|
+
'Agentic preview preflights UiCompositionPlan against the target registry and applies the attested WidgetPageDefinition; persistence rebuilds patch.page from that page while the surrounding envelope remains diagnostics/audit data.',
|
|
14422
14432
|
'Save uses pageIdentity and ETag policy from praxis-config-starter; pageIdentity is not persisted inside WidgetPageDefinition.',
|
|
14423
14433
|
'Settings panel apply/save/reset/reopen must preserve page settings and widget shell config without hidden normalization drift.',
|
|
14424
14434
|
],
|
|
@@ -14545,6 +14555,358 @@ const PRAXIS_PAGE_BUILDER_AUTHORING_MANIFEST = {
|
|
|
14545
14555
|
],
|
|
14546
14556
|
};
|
|
14547
14557
|
|
|
14558
|
+
const MASTER_DETAIL_PRESET_ID = 'master-detail-dashboard';
|
|
14559
|
+
const MASTER_DETAIL_DETAIL_SLOTS = [
|
|
14560
|
+
'detail-table',
|
|
14561
|
+
'detail-chart-a',
|
|
14562
|
+
'detail-chart-b',
|
|
14563
|
+
'detail-kpis',
|
|
14564
|
+
];
|
|
14565
|
+
const MASTER_DETAIL_SLOT_REGIONS = {
|
|
14566
|
+
master: 'master',
|
|
14567
|
+
'detail-table': 'detail',
|
|
14568
|
+
'detail-chart-a': 'detail',
|
|
14569
|
+
'detail-chart-b': 'detail',
|
|
14570
|
+
'detail-kpis': 'detail',
|
|
14571
|
+
};
|
|
14572
|
+
const MASTER_DETAIL_ROLE_REGIONS = {
|
|
14573
|
+
master: 'master',
|
|
14574
|
+
detail: 'detail',
|
|
14575
|
+
filters: 'filters',
|
|
14576
|
+
actions: 'actions',
|
|
14577
|
+
supporting: 'supporting',
|
|
14578
|
+
};
|
|
14579
|
+
function materializeUiCompositionPlanLayout(plan) {
|
|
14580
|
+
if (!isMasterDetailPlan(plan)) {
|
|
14581
|
+
return {
|
|
14582
|
+
layoutPresetOptions: clone$1(plan.layoutPresetOptions),
|
|
14583
|
+
canvas: resolveNeutralCanvas(plan),
|
|
14584
|
+
deviceLayouts: clone$1(plan.deviceLayouts),
|
|
14585
|
+
slotAssignments: clone$1(plan.slotAssignments),
|
|
14586
|
+
diagnostics: [],
|
|
14587
|
+
};
|
|
14588
|
+
}
|
|
14589
|
+
const analysis = analyzeMasterDetailPlan(plan);
|
|
14590
|
+
const canvas = plan.canvas
|
|
14591
|
+
? clone$1(plan.canvas)
|
|
14592
|
+
: masterDetailCanvas(analysis.widgets, 12, 'desktop');
|
|
14593
|
+
const deviceLayouts = plan.deviceLayouts
|
|
14594
|
+
? clone$1(plan.deviceLayouts)
|
|
14595
|
+
: materializeMasterDetailDeviceLayouts(analysis.widgets, canvas);
|
|
14596
|
+
return {
|
|
14597
|
+
layoutPresetOptions: materializeMasterDetailPresetOptions(plan),
|
|
14598
|
+
canvas,
|
|
14599
|
+
deviceLayouts,
|
|
14600
|
+
slotAssignments: Object.keys(analysis.slotAssignments).length
|
|
14601
|
+
? analysis.slotAssignments
|
|
14602
|
+
: undefined,
|
|
14603
|
+
diagnostics: analysis.diagnostics,
|
|
14604
|
+
};
|
|
14605
|
+
}
|
|
14606
|
+
function validateUiCompositionPlanLayout(plan) {
|
|
14607
|
+
return isMasterDetailPlan(plan) ? analyzeMasterDetailPlan(plan).errors : [];
|
|
14608
|
+
}
|
|
14609
|
+
function isMasterDetailPlan(plan) {
|
|
14610
|
+
const preset = resolvePlanLayoutPreset(plan);
|
|
14611
|
+
const assignedRegions = Object.values(plan.slotAssignments ?? {})
|
|
14612
|
+
.map((slot) => MASTER_DETAIL_SLOT_REGIONS[String(slot || '').trim()])
|
|
14613
|
+
.filter((region) => !!region);
|
|
14614
|
+
const explicitRoles = (plan.widgets ?? [])
|
|
14615
|
+
.map((widget) => roleRegion(widget.role))
|
|
14616
|
+
.filter((region) => !!region);
|
|
14617
|
+
const regions = [...assignedRegions, ...explicitRoles];
|
|
14618
|
+
return (preset?.category === 'master-detail' ||
|
|
14619
|
+
(regions.includes('master') && regions.includes('detail')));
|
|
14620
|
+
}
|
|
14621
|
+
function roleRegion(role) {
|
|
14622
|
+
const semanticRole = String(role || '').trim();
|
|
14623
|
+
return (MASTER_DETAIL_ROLE_REGIONS[semanticRole] ??
|
|
14624
|
+
MASTER_DETAIL_SLOT_REGIONS[semanticRole]);
|
|
14625
|
+
}
|
|
14626
|
+
function resolvePlanLayoutPreset(plan) {
|
|
14627
|
+
const layoutPreset = String(plan.layoutPreset || '').trim();
|
|
14628
|
+
if (layoutPreset && BUILTIN_PAGE_LAYOUT_PRESETS[layoutPreset]) {
|
|
14629
|
+
return BUILTIN_PAGE_LAYOUT_PRESETS[layoutPreset];
|
|
14630
|
+
}
|
|
14631
|
+
const presetFamily = typeof plan.layoutPresetOptions?.['presetFamily'] === 'string'
|
|
14632
|
+
? plan.layoutPresetOptions['presetFamily'].trim()
|
|
14633
|
+
: '';
|
|
14634
|
+
return presetFamily ? BUILTIN_PAGE_LAYOUT_PRESETS[presetFamily] : undefined;
|
|
14635
|
+
}
|
|
14636
|
+
function materializeMasterDetailPresetOptions(plan) {
|
|
14637
|
+
const options = clone$1(plan.layoutPresetOptions) ?? {};
|
|
14638
|
+
if (!resolvePlanLayoutPreset(plan) &&
|
|
14639
|
+
typeof options['presetFamily'] !== 'string') {
|
|
14640
|
+
options['presetFamily'] = MASTER_DETAIL_PRESET_ID;
|
|
14641
|
+
}
|
|
14642
|
+
return Object.keys(options).length ? options : undefined;
|
|
14643
|
+
}
|
|
14644
|
+
function analyzeMasterDetailPlan(plan) {
|
|
14645
|
+
const diagnostics = [];
|
|
14646
|
+
const errors = [];
|
|
14647
|
+
const explicitAssignments = clone$1(plan.slotAssignments) ?? {};
|
|
14648
|
+
const slotAssignments = { ...explicitAssignments };
|
|
14649
|
+
const resolvedPreset = resolvePlanLayoutPreset(plan);
|
|
14650
|
+
const layoutPreset = String(plan.layoutPreset || '').trim();
|
|
14651
|
+
const presetFamily = typeof plan.layoutPresetOptions?.['presetFamily'] === 'string'
|
|
14652
|
+
? plan.layoutPresetOptions['presetFamily'].trim()
|
|
14653
|
+
: '';
|
|
14654
|
+
if (!BUILTIN_PAGE_LAYOUT_PRESETS[layoutPreset] &&
|
|
14655
|
+
presetFamily &&
|
|
14656
|
+
!BUILTIN_PAGE_LAYOUT_PRESETS[presetFamily]) {
|
|
14657
|
+
errors.push({
|
|
14658
|
+
code: 'master-detail-preset-family-unknown',
|
|
14659
|
+
message: `Preset family "${presetFamily}" is not declared by the layout preset catalog.`,
|
|
14660
|
+
path: 'layoutPresetOptions.presetFamily',
|
|
14661
|
+
});
|
|
14662
|
+
}
|
|
14663
|
+
const preset = resolvedPreset ?? BUILTIN_PAGE_LAYOUT_PRESETS[MASTER_DETAIL_PRESET_ID];
|
|
14664
|
+
const knownSlots = new Set(preset?.slotModel.map((slot) => slot.id) ?? []);
|
|
14665
|
+
const occupiedSlots = new Map();
|
|
14666
|
+
const widgets = [];
|
|
14667
|
+
for (const [index, widget] of (plan.widgets ?? []).entries()) {
|
|
14668
|
+
const explicitSlot = String(explicitAssignments[widget.key] || '').trim();
|
|
14669
|
+
const semanticRoleRegion = roleRegion(widget.role);
|
|
14670
|
+
const slotRegion = MASTER_DETAIL_SLOT_REGIONS[explicitSlot];
|
|
14671
|
+
if (explicitSlot && knownSlots.size && !knownSlots.has(explicitSlot)) {
|
|
14672
|
+
errors.push({
|
|
14673
|
+
code: 'master-detail-slot-unknown',
|
|
14674
|
+
message: `Slot "${explicitSlot}" is not declared by the resolved master-detail preset.`,
|
|
14675
|
+
path: `slotAssignments.${widget.key}`,
|
|
14676
|
+
});
|
|
14677
|
+
}
|
|
14678
|
+
if (semanticRoleRegion && slotRegion && semanticRoleRegion !== slotRegion) {
|
|
14679
|
+
errors.push({
|
|
14680
|
+
code: 'master-detail-role-slot-conflict',
|
|
14681
|
+
message: `Widget "${widget.key}" has conflicting semantic role and slot assignment.`,
|
|
14682
|
+
path: `slotAssignments.${widget.key}`,
|
|
14683
|
+
});
|
|
14684
|
+
}
|
|
14685
|
+
const region = slotRegion ?? semanticRoleRegion ?? 'supporting';
|
|
14686
|
+
if (!slotRegion && !semanticRoleRegion) {
|
|
14687
|
+
diagnostics.push({
|
|
14688
|
+
code: 'master-detail-widget-role-fallback',
|
|
14689
|
+
message: `Widget "${widget.key}" has no recognized semantic role or preset slot and was placed in supporting content.`,
|
|
14690
|
+
path: `widgets.${index}.role`,
|
|
14691
|
+
});
|
|
14692
|
+
}
|
|
14693
|
+
widgets.push({ widget, index, region, slot: explicitSlot || undefined });
|
|
14694
|
+
}
|
|
14695
|
+
if (resolvedPreset && resolvedPreset.category !== 'master-detail') {
|
|
14696
|
+
errors.push({
|
|
14697
|
+
code: 'master-detail-preset-conflict',
|
|
14698
|
+
message: 'Master and detail semantic roles require a master-detail layout preset family.',
|
|
14699
|
+
path: typeof plan.layoutPresetOptions?.['presetFamily'] === 'string'
|
|
14700
|
+
? 'layoutPresetOptions.presetFamily'
|
|
14701
|
+
: 'layoutPreset',
|
|
14702
|
+
});
|
|
14703
|
+
}
|
|
14704
|
+
const masters = widgets.filter((item) => item.region === 'master');
|
|
14705
|
+
const details = widgets.filter((item) => item.region === 'detail');
|
|
14706
|
+
if (!masters.length) {
|
|
14707
|
+
errors.push({
|
|
14708
|
+
code: 'master-detail-master-required',
|
|
14709
|
+
message: 'A master-detail plan must declare exactly one master widget through role or slot assignment.',
|
|
14710
|
+
path: 'widgets',
|
|
14711
|
+
});
|
|
14712
|
+
}
|
|
14713
|
+
else if (masters.length > 1) {
|
|
14714
|
+
errors.push({
|
|
14715
|
+
code: 'master-detail-master-ambiguous',
|
|
14716
|
+
message: 'A master-detail plan cannot declare more than one master widget.',
|
|
14717
|
+
path: 'widgets',
|
|
14718
|
+
});
|
|
14719
|
+
}
|
|
14720
|
+
if (!details.length) {
|
|
14721
|
+
errors.push({
|
|
14722
|
+
code: 'master-detail-detail-required',
|
|
14723
|
+
message: 'A master-detail plan must declare at least one detail widget through role or slot assignment.',
|
|
14724
|
+
path: 'widgets',
|
|
14725
|
+
});
|
|
14726
|
+
}
|
|
14727
|
+
for (const item of widgets) {
|
|
14728
|
+
if (!item.slot)
|
|
14729
|
+
continue;
|
|
14730
|
+
const occupants = occupiedSlots.get(item.slot) ?? [];
|
|
14731
|
+
occupiedSlots.set(item.slot, [...occupants, item.widget.key]);
|
|
14732
|
+
}
|
|
14733
|
+
for (const slot of preset?.slotModel ?? []) {
|
|
14734
|
+
const occupants = occupiedSlots.get(slot.id) ?? [];
|
|
14735
|
+
if (slot.maxItems != null && occupants.length > slot.maxItems) {
|
|
14736
|
+
errors.push({
|
|
14737
|
+
code: 'master-detail-slot-cardinality-exceeded',
|
|
14738
|
+
message: `Slot "${slot.id}" accepts at most ${slot.maxItems} widget(s).`,
|
|
14739
|
+
path: 'slotAssignments',
|
|
14740
|
+
});
|
|
14741
|
+
}
|
|
14742
|
+
}
|
|
14743
|
+
const availableDetailSlots = MASTER_DETAIL_DETAIL_SLOTS.filter((slot) => knownSlots.has(slot) && !occupiedSlots.has(slot));
|
|
14744
|
+
for (const item of widgets) {
|
|
14745
|
+
if (item.slot || item.region !== 'detail')
|
|
14746
|
+
continue;
|
|
14747
|
+
const preferredRoleSlot = exactDetailSlot(item.widget.role, knownSlots);
|
|
14748
|
+
const slot = preferredRoleSlot && !occupiedSlots.has(preferredRoleSlot)
|
|
14749
|
+
? preferredRoleSlot
|
|
14750
|
+
: availableDetailSlots.find((candidate) => !occupiedSlots.has(candidate));
|
|
14751
|
+
if (!slot) {
|
|
14752
|
+
diagnostics.push({
|
|
14753
|
+
code: 'master-detail-detail-slot-fallback',
|
|
14754
|
+
message: `Widget "${item.widget.key}" exceeded the available detail slots and remains deterministic supporting content for grouping.`,
|
|
14755
|
+
path: `widgets.${item.index}.role`,
|
|
14756
|
+
});
|
|
14757
|
+
continue;
|
|
14758
|
+
}
|
|
14759
|
+
slotAssignments[item.widget.key] = slot;
|
|
14760
|
+
item.slot = slot;
|
|
14761
|
+
occupiedSlots.set(slot, [item.widget.key]);
|
|
14762
|
+
}
|
|
14763
|
+
const master = masters[0];
|
|
14764
|
+
if (master && !master.slot && knownSlots.has('master')) {
|
|
14765
|
+
slotAssignments[master.widget.key] = 'master';
|
|
14766
|
+
master.slot = 'master';
|
|
14767
|
+
}
|
|
14768
|
+
return { widgets, slotAssignments, diagnostics, errors };
|
|
14769
|
+
}
|
|
14770
|
+
function exactDetailSlot(role, knownSlots) {
|
|
14771
|
+
const candidate = String(role || '').trim();
|
|
14772
|
+
return MASTER_DETAIL_SLOT_REGIONS[candidate] === 'detail' &&
|
|
14773
|
+
knownSlots.has(candidate)
|
|
14774
|
+
? candidate
|
|
14775
|
+
: undefined;
|
|
14776
|
+
}
|
|
14777
|
+
function resolveNeutralCanvas(plan) {
|
|
14778
|
+
if (plan.canvas)
|
|
14779
|
+
return clone$1(plan.canvas);
|
|
14780
|
+
if (!plan.widgets?.length)
|
|
14781
|
+
return undefined;
|
|
14782
|
+
if (plan.layout && !plan.layoutPreset?.trim())
|
|
14783
|
+
return undefined;
|
|
14784
|
+
const items = {};
|
|
14785
|
+
let row = 1;
|
|
14786
|
+
for (const widget of plan.widgets ?? []) {
|
|
14787
|
+
items[widget.key] = { col: 1, row, colSpan: 12, rowSpan: 4 };
|
|
14788
|
+
row += 4;
|
|
14789
|
+
}
|
|
14790
|
+
return defaultCanvas(items, 12, 'desktop');
|
|
14791
|
+
}
|
|
14792
|
+
function masterDetailCanvas(semantics, columns, device) {
|
|
14793
|
+
const items = {};
|
|
14794
|
+
const filters = semantics.filter((item) => item.region === 'filters');
|
|
14795
|
+
const actions = semantics.filter((item) => item.region === 'actions');
|
|
14796
|
+
const masters = semantics.filter((item) => item.region === 'master');
|
|
14797
|
+
const details = semantics.filter((item) => item.region === 'detail');
|
|
14798
|
+
const supporting = semantics.filter((item) => item.region === 'supporting');
|
|
14799
|
+
let row = 1;
|
|
14800
|
+
if (device === 'desktop' && filters.length && actions.length) {
|
|
14801
|
+
const filterEnd = placeHorizontal(items, filters, row, 1, 8, 'filters');
|
|
14802
|
+
const actionEnd = placeHorizontal(items, actions, row, 9, 4, 'actions');
|
|
14803
|
+
row = Math.max(filterEnd, actionEnd);
|
|
14804
|
+
}
|
|
14805
|
+
else {
|
|
14806
|
+
row = placeStacked(items, filters, row, columns, device);
|
|
14807
|
+
row = placeStacked(items, actions, row, columns, device);
|
|
14808
|
+
}
|
|
14809
|
+
if (device === 'desktop') {
|
|
14810
|
+
const contentRow = row;
|
|
14811
|
+
let detailRow = contentRow;
|
|
14812
|
+
for (const item of details) {
|
|
14813
|
+
const rowSpan = preferredRowSpan(item, device);
|
|
14814
|
+
items[item.widget.key] = { col: 5, row: detailRow, colSpan: 8, rowSpan };
|
|
14815
|
+
detailRow += rowSpan;
|
|
14816
|
+
}
|
|
14817
|
+
const contentHeight = Math.max(8, detailRow - contentRow);
|
|
14818
|
+
for (const item of masters) {
|
|
14819
|
+
items[item.widget.key] = {
|
|
14820
|
+
col: 1,
|
|
14821
|
+
row: contentRow,
|
|
14822
|
+
colSpan: 4,
|
|
14823
|
+
rowSpan: contentHeight,
|
|
14824
|
+
};
|
|
14825
|
+
}
|
|
14826
|
+
row = contentRow + contentHeight;
|
|
14827
|
+
}
|
|
14828
|
+
else {
|
|
14829
|
+
row = placeStacked(items, masters, row, columns, device);
|
|
14830
|
+
row = placeStacked(items, details, row, columns, device);
|
|
14831
|
+
}
|
|
14832
|
+
placeStacked(items, supporting, row, columns, device);
|
|
14833
|
+
return defaultCanvas(items, columns, device);
|
|
14834
|
+
}
|
|
14835
|
+
function placeHorizontal(items, semantics, row, col, colSpan, region) {
|
|
14836
|
+
let nextRow = row;
|
|
14837
|
+
for (const item of semantics) {
|
|
14838
|
+
const rowSpan = preferredRowSpan({ ...item, region }, 'desktop');
|
|
14839
|
+
items[item.widget.key] = { col, row: nextRow, colSpan, rowSpan };
|
|
14840
|
+
nextRow += rowSpan;
|
|
14841
|
+
}
|
|
14842
|
+
return nextRow;
|
|
14843
|
+
}
|
|
14844
|
+
function placeStacked(items, semantics, row, columns, device) {
|
|
14845
|
+
let nextRow = row;
|
|
14846
|
+
for (const item of semantics) {
|
|
14847
|
+
const rowSpan = preferredRowSpan(item, device);
|
|
14848
|
+
items[item.widget.key] = {
|
|
14849
|
+
col: 1,
|
|
14850
|
+
row: nextRow,
|
|
14851
|
+
colSpan: columns,
|
|
14852
|
+
rowSpan,
|
|
14853
|
+
};
|
|
14854
|
+
nextRow += rowSpan;
|
|
14855
|
+
}
|
|
14856
|
+
return nextRow;
|
|
14857
|
+
}
|
|
14858
|
+
function materializeMasterDetailDeviceLayouts(semantics, desktopCanvas) {
|
|
14859
|
+
const tablet = masterDetailCanvas(semantics, 6, 'tablet');
|
|
14860
|
+
const mobile = masterDetailCanvas(semantics, 1, 'mobile');
|
|
14861
|
+
return {
|
|
14862
|
+
desktop: { canvas: canvasVariant(desktopCanvas) },
|
|
14863
|
+
tablet: { canvas: canvasVariant(tablet) },
|
|
14864
|
+
mobile: { canvas: canvasVariant(mobile) },
|
|
14865
|
+
};
|
|
14866
|
+
}
|
|
14867
|
+
function canvasVariant(canvas) {
|
|
14868
|
+
return {
|
|
14869
|
+
columns: canvas.columns,
|
|
14870
|
+
rowUnit: canvas.rowUnit,
|
|
14871
|
+
gap: canvas.gap,
|
|
14872
|
+
autoRows: canvas.autoRows,
|
|
14873
|
+
...(canvas.collisionPolicy
|
|
14874
|
+
? { collisionPolicy: canvas.collisionPolicy }
|
|
14875
|
+
: {}),
|
|
14876
|
+
items: clone$1(canvas.items),
|
|
14877
|
+
};
|
|
14878
|
+
}
|
|
14879
|
+
function defaultCanvas(items, columns, device) {
|
|
14880
|
+
return {
|
|
14881
|
+
mode: 'grid',
|
|
14882
|
+
columns,
|
|
14883
|
+
rowUnit: device === 'desktop' ? '80px' : device === 'tablet' ? '72px' : '64px',
|
|
14884
|
+
gap: device === 'desktop' ? '16px' : '12px',
|
|
14885
|
+
autoRows: 'fixed',
|
|
14886
|
+
items,
|
|
14887
|
+
};
|
|
14888
|
+
}
|
|
14889
|
+
function preferredRowSpan(item, device) {
|
|
14890
|
+
if (item.region === 'filters' || item.region === 'actions')
|
|
14891
|
+
return 2;
|
|
14892
|
+
if (item.region === 'master')
|
|
14893
|
+
return device === 'desktop' ? 8 : 6;
|
|
14894
|
+
if (item.slot === 'detail-kpis')
|
|
14895
|
+
return 2;
|
|
14896
|
+
if (item.slot === 'detail-chart-a' || item.slot === 'detail-chart-b')
|
|
14897
|
+
return 5;
|
|
14898
|
+
if (item.slot === 'detail-table')
|
|
14899
|
+
return device === 'mobile' ? 6 : 7;
|
|
14900
|
+
if (item.region === 'detail')
|
|
14901
|
+
return device === 'mobile' ? 6 : 7;
|
|
14902
|
+
return 4;
|
|
14903
|
+
}
|
|
14904
|
+
function clone$1(value) {
|
|
14905
|
+
if (value === undefined || value === null)
|
|
14906
|
+
return value;
|
|
14907
|
+
return JSON.parse(JSON.stringify(value));
|
|
14908
|
+
}
|
|
14909
|
+
|
|
14548
14910
|
/**
|
|
14549
14911
|
* Pure, fail-closed normalization for an exact template reference.
|
|
14550
14912
|
*
|
|
@@ -14606,16 +14968,17 @@ function compileUiCompositionPlan(plan) {
|
|
|
14606
14968
|
}
|
|
14607
14969
|
const bindings = expandedBindings(plan);
|
|
14608
14970
|
const widgets = materializeContextScopeInputs(plan, plan.widgets.map((widget) => toWidgetInstance(widget, bindings)));
|
|
14971
|
+
const layoutMaterialization = materializeUiCompositionPlanLayout(plan);
|
|
14609
14972
|
const page = {
|
|
14610
14973
|
i18n: clone(plan.i18n),
|
|
14611
14974
|
context: clone(plan.context),
|
|
14612
14975
|
layout: clone(plan.layout),
|
|
14613
14976
|
layoutPreset: plan.layoutPreset,
|
|
14614
|
-
layoutPresetOptions:
|
|
14615
|
-
canvas:
|
|
14616
|
-
deviceLayouts:
|
|
14977
|
+
layoutPresetOptions: layoutMaterialization.layoutPresetOptions,
|
|
14978
|
+
canvas: layoutMaterialization.canvas,
|
|
14979
|
+
deviceLayouts: layoutMaterialization.deviceLayouts,
|
|
14617
14980
|
grouping: clone(plan.grouping),
|
|
14618
|
-
slotAssignments:
|
|
14981
|
+
slotAssignments: layoutMaterialization.slotAssignments,
|
|
14619
14982
|
state: clone(plan.state),
|
|
14620
14983
|
themePreset: plan.themePreset,
|
|
14621
14984
|
widgets,
|
|
@@ -14627,7 +14990,7 @@ function compileUiCompositionPlan(plan) {
|
|
|
14627
14990
|
return {
|
|
14628
14991
|
valid: true,
|
|
14629
14992
|
page: materializeLocalizedFallbacks(page),
|
|
14630
|
-
diagnostics:
|
|
14993
|
+
diagnostics: layoutMaterialization.diagnostics,
|
|
14631
14994
|
};
|
|
14632
14995
|
}
|
|
14633
14996
|
const SHA_256_PATTERN = /^[0-9a-f]{64}$/;
|
|
@@ -14714,95 +15077,6 @@ function materializeLocalizedFallbacks(page) {
|
|
|
14714
15077
|
visit(materialized);
|
|
14715
15078
|
return materialized;
|
|
14716
15079
|
}
|
|
14717
|
-
function resolveCanvas(plan) {
|
|
14718
|
-
if (plan.canvas) {
|
|
14719
|
-
return clone(plan.canvas);
|
|
14720
|
-
}
|
|
14721
|
-
if (!plan.widgets?.length) {
|
|
14722
|
-
return undefined;
|
|
14723
|
-
}
|
|
14724
|
-
// An explicitly authored flow/grid layout is already a complete spatial
|
|
14725
|
-
// decision. Synthesizing a canvas here would silently take precedence over
|
|
14726
|
-
// that layout in the runtime. Preset- or role-driven plans still opt into
|
|
14727
|
-
// automatic canvas materialization below.
|
|
14728
|
-
if (plan.layout && !plan.layoutPreset?.trim() && !isMasterDetailPlan(plan)) {
|
|
14729
|
-
return undefined;
|
|
14730
|
-
}
|
|
14731
|
-
if (isMasterDetailPlan(plan)) {
|
|
14732
|
-
return masterDetailCanvas(plan.widgets);
|
|
14733
|
-
}
|
|
14734
|
-
return stackedCanvas(plan.widgets);
|
|
14735
|
-
}
|
|
14736
|
-
function isMasterDetailPlan(plan) {
|
|
14737
|
-
const preset = (plan.layoutPreset ?? '').toLocaleLowerCase('en-US');
|
|
14738
|
-
const hasPreset = preset.includes('master-detail') || preset.includes('master detail');
|
|
14739
|
-
const hasSemanticRoles = plan.widgets.some((widget) => widget.role === 'master')
|
|
14740
|
-
&& plan.widgets.some((widget) => widget.role === 'detail');
|
|
14741
|
-
return hasPreset || hasSemanticRoles;
|
|
14742
|
-
}
|
|
14743
|
-
function masterDetailCanvas(widgets) {
|
|
14744
|
-
const master = widgets.find((widget) => widget.role === 'master') ?? widgets[0];
|
|
14745
|
-
const details = widgets.filter((widget) => widget.key !== master.key);
|
|
14746
|
-
const items = {};
|
|
14747
|
-
let row = 1;
|
|
14748
|
-
items[master.key] = {
|
|
14749
|
-
col: 1,
|
|
14750
|
-
row,
|
|
14751
|
-
colSpan: 12,
|
|
14752
|
-
rowSpan: preferredRowSpan(master),
|
|
14753
|
-
};
|
|
14754
|
-
row += items[master.key].rowSpan;
|
|
14755
|
-
for (const widget of details) {
|
|
14756
|
-
const rowSpan = preferredRowSpan(widget);
|
|
14757
|
-
items[widget.key] = {
|
|
14758
|
-
col: 1,
|
|
14759
|
-
row,
|
|
14760
|
-
colSpan: 12,
|
|
14761
|
-
rowSpan,
|
|
14762
|
-
};
|
|
14763
|
-
row += rowSpan;
|
|
14764
|
-
}
|
|
14765
|
-
return defaultCanvas(items);
|
|
14766
|
-
}
|
|
14767
|
-
function stackedCanvas(widgets) {
|
|
14768
|
-
const items = {};
|
|
14769
|
-
let row = 1;
|
|
14770
|
-
for (const widget of widgets) {
|
|
14771
|
-
const rowSpan = preferredRowSpan(widget);
|
|
14772
|
-
items[widget.key] = {
|
|
14773
|
-
col: 1,
|
|
14774
|
-
row,
|
|
14775
|
-
colSpan: 12,
|
|
14776
|
-
rowSpan,
|
|
14777
|
-
};
|
|
14778
|
-
row += rowSpan;
|
|
14779
|
-
}
|
|
14780
|
-
return defaultCanvas(items);
|
|
14781
|
-
}
|
|
14782
|
-
function defaultCanvas(items) {
|
|
14783
|
-
return {
|
|
14784
|
-
mode: 'grid',
|
|
14785
|
-
columns: 12,
|
|
14786
|
-
rowUnit: '80px',
|
|
14787
|
-
gap: '16px',
|
|
14788
|
-
autoRows: 'fixed',
|
|
14789
|
-
items,
|
|
14790
|
-
};
|
|
14791
|
-
}
|
|
14792
|
-
function preferredRowSpan(widget) {
|
|
14793
|
-
const componentId = widget.componentId.toLocaleLowerCase('en-US');
|
|
14794
|
-
if (componentId.includes('dynamic-form') || widget.role === 'detail')
|
|
14795
|
-
return 8;
|
|
14796
|
-
if (componentId.includes('table') || componentId.includes('list'))
|
|
14797
|
-
return 7;
|
|
14798
|
-
if (componentId.includes('chart'))
|
|
14799
|
-
return 5;
|
|
14800
|
-
if (componentId.includes('kpi') || componentId.includes('filter'))
|
|
14801
|
-
return 2;
|
|
14802
|
-
if (componentId.includes('rich-content'))
|
|
14803
|
-
return 3;
|
|
14804
|
-
return 4;
|
|
14805
|
-
}
|
|
14806
15080
|
function validateUiCompositionPlan(plan) {
|
|
14807
15081
|
const diagnostics = [];
|
|
14808
15082
|
const widgetKeys = new Set();
|
|
@@ -14866,6 +15140,7 @@ function validateUiCompositionPlan(plan) {
|
|
|
14866
15140
|
validateGrouping(plan.grouping, widgetKeys, diagnostics);
|
|
14867
15141
|
validateDeviceLayouts(plan.deviceLayouts, widgetKeys, diagnostics);
|
|
14868
15142
|
validateSlotAssignments(plan.slotAssignments, widgetKeys, diagnostics);
|
|
15143
|
+
diagnostics.push(...validateUiCompositionPlanLayout(plan));
|
|
14869
15144
|
return diagnostics;
|
|
14870
15145
|
}
|
|
14871
15146
|
function validateSelectionSync(selectionSync, widgetKeys, diagnostics, path) {
|
|
@@ -15529,6 +15804,67 @@ function clone(value) {
|
|
|
15529
15804
|
}
|
|
15530
15805
|
}
|
|
15531
15806
|
|
|
15807
|
+
/**
|
|
15808
|
+
* Fail-closed pre-apply attestation against the registry that will materialize the page.
|
|
15809
|
+
* It does not render widgets, execute links, call HTTP endpoints or persist configuration.
|
|
15810
|
+
*/
|
|
15811
|
+
function preflightUiCompositionPlan(plan, registry, validator = new CompositionValidatorService()) {
|
|
15812
|
+
const compiled = compileUiCompositionPlan(plan);
|
|
15813
|
+
const compilerDiagnostics = compiled.diagnostics.map((diagnostic, index) => fromCompilerDiagnostic(diagnostic, index, !compiled.valid));
|
|
15814
|
+
if (!compiled.valid) {
|
|
15815
|
+
return {
|
|
15816
|
+
valid: false,
|
|
15817
|
+
page: null,
|
|
15818
|
+
diagnostics: compilerDiagnostics,
|
|
15819
|
+
};
|
|
15820
|
+
}
|
|
15821
|
+
if (!registry) {
|
|
15822
|
+
return {
|
|
15823
|
+
valid: false,
|
|
15824
|
+
page: null,
|
|
15825
|
+
diagnostics: [
|
|
15826
|
+
...compilerDiagnostics,
|
|
15827
|
+
createAuthoringDiagnostic('ui-composition-plan:target-registry-required', 'UI_COMPOSITION_TARGET_REGISTRY_REQUIRED', 'The target ComponentMetadataRegistry is required before applying a UiCompositionPlan.', undefined, true, 'target-registry'),
|
|
15828
|
+
],
|
|
15829
|
+
};
|
|
15830
|
+
}
|
|
15831
|
+
const links = (compiled.page.composition?.links || []);
|
|
15832
|
+
const semanticDiagnostics = validator.validatePage(compiled.page, {
|
|
15833
|
+
registry,
|
|
15834
|
+
links,
|
|
15835
|
+
});
|
|
15836
|
+
const diagnostics = [...compilerDiagnostics, ...semanticDiagnostics];
|
|
15837
|
+
const blocking = diagnostics.some((diagnostic) => diagnostic.blocking !== false
|
|
15838
|
+
&& (diagnostic.severity === 'error' || diagnostic.severity === 'fatal'));
|
|
15839
|
+
return blocking
|
|
15840
|
+
? { valid: false, page: null, diagnostics }
|
|
15841
|
+
: { valid: true, page: compiled.page, diagnostics };
|
|
15842
|
+
}
|
|
15843
|
+
function fromCompilerDiagnostic(diagnostic, index, blocking) {
|
|
15844
|
+
return createAuthoringDiagnostic(`ui-composition-plan:compiler:${index}:${diagnostic.code}:${diagnostic.path || 'page'}`, diagnostic.code, diagnostic.message, diagnostic.path, blocking, blocking ? 'document-validation' : 'layout-materialization');
|
|
15845
|
+
}
|
|
15846
|
+
function createAuthoringDiagnostic(id, code, message, path, blocking, stage) {
|
|
15847
|
+
return {
|
|
15848
|
+
id,
|
|
15849
|
+
code,
|
|
15850
|
+
severity: blocking ? 'error' : 'warning',
|
|
15851
|
+
phase: 'page-lint',
|
|
15852
|
+
message,
|
|
15853
|
+
summary: message,
|
|
15854
|
+
subject: {
|
|
15855
|
+
kind: 'page',
|
|
15856
|
+
path,
|
|
15857
|
+
},
|
|
15858
|
+
details: {
|
|
15859
|
+
provenance: 'ui-composition-plan-preflight',
|
|
15860
|
+
stage,
|
|
15861
|
+
},
|
|
15862
|
+
source: 'authoring',
|
|
15863
|
+
blocking,
|
|
15864
|
+
createdAt: new Date().toISOString(),
|
|
15865
|
+
};
|
|
15866
|
+
}
|
|
15867
|
+
|
|
15532
15868
|
const RUNTIME_AUTHORING_METADATA_INPUTS = [
|
|
15533
15869
|
'schemaVerification',
|
|
15534
15870
|
'schemaEvidenceSource',
|
|
@@ -15685,25 +16021,32 @@ class PageBuilderAiAdapter {
|
|
|
15685
16021
|
return this.applyPatch({ page: materializedPage }, 'agentic-authoring.compiled-form-patch');
|
|
15686
16022
|
}
|
|
15687
16023
|
async applyUiCompositionPlan(plan) {
|
|
15688
|
-
const
|
|
15689
|
-
if (!
|
|
16024
|
+
const preflight = preflightUiCompositionPlan(plan, this.registry);
|
|
16025
|
+
if (!preflight.valid) {
|
|
15690
16026
|
return {
|
|
15691
16027
|
success: false,
|
|
15692
|
-
error:
|
|
15693
|
-
|
|
15694
|
-
|
|
16028
|
+
error: preflight.diagnostics.map((diagnostic) => diagnostic.subject.path
|
|
16029
|
+
? `${diagnostic.code} at ${diagnostic.subject.path}: ${diagnostic.message}`
|
|
16030
|
+
: `${diagnostic.code}: ${diagnostic.message}`).join('\n'),
|
|
16031
|
+
warnings: preflight.diagnostics.map((diagnostic) => diagnostic.subject.path
|
|
16032
|
+
? `${diagnostic.code} at ${diagnostic.subject.path}`
|
|
15695
16033
|
: diagnostic.code),
|
|
16034
|
+
diagnostics: preflight.diagnostics,
|
|
15696
16035
|
};
|
|
15697
16036
|
}
|
|
15698
16037
|
try {
|
|
15699
|
-
const warnings =
|
|
16038
|
+
const warnings = preflight.diagnostics.map((diagnostic) => diagnostic.subject.path
|
|
16039
|
+
? `${diagnostic.code} at ${diagnostic.subject.path}`
|
|
16040
|
+
: diagnostic.code);
|
|
15700
16041
|
const current = this.getCurrentConfig();
|
|
15701
16042
|
const next = this.normalizeConfigForRuntime({
|
|
15702
16043
|
...current,
|
|
15703
|
-
page: this.clone(
|
|
16044
|
+
page: this.clone(preflight.page),
|
|
15704
16045
|
}, warnings);
|
|
15705
16046
|
this.applyConfig(next);
|
|
15706
|
-
return warnings.length
|
|
16047
|
+
return warnings.length
|
|
16048
|
+
? { success: true, warnings, diagnostics: preflight.diagnostics }
|
|
16049
|
+
: { success: true, diagnostics: preflight.diagnostics };
|
|
15707
16050
|
}
|
|
15708
16051
|
catch (error) {
|
|
15709
16052
|
const message = error instanceof Error ? error.message : 'Unknown error applying UI composition plan';
|
|
@@ -24178,11 +24521,20 @@ class DynamicPageBuilderComponent {
|
|
|
24178
24521
|
async applyAgenticPreviewLocally(result) {
|
|
24179
24522
|
const adapter = new PageBuilderAiAdapter(this.createAdapterHost(), this.componentMetadata);
|
|
24180
24523
|
const snapshot = adapter.createSnapshot();
|
|
24181
|
-
const applied = await (result.
|
|
24182
|
-
? adapter.
|
|
24183
|
-
: result.
|
|
24184
|
-
|
|
24185
|
-
|
|
24524
|
+
const applied = await (result.uiCompositionPlan
|
|
24525
|
+
? adapter.applyUiCompositionPlan(result.uiCompositionPlan)
|
|
24526
|
+
: adapter.applyCompiledFormPatch(result.compiledFormPatch));
|
|
24527
|
+
if (applied.warnings?.length) {
|
|
24528
|
+
result.warnings = Array.from(new Set([...(result.warnings || []), ...applied.warnings]));
|
|
24529
|
+
}
|
|
24530
|
+
if (!applied.success && applied.diagnostics?.length) {
|
|
24531
|
+
result.failureCodes = Array.from(new Set([
|
|
24532
|
+
...(result.failureCodes || []),
|
|
24533
|
+
...applied.diagnostics
|
|
24534
|
+
.filter((diagnostic) => diagnostic.blocking !== false)
|
|
24535
|
+
.map((diagnostic) => diagnostic.code),
|
|
24536
|
+
]));
|
|
24537
|
+
}
|
|
24186
24538
|
if (!applied.success && this.isAgenticTableContractError(applied.error)) {
|
|
24187
24539
|
await adapter.restoreSnapshot(snapshot);
|
|
24188
24540
|
return {
|
|
@@ -24844,9 +25196,6 @@ class DynamicPageBuilderComponent {
|
|
|
24844
25196
|
return value;
|
|
24845
25197
|
}
|
|
24846
25198
|
resolvePreviewCompiledFormPatch(preview) {
|
|
24847
|
-
if (preview.compiledFormPatch?.patch?.page) {
|
|
24848
|
-
return preview.compiledFormPatch;
|
|
24849
|
-
}
|
|
24850
25199
|
if (preview.uiCompositionPlan) {
|
|
24851
25200
|
const planDiagnostics = this.toRecord(this.toRecord(preview.uiCompositionPlan)?.['diagnostics']);
|
|
24852
25201
|
return {
|
|
@@ -24858,6 +25207,9 @@ class DynamicPageBuilderComponent {
|
|
|
24858
25207
|
},
|
|
24859
25208
|
};
|
|
24860
25209
|
}
|
|
25210
|
+
if (preview.compiledFormPatch?.patch?.page) {
|
|
25211
|
+
return preview.compiledFormPatch;
|
|
25212
|
+
}
|
|
24861
25213
|
return preview.compiledFormPatch;
|
|
24862
25214
|
}
|
|
24863
25215
|
focusCanvasWidget(widgetKey) {
|
|
@@ -26395,4 +26747,4 @@ function providePraxisPageBuilderMetadata() {
|
|
|
26395
26747
|
* Generated bundle index. Do not edit.
|
|
26396
26748
|
*/
|
|
26397
26749
|
|
|
26398
|
-
export { ComponentPaletteDialogComponent, ConfirmDialogComponent, ConnectionEditorComponent, DynamicPageBuilderComponent, DynamicPageConfigEditorComponent, FloatingToolbarComponent, PAGE_BUILDER_AGENTIC_AUTHORING_OPTIONS, PAGE_BUILDER_AI_CAPABILITIES, PAGE_BUILDER_COMPONENT_CONTEXT_PACK, PAGE_BUILDER_WIDGET_AI_CATALOGS, PLACEHOLDER, PRAXIS_PAGE_BUILDER_AUTHORING_MANIFEST, PRAXIS_PAGE_BUILDER_COMPONENT_METADATA, PageBuilderAgenticAuthoringService, PageConfigEditorComponent, TileToolbarComponent, WidgetShellEditorComponent, clearWidgetAiCatalogs, compileUiCompositionPlan, getPageAiCatalog, getWidgetAiCapabilities, providePageBuilderWidgetAiCatalogs, providePraxisPageBuilderMetadata, registerWidgetAiCatalog, registerWidgetAiCatalogs, resolveUiCompositionPlanTemplate, validateUiCompositionPlan };
|
|
26750
|
+
export { ComponentPaletteDialogComponent, ConfirmDialogComponent, ConnectionEditorComponent, DynamicPageBuilderComponent, DynamicPageConfigEditorComponent, FloatingToolbarComponent, PAGE_BUILDER_AGENTIC_AUTHORING_OPTIONS, PAGE_BUILDER_AI_CAPABILITIES, PAGE_BUILDER_COMPONENT_CONTEXT_PACK, PAGE_BUILDER_WIDGET_AI_CATALOGS, PLACEHOLDER, PRAXIS_PAGE_BUILDER_AUTHORING_MANIFEST, PRAXIS_PAGE_BUILDER_COMPONENT_METADATA, PageBuilderAgenticAuthoringService, PageConfigEditorComponent, TileToolbarComponent, WidgetShellEditorComponent, clearWidgetAiCatalogs, compileUiCompositionPlan, getPageAiCatalog, getWidgetAiCapabilities, preflightUiCompositionPlan, providePageBuilderWidgetAiCatalogs, providePraxisPageBuilderMetadata, registerWidgetAiCatalog, registerWidgetAiCatalogs, resolveUiCompositionPlanTemplate, validateUiCompositionPlan };
|