@praxisui/page-builder 10.0.0-rc.38 → 10.0.0-rc.39
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.
|
@@ -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_WIDGET_SHELL_PRESETS, SETTINGS_EDITOR_SECTIONS_HOSTED, PraxisI18nService, getWidgetShellActionIdError, WidgetShellComponent, SettingsSectionTabsComponent, PraxisIconButtonComponent, NestedWidgetConfigAccessor, GLOBAL_ACTION_CATALOG, ComponentMetadataRegistry, CompositionValidatorService, getGlobalActionCatalog, PRAXIS_GLOBAL_ACTION_CATALOG, SurfaceOpenActionEditorComponent, BUILTIN_PAGE_LAYOUT_PRESETS, resolveWidgetPageLayoutPreset, BUILTIN_PAGE_THEME_PRESETS, prepareCanvasArrangement, SETTINGS_PANEL_DATA as SETTINGS_PANEL_DATA$1, ResourceDiscoveryService, WidgetPageDataSourceRuntimeService, WidgetPageStateRuntimeService, applyDeclarativeValuePipe, mapCatalogFieldsToMetadata, humanizeCatalogFieldName, validateCanvasArrangement, ResourceQuickConnectComponent, PraxisJsonLogicService, canonicalJsonSha256, ComponentRuntimeProfileService, buildApiUrl, deepMerge, generateId, ObservabilityDashboardService, PraxisRuntimeComponentObservationRegistryService, PraxisCustomizationModeService, domainKnowledgeTimelineToRichContentDocument, DomainRuleService, DomainKnowledgeService, SETTINGS_PANEL_BRIDGE, DynamicWidgetPageComponent, DYNAMIC_PAGE_SHELL_EDITOR, IconPickerService } from '@praxisui/core';
|
|
10
|
+
import { PraxisIconDirective, providePraxisI18n, BUILTIN_WIDGET_SHELL_PRESETS, SETTINGS_EDITOR_SECTIONS_HOSTED, PraxisI18nService, getWidgetShellActionIdError, WidgetShellComponent, SettingsSectionTabsComponent, PraxisIconButtonComponent, NestedWidgetConfigAccessor, GLOBAL_ACTION_CATALOG, ComponentMetadataRegistry, CompositionValidatorService, getGlobalActionCatalog, PRAXIS_GLOBAL_ACTION_CATALOG, SurfaceOpenActionEditorComponent, BUILTIN_PAGE_LAYOUT_PRESETS, resolveWidgetPageLayoutPreset, BUILTIN_PAGE_THEME_PRESETS, prepareCanvasArrangement, SETTINGS_PANEL_DATA as SETTINGS_PANEL_DATA$1, ResourceDiscoveryService, WidgetPageDataSourceRuntimeService, WidgetPageStateRuntimeService, applyDeclarativeValuePipe, mapCatalogFieldsToMetadata, humanizeCatalogFieldName, validateCanvasArrangement, ResourceQuickConnectComponent, PraxisJsonLogicService, canonicalJsonSha256, ComponentRuntimeProfileService, buildApiUrl, deepMerge, generateId, ObservabilityDashboardService, PraxisRuntimeComponentObservationRegistryService, PraxisCustomizationModeService, domainKnowledgeTimelineToRichContentDocument, DomainRuleService, DomainKnowledgeService, SETTINGS_PANEL_BRIDGE, DynamicWidgetPageComponent, DYNAMIC_PAGE_SHELL_EDITOR, IconPickerService, createEmptyNavigationGroupsAuthoringDocument, GOVERNED_PAGE_DEFAULT_ALLOWED_ICONS, upsertNavigationGroupEntry, UiLayoutLifecycleContractError, UiLayoutLifecycleHttpError, decodePageAuthoringDocument, decodeNavigationGroupsAuthoringDocument, buildGovernedPageTarget, buildPublishedPageIdentity, validateRuntimeNavigationMenuNodes, validatePageAuthoringDocument, uiLayoutJsonIdentity, upsertPageNavigationBinding, materializeGovernedPagePreview } from '@praxisui/core';
|
|
11
11
|
import * as i3 from '@angular/material/tooltip';
|
|
12
12
|
import { MatTooltipModule } from '@angular/material/tooltip';
|
|
13
13
|
import { A11yModule, CdkTrapFocus } from '@angular/cdk/a11y';
|
|
@@ -25,7 +25,7 @@ import * as i4$1 from '@angular/material/input';
|
|
|
25
25
|
import { MatInputModule } from '@angular/material/input';
|
|
26
26
|
import * as i3$2 from '@angular/material/select';
|
|
27
27
|
import { MatSelectModule } from '@angular/material/select';
|
|
28
|
-
import { BehaviorSubject, merge, timeout, map, catchError, throwError, firstValueFrom, from, share, concatMap, of, timer, takeUntil, Subject, tap, filter, take, isObservable } from 'rxjs';
|
|
28
|
+
import { BehaviorSubject, merge, timeout, map, catchError, throwError, firstValueFrom, from, share, concatMap, of, timer, takeUntil, Subject, tap, filter, take, isObservable, Observable } from 'rxjs';
|
|
29
29
|
import { SETTINGS_PANEL_DATA } from '@praxisui/settings-panel';
|
|
30
30
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
31
31
|
import * as i2$3 from '@angular/material/autocomplete';
|
|
@@ -35,6 +35,8 @@ import { MatTabsModule } from '@angular/material/tabs';
|
|
|
35
35
|
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
|
36
36
|
import { AgenticAuthoringTurnClientService, withAuthoringScopePolicyContextHints, toPraxisAssistantConversationMessages, PraxisAssistantTurnOrchestratorService, PraxisAssistantSessionRegistryService, createPraxisAssistantViewportLayout, PraxisAiAssistantShellComponent } from '@praxisui/ai';
|
|
37
37
|
import { RouterLink } from '@angular/router';
|
|
38
|
+
import { validateDynamicFormPageWidget, DYNAMIC_FORM_GOVERNED_PAGE_PREVIEW_ADAPTER } from '@praxisui/dynamic-form';
|
|
39
|
+
import { validateTablePageWidget, TABLE_GOVERNED_PAGE_PREVIEW_ADAPTER } from '@praxisui/table';
|
|
38
40
|
|
|
39
41
|
const PLACEHOLDER = 1;
|
|
40
42
|
|
|
@@ -34734,6 +34736,7 @@ class DynamicPageBuilderComponent {
|
|
|
34734
34736
|
|
|
34735
34737
|
<div class="builder-governance-persistence" data-testid="dashboard-layout-status" role="status"
|
|
34736
34738
|
aria-live="polite" aria-atomic="true"
|
|
34739
|
+
[class.busy]="isGovernanceBusy()"
|
|
34737
34740
|
[class.customized]="isLayoutCustomized()" [class.governed]="!isLayoutCustomized()">
|
|
34738
34741
|
<span>{{ governancePersistenceLabel() }}</span>
|
|
34739
34742
|
@if (governancePersistence().status === 'saved' && governanceLastSavedAt(); as saved) {
|
|
@@ -35307,7 +35310,7 @@ class DynamicPageBuilderComponent {
|
|
|
35307
35310
|
}
|
|
35308
35311
|
</praxis-floating-toolbar>
|
|
35309
35312
|
</div>
|
|
35310
|
-
`, isInline: true, styles: [":host{display:block;position:relative;min-height:var(--pdx-page-builder-min-height, 420px)}.builder-shell{display:block;position:relative;min-height:inherit;height:100%;box-sizing:border-box}.builder-shell--bottom-toolbar{padding-block-end:calc(80px + env(safe-area-inset-bottom,0px))}.builder-shell:has(.pdx-shell.fullscreen) praxis-floating-toolbar{display:none}.builder-governance-toolbar{display:flex;justify-content:space-between;align-items:center;padding:10px 18px;margin-bottom:16px;border-radius:14px;background:var(--surface-gradient, color-mix(in srgb, var(--md-sys-color-surface-container, #1e293b) 95%, transparent));border:1px solid var(--border, color-mix(in srgb, var(--md-sys-color-outline-variant, #334155) 60%, transparent));gap:16px;flex-wrap:wrap}.builder-governance-brand{display:flex;align-items:center;gap:12px;flex-wrap:wrap}.builder-governance-status-indicator{display:inline-flex;align-items:center;gap:8px;padding:3px 10px;border-radius:9999px;background:color-mix(in srgb,var(--ready, #22c55e) 12%,transparent);border:1px solid color-mix(in srgb,var(--ready, #22c55e) 28%,transparent)}.builder-governance-dot-pulse{width:7px;height:7px;border-radius:50%;background:var(--ready, #22c55e);box-shadow:0 0 8px var(--ready, #22c55e);animation:builderGovernancePulse 2s infinite cubic-bezier(.4,0,.6,1)}.builder-governance-title{font-size:11px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--ready, #22c55e)}.builder-governance-badge{display:inline-flex;align-items:center;gap:6px;padding:4px 10px;border-radius:8px;background:color-mix(in srgb,var(--primary, #3b82f6) 12%,transparent);border:1px solid color-mix(in srgb,var(--primary, #3b82f6) 24%,transparent);font-size:11px;font-weight:600;color:var(--primary, #3b82f6)}.builder-governance-badge .material-symbols-outlined{font-size:14px}.builder-governance-persistence{display:inline-flex;align-items:center;gap:6px;padding:3px 10px;border-radius:9999px;font-size:11px;font-weight:600;letter-spacing:.02em;border:1px solid transparent;transition:all .2s ease}.builder-governance-persistence.governed{background:color-mix(in srgb,var(--muted, #64748b) 15%,transparent);border-color:color-mix(in srgb,var(--border, #334155) 80%,transparent);color:var(--muted-foreground, #94a3b8)}.builder-governance-persistence.customized{background:color-mix(in srgb,var(--primary, #3b82f6) 14%,transparent);border-color:color-mix(in srgb,var(--primary, #3b82f6) 35%,transparent);color:var(--primary, #3b82f6)}.builder-governance-persistence .builder-governance-saved-time{opacity:.75;font-weight:400}.builder-governance-actions{display:flex;align-items:center;gap:10px;margin-left:auto}.builder-governance-btn{display:inline-flex;align-items:center;gap:6px;padding:7px 13px;border-radius:9px;font-size:12px;font-weight:600;cursor:pointer;border:1px solid var(--border, #334155);background:var(--surface, #1e293b);color:var(--foreground, #f8fafc);transition:all .15s ease}.builder-governance-btn:hover:not(:disabled){border-color:var(--primary, #3b82f6);background:color-mix(in srgb,var(--primary, #3b82f6) 10%,var(--surface, #1e293b))}.builder-governance-btn:disabled{opacity:.5;cursor:not-allowed}.builder-governance-btn--reset{border-color:color-mix(in srgb,var(--warning, #eab308) 40%,transparent);color:var(--warning, #eab308)}.builder-governance-btn--reset:hover:not(:disabled){border-color:var(--warning, #eab308);background:color-mix(in srgb,var(--warning, #eab308) 12%,transparent)}.builder-governance-btn .material-symbols-outlined,.builder-governance-toggle-btn .material-symbols-outlined{font-size:16px}.builder-governance-toggle-btn{display:inline-flex;align-items:center;gap:7px;padding:7px 15px;border-radius:9px;font-size:12px;font-weight:600;cursor:pointer;border:1px solid var(--border, #334155);background:var(--surface, #1e293b);color:var(--foreground, #f8fafc);transition:all .2s cubic-bezier(.16,1,.3,1)}.builder-governance-toggle-btn:hover:not(:disabled){border-color:var(--primary, #3b82f6);transform:translateY(-1px);box-shadow:0 4px 12px color-mix(in srgb,var(--primary, #3b82f6) 20%,transparent)}.builder-governance-toggle-btn.active{background:linear-gradient(135deg,var(--primary, #3b82f6),color-mix(in srgb,var(--primary, #3b82f6) 80%,black));border-color:var(--primary, #3b82f6);color:#fff;box-shadow:0 0 16px color-mix(in srgb,var(--primary, #3b82f6) 35%,transparent)}.builder-governance-toggle-btn:disabled{opacity:.5;cursor:not-allowed}@keyframes builderGovernancePulse{0%,to{opacity:1;transform:scale(1)}50%{opacity:.4;transform:scale(.85)}}.builder-authoring-lineage-alert{display:grid;grid-template-columns:auto minmax(0,1fr);gap:10px;margin:0 0 14px;padding:12px 14px;border:1px solid var(--md-sys-color-error, #b3261e);border-radius:10px;background:var(--md-sys-color-error-container, #f9dedc);color:var(--md-sys-color-on-error-container, #410e0b)}.builder-authoring-lineage-alert strong{display:block}.builder-authoring-lineage-alert p{margin:4px 0 0}.builder-authoring-lineage-alert ul{margin:8px 0 0;padding-inline-start:20px}@media(forced-colors:active){.builder-authoring-lineage-alert{border:2px solid CanvasText;background:Canvas;color:CanvasText}}.agentic-diagnostics-panel{position:absolute;z-index:21;right:16px;width:min(520px,calc(100% - 32px));max-height:min(440px,calc(100% - 32px));overflow:auto;padding:12px;border:1px solid rgba(107,114,128,.28);border-radius:8px;background:#fffffff5;color:#111827;box-shadow:0 14px 36px #0000003d}.agentic-diagnostics-panel--collapsed{width:min(280px,calc(100% - 32px));max-height:58px;overflow:hidden;padding:10px 12px;pointer-events:auto}.agentic-diagnostics-panel__header{display:flex;align-items:center;justify-content:space-between;gap:12px;font-size:13px;font-weight:700}.agentic-diagnostics-panel__header-actions{display:flex;align-items:center;gap:8px}.agentic-diagnostics-panel__badge{padding:2px 6px;border:1px solid rgba(107,114,128,.28);border-radius:8px;font-size:11px;font-weight:600;color:#4b5563}.agentic-diagnostics-panel__toggle,.shared-rule-cockpit__toggle{appearance:none;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 18%,transparent);border-radius:999px;background:color-mix(in srgb,var(--md-sys-color-surface-container-lowest) 84%,transparent);color:var(--md-sys-color-primary);cursor:pointer;font-size:11px;font-weight:700;line-height:1;padding:6px 9px;white-space:nowrap}.agentic-diagnostics-panel__description{margin:8px 0 10px;color:#4b5563;font-size:12px;line-height:1.4}.agentic-diagnostics-panel pre{margin:0;white-space:pre-wrap;overflow-wrap:anywhere;font-size:11px;line-height:1.45;color:#1f2937}.agentic-diagnostics-panel__health{display:grid;gap:8px;margin-bottom:12px}.agentic-diagnostics-panel__health h3,.agentic-diagnostics-panel__health p{margin:0}.agentic-diagnostics-panel__health h3{font-size:13px}.agentic-diagnostics-panel__health p{color:#4b5563;font-size:11px}.agentic-diagnostics-panel__metrics{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:0}.agentic-diagnostics-panel__metrics div{padding:8px;border-radius:8px;background:#f3f4f6}.agentic-diagnostics-panel__metrics dt{color:#4b5563;font-size:10px}.agentic-diagnostics-panel__metrics dd{margin:3px 0 0;font-size:14px;font-weight:700}.agentic-diagnostics-panel__health-table{width:100%;border-collapse:collapse;font-size:10px}.agentic-diagnostics-panel__health-table caption{margin-bottom:4px;text-align:left;font-weight:700}.agentic-diagnostics-panel__health-table th,.agentic-diagnostics-panel__health-table td{padding:5px 4px;border-bottom:1px solid #e5e7eb;text-align:left}.agentic-diagnostics-panel__raw summary{margin-bottom:8px;cursor:pointer;font-size:11px;font-weight:700}@media(max-width:520px){.agentic-diagnostics-panel__metrics{grid-template-columns:repeat(2,minmax(0,1fr))}}praxis-ai-assistant-shell{position:fixed;inset:0;z-index:140;pointer-events:none}@media(min-width:1100px){.builder-shell--agentic-review-rail{box-sizing:border-box;padding-right:464px}}.builder-shell__agentic-toggle{position:relative}.builder-shell__agentic-toggle--open,.builder-shell__agentic-toggle--minimized{outline:2px solid rgba(96,165,250,.52);outline-offset:2px}.builder-shell__agentic-toggle--minimized:after{content:\"\";position:absolute;top:6px;right:6px;width:8px;height:8px;border:2px solid var(--md-sys-color-surface, #111827);border-radius:999px;background:#34d399}.builder-shell__connections-toggle--open{outline:2px solid color-mix(in srgb,var(--md-sys-color-primary, #6750a4) 52%,transparent);outline-offset:2px}.builder-shell__connections-toggle{position:relative}.builder-shell__connections-toggle--recommended:not(.builder-shell__connections-toggle--open){outline:2px solid rgba(34,211,238,.48);outline-offset:2px}.builder-shell__connections-toggle-badge{position:absolute;top:5px;right:5px;width:9px;height:9px;border:2px solid var(--md-sys-color-surface, #111827);border-radius:999px;background:#22d3ee;box-shadow:0 0 0 3px #22d3ee2e}.shared-rule-cockpit{position:absolute;z-index:120;left:16px;bottom:88px;width:min(720px,calc(100% - 32px));max-height:min(560px,calc(100vh - 140px));padding:14px;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 24%,transparent);border-radius:12px;background:linear-gradient(135deg,color-mix(in srgb,var(--md-sys-color-primary-container) 24%,var(--md-sys-color-surface-container-high) 76%),color-mix(in srgb,var(--md-sys-color-tertiary-container) 18%,var(--md-sys-color-surface-container) 82%));color:var(--md-sys-color-on-surface);box-shadow:0 18px 46px color-mix(in srgb,var(--md-sys-color-shadow, #000) 24%,transparent);overflow:auto;overscroll-behavior:contain}.shared-rule-cockpit--collapsed{width:min(360px,calc(100% - 32px));max-height:72px;overflow:hidden;padding:10px 12px}.shared-rule-cockpit__header{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.shared-rule-cockpit__header-actions{display:flex;align-items:center;gap:8px}.shared-rule-cockpit__header div{display:grid;gap:2px}.shared-rule-cockpit__header strong{font-size:14px;line-height:1.2}.shared-rule-cockpit__header span{color:var(--md-sys-color-on-surface-variant);font-size:12px;overflow-wrap:anywhere}.shared-rule-cockpit__badge{flex:0 0 auto;max-width:220px;padding:3px 8px;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 20%,transparent);border-radius:999px;background:color-mix(in srgb,var(--md-sys-color-surface-container-lowest) 74%,transparent);color:var(--md-sys-color-primary);font-size:11px;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.shared-rule-cockpit__description,.shared-rule-cockpit__status,.shared-rule-cockpit__error{margin:10px 0 0;color:var(--md-sys-color-on-surface-variant);font-size:12px;line-height:1.45}.shared-rule-cockpit__error{color:var(--md-sys-color-error);font-weight:600}.shared-rule-cockpit__actions{display:flex;flex-wrap:wrap;gap:8px;margin-top:12px}.shared-rule-cockpit__facts{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px;margin:12px 0 0}.shared-rule-cockpit__facts div{min-width:0;padding:8px;border-radius:10px;background:color-mix(in srgb,var(--md-sys-color-surface-container-lowest) 72%,transparent)}.shared-rule-cockpit__facts dt{color:var(--md-sys-color-on-surface-variant);font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.04em}.shared-rule-cockpit__facts dd{margin:4px 0 0;color:var(--md-sys-color-on-surface);font-size:12px;overflow-wrap:anywhere}.shared-rule-cockpit__enforcement-matrix{display:grid;gap:10px;margin-top:12px;padding:10px;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 14%,transparent);border-radius:12px;background:color-mix(in srgb,var(--md-sys-color-surface-container-lowest) 58%,transparent)}.shared-rule-cockpit__matrix-header{display:flex;align-items:flex-start;justify-content:space-between;gap:10px}.shared-rule-cockpit__matrix-header div{display:grid;gap:2px}.shared-rule-cockpit__matrix-header strong{color:var(--md-sys-color-on-surface);font-size:12px}.shared-rule-cockpit__matrix-header span,.shared-rule-cockpit__matrix-source{color:var(--md-sys-color-on-surface-variant);font-size:11px;line-height:1.35;overflow-wrap:anywhere}.shared-rule-cockpit__matrix-badge{flex:0 0 auto;padding:3px 8px;border-radius:999px;background:#16a34a1f;color:#166534;font-weight:800;white-space:nowrap}.shared-rule-cockpit__projection-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}.shared-rule-cockpit__projection{display:grid;gap:3px;min-width:0;padding:9px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 52%,transparent);border-radius:11px;background:color-mix(in srgb,var(--md-sys-color-surface-container-low) 82%,transparent)}.shared-rule-cockpit__projection[data-status=ready]{border-color:color-mix(in srgb,var(--md-sys-color-tertiary) 24%,transparent);background:color-mix(in srgb,var(--md-sys-color-tertiary-container) 32%,var(--md-sys-color-surface-container-low) 68%)}.shared-rule-cockpit__projection span{color:var(--md-sys-color-primary);font-size:10px;font-weight:800;letter-spacing:.04em;text-transform:uppercase;overflow-wrap:anywhere}.shared-rule-cockpit__projection strong{color:var(--md-sys-color-on-surface);font-size:12px}.shared-rule-cockpit__projection small,.shared-rule-cockpit__projection em{color:var(--md-sys-color-on-surface-variant);font-size:11px;font-style:normal;overflow-wrap:anywhere}.shared-rule-cockpit__matrix-source{margin:0}.shared-rule-cockpit__timeline{display:grid;gap:8px;max-height:190px;margin:12px 0 0;padding:0;overflow:auto;list-style:none}.shared-rule-cockpit__timeline-item{display:grid;grid-template-columns:auto minmax(0,1fr);gap:10px;padding:10px;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 12%,transparent);border-radius:12px;background:color-mix(in srgb,var(--md-sys-color-surface-container-lowest) 76%,transparent)}.shared-rule-cockpit__timeline-icon{display:grid;width:24px;height:24px;place-items:center;border-radius:999px;background:#1e40af1a;color:var(--md-sys-color-primary);font-family:Material Icons;font-size:16px;line-height:1}.shared-rule-cockpit__timeline-item strong{color:var(--md-sys-color-on-surface);font-size:12px;line-height:1.35}.shared-rule-cockpit__timeline-item p,.shared-rule-cockpit__timeline-item small,.shared-rule-cockpit__timeline-item time,.shared-rule-cockpit__timeline-empty{display:block;margin:3px 0 0;color:var(--md-sys-color-on-surface-variant);font-size:11px;line-height:1.35;overflow-wrap:anywhere}.shared-rule-cockpit__timeline-badge{display:inline-flex;margin-left:6px;padding:2px 6px;border-radius:999px;background:color-mix(in srgb,var(--md-sys-color-secondary-container) 42%,transparent);color:var(--md-sys-color-on-secondary-container);font-size:10px;font-weight:700}.shared-rule-cockpit__timeline-empty{padding:10px;border-radius:12px;background:color-mix(in srgb,var(--md-sys-color-surface-container-lowest) 64%,transparent)}@media(max-width:720px){.shared-rule-cockpit{bottom:72px;max-height:calc(100vh - 112px)}.shared-rule-cockpit__facts,.shared-rule-cockpit__projection-grid{grid-template-columns:1fr}}\n"], dependencies: [{ kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i2.MatMiniFabButton, selector: "button[mat-mini-fab], a[mat-mini-fab], button[matMiniFab], a[matMiniFab]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i3.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "component", type: DynamicWidgetPageComponent, selector: "praxis-dynamic-page", inputs: ["page", "context", "strictValidation", "enableCustomization", "authoringCapabilities", "showPageSettingsButton", "shellEditorComponent", "pageEditorComponent", "autoPersist", "pageIdentity", "componentInstanceId", "showWidgetAssistantButton"], outputs: ["pageChange", "persistenceChange", "pageAuthoringChange", "widgetEvent", "widgetSelectionChange", "widgetAssistantRequested", "widgetDiagnosticsChange"] }, { kind: "component", type: FloatingToolbarComponent, selector: "praxis-floating-toolbar", inputs: ["visible", "canUndo", "canRedo", "showSave", "showPreview", "showAdd", "showSettings", "editingEnabled", "previewActive", "connectionMode", "modeLabel", "addLabel", "saveLabel", "undoLabel", "redoLabel", "settingsLabel", "previewLabel", "exitPreviewLabel"], outputs: ["add", "undo", "redo", "settings", "preview", "save"] }, { kind: "component", type: ConnectionEditorComponent, selector: "praxis-connection-editor", inputs: ["open", "page"], outputs: ["pageChange", "focusWidget", "openPageSettings", "close"] }, { kind: "component", type: PraxisAiAssistantShellComponent, selector: "praxis-ai-assistant-shell", inputs: ["labels", "mode", "state", "contextItems", "attachments", "messages", "quickReplies", "recommendedIntents", "prompt", "statusText", "errorText", "testIdPrefix", "panelTestId", "submitTestId", "applyTestId", "primaryAction", "secondaryActions", "governanceActions", "busy", "canSubmit", "canApply", "submitOnEnter", "showAttachAction", "enablePastedAttachments", "enableFileAttachments", "attachmentAccept", "attachmentMultiple", "voiceInputMode", "voiceLanguage", "draggable", "resizable", "minWidth", "minHeight", "margin", "layout"], outputs: ["promptChange", "submitPrompt", "apply", "retryTurn", "cancelTurn", "shellAction", "close", "attach", "attachmentsPasted", "attachmentsSelected", "removeAttachment", "messageAction", "editMessage", "resendMessage", "quickReply", "recommendedIntent", "layoutChange"] }] });
|
|
35313
|
+
`, isInline: true, styles: [":host{display:block;position:relative;min-height:var(--pdx-page-builder-min-height, 420px)}.builder-shell{display:block;position:relative;min-height:inherit;height:100%;box-sizing:border-box}.builder-shell--bottom-toolbar{padding-block-end:calc(80px + env(safe-area-inset-bottom,0px))}.builder-shell:has(.pdx-shell.fullscreen) praxis-floating-toolbar{display:none}.builder-governance-toolbar{display:flex;justify-content:space-between;align-items:center;padding:10px 18px;margin-bottom:16px;border-radius:14px;background:var(--surface-gradient, color-mix(in srgb, var(--md-sys-color-surface-container, #1e293b) 95%, transparent));border:1px solid var(--border, color-mix(in srgb, var(--md-sys-color-outline-variant, #334155) 60%, transparent));gap:16px;flex-wrap:wrap}.builder-governance-brand{display:flex;align-items:center;gap:12px;flex-wrap:wrap}.builder-governance-status-indicator{display:inline-flex;align-items:center;gap:8px;padding:3px 10px;border-radius:9999px;background:color-mix(in srgb,var(--ready, #22c55e) 12%,transparent);border:1px solid color-mix(in srgb,var(--ready, #22c55e) 28%,transparent)}.builder-governance-dot-pulse{width:7px;height:7px;border-radius:50%;background:var(--ready, #22c55e);box-shadow:0 0 8px var(--ready, #22c55e);animation:builderGovernancePulse 2s infinite cubic-bezier(.4,0,.6,1)}.builder-governance-title{font-size:11px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--ready, #22c55e)}.builder-governance-badge{display:inline-flex;align-items:center;gap:6px;padding:4px 10px;border-radius:8px;background:color-mix(in srgb,var(--primary, #3b82f6) 12%,transparent);border:1px solid color-mix(in srgb,var(--primary, #3b82f6) 24%,transparent);font-size:11px;font-weight:600;color:var(--primary, #3b82f6)}.builder-governance-badge .material-symbols-outlined{font-size:14px}.builder-governance-persistence{display:inline-flex;align-items:center;gap:6px;padding:3px 10px;border-radius:9999px;font-size:11px;font-weight:600;letter-spacing:.02em;border:1px solid transparent;transition:all .2s ease}.builder-governance-persistence.governed{background:color-mix(in srgb,var(--muted, #64748b) 15%,transparent);border-color:color-mix(in srgb,var(--border, #334155) 80%,transparent);color:var(--muted-foreground, #94a3b8)}.builder-governance-persistence.customized{background:color-mix(in srgb,var(--primary, #3b82f6) 14%,transparent);border-color:color-mix(in srgb,var(--primary, #3b82f6) 35%,transparent);color:var(--primary, #3b82f6)}.builder-governance-persistence .builder-governance-saved-time{opacity:.75;font-weight:400}.builder-governance-actions{display:flex;align-items:center;gap:10px;margin-left:auto}.builder-governance-btn{display:inline-flex;align-items:center;gap:6px;padding:7px 13px;border-radius:9px;font-size:12px;font-weight:600;cursor:pointer;border:1px solid var(--border, #334155);background:var(--surface, #1e293b);color:var(--foreground, #f8fafc);transition:all .15s ease}.builder-governance-btn:hover:not(:disabled){border-color:var(--primary, #3b82f6);background:color-mix(in srgb,var(--primary, #3b82f6) 10%,var(--surface, #1e293b))}.builder-governance-btn:disabled{opacity:.5;cursor:not-allowed}.builder-governance-btn--reset{border-color:color-mix(in srgb,var(--warning, #eab308) 40%,transparent);color:var(--warning, #eab308)}.builder-governance-btn--reset:hover:not(:disabled){border-color:var(--warning, #eab308);background:color-mix(in srgb,var(--warning, #eab308) 12%,transparent)}.builder-governance-btn .material-symbols-outlined,.builder-governance-toggle-btn .material-symbols-outlined{font-size:16px}.builder-governance-toggle-btn{display:inline-flex;align-items:center;gap:7px;padding:7px 15px;border-radius:9px;font-size:12px;font-weight:600;cursor:pointer;border:1px solid var(--border, #334155);background:var(--surface, #1e293b);color:var(--foreground, #f8fafc);transition:all .2s cubic-bezier(.16,1,.3,1)}.builder-governance-toggle-btn:hover:not(:disabled){border-color:var(--primary, #3b82f6);transform:translateY(-1px);box-shadow:0 4px 12px color-mix(in srgb,var(--primary, #3b82f6) 20%,transparent)}.builder-governance-toggle-btn.active{background:linear-gradient(135deg,var(--primary, #3b82f6),color-mix(in srgb,var(--primary, #3b82f6) 80%,black));border-color:var(--primary, #3b82f6);color:#fff;box-shadow:0 0 16px color-mix(in srgb,var(--primary, #3b82f6) 35%,transparent)}.builder-governance-toggle-btn:disabled{opacity:.5;cursor:not-allowed}@keyframes builderGovernancePulse{0%,to{opacity:1;transform:scale(1)}50%{opacity:.4;transform:scale(.85)}}.builder-authoring-lineage-alert{display:grid;grid-template-columns:auto minmax(0,1fr);gap:10px;margin:0 0 14px;padding:12px 14px;border:1px solid var(--md-sys-color-error, #b3261e);border-radius:10px;background:var(--md-sys-color-error-container, #f9dedc);color:var(--md-sys-color-on-error-container, #410e0b)}.builder-authoring-lineage-alert strong{display:block}.builder-authoring-lineage-alert p{margin:4px 0 0}.builder-authoring-lineage-alert ul{margin:8px 0 0;padding-inline-start:20px}@media(forced-colors:active){.builder-authoring-lineage-alert{border:2px solid CanvasText;background:Canvas;color:CanvasText}}.agentic-diagnostics-panel{position:absolute;z-index:21;right:16px;width:min(520px,calc(100% - 32px));max-height:min(440px,calc(100% - 32px));overflow:auto;padding:12px;border:1px solid rgba(107,114,128,.28);border-radius:8px;background:#fffffff5;color:#111827;box-shadow:0 14px 36px #0000003d}.agentic-diagnostics-panel--collapsed{width:min(280px,calc(100% - 32px));max-height:58px;overflow:hidden;padding:10px 12px;pointer-events:auto}.agentic-diagnostics-panel__header{display:flex;align-items:center;justify-content:space-between;gap:12px;font-size:13px;font-weight:700}.agentic-diagnostics-panel__header-actions{display:flex;align-items:center;gap:8px}.agentic-diagnostics-panel__badge{padding:2px 6px;border:1px solid rgba(107,114,128,.28);border-radius:8px;font-size:11px;font-weight:600;color:#4b5563}.agentic-diagnostics-panel__toggle,.shared-rule-cockpit__toggle{appearance:none;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 18%,transparent);border-radius:999px;background:color-mix(in srgb,var(--md-sys-color-surface-container-lowest) 84%,transparent);color:var(--md-sys-color-primary);cursor:pointer;font-size:11px;font-weight:700;line-height:1;padding:6px 9px;white-space:nowrap}.agentic-diagnostics-panel__description{margin:8px 0 10px;color:#4b5563;font-size:12px;line-height:1.4}.agentic-diagnostics-panel pre{margin:0;white-space:pre-wrap;overflow-wrap:anywhere;font-size:11px;line-height:1.45;color:#1f2937}.agentic-diagnostics-panel__health{display:grid;gap:8px;margin-bottom:12px}.agentic-diagnostics-panel__health h3,.agentic-diagnostics-panel__health p{margin:0}.agentic-diagnostics-panel__health h3{font-size:13px}.agentic-diagnostics-panel__health p{color:#4b5563;font-size:11px}.agentic-diagnostics-panel__metrics{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:0}.agentic-diagnostics-panel__metrics div{padding:8px;border-radius:8px;background:#f3f4f6}.agentic-diagnostics-panel__metrics dt{color:#4b5563;font-size:10px}.agentic-diagnostics-panel__metrics dd{margin:3px 0 0;font-size:14px;font-weight:700}.agentic-diagnostics-panel__health-table{width:100%;border-collapse:collapse;font-size:10px}.agentic-diagnostics-panel__health-table caption{margin-bottom:4px;text-align:left;font-weight:700}.agentic-diagnostics-panel__health-table th,.agentic-diagnostics-panel__health-table td{padding:5px 4px;border-bottom:1px solid #e5e7eb;text-align:left}.agentic-diagnostics-panel__raw summary{margin-bottom:8px;cursor:pointer;font-size:11px;font-weight:700}@media(max-width:520px){.agentic-diagnostics-panel__metrics{grid-template-columns:repeat(2,minmax(0,1fr))}}praxis-ai-assistant-shell{position:fixed;inset:0;z-index:140;pointer-events:none}@media(min-width:1100px){.builder-shell--agentic-review-rail{box-sizing:border-box;padding-right:464px}}.builder-shell__agentic-toggle{position:relative}.builder-shell__agentic-toggle--open,.builder-shell__agentic-toggle--minimized{outline:2px solid rgba(96,165,250,.52);outline-offset:2px}.builder-shell__agentic-toggle--minimized:after{content:\"\";position:absolute;top:6px;right:6px;width:8px;height:8px;border:2px solid var(--md-sys-color-surface, #111827);border-radius:999px;background:#34d399}.builder-shell__connections-toggle--open{outline:2px solid color-mix(in srgb,var(--md-sys-color-primary, #6750a4) 52%,transparent);outline-offset:2px}.builder-shell__connections-toggle{position:relative}.builder-shell__connections-toggle--recommended:not(.builder-shell__connections-toggle--open){outline:2px solid rgba(34,211,238,.48);outline-offset:2px}.builder-shell__connections-toggle-badge{position:absolute;top:5px;right:5px;width:9px;height:9px;border:2px solid var(--md-sys-color-surface, #111827);border-radius:999px;background:#22d3ee;box-shadow:0 0 0 3px #22d3ee2e}.shared-rule-cockpit{position:absolute;z-index:120;left:16px;bottom:88px;width:min(720px,calc(100% - 32px));max-height:min(560px,calc(100vh - 140px));padding:14px;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 24%,transparent);border-radius:12px;background:linear-gradient(135deg,color-mix(in srgb,var(--md-sys-color-primary-container) 24%,var(--md-sys-color-surface-container-high) 76%),color-mix(in srgb,var(--md-sys-color-tertiary-container) 18%,var(--md-sys-color-surface-container) 82%));color:var(--md-sys-color-on-surface);box-shadow:0 18px 46px color-mix(in srgb,var(--md-sys-color-shadow, #000) 24%,transparent);overflow:auto;overscroll-behavior:contain}.shared-rule-cockpit--collapsed{width:min(360px,calc(100% - 32px));max-height:72px;overflow:hidden;padding:10px 12px}.shared-rule-cockpit__header{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.shared-rule-cockpit__header-actions{display:flex;align-items:center;gap:8px}.shared-rule-cockpit__header div{display:grid;gap:2px}.shared-rule-cockpit__header strong{font-size:14px;line-height:1.2}.shared-rule-cockpit__header span{color:var(--md-sys-color-on-surface-variant);font-size:12px;overflow-wrap:anywhere}.shared-rule-cockpit__badge{flex:0 0 auto;max-width:220px;padding:3px 8px;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 20%,transparent);border-radius:999px;background:color-mix(in srgb,var(--md-sys-color-surface-container-lowest) 74%,transparent);color:var(--md-sys-color-primary);font-size:11px;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.shared-rule-cockpit__description,.shared-rule-cockpit__status,.shared-rule-cockpit__error{margin:10px 0 0;color:var(--md-sys-color-on-surface-variant);font-size:12px;line-height:1.45}.shared-rule-cockpit__status,.builder-governance-persistence.busy>span:first-child{font-weight:550;color:var(--pdx-text-shimmer-base, var(--md-sys-color-on-surface-variant))}@supports ((-webkit-background-clip: text) or (background-clip: text)){.shared-rule-cockpit__status,.builder-governance-persistence.busy>span:first-child{background-image:var( --pdx-text-shimmer-gradient, linear-gradient( 105deg, color-mix(in oklab, var(--md-sys-color-on-surface-variant, #64748b) 82%, var(--md-sys-color-on-surface, #0f172a) 18%) 0%, color-mix(in oklab, var(--md-sys-color-on-surface-variant, #64748b) 82%, var(--md-sys-color-on-surface, #0f172a) 18%) 26%, color-mix(in oklab, var(--md-sys-color-tertiary, #6366f1) 74%, #a855f7 26%) 40%, var(--pdx-text-shimmer-peak, var(--md-sys-color-on-surface, #ffffff)) 50%, color-mix(in oklab, var(--md-sys-color-primary, #006a6a) 74%, #ec4899 26%) 60%, color-mix(in oklab, var(--md-sys-color-on-surface-variant, #64748b) 82%, var(--md-sys-color-on-surface, #0f172a) 18%) 74%, color-mix(in oklab, var(--md-sys-color-on-surface-variant, #64748b) 82%, var(--md-sys-color-on-surface, #0f172a) 18%) 100% ) );background-size:300% 100%;background-position:100% 50%;background-repeat:no-repeat;-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;color:transparent;animation:sharedRuleCockpitTextShimmer var(--pdx-text-shimmer-duration, 2.6s) linear infinite;will-change:background-position}}@media(prefers-reduced-motion:reduce),(forced-colors:active),print{.shared-rule-cockpit__status,.builder-governance-persistence.busy>span:first-child{animation:none!important;background-image:none!important;-webkit-text-fill-color:currentColor!important;color:var(--md-sys-color-primary)!important;will-change:auto}}@media(forced-colors:active){.shared-rule-cockpit__status,.builder-governance-persistence.busy>span:first-child{color:CanvasText!important;-webkit-text-fill-color:CanvasText!important}}@keyframes sharedRuleCockpitTextShimmer{0%{background-position:100% 50%}to{background-position:0% 50%}}.shared-rule-cockpit__error{color:var(--md-sys-color-error);font-weight:600}.shared-rule-cockpit__actions{display:flex;flex-wrap:wrap;gap:8px;margin-top:12px}.shared-rule-cockpit__facts{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px;margin:12px 0 0}.shared-rule-cockpit__facts div{min-width:0;padding:8px;border-radius:10px;background:color-mix(in srgb,var(--md-sys-color-surface-container-lowest) 72%,transparent)}.shared-rule-cockpit__facts dt{color:var(--md-sys-color-on-surface-variant);font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.04em}.shared-rule-cockpit__facts dd{margin:4px 0 0;color:var(--md-sys-color-on-surface);font-size:12px;overflow-wrap:anywhere}.shared-rule-cockpit__enforcement-matrix{display:grid;gap:10px;margin-top:12px;padding:10px;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 14%,transparent);border-radius:12px;background:color-mix(in srgb,var(--md-sys-color-surface-container-lowest) 58%,transparent)}.shared-rule-cockpit__matrix-header{display:flex;align-items:flex-start;justify-content:space-between;gap:10px}.shared-rule-cockpit__matrix-header div{display:grid;gap:2px}.shared-rule-cockpit__matrix-header strong{color:var(--md-sys-color-on-surface);font-size:12px}.shared-rule-cockpit__matrix-header span,.shared-rule-cockpit__matrix-source{color:var(--md-sys-color-on-surface-variant);font-size:11px;line-height:1.35;overflow-wrap:anywhere}.shared-rule-cockpit__matrix-badge{flex:0 0 auto;padding:3px 8px;border-radius:999px;background:#16a34a1f;color:#166534;font-weight:800;white-space:nowrap}.shared-rule-cockpit__projection-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}.shared-rule-cockpit__projection{display:grid;gap:3px;min-width:0;padding:9px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 52%,transparent);border-radius:11px;background:color-mix(in srgb,var(--md-sys-color-surface-container-low) 82%,transparent)}.shared-rule-cockpit__projection[data-status=ready]{border-color:color-mix(in srgb,var(--md-sys-color-tertiary) 24%,transparent);background:color-mix(in srgb,var(--md-sys-color-tertiary-container) 32%,var(--md-sys-color-surface-container-low) 68%)}.shared-rule-cockpit__projection span{color:var(--md-sys-color-primary);font-size:10px;font-weight:800;letter-spacing:.04em;text-transform:uppercase;overflow-wrap:anywhere}.shared-rule-cockpit__projection strong{color:var(--md-sys-color-on-surface);font-size:12px}.shared-rule-cockpit__projection small,.shared-rule-cockpit__projection em{color:var(--md-sys-color-on-surface-variant);font-size:11px;font-style:normal;overflow-wrap:anywhere}.shared-rule-cockpit__matrix-source{margin:0}.shared-rule-cockpit__timeline{display:grid;gap:8px;max-height:190px;margin:12px 0 0;padding:0;overflow:auto;list-style:none}.shared-rule-cockpit__timeline-item{display:grid;grid-template-columns:auto minmax(0,1fr);gap:10px;padding:10px;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 12%,transparent);border-radius:12px;background:color-mix(in srgb,var(--md-sys-color-surface-container-lowest) 76%,transparent)}.shared-rule-cockpit__timeline-icon{display:grid;width:24px;height:24px;place-items:center;border-radius:999px;background:#1e40af1a;color:var(--md-sys-color-primary);font-family:Material Icons;font-size:16px;line-height:1}.shared-rule-cockpit__timeline-item strong{color:var(--md-sys-color-on-surface);font-size:12px;line-height:1.35}.shared-rule-cockpit__timeline-item p,.shared-rule-cockpit__timeline-item small,.shared-rule-cockpit__timeline-item time,.shared-rule-cockpit__timeline-empty{display:block;margin:3px 0 0;color:var(--md-sys-color-on-surface-variant);font-size:11px;line-height:1.35;overflow-wrap:anywhere}.shared-rule-cockpit__timeline-badge{display:inline-flex;margin-left:6px;padding:2px 6px;border-radius:999px;background:color-mix(in srgb,var(--md-sys-color-secondary-container) 42%,transparent);color:var(--md-sys-color-on-secondary-container);font-size:10px;font-weight:700}.shared-rule-cockpit__timeline-empty{padding:10px;border-radius:12px;background:color-mix(in srgb,var(--md-sys-color-surface-container-lowest) 64%,transparent)}@media(max-width:720px){.shared-rule-cockpit{bottom:72px;max-height:calc(100vh - 112px)}.shared-rule-cockpit__facts,.shared-rule-cockpit__projection-grid{grid-template-columns:1fr}}\n"], dependencies: [{ kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i2.MatMiniFabButton, selector: "button[mat-mini-fab], a[mat-mini-fab], button[matMiniFab], a[matMiniFab]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i3.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "component", type: DynamicWidgetPageComponent, selector: "praxis-dynamic-page", inputs: ["page", "context", "strictValidation", "enableCustomization", "authoringCapabilities", "showPageSettingsButton", "shellEditorComponent", "pageEditorComponent", "autoPersist", "pageIdentity", "componentInstanceId", "showWidgetAssistantButton", "blockMutations"], outputs: ["pageChange", "persistenceChange", "pageAuthoringChange", "widgetEvent", "widgetSelectionChange", "widgetAssistantRequested", "widgetDiagnosticsChange"] }, { kind: "component", type: FloatingToolbarComponent, selector: "praxis-floating-toolbar", inputs: ["visible", "canUndo", "canRedo", "showSave", "showPreview", "showAdd", "showSettings", "editingEnabled", "previewActive", "connectionMode", "modeLabel", "addLabel", "saveLabel", "undoLabel", "redoLabel", "settingsLabel", "previewLabel", "exitPreviewLabel"], outputs: ["add", "undo", "redo", "settings", "preview", "save"] }, { kind: "component", type: ConnectionEditorComponent, selector: "praxis-connection-editor", inputs: ["open", "page"], outputs: ["pageChange", "focusWidget", "openPageSettings", "close"] }, { kind: "component", type: PraxisAiAssistantShellComponent, selector: "praxis-ai-assistant-shell", inputs: ["labels", "mode", "state", "contextItems", "attachments", "messages", "quickReplies", "recommendedIntents", "prompt", "statusText", "errorText", "testIdPrefix", "panelTestId", "submitTestId", "applyTestId", "primaryAction", "secondaryActions", "governanceActions", "busy", "canSubmit", "canApply", "submitOnEnter", "showAttachAction", "enablePastedAttachments", "enableFileAttachments", "attachmentAccept", "attachmentMultiple", "voiceInputMode", "voiceLanguage", "draggable", "resizable", "minWidth", "minHeight", "margin", "layout"], outputs: ["promptChange", "submitPrompt", "apply", "retryTurn", "cancelTurn", "shellAction", "close", "attach", "attachmentsPasted", "attachmentsSelected", "removeAttachment", "messageAction", "editMessage", "resendMessage", "quickReply", "recommendedIntent", "layoutChange"] }] });
|
|
35311
35314
|
}
|
|
35312
35315
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DynamicPageBuilderComponent, decorators: [{
|
|
35313
35316
|
type: Component,
|
|
@@ -35344,6 +35347,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
35344
35347
|
|
|
35345
35348
|
<div class="builder-governance-persistence" data-testid="dashboard-layout-status" role="status"
|
|
35346
35349
|
aria-live="polite" aria-atomic="true"
|
|
35350
|
+
[class.busy]="isGovernanceBusy()"
|
|
35347
35351
|
[class.customized]="isLayoutCustomized()" [class.governed]="!isLayoutCustomized()">
|
|
35348
35352
|
<span>{{ governancePersistenceLabel() }}</span>
|
|
35349
35353
|
@if (governancePersistence().status === 'saved' && governanceLastSavedAt(); as saved) {
|
|
@@ -35917,7 +35921,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
35917
35921
|
}
|
|
35918
35922
|
</praxis-floating-toolbar>
|
|
35919
35923
|
</div>
|
|
35920
|
-
`, styles: [":host{display:block;position:relative;min-height:var(--pdx-page-builder-min-height, 420px)}.builder-shell{display:block;position:relative;min-height:inherit;height:100%;box-sizing:border-box}.builder-shell--bottom-toolbar{padding-block-end:calc(80px + env(safe-area-inset-bottom,0px))}.builder-shell:has(.pdx-shell.fullscreen) praxis-floating-toolbar{display:none}.builder-governance-toolbar{display:flex;justify-content:space-between;align-items:center;padding:10px 18px;margin-bottom:16px;border-radius:14px;background:var(--surface-gradient, color-mix(in srgb, var(--md-sys-color-surface-container, #1e293b) 95%, transparent));border:1px solid var(--border, color-mix(in srgb, var(--md-sys-color-outline-variant, #334155) 60%, transparent));gap:16px;flex-wrap:wrap}.builder-governance-brand{display:flex;align-items:center;gap:12px;flex-wrap:wrap}.builder-governance-status-indicator{display:inline-flex;align-items:center;gap:8px;padding:3px 10px;border-radius:9999px;background:color-mix(in srgb,var(--ready, #22c55e) 12%,transparent);border:1px solid color-mix(in srgb,var(--ready, #22c55e) 28%,transparent)}.builder-governance-dot-pulse{width:7px;height:7px;border-radius:50%;background:var(--ready, #22c55e);box-shadow:0 0 8px var(--ready, #22c55e);animation:builderGovernancePulse 2s infinite cubic-bezier(.4,0,.6,1)}.builder-governance-title{font-size:11px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--ready, #22c55e)}.builder-governance-badge{display:inline-flex;align-items:center;gap:6px;padding:4px 10px;border-radius:8px;background:color-mix(in srgb,var(--primary, #3b82f6) 12%,transparent);border:1px solid color-mix(in srgb,var(--primary, #3b82f6) 24%,transparent);font-size:11px;font-weight:600;color:var(--primary, #3b82f6)}.builder-governance-badge .material-symbols-outlined{font-size:14px}.builder-governance-persistence{display:inline-flex;align-items:center;gap:6px;padding:3px 10px;border-radius:9999px;font-size:11px;font-weight:600;letter-spacing:.02em;border:1px solid transparent;transition:all .2s ease}.builder-governance-persistence.governed{background:color-mix(in srgb,var(--muted, #64748b) 15%,transparent);border-color:color-mix(in srgb,var(--border, #334155) 80%,transparent);color:var(--muted-foreground, #94a3b8)}.builder-governance-persistence.customized{background:color-mix(in srgb,var(--primary, #3b82f6) 14%,transparent);border-color:color-mix(in srgb,var(--primary, #3b82f6) 35%,transparent);color:var(--primary, #3b82f6)}.builder-governance-persistence .builder-governance-saved-time{opacity:.75;font-weight:400}.builder-governance-actions{display:flex;align-items:center;gap:10px;margin-left:auto}.builder-governance-btn{display:inline-flex;align-items:center;gap:6px;padding:7px 13px;border-radius:9px;font-size:12px;font-weight:600;cursor:pointer;border:1px solid var(--border, #334155);background:var(--surface, #1e293b);color:var(--foreground, #f8fafc);transition:all .15s ease}.builder-governance-btn:hover:not(:disabled){border-color:var(--primary, #3b82f6);background:color-mix(in srgb,var(--primary, #3b82f6) 10%,var(--surface, #1e293b))}.builder-governance-btn:disabled{opacity:.5;cursor:not-allowed}.builder-governance-btn--reset{border-color:color-mix(in srgb,var(--warning, #eab308) 40%,transparent);color:var(--warning, #eab308)}.builder-governance-btn--reset:hover:not(:disabled){border-color:var(--warning, #eab308);background:color-mix(in srgb,var(--warning, #eab308) 12%,transparent)}.builder-governance-btn .material-symbols-outlined,.builder-governance-toggle-btn .material-symbols-outlined{font-size:16px}.builder-governance-toggle-btn{display:inline-flex;align-items:center;gap:7px;padding:7px 15px;border-radius:9px;font-size:12px;font-weight:600;cursor:pointer;border:1px solid var(--border, #334155);background:var(--surface, #1e293b);color:var(--foreground, #f8fafc);transition:all .2s cubic-bezier(.16,1,.3,1)}.builder-governance-toggle-btn:hover:not(:disabled){border-color:var(--primary, #3b82f6);transform:translateY(-1px);box-shadow:0 4px 12px color-mix(in srgb,var(--primary, #3b82f6) 20%,transparent)}.builder-governance-toggle-btn.active{background:linear-gradient(135deg,var(--primary, #3b82f6),color-mix(in srgb,var(--primary, #3b82f6) 80%,black));border-color:var(--primary, #3b82f6);color:#fff;box-shadow:0 0 16px color-mix(in srgb,var(--primary, #3b82f6) 35%,transparent)}.builder-governance-toggle-btn:disabled{opacity:.5;cursor:not-allowed}@keyframes builderGovernancePulse{0%,to{opacity:1;transform:scale(1)}50%{opacity:.4;transform:scale(.85)}}.builder-authoring-lineage-alert{display:grid;grid-template-columns:auto minmax(0,1fr);gap:10px;margin:0 0 14px;padding:12px 14px;border:1px solid var(--md-sys-color-error, #b3261e);border-radius:10px;background:var(--md-sys-color-error-container, #f9dedc);color:var(--md-sys-color-on-error-container, #410e0b)}.builder-authoring-lineage-alert strong{display:block}.builder-authoring-lineage-alert p{margin:4px 0 0}.builder-authoring-lineage-alert ul{margin:8px 0 0;padding-inline-start:20px}@media(forced-colors:active){.builder-authoring-lineage-alert{border:2px solid CanvasText;background:Canvas;color:CanvasText}}.agentic-diagnostics-panel{position:absolute;z-index:21;right:16px;width:min(520px,calc(100% - 32px));max-height:min(440px,calc(100% - 32px));overflow:auto;padding:12px;border:1px solid rgba(107,114,128,.28);border-radius:8px;background:#fffffff5;color:#111827;box-shadow:0 14px 36px #0000003d}.agentic-diagnostics-panel--collapsed{width:min(280px,calc(100% - 32px));max-height:58px;overflow:hidden;padding:10px 12px;pointer-events:auto}.agentic-diagnostics-panel__header{display:flex;align-items:center;justify-content:space-between;gap:12px;font-size:13px;font-weight:700}.agentic-diagnostics-panel__header-actions{display:flex;align-items:center;gap:8px}.agentic-diagnostics-panel__badge{padding:2px 6px;border:1px solid rgba(107,114,128,.28);border-radius:8px;font-size:11px;font-weight:600;color:#4b5563}.agentic-diagnostics-panel__toggle,.shared-rule-cockpit__toggle{appearance:none;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 18%,transparent);border-radius:999px;background:color-mix(in srgb,var(--md-sys-color-surface-container-lowest) 84%,transparent);color:var(--md-sys-color-primary);cursor:pointer;font-size:11px;font-weight:700;line-height:1;padding:6px 9px;white-space:nowrap}.agentic-diagnostics-panel__description{margin:8px 0 10px;color:#4b5563;font-size:12px;line-height:1.4}.agentic-diagnostics-panel pre{margin:0;white-space:pre-wrap;overflow-wrap:anywhere;font-size:11px;line-height:1.45;color:#1f2937}.agentic-diagnostics-panel__health{display:grid;gap:8px;margin-bottom:12px}.agentic-diagnostics-panel__health h3,.agentic-diagnostics-panel__health p{margin:0}.agentic-diagnostics-panel__health h3{font-size:13px}.agentic-diagnostics-panel__health p{color:#4b5563;font-size:11px}.agentic-diagnostics-panel__metrics{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:0}.agentic-diagnostics-panel__metrics div{padding:8px;border-radius:8px;background:#f3f4f6}.agentic-diagnostics-panel__metrics dt{color:#4b5563;font-size:10px}.agentic-diagnostics-panel__metrics dd{margin:3px 0 0;font-size:14px;font-weight:700}.agentic-diagnostics-panel__health-table{width:100%;border-collapse:collapse;font-size:10px}.agentic-diagnostics-panel__health-table caption{margin-bottom:4px;text-align:left;font-weight:700}.agentic-diagnostics-panel__health-table th,.agentic-diagnostics-panel__health-table td{padding:5px 4px;border-bottom:1px solid #e5e7eb;text-align:left}.agentic-diagnostics-panel__raw summary{margin-bottom:8px;cursor:pointer;font-size:11px;font-weight:700}@media(max-width:520px){.agentic-diagnostics-panel__metrics{grid-template-columns:repeat(2,minmax(0,1fr))}}praxis-ai-assistant-shell{position:fixed;inset:0;z-index:140;pointer-events:none}@media(min-width:1100px){.builder-shell--agentic-review-rail{box-sizing:border-box;padding-right:464px}}.builder-shell__agentic-toggle{position:relative}.builder-shell__agentic-toggle--open,.builder-shell__agentic-toggle--minimized{outline:2px solid rgba(96,165,250,.52);outline-offset:2px}.builder-shell__agentic-toggle--minimized:after{content:\"\";position:absolute;top:6px;right:6px;width:8px;height:8px;border:2px solid var(--md-sys-color-surface, #111827);border-radius:999px;background:#34d399}.builder-shell__connections-toggle--open{outline:2px solid color-mix(in srgb,var(--md-sys-color-primary, #6750a4) 52%,transparent);outline-offset:2px}.builder-shell__connections-toggle{position:relative}.builder-shell__connections-toggle--recommended:not(.builder-shell__connections-toggle--open){outline:2px solid rgba(34,211,238,.48);outline-offset:2px}.builder-shell__connections-toggle-badge{position:absolute;top:5px;right:5px;width:9px;height:9px;border:2px solid var(--md-sys-color-surface, #111827);border-radius:999px;background:#22d3ee;box-shadow:0 0 0 3px #22d3ee2e}.shared-rule-cockpit{position:absolute;z-index:120;left:16px;bottom:88px;width:min(720px,calc(100% - 32px));max-height:min(560px,calc(100vh - 140px));padding:14px;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 24%,transparent);border-radius:12px;background:linear-gradient(135deg,color-mix(in srgb,var(--md-sys-color-primary-container) 24%,var(--md-sys-color-surface-container-high) 76%),color-mix(in srgb,var(--md-sys-color-tertiary-container) 18%,var(--md-sys-color-surface-container) 82%));color:var(--md-sys-color-on-surface);box-shadow:0 18px 46px color-mix(in srgb,var(--md-sys-color-shadow, #000) 24%,transparent);overflow:auto;overscroll-behavior:contain}.shared-rule-cockpit--collapsed{width:min(360px,calc(100% - 32px));max-height:72px;overflow:hidden;padding:10px 12px}.shared-rule-cockpit__header{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.shared-rule-cockpit__header-actions{display:flex;align-items:center;gap:8px}.shared-rule-cockpit__header div{display:grid;gap:2px}.shared-rule-cockpit__header strong{font-size:14px;line-height:1.2}.shared-rule-cockpit__header span{color:var(--md-sys-color-on-surface-variant);font-size:12px;overflow-wrap:anywhere}.shared-rule-cockpit__badge{flex:0 0 auto;max-width:220px;padding:3px 8px;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 20%,transparent);border-radius:999px;background:color-mix(in srgb,var(--md-sys-color-surface-container-lowest) 74%,transparent);color:var(--md-sys-color-primary);font-size:11px;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.shared-rule-cockpit__description,.shared-rule-cockpit__status,.shared-rule-cockpit__error{margin:10px 0 0;color:var(--md-sys-color-on-surface-variant);font-size:12px;line-height:1.45}.shared-rule-cockpit__error{color:var(--md-sys-color-error);font-weight:600}.shared-rule-cockpit__actions{display:flex;flex-wrap:wrap;gap:8px;margin-top:12px}.shared-rule-cockpit__facts{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px;margin:12px 0 0}.shared-rule-cockpit__facts div{min-width:0;padding:8px;border-radius:10px;background:color-mix(in srgb,var(--md-sys-color-surface-container-lowest) 72%,transparent)}.shared-rule-cockpit__facts dt{color:var(--md-sys-color-on-surface-variant);font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.04em}.shared-rule-cockpit__facts dd{margin:4px 0 0;color:var(--md-sys-color-on-surface);font-size:12px;overflow-wrap:anywhere}.shared-rule-cockpit__enforcement-matrix{display:grid;gap:10px;margin-top:12px;padding:10px;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 14%,transparent);border-radius:12px;background:color-mix(in srgb,var(--md-sys-color-surface-container-lowest) 58%,transparent)}.shared-rule-cockpit__matrix-header{display:flex;align-items:flex-start;justify-content:space-between;gap:10px}.shared-rule-cockpit__matrix-header div{display:grid;gap:2px}.shared-rule-cockpit__matrix-header strong{color:var(--md-sys-color-on-surface);font-size:12px}.shared-rule-cockpit__matrix-header span,.shared-rule-cockpit__matrix-source{color:var(--md-sys-color-on-surface-variant);font-size:11px;line-height:1.35;overflow-wrap:anywhere}.shared-rule-cockpit__matrix-badge{flex:0 0 auto;padding:3px 8px;border-radius:999px;background:#16a34a1f;color:#166534;font-weight:800;white-space:nowrap}.shared-rule-cockpit__projection-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}.shared-rule-cockpit__projection{display:grid;gap:3px;min-width:0;padding:9px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 52%,transparent);border-radius:11px;background:color-mix(in srgb,var(--md-sys-color-surface-container-low) 82%,transparent)}.shared-rule-cockpit__projection[data-status=ready]{border-color:color-mix(in srgb,var(--md-sys-color-tertiary) 24%,transparent);background:color-mix(in srgb,var(--md-sys-color-tertiary-container) 32%,var(--md-sys-color-surface-container-low) 68%)}.shared-rule-cockpit__projection span{color:var(--md-sys-color-primary);font-size:10px;font-weight:800;letter-spacing:.04em;text-transform:uppercase;overflow-wrap:anywhere}.shared-rule-cockpit__projection strong{color:var(--md-sys-color-on-surface);font-size:12px}.shared-rule-cockpit__projection small,.shared-rule-cockpit__projection em{color:var(--md-sys-color-on-surface-variant);font-size:11px;font-style:normal;overflow-wrap:anywhere}.shared-rule-cockpit__matrix-source{margin:0}.shared-rule-cockpit__timeline{display:grid;gap:8px;max-height:190px;margin:12px 0 0;padding:0;overflow:auto;list-style:none}.shared-rule-cockpit__timeline-item{display:grid;grid-template-columns:auto minmax(0,1fr);gap:10px;padding:10px;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 12%,transparent);border-radius:12px;background:color-mix(in srgb,var(--md-sys-color-surface-container-lowest) 76%,transparent)}.shared-rule-cockpit__timeline-icon{display:grid;width:24px;height:24px;place-items:center;border-radius:999px;background:#1e40af1a;color:var(--md-sys-color-primary);font-family:Material Icons;font-size:16px;line-height:1}.shared-rule-cockpit__timeline-item strong{color:var(--md-sys-color-on-surface);font-size:12px;line-height:1.35}.shared-rule-cockpit__timeline-item p,.shared-rule-cockpit__timeline-item small,.shared-rule-cockpit__timeline-item time,.shared-rule-cockpit__timeline-empty{display:block;margin:3px 0 0;color:var(--md-sys-color-on-surface-variant);font-size:11px;line-height:1.35;overflow-wrap:anywhere}.shared-rule-cockpit__timeline-badge{display:inline-flex;margin-left:6px;padding:2px 6px;border-radius:999px;background:color-mix(in srgb,var(--md-sys-color-secondary-container) 42%,transparent);color:var(--md-sys-color-on-secondary-container);font-size:10px;font-weight:700}.shared-rule-cockpit__timeline-empty{padding:10px;border-radius:12px;background:color-mix(in srgb,var(--md-sys-color-surface-container-lowest) 64%,transparent)}@media(max-width:720px){.shared-rule-cockpit{bottom:72px;max-height:calc(100vh - 112px)}.shared-rule-cockpit__facts,.shared-rule-cockpit__projection-grid{grid-template-columns:1fr}}\n"] }]
|
|
35924
|
+
`, styles: [":host{display:block;position:relative;min-height:var(--pdx-page-builder-min-height, 420px)}.builder-shell{display:block;position:relative;min-height:inherit;height:100%;box-sizing:border-box}.builder-shell--bottom-toolbar{padding-block-end:calc(80px + env(safe-area-inset-bottom,0px))}.builder-shell:has(.pdx-shell.fullscreen) praxis-floating-toolbar{display:none}.builder-governance-toolbar{display:flex;justify-content:space-between;align-items:center;padding:10px 18px;margin-bottom:16px;border-radius:14px;background:var(--surface-gradient, color-mix(in srgb, var(--md-sys-color-surface-container, #1e293b) 95%, transparent));border:1px solid var(--border, color-mix(in srgb, var(--md-sys-color-outline-variant, #334155) 60%, transparent));gap:16px;flex-wrap:wrap}.builder-governance-brand{display:flex;align-items:center;gap:12px;flex-wrap:wrap}.builder-governance-status-indicator{display:inline-flex;align-items:center;gap:8px;padding:3px 10px;border-radius:9999px;background:color-mix(in srgb,var(--ready, #22c55e) 12%,transparent);border:1px solid color-mix(in srgb,var(--ready, #22c55e) 28%,transparent)}.builder-governance-dot-pulse{width:7px;height:7px;border-radius:50%;background:var(--ready, #22c55e);box-shadow:0 0 8px var(--ready, #22c55e);animation:builderGovernancePulse 2s infinite cubic-bezier(.4,0,.6,1)}.builder-governance-title{font-size:11px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--ready, #22c55e)}.builder-governance-badge{display:inline-flex;align-items:center;gap:6px;padding:4px 10px;border-radius:8px;background:color-mix(in srgb,var(--primary, #3b82f6) 12%,transparent);border:1px solid color-mix(in srgb,var(--primary, #3b82f6) 24%,transparent);font-size:11px;font-weight:600;color:var(--primary, #3b82f6)}.builder-governance-badge .material-symbols-outlined{font-size:14px}.builder-governance-persistence{display:inline-flex;align-items:center;gap:6px;padding:3px 10px;border-radius:9999px;font-size:11px;font-weight:600;letter-spacing:.02em;border:1px solid transparent;transition:all .2s ease}.builder-governance-persistence.governed{background:color-mix(in srgb,var(--muted, #64748b) 15%,transparent);border-color:color-mix(in srgb,var(--border, #334155) 80%,transparent);color:var(--muted-foreground, #94a3b8)}.builder-governance-persistence.customized{background:color-mix(in srgb,var(--primary, #3b82f6) 14%,transparent);border-color:color-mix(in srgb,var(--primary, #3b82f6) 35%,transparent);color:var(--primary, #3b82f6)}.builder-governance-persistence .builder-governance-saved-time{opacity:.75;font-weight:400}.builder-governance-actions{display:flex;align-items:center;gap:10px;margin-left:auto}.builder-governance-btn{display:inline-flex;align-items:center;gap:6px;padding:7px 13px;border-radius:9px;font-size:12px;font-weight:600;cursor:pointer;border:1px solid var(--border, #334155);background:var(--surface, #1e293b);color:var(--foreground, #f8fafc);transition:all .15s ease}.builder-governance-btn:hover:not(:disabled){border-color:var(--primary, #3b82f6);background:color-mix(in srgb,var(--primary, #3b82f6) 10%,var(--surface, #1e293b))}.builder-governance-btn:disabled{opacity:.5;cursor:not-allowed}.builder-governance-btn--reset{border-color:color-mix(in srgb,var(--warning, #eab308) 40%,transparent);color:var(--warning, #eab308)}.builder-governance-btn--reset:hover:not(:disabled){border-color:var(--warning, #eab308);background:color-mix(in srgb,var(--warning, #eab308) 12%,transparent)}.builder-governance-btn .material-symbols-outlined,.builder-governance-toggle-btn .material-symbols-outlined{font-size:16px}.builder-governance-toggle-btn{display:inline-flex;align-items:center;gap:7px;padding:7px 15px;border-radius:9px;font-size:12px;font-weight:600;cursor:pointer;border:1px solid var(--border, #334155);background:var(--surface, #1e293b);color:var(--foreground, #f8fafc);transition:all .2s cubic-bezier(.16,1,.3,1)}.builder-governance-toggle-btn:hover:not(:disabled){border-color:var(--primary, #3b82f6);transform:translateY(-1px);box-shadow:0 4px 12px color-mix(in srgb,var(--primary, #3b82f6) 20%,transparent)}.builder-governance-toggle-btn.active{background:linear-gradient(135deg,var(--primary, #3b82f6),color-mix(in srgb,var(--primary, #3b82f6) 80%,black));border-color:var(--primary, #3b82f6);color:#fff;box-shadow:0 0 16px color-mix(in srgb,var(--primary, #3b82f6) 35%,transparent)}.builder-governance-toggle-btn:disabled{opacity:.5;cursor:not-allowed}@keyframes builderGovernancePulse{0%,to{opacity:1;transform:scale(1)}50%{opacity:.4;transform:scale(.85)}}.builder-authoring-lineage-alert{display:grid;grid-template-columns:auto minmax(0,1fr);gap:10px;margin:0 0 14px;padding:12px 14px;border:1px solid var(--md-sys-color-error, #b3261e);border-radius:10px;background:var(--md-sys-color-error-container, #f9dedc);color:var(--md-sys-color-on-error-container, #410e0b)}.builder-authoring-lineage-alert strong{display:block}.builder-authoring-lineage-alert p{margin:4px 0 0}.builder-authoring-lineage-alert ul{margin:8px 0 0;padding-inline-start:20px}@media(forced-colors:active){.builder-authoring-lineage-alert{border:2px solid CanvasText;background:Canvas;color:CanvasText}}.agentic-diagnostics-panel{position:absolute;z-index:21;right:16px;width:min(520px,calc(100% - 32px));max-height:min(440px,calc(100% - 32px));overflow:auto;padding:12px;border:1px solid rgba(107,114,128,.28);border-radius:8px;background:#fffffff5;color:#111827;box-shadow:0 14px 36px #0000003d}.agentic-diagnostics-panel--collapsed{width:min(280px,calc(100% - 32px));max-height:58px;overflow:hidden;padding:10px 12px;pointer-events:auto}.agentic-diagnostics-panel__header{display:flex;align-items:center;justify-content:space-between;gap:12px;font-size:13px;font-weight:700}.agentic-diagnostics-panel__header-actions{display:flex;align-items:center;gap:8px}.agentic-diagnostics-panel__badge{padding:2px 6px;border:1px solid rgba(107,114,128,.28);border-radius:8px;font-size:11px;font-weight:600;color:#4b5563}.agentic-diagnostics-panel__toggle,.shared-rule-cockpit__toggle{appearance:none;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 18%,transparent);border-radius:999px;background:color-mix(in srgb,var(--md-sys-color-surface-container-lowest) 84%,transparent);color:var(--md-sys-color-primary);cursor:pointer;font-size:11px;font-weight:700;line-height:1;padding:6px 9px;white-space:nowrap}.agentic-diagnostics-panel__description{margin:8px 0 10px;color:#4b5563;font-size:12px;line-height:1.4}.agentic-diagnostics-panel pre{margin:0;white-space:pre-wrap;overflow-wrap:anywhere;font-size:11px;line-height:1.45;color:#1f2937}.agentic-diagnostics-panel__health{display:grid;gap:8px;margin-bottom:12px}.agentic-diagnostics-panel__health h3,.agentic-diagnostics-panel__health p{margin:0}.agentic-diagnostics-panel__health h3{font-size:13px}.agentic-diagnostics-panel__health p{color:#4b5563;font-size:11px}.agentic-diagnostics-panel__metrics{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:0}.agentic-diagnostics-panel__metrics div{padding:8px;border-radius:8px;background:#f3f4f6}.agentic-diagnostics-panel__metrics dt{color:#4b5563;font-size:10px}.agentic-diagnostics-panel__metrics dd{margin:3px 0 0;font-size:14px;font-weight:700}.agentic-diagnostics-panel__health-table{width:100%;border-collapse:collapse;font-size:10px}.agentic-diagnostics-panel__health-table caption{margin-bottom:4px;text-align:left;font-weight:700}.agentic-diagnostics-panel__health-table th,.agentic-diagnostics-panel__health-table td{padding:5px 4px;border-bottom:1px solid #e5e7eb;text-align:left}.agentic-diagnostics-panel__raw summary{margin-bottom:8px;cursor:pointer;font-size:11px;font-weight:700}@media(max-width:520px){.agentic-diagnostics-panel__metrics{grid-template-columns:repeat(2,minmax(0,1fr))}}praxis-ai-assistant-shell{position:fixed;inset:0;z-index:140;pointer-events:none}@media(min-width:1100px){.builder-shell--agentic-review-rail{box-sizing:border-box;padding-right:464px}}.builder-shell__agentic-toggle{position:relative}.builder-shell__agentic-toggle--open,.builder-shell__agentic-toggle--minimized{outline:2px solid rgba(96,165,250,.52);outline-offset:2px}.builder-shell__agentic-toggle--minimized:after{content:\"\";position:absolute;top:6px;right:6px;width:8px;height:8px;border:2px solid var(--md-sys-color-surface, #111827);border-radius:999px;background:#34d399}.builder-shell__connections-toggle--open{outline:2px solid color-mix(in srgb,var(--md-sys-color-primary, #6750a4) 52%,transparent);outline-offset:2px}.builder-shell__connections-toggle{position:relative}.builder-shell__connections-toggle--recommended:not(.builder-shell__connections-toggle--open){outline:2px solid rgba(34,211,238,.48);outline-offset:2px}.builder-shell__connections-toggle-badge{position:absolute;top:5px;right:5px;width:9px;height:9px;border:2px solid var(--md-sys-color-surface, #111827);border-radius:999px;background:#22d3ee;box-shadow:0 0 0 3px #22d3ee2e}.shared-rule-cockpit{position:absolute;z-index:120;left:16px;bottom:88px;width:min(720px,calc(100% - 32px));max-height:min(560px,calc(100vh - 140px));padding:14px;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 24%,transparent);border-radius:12px;background:linear-gradient(135deg,color-mix(in srgb,var(--md-sys-color-primary-container) 24%,var(--md-sys-color-surface-container-high) 76%),color-mix(in srgb,var(--md-sys-color-tertiary-container) 18%,var(--md-sys-color-surface-container) 82%));color:var(--md-sys-color-on-surface);box-shadow:0 18px 46px color-mix(in srgb,var(--md-sys-color-shadow, #000) 24%,transparent);overflow:auto;overscroll-behavior:contain}.shared-rule-cockpit--collapsed{width:min(360px,calc(100% - 32px));max-height:72px;overflow:hidden;padding:10px 12px}.shared-rule-cockpit__header{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.shared-rule-cockpit__header-actions{display:flex;align-items:center;gap:8px}.shared-rule-cockpit__header div{display:grid;gap:2px}.shared-rule-cockpit__header strong{font-size:14px;line-height:1.2}.shared-rule-cockpit__header span{color:var(--md-sys-color-on-surface-variant);font-size:12px;overflow-wrap:anywhere}.shared-rule-cockpit__badge{flex:0 0 auto;max-width:220px;padding:3px 8px;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 20%,transparent);border-radius:999px;background:color-mix(in srgb,var(--md-sys-color-surface-container-lowest) 74%,transparent);color:var(--md-sys-color-primary);font-size:11px;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.shared-rule-cockpit__description,.shared-rule-cockpit__status,.shared-rule-cockpit__error{margin:10px 0 0;color:var(--md-sys-color-on-surface-variant);font-size:12px;line-height:1.45}.shared-rule-cockpit__status,.builder-governance-persistence.busy>span:first-child{font-weight:550;color:var(--pdx-text-shimmer-base, var(--md-sys-color-on-surface-variant))}@supports ((-webkit-background-clip: text) or (background-clip: text)){.shared-rule-cockpit__status,.builder-governance-persistence.busy>span:first-child{background-image:var( --pdx-text-shimmer-gradient, linear-gradient( 105deg, color-mix(in oklab, var(--md-sys-color-on-surface-variant, #64748b) 82%, var(--md-sys-color-on-surface, #0f172a) 18%) 0%, color-mix(in oklab, var(--md-sys-color-on-surface-variant, #64748b) 82%, var(--md-sys-color-on-surface, #0f172a) 18%) 26%, color-mix(in oklab, var(--md-sys-color-tertiary, #6366f1) 74%, #a855f7 26%) 40%, var(--pdx-text-shimmer-peak, var(--md-sys-color-on-surface, #ffffff)) 50%, color-mix(in oklab, var(--md-sys-color-primary, #006a6a) 74%, #ec4899 26%) 60%, color-mix(in oklab, var(--md-sys-color-on-surface-variant, #64748b) 82%, var(--md-sys-color-on-surface, #0f172a) 18%) 74%, color-mix(in oklab, var(--md-sys-color-on-surface-variant, #64748b) 82%, var(--md-sys-color-on-surface, #0f172a) 18%) 100% ) );background-size:300% 100%;background-position:100% 50%;background-repeat:no-repeat;-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;color:transparent;animation:sharedRuleCockpitTextShimmer var(--pdx-text-shimmer-duration, 2.6s) linear infinite;will-change:background-position}}@media(prefers-reduced-motion:reduce),(forced-colors:active),print{.shared-rule-cockpit__status,.builder-governance-persistence.busy>span:first-child{animation:none!important;background-image:none!important;-webkit-text-fill-color:currentColor!important;color:var(--md-sys-color-primary)!important;will-change:auto}}@media(forced-colors:active){.shared-rule-cockpit__status,.builder-governance-persistence.busy>span:first-child{color:CanvasText!important;-webkit-text-fill-color:CanvasText!important}}@keyframes sharedRuleCockpitTextShimmer{0%{background-position:100% 50%}to{background-position:0% 50%}}.shared-rule-cockpit__error{color:var(--md-sys-color-error);font-weight:600}.shared-rule-cockpit__actions{display:flex;flex-wrap:wrap;gap:8px;margin-top:12px}.shared-rule-cockpit__facts{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px;margin:12px 0 0}.shared-rule-cockpit__facts div{min-width:0;padding:8px;border-radius:10px;background:color-mix(in srgb,var(--md-sys-color-surface-container-lowest) 72%,transparent)}.shared-rule-cockpit__facts dt{color:var(--md-sys-color-on-surface-variant);font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.04em}.shared-rule-cockpit__facts dd{margin:4px 0 0;color:var(--md-sys-color-on-surface);font-size:12px;overflow-wrap:anywhere}.shared-rule-cockpit__enforcement-matrix{display:grid;gap:10px;margin-top:12px;padding:10px;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 14%,transparent);border-radius:12px;background:color-mix(in srgb,var(--md-sys-color-surface-container-lowest) 58%,transparent)}.shared-rule-cockpit__matrix-header{display:flex;align-items:flex-start;justify-content:space-between;gap:10px}.shared-rule-cockpit__matrix-header div{display:grid;gap:2px}.shared-rule-cockpit__matrix-header strong{color:var(--md-sys-color-on-surface);font-size:12px}.shared-rule-cockpit__matrix-header span,.shared-rule-cockpit__matrix-source{color:var(--md-sys-color-on-surface-variant);font-size:11px;line-height:1.35;overflow-wrap:anywhere}.shared-rule-cockpit__matrix-badge{flex:0 0 auto;padding:3px 8px;border-radius:999px;background:#16a34a1f;color:#166534;font-weight:800;white-space:nowrap}.shared-rule-cockpit__projection-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}.shared-rule-cockpit__projection{display:grid;gap:3px;min-width:0;padding:9px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 52%,transparent);border-radius:11px;background:color-mix(in srgb,var(--md-sys-color-surface-container-low) 82%,transparent)}.shared-rule-cockpit__projection[data-status=ready]{border-color:color-mix(in srgb,var(--md-sys-color-tertiary) 24%,transparent);background:color-mix(in srgb,var(--md-sys-color-tertiary-container) 32%,var(--md-sys-color-surface-container-low) 68%)}.shared-rule-cockpit__projection span{color:var(--md-sys-color-primary);font-size:10px;font-weight:800;letter-spacing:.04em;text-transform:uppercase;overflow-wrap:anywhere}.shared-rule-cockpit__projection strong{color:var(--md-sys-color-on-surface);font-size:12px}.shared-rule-cockpit__projection small,.shared-rule-cockpit__projection em{color:var(--md-sys-color-on-surface-variant);font-size:11px;font-style:normal;overflow-wrap:anywhere}.shared-rule-cockpit__matrix-source{margin:0}.shared-rule-cockpit__timeline{display:grid;gap:8px;max-height:190px;margin:12px 0 0;padding:0;overflow:auto;list-style:none}.shared-rule-cockpit__timeline-item{display:grid;grid-template-columns:auto minmax(0,1fr);gap:10px;padding:10px;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 12%,transparent);border-radius:12px;background:color-mix(in srgb,var(--md-sys-color-surface-container-lowest) 76%,transparent)}.shared-rule-cockpit__timeline-icon{display:grid;width:24px;height:24px;place-items:center;border-radius:999px;background:#1e40af1a;color:var(--md-sys-color-primary);font-family:Material Icons;font-size:16px;line-height:1}.shared-rule-cockpit__timeline-item strong{color:var(--md-sys-color-on-surface);font-size:12px;line-height:1.35}.shared-rule-cockpit__timeline-item p,.shared-rule-cockpit__timeline-item small,.shared-rule-cockpit__timeline-item time,.shared-rule-cockpit__timeline-empty{display:block;margin:3px 0 0;color:var(--md-sys-color-on-surface-variant);font-size:11px;line-height:1.35;overflow-wrap:anywhere}.shared-rule-cockpit__timeline-badge{display:inline-flex;margin-left:6px;padding:2px 6px;border-radius:999px;background:color-mix(in srgb,var(--md-sys-color-secondary-container) 42%,transparent);color:var(--md-sys-color-on-secondary-container);font-size:10px;font-weight:700}.shared-rule-cockpit__timeline-empty{padding:10px;border-radius:12px;background:color-mix(in srgb,var(--md-sys-color-surface-container-lowest) 64%,transparent)}@media(max-width:720px){.shared-rule-cockpit{bottom:72px;max-height:calc(100vh - 112px)}.shared-rule-cockpit__facts,.shared-rule-cockpit__projection-grid{grid-template-columns:1fr}}\n"] }]
|
|
35921
35925
|
}], ctorParameters: () => [{ type: i1.MatDialog }, { type: undefined, decorators: [{
|
|
35922
35926
|
type: Optional
|
|
35923
35927
|
}, {
|
|
@@ -36305,6 +36309,1819 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
36305
36309
|
}]
|
|
36306
36310
|
}], propDecorators: { page: [{ type: i0.Input, args: [{ isSignal: true, alias: "page", required: false }] }], canCopy: [{ type: i0.Input, args: [{ isSignal: true, alias: "canCopy", required: false }] }], copyRequested: [{ type: i0.Output, args: ["copyRequested"] }] } });
|
|
36307
36311
|
|
|
36312
|
+
const GOVERNED_PAGE_CAPABILITY_KEYS = Object.freeze({
|
|
36313
|
+
authorPages: 'praxis.config.pages.author',
|
|
36314
|
+
submitForReview: 'praxis.config.pages.submit',
|
|
36315
|
+
approveReleases: 'praxis.config.pages.approve',
|
|
36316
|
+
publishReleases: 'praxis.config.pages.publish',
|
|
36317
|
+
manageNavigationGroups: 'praxis.config.navigation.manage',
|
|
36318
|
+
});
|
|
36319
|
+
/**
|
|
36320
|
+
* Derives governed page capabilities from canonical server-issued capability keys.
|
|
36321
|
+
* Never fabricates approval or publication authority when a capability is absent.
|
|
36322
|
+
*/
|
|
36323
|
+
function deriveGovernedPageCapabilities(grantedCapabilities) {
|
|
36324
|
+
const granted = new Set(grantedCapabilities
|
|
36325
|
+
? Array.from(grantedCapabilities, item => String(item ?? '').trim())
|
|
36326
|
+
: []);
|
|
36327
|
+
const hasWildcard = granted.has('*') || granted.has('praxis.config.*');
|
|
36328
|
+
return {
|
|
36329
|
+
canAuthorPages: hasWildcard ||
|
|
36330
|
+
granted.has(GOVERNED_PAGE_CAPABILITY_KEYS.authorPages) ||
|
|
36331
|
+
granted.has('ui.authoring.configure') ||
|
|
36332
|
+
granted.has('ui-layout:write') ||
|
|
36333
|
+
granted.has('pages:write'),
|
|
36334
|
+
canSubmitForReview: hasWildcard ||
|
|
36335
|
+
granted.has(GOVERNED_PAGE_CAPABILITY_KEYS.submitForReview) ||
|
|
36336
|
+
granted.has('ui.authoring.configure') ||
|
|
36337
|
+
granted.has('ui-layout:submit') ||
|
|
36338
|
+
granted.has('pages:submit'),
|
|
36339
|
+
canApproveReleases: hasWildcard ||
|
|
36340
|
+
granted.has(GOVERNED_PAGE_CAPABILITY_KEYS.approveReleases) ||
|
|
36341
|
+
granted.has('ui.layout.publish') ||
|
|
36342
|
+
granted.has('ui-layout:approve') ||
|
|
36343
|
+
granted.has('pages:approve'),
|
|
36344
|
+
canPublishReleases: hasWildcard ||
|
|
36345
|
+
granted.has(GOVERNED_PAGE_CAPABILITY_KEYS.publishReleases) ||
|
|
36346
|
+
granted.has('ui.layout.publish') ||
|
|
36347
|
+
granted.has('ui-layout:publish') ||
|
|
36348
|
+
granted.has('pages:publish'),
|
|
36349
|
+
canManageNavigationGroups: hasWildcard ||
|
|
36350
|
+
granted.has(GOVERNED_PAGE_CAPABILITY_KEYS.manageNavigationGroups) ||
|
|
36351
|
+
granted.has(GOVERNED_PAGE_CAPABILITY_KEYS.authorPages) ||
|
|
36352
|
+
granted.has('ui.authoring.configure'),
|
|
36353
|
+
};
|
|
36354
|
+
}
|
|
36355
|
+
function createCorporateGovernedPagePersistenceAdapter(lifecycle) {
|
|
36356
|
+
return {
|
|
36357
|
+
mode: 'corporate',
|
|
36358
|
+
createPage: command => lifecycle.createPage(command),
|
|
36359
|
+
readPageByCreationKey: creationKey => lifecycle.readPageByCreationKey(creationKey),
|
|
36360
|
+
readPageCatalog: options => lifecycle.readPageCatalog(options),
|
|
36361
|
+
readPublishedPage: (pageId, ifNoneMatch) => lifecycle.readPublishedPage(pageId, ifNoneMatch),
|
|
36362
|
+
readDraftByCreationKey: (target, creationKey) => lifecycle.readDraftByCreationKey(target, creationKey),
|
|
36363
|
+
readDraft: (target, draftId, ifNoneMatch) => lifecycle.readDraft(target, draftId, ifNoneMatch),
|
|
36364
|
+
readReview: (target, releaseId, ifNoneMatch) => lifecycle.readReview(target, releaseId, ifNoneMatch),
|
|
36365
|
+
readHead: (target, ifNoneMatch) => lifecycle.readHead(target, ifNoneMatch),
|
|
36366
|
+
createDraft: (target, idempotencyKey) => lifecycle.createDraft(target, idempotencyKey),
|
|
36367
|
+
createRevision: (target, draftId, draftEtag, command) => lifecycle.createRevision(target, draftId, draftEtag, command),
|
|
36368
|
+
createAssignment: (target, draftId, draftEtag, command) => lifecycle.createAssignment(target, draftId, draftEtag, command),
|
|
36369
|
+
freezeRelease: (target, draftId, draftEtag, command) => lifecycle.freezeRelease(target, draftId, draftEtag, command),
|
|
36370
|
+
submit: (target, releaseId, reviewEtag) => lifecycle.submit(target, releaseId, reviewEtag),
|
|
36371
|
+
approve: (target, releaseId, reviewEtag, command) => lifecycle.approve(target, releaseId, reviewEtag, command),
|
|
36372
|
+
publish: (target, condition, command) => lifecycle.publish(target, condition, command),
|
|
36373
|
+
};
|
|
36374
|
+
}
|
|
36375
|
+
function createLocalPageSaveAsAdapter(handler) {
|
|
36376
|
+
return {
|
|
36377
|
+
mode: 'local',
|
|
36378
|
+
save: request => handler(request),
|
|
36379
|
+
};
|
|
36380
|
+
}
|
|
36381
|
+
const LEGACY_PUBLISHED_PAGE_STORAGE_PREFIX = 'published-page:';
|
|
36382
|
+
|
|
36383
|
+
const DEFAULT_CORPORATE_NAVIGATION_GROUPS_TARGET = Object.freeze({
|
|
36384
|
+
componentType: 'praxis-app-shell',
|
|
36385
|
+
componentId: 'b9debe04-41ba-498b-86aa-cb30a464fb82',
|
|
36386
|
+
});
|
|
36387
|
+
class GovernedPageNavigationController {
|
|
36388
|
+
persistence;
|
|
36389
|
+
observeTargetDoc;
|
|
36390
|
+
navigationGroupsTarget;
|
|
36391
|
+
registeredNamespace;
|
|
36392
|
+
allowedIcons;
|
|
36393
|
+
pageSize;
|
|
36394
|
+
maxItemsPerLevel;
|
|
36395
|
+
idGenerator;
|
|
36396
|
+
epoch = 0;
|
|
36397
|
+
contextKey;
|
|
36398
|
+
groupsSub = null;
|
|
36399
|
+
pageSubs = new Map();
|
|
36400
|
+
pageDocuments = new Map();
|
|
36401
|
+
expandedGroups = new Set();
|
|
36402
|
+
fencedAttempts = new Map();
|
|
36403
|
+
latestGroupsDoc = createEmptyNavigationGroupsAuthoringDocument();
|
|
36404
|
+
latestCatalogEntries = [];
|
|
36405
|
+
stateSubject;
|
|
36406
|
+
snapshot$;
|
|
36407
|
+
constructor(config) {
|
|
36408
|
+
this.persistence = config.persistence;
|
|
36409
|
+
this.navigationGroupsTarget =
|
|
36410
|
+
config.navigationGroupsTarget ?? DEFAULT_CORPORATE_NAVIGATION_GROUPS_TARGET;
|
|
36411
|
+
this.registeredNamespace = config.registeredNamespace ?? '/pages';
|
|
36412
|
+
this.allowedIcons = new Set(Array.from(config.allowedIcons ?? GOVERNED_PAGE_DEFAULT_ALLOWED_ICONS, icon => String(icon ?? '').trim()));
|
|
36413
|
+
this.pageSize = Math.max(1, Math.min(200, Math.trunc(config.pageSize ?? 50)));
|
|
36414
|
+
this.maxItemsPerLevel = Math.max(1, Math.min(200, Math.trunc(config.maxItemsPerLevel ?? 200)));
|
|
36415
|
+
this.contextKey = config.initialContextKey ?? 'default';
|
|
36416
|
+
this.idGenerator =
|
|
36417
|
+
config.idGenerator ??
|
|
36418
|
+
(() => typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function'
|
|
36419
|
+
? crypto.randomUUID()
|
|
36420
|
+
: `00000000-0000-4000-8000-${Date.now().toString(16).padStart(12, '0').slice(-12)}`);
|
|
36421
|
+
if (config.observeTargetDocument) {
|
|
36422
|
+
this.observeTargetDoc = config.observeTargetDocument;
|
|
36423
|
+
}
|
|
36424
|
+
else if (config.effectiveLayoutService) {
|
|
36425
|
+
const effectiveLayout = config.effectiveLayoutService;
|
|
36426
|
+
this.observeTargetDoc = (target) => new Observable(subscriber => {
|
|
36427
|
+
let cancelled = false;
|
|
36428
|
+
void effectiveLayout
|
|
36429
|
+
.resolve(target)
|
|
36430
|
+
.then(response => {
|
|
36431
|
+
if (!cancelled) {
|
|
36432
|
+
subscriber.next(response.document ?? null);
|
|
36433
|
+
subscriber.complete();
|
|
36434
|
+
}
|
|
36435
|
+
})
|
|
36436
|
+
.catch((err) => {
|
|
36437
|
+
if (!cancelled) {
|
|
36438
|
+
subscriber.error(err);
|
|
36439
|
+
}
|
|
36440
|
+
});
|
|
36441
|
+
return () => {
|
|
36442
|
+
cancelled = true;
|
|
36443
|
+
};
|
|
36444
|
+
});
|
|
36445
|
+
}
|
|
36446
|
+
this.stateSubject = new BehaviorSubject({
|
|
36447
|
+
epoch: this.epoch,
|
|
36448
|
+
contextKey: this.contextKey,
|
|
36449
|
+
catalogEntries: [],
|
|
36450
|
+
groupsDocument: this.latestGroupsDoc,
|
|
36451
|
+
menuNodes: [],
|
|
36452
|
+
expandedGroupIds: [],
|
|
36453
|
+
loading: false,
|
|
36454
|
+
diagnostics: [],
|
|
36455
|
+
fencedAttempts: {},
|
|
36456
|
+
lastError: null,
|
|
36457
|
+
});
|
|
36458
|
+
this.snapshot$ = this.stateSubject.asObservable();
|
|
36459
|
+
}
|
|
36460
|
+
get snapshot() {
|
|
36461
|
+
return this.stateSubject.value;
|
|
36462
|
+
}
|
|
36463
|
+
setContextKey(nextContextKey) {
|
|
36464
|
+
const normalized = String(nextContextKey ?? '').trim() || 'default';
|
|
36465
|
+
this.contextKey = normalized;
|
|
36466
|
+
this.invalidate();
|
|
36467
|
+
}
|
|
36468
|
+
invalidate() {
|
|
36469
|
+
this.epoch += 1;
|
|
36470
|
+
this.teardownSubscriptions();
|
|
36471
|
+
this.pageDocuments.clear();
|
|
36472
|
+
this.latestCatalogEntries = [];
|
|
36473
|
+
this.latestGroupsDoc = createEmptyNavigationGroupsAuthoringDocument();
|
|
36474
|
+
this.fencedAttempts.clear();
|
|
36475
|
+
this.publishSnapshot({
|
|
36476
|
+
epoch: this.epoch,
|
|
36477
|
+
contextKey: this.contextKey,
|
|
36478
|
+
catalogEntries: [],
|
|
36479
|
+
groupsDocument: this.latestGroupsDoc,
|
|
36480
|
+
menuNodes: [],
|
|
36481
|
+
expandedGroupIds: Array.from(this.expandedGroups),
|
|
36482
|
+
loading: false,
|
|
36483
|
+
diagnostics: [],
|
|
36484
|
+
fencedAttempts: {},
|
|
36485
|
+
lastError: null,
|
|
36486
|
+
});
|
|
36487
|
+
}
|
|
36488
|
+
destroy() {
|
|
36489
|
+
this.epoch += 1;
|
|
36490
|
+
this.teardownSubscriptions();
|
|
36491
|
+
this.stateSubject.complete();
|
|
36492
|
+
}
|
|
36493
|
+
async refresh() {
|
|
36494
|
+
const activeEpoch = ++this.epoch;
|
|
36495
|
+
const activeContextKey = this.contextKey;
|
|
36496
|
+
this.teardownSubscriptions();
|
|
36497
|
+
this.pageDocuments.clear();
|
|
36498
|
+
this.publishSnapshot({
|
|
36499
|
+
...this.snapshot,
|
|
36500
|
+
epoch: activeEpoch,
|
|
36501
|
+
contextKey: activeContextKey,
|
|
36502
|
+
loading: true,
|
|
36503
|
+
lastError: null,
|
|
36504
|
+
});
|
|
36505
|
+
this.bindGroupsObservation(activeEpoch, activeContextKey);
|
|
36506
|
+
try {
|
|
36507
|
+
const entries = await this.readAllCatalogEntries(activeEpoch, activeContextKey);
|
|
36508
|
+
if (this.isStale(activeEpoch, activeContextKey)) {
|
|
36509
|
+
return this.snapshot;
|
|
36510
|
+
}
|
|
36511
|
+
this.latestCatalogEntries = entries;
|
|
36512
|
+
await this.hydrateCatalogPages(entries, activeEpoch, activeContextKey);
|
|
36513
|
+
if (this.isStale(activeEpoch, activeContextKey)) {
|
|
36514
|
+
return this.snapshot;
|
|
36515
|
+
}
|
|
36516
|
+
this.recomputeProjection(activeEpoch, activeContextKey, false);
|
|
36517
|
+
return this.snapshot;
|
|
36518
|
+
}
|
|
36519
|
+
catch (error) {
|
|
36520
|
+
if (this.isStale(activeEpoch, activeContextKey)) {
|
|
36521
|
+
return this.snapshot;
|
|
36522
|
+
}
|
|
36523
|
+
this.publishSnapshot({
|
|
36524
|
+
...this.snapshot,
|
|
36525
|
+
loading: false,
|
|
36526
|
+
lastError: error instanceof Error ? error.message : String(error),
|
|
36527
|
+
});
|
|
36528
|
+
return this.snapshot;
|
|
36529
|
+
}
|
|
36530
|
+
}
|
|
36531
|
+
toggleGroupExpanded(groupId) {
|
|
36532
|
+
const normalized = String(groupId ?? '').trim().toLowerCase();
|
|
36533
|
+
if (!normalized) {
|
|
36534
|
+
return;
|
|
36535
|
+
}
|
|
36536
|
+
if (this.expandedGroups.has(normalized)) {
|
|
36537
|
+
this.expandedGroups.delete(normalized);
|
|
36538
|
+
}
|
|
36539
|
+
else {
|
|
36540
|
+
this.expandedGroups.add(normalized);
|
|
36541
|
+
this.reobserveGroupChildren(normalized, this.epoch, this.contextKey);
|
|
36542
|
+
}
|
|
36543
|
+
this.publishSnapshot({
|
|
36544
|
+
...this.snapshot,
|
|
36545
|
+
expandedGroupIds: Array.from(this.expandedGroups),
|
|
36546
|
+
});
|
|
36547
|
+
}
|
|
36548
|
+
clearFencedAttempt(attemptKey) {
|
|
36549
|
+
this.fencedAttempts.delete(attemptKey);
|
|
36550
|
+
this.publishSnapshot({
|
|
36551
|
+
...this.snapshot,
|
|
36552
|
+
fencedAttempts: Object.fromEntries(this.fencedAttempts),
|
|
36553
|
+
});
|
|
36554
|
+
}
|
|
36555
|
+
async upsertAndPublishNavigationGroup(input) {
|
|
36556
|
+
const activeEpoch = this.epoch;
|
|
36557
|
+
const activeContextKey = this.contextKey;
|
|
36558
|
+
const attemptKey = `navigation-group:${input.entry.id}`;
|
|
36559
|
+
if (this.fencedAttempts.has(attemptKey)) {
|
|
36560
|
+
throw new Error(`Navigation group operation is fenced pending reconciliation: ${attemptKey}`);
|
|
36561
|
+
}
|
|
36562
|
+
const updated = upsertNavigationGroupEntry(this.latestGroupsDoc, input.entry, {
|
|
36563
|
+
allowedIcons: this.allowedIcons,
|
|
36564
|
+
});
|
|
36565
|
+
if (!updated.ok) {
|
|
36566
|
+
throw new Error(updated.diagnostics[0]?.code ?? 'NAVIGATION_GROUPS_PROFILE_INVALID');
|
|
36567
|
+
}
|
|
36568
|
+
const draftCreationKey = input.draftCreationKey ?? this.idGenerator();
|
|
36569
|
+
const revisionCommandRef = input.revisionCommandRef ?? this.idGenerator();
|
|
36570
|
+
const assignmentCommandRef = input.assignmentCommandRef ?? this.idGenerator();
|
|
36571
|
+
const freezeCommandRef = input.freezeCommandRef ?? this.idGenerator();
|
|
36572
|
+
const reason = input.reason?.trim() || 'Publish navigation group update';
|
|
36573
|
+
try {
|
|
36574
|
+
const createdDraft = await this.persistence.createDraft(this.navigationGroupsTarget, draftCreationKey);
|
|
36575
|
+
if (this.isStale(activeEpoch, activeContextKey)) {
|
|
36576
|
+
throw new UiLayoutLifecycleContractError('CONTEXT_OBSOLETE');
|
|
36577
|
+
}
|
|
36578
|
+
let draftEtag = createdDraft.etag;
|
|
36579
|
+
const draftId = createdDraft.body.draft.draftRef;
|
|
36580
|
+
const revisionReceipt = await this.persistence.createRevision(this.navigationGroupsTarget, draftId, draftEtag, {
|
|
36581
|
+
commandRef: revisionCommandRef,
|
|
36582
|
+
target: this.navigationGroupsTarget,
|
|
36583
|
+
authoringDocument: updated.value,
|
|
36584
|
+
reason,
|
|
36585
|
+
});
|
|
36586
|
+
if (this.isStale(activeEpoch, activeContextKey)) {
|
|
36587
|
+
throw new UiLayoutLifecycleContractError('CONTEXT_OBSOLETE');
|
|
36588
|
+
}
|
|
36589
|
+
if (revisionReceipt.etag === draftEtag) {
|
|
36590
|
+
throw new Error('Navigation groups draft ETag did not progress after revision.');
|
|
36591
|
+
}
|
|
36592
|
+
draftEtag = revisionReceipt.etag;
|
|
36593
|
+
const assignmentReceipt = await this.persistence.createAssignment(this.navigationGroupsTarget, draftId, draftEtag, {
|
|
36594
|
+
commandRef: assignmentCommandRef,
|
|
36595
|
+
target: this.navigationGroupsTarget,
|
|
36596
|
+
layerClass: input.layerClass ?? 'TENANT',
|
|
36597
|
+
selector: input.selector,
|
|
36598
|
+
priority: input.priority ?? 100,
|
|
36599
|
+
contributionKey: input.contributionKey ?? 'corporate:navigation-groups',
|
|
36600
|
+
});
|
|
36601
|
+
if (this.isStale(activeEpoch, activeContextKey)) {
|
|
36602
|
+
throw new UiLayoutLifecycleContractError('CONTEXT_OBSOLETE');
|
|
36603
|
+
}
|
|
36604
|
+
if (assignmentReceipt.etag === draftEtag) {
|
|
36605
|
+
throw new Error('Navigation groups draft ETag did not progress after assignment.');
|
|
36606
|
+
}
|
|
36607
|
+
draftEtag = assignmentReceipt.etag;
|
|
36608
|
+
const freezeReceipt = await this.persistence.freezeRelease(this.navigationGroupsTarget, draftId, draftEtag, { commandRef: freezeCommandRef });
|
|
36609
|
+
if (this.isStale(activeEpoch, activeContextKey)) {
|
|
36610
|
+
throw new UiLayoutLifecycleContractError('CONTEXT_OBSOLETE');
|
|
36611
|
+
}
|
|
36612
|
+
if (freezeReceipt.etag === draftEtag) {
|
|
36613
|
+
throw new Error('Navigation groups draft ETag did not progress after freeze.');
|
|
36614
|
+
}
|
|
36615
|
+
const releaseId = freezeReceipt.body.release.releaseRef;
|
|
36616
|
+
let reviewEtag = freezeReceipt.body.review.reviewEtag;
|
|
36617
|
+
const submitReceipt = await this.persistence.submit(this.navigationGroupsTarget, releaseId, reviewEtag);
|
|
36618
|
+
if (this.isStale(activeEpoch, activeContextKey)) {
|
|
36619
|
+
throw new UiLayoutLifecycleContractError('CONTEXT_OBSOLETE');
|
|
36620
|
+
}
|
|
36621
|
+
if (submitReceipt.etag === reviewEtag) {
|
|
36622
|
+
throw new Error('Navigation groups review ETag did not progress after submit.');
|
|
36623
|
+
}
|
|
36624
|
+
reviewEtag = submitReceipt.etag;
|
|
36625
|
+
if (submitReceipt.body.state !== 'APPROVED') {
|
|
36626
|
+
const approveReceipt = await this.persistence.approve(this.navigationGroupsTarget, releaseId, reviewEtag, {
|
|
36627
|
+
reason,
|
|
36628
|
+
});
|
|
36629
|
+
if (this.isStale(activeEpoch, activeContextKey)) {
|
|
36630
|
+
throw new UiLayoutLifecycleContractError('CONTEXT_OBSOLETE');
|
|
36631
|
+
}
|
|
36632
|
+
if (approveReceipt.etag === reviewEtag) {
|
|
36633
|
+
throw new Error('Navigation groups review ETag did not progress after approve.');
|
|
36634
|
+
}
|
|
36635
|
+
}
|
|
36636
|
+
let existingHeadReleaseRef = null;
|
|
36637
|
+
let existingHeadEtag = null;
|
|
36638
|
+
try {
|
|
36639
|
+
const headResult = await this.persistence.readHead(this.navigationGroupsTarget);
|
|
36640
|
+
if (this.isStale(activeEpoch, activeContextKey)) {
|
|
36641
|
+
throw new UiLayoutLifecycleContractError('CONTEXT_OBSOLETE');
|
|
36642
|
+
}
|
|
36643
|
+
if ('body' in headResult) {
|
|
36644
|
+
existingHeadReleaseRef = headResult.body.activeReleaseRef;
|
|
36645
|
+
existingHeadEtag = headResult.etag;
|
|
36646
|
+
}
|
|
36647
|
+
}
|
|
36648
|
+
catch (headError) {
|
|
36649
|
+
if (this.isStale(activeEpoch, activeContextKey)) {
|
|
36650
|
+
throw new UiLayoutLifecycleContractError('CONTEXT_OBSOLETE');
|
|
36651
|
+
}
|
|
36652
|
+
if (!(headError instanceof UiLayoutLifecycleHttpError && headError.status === 404)) {
|
|
36653
|
+
throw headError;
|
|
36654
|
+
}
|
|
36655
|
+
}
|
|
36656
|
+
if (existingHeadReleaseRef !== releaseId) {
|
|
36657
|
+
const condition = existingHeadEtag
|
|
36658
|
+
? { kind: 'match', etag: existingHeadEtag }
|
|
36659
|
+
: { kind: 'first-publish' };
|
|
36660
|
+
await this.persistence.publish(this.navigationGroupsTarget, condition, {
|
|
36661
|
+
releaseId,
|
|
36662
|
+
reason,
|
|
36663
|
+
});
|
|
36664
|
+
}
|
|
36665
|
+
this.latestGroupsDoc = updated.value;
|
|
36666
|
+
this.recomputeProjection(activeEpoch, activeContextKey, false);
|
|
36667
|
+
return updated.value;
|
|
36668
|
+
}
|
|
36669
|
+
catch (error) {
|
|
36670
|
+
if (error instanceof UiLayoutLifecycleContractError &&
|
|
36671
|
+
error.code === 'COMMAND_OUTCOME_UNCERTAIN') {
|
|
36672
|
+
this.fencedAttempts.set(attemptKey, draftCreationKey);
|
|
36673
|
+
this.publishSnapshot({
|
|
36674
|
+
...this.snapshot,
|
|
36675
|
+
fencedAttempts: Object.fromEntries(this.fencedAttempts),
|
|
36676
|
+
lastError: error.message,
|
|
36677
|
+
});
|
|
36678
|
+
}
|
|
36679
|
+
throw error;
|
|
36680
|
+
}
|
|
36681
|
+
}
|
|
36682
|
+
async readAllCatalogEntries(activeEpoch, activeContextKey) {
|
|
36683
|
+
const collected = [];
|
|
36684
|
+
const seenPageIds = new Set();
|
|
36685
|
+
const seenCursors = new Set();
|
|
36686
|
+
let cursor = null;
|
|
36687
|
+
while (collected.length < this.maxItemsPerLevel) {
|
|
36688
|
+
const response = await this.persistence.readPageCatalog({
|
|
36689
|
+
cursor,
|
|
36690
|
+
limit: Math.min(this.pageSize, this.maxItemsPerLevel - collected.length),
|
|
36691
|
+
});
|
|
36692
|
+
if (this.isStale(activeEpoch, activeContextKey)) {
|
|
36693
|
+
return [];
|
|
36694
|
+
}
|
|
36695
|
+
for (const item of response.items ?? []) {
|
|
36696
|
+
const normalizedId = String(item?.pageId ?? '').trim().toLowerCase();
|
|
36697
|
+
if (!normalizedId || seenPageIds.has(normalizedId)) {
|
|
36698
|
+
continue;
|
|
36699
|
+
}
|
|
36700
|
+
seenPageIds.add(normalizedId);
|
|
36701
|
+
collected.push(item);
|
|
36702
|
+
if (collected.length >= this.maxItemsPerLevel) {
|
|
36703
|
+
break;
|
|
36704
|
+
}
|
|
36705
|
+
}
|
|
36706
|
+
const nextCursor = response.nextCursor?.trim() || null;
|
|
36707
|
+
if (!nextCursor) {
|
|
36708
|
+
break;
|
|
36709
|
+
}
|
|
36710
|
+
if (seenCursors.has(nextCursor)) {
|
|
36711
|
+
throw new Error(`Detected pagination cursor cycle in page catalog: ${nextCursor}`);
|
|
36712
|
+
}
|
|
36713
|
+
seenCursors.add(nextCursor);
|
|
36714
|
+
cursor = nextCursor;
|
|
36715
|
+
}
|
|
36716
|
+
return collected;
|
|
36717
|
+
}
|
|
36718
|
+
async hydrateCatalogPages(entries, activeEpoch, activeContextKey) {
|
|
36719
|
+
for (const entry of entries) {
|
|
36720
|
+
if (this.isStale(activeEpoch, activeContextKey)) {
|
|
36721
|
+
return;
|
|
36722
|
+
}
|
|
36723
|
+
const pageId = String(entry.pageId ?? '').trim().toLowerCase();
|
|
36724
|
+
if (!pageId) {
|
|
36725
|
+
continue;
|
|
36726
|
+
}
|
|
36727
|
+
try {
|
|
36728
|
+
const published = await this.persistence.readPublishedPage(pageId);
|
|
36729
|
+
if (this.isStale(activeEpoch, activeContextKey)) {
|
|
36730
|
+
return;
|
|
36731
|
+
}
|
|
36732
|
+
if ('body' in published) {
|
|
36733
|
+
const decoded = decodePageAuthoringDocument(published.body.document);
|
|
36734
|
+
if (decoded.ok) {
|
|
36735
|
+
this.pageDocuments.set(pageId, decoded.value);
|
|
36736
|
+
}
|
|
36737
|
+
}
|
|
36738
|
+
}
|
|
36739
|
+
catch {
|
|
36740
|
+
// Individual page read failure does not abort the rest of the catalog projection.
|
|
36741
|
+
}
|
|
36742
|
+
this.bindPageObservation(pageId, activeEpoch, activeContextKey);
|
|
36743
|
+
}
|
|
36744
|
+
}
|
|
36745
|
+
bindGroupsObservation(activeEpoch, activeContextKey) {
|
|
36746
|
+
if (!this.observeTargetDoc) {
|
|
36747
|
+
return;
|
|
36748
|
+
}
|
|
36749
|
+
this.groupsSub?.unsubscribe();
|
|
36750
|
+
this.groupsSub = this.observeTargetDoc(this.navigationGroupsTarget).subscribe({
|
|
36751
|
+
next: rawDoc => {
|
|
36752
|
+
if (this.isStale(activeEpoch, activeContextKey) || !rawDoc) {
|
|
36753
|
+
return;
|
|
36754
|
+
}
|
|
36755
|
+
const decoded = decodeNavigationGroupsAuthoringDocument(rawDoc, {
|
|
36756
|
+
allowedIcons: this.allowedIcons,
|
|
36757
|
+
});
|
|
36758
|
+
if (decoded.ok) {
|
|
36759
|
+
this.latestGroupsDoc = decoded.value;
|
|
36760
|
+
this.recomputeProjection(activeEpoch, activeContextKey, false);
|
|
36761
|
+
}
|
|
36762
|
+
},
|
|
36763
|
+
});
|
|
36764
|
+
}
|
|
36765
|
+
bindPageObservation(pageId, activeEpoch, activeContextKey) {
|
|
36766
|
+
if (!this.observeTargetDoc) {
|
|
36767
|
+
return;
|
|
36768
|
+
}
|
|
36769
|
+
this.pageSubs.get(pageId)?.unsubscribe();
|
|
36770
|
+
let target;
|
|
36771
|
+
try {
|
|
36772
|
+
target = buildGovernedPageTarget(pageId);
|
|
36773
|
+
}
|
|
36774
|
+
catch {
|
|
36775
|
+
return;
|
|
36776
|
+
}
|
|
36777
|
+
const sub = this.observeTargetDoc(target).subscribe({
|
|
36778
|
+
next: rawDoc => {
|
|
36779
|
+
if (this.isStale(activeEpoch, activeContextKey) || !rawDoc) {
|
|
36780
|
+
return;
|
|
36781
|
+
}
|
|
36782
|
+
const decoded = decodePageAuthoringDocument(rawDoc);
|
|
36783
|
+
if (decoded.ok) {
|
|
36784
|
+
this.pageDocuments.set(pageId, decoded.value);
|
|
36785
|
+
this.recomputeProjection(activeEpoch, activeContextKey, false);
|
|
36786
|
+
}
|
|
36787
|
+
},
|
|
36788
|
+
});
|
|
36789
|
+
this.pageSubs.set(pageId, sub);
|
|
36790
|
+
}
|
|
36791
|
+
reobserveGroupChildren(groupId, activeEpoch, activeContextKey) {
|
|
36792
|
+
for (const [pageId, doc] of this.pageDocuments.entries()) {
|
|
36793
|
+
if (doc.config.navigation.some(binding => binding.groupId.toLowerCase() === groupId)) {
|
|
36794
|
+
this.bindPageObservation(pageId, activeEpoch, activeContextKey);
|
|
36795
|
+
}
|
|
36796
|
+
}
|
|
36797
|
+
}
|
|
36798
|
+
recomputeProjection(activeEpoch, activeContextKey, loading) {
|
|
36799
|
+
if (this.isStale(activeEpoch, activeContextKey)) {
|
|
36800
|
+
return;
|
|
36801
|
+
}
|
|
36802
|
+
const diagnostics = [];
|
|
36803
|
+
const groups = this.latestGroupsDoc.config.groups.slice(0, this.maxItemsPerLevel);
|
|
36804
|
+
const groupNodesById = new Map();
|
|
36805
|
+
for (const group of groups) {
|
|
36806
|
+
groupNodesById.set(group.id.toLowerCase(), {
|
|
36807
|
+
id: group.id.toLowerCase(),
|
|
36808
|
+
kind: 'group',
|
|
36809
|
+
label: group.label,
|
|
36810
|
+
icon: group.icon,
|
|
36811
|
+
order: group.order,
|
|
36812
|
+
children: [],
|
|
36813
|
+
});
|
|
36814
|
+
}
|
|
36815
|
+
for (const [pageId, doc] of this.pageDocuments.entries()) {
|
|
36816
|
+
const routePath = buildPublishedPageIdentity(pageId, this.registeredNamespace).routePath;
|
|
36817
|
+
for (const binding of doc.config.navigation) {
|
|
36818
|
+
if (!this.allowedIcons.has(binding.icon)) {
|
|
36819
|
+
diagnostics.push({
|
|
36820
|
+
code: 'NAVIGATION_ICON_NOT_ALLOWED',
|
|
36821
|
+
path: `/pages/${pageId}/navigation/${binding.id}/icon`,
|
|
36822
|
+
});
|
|
36823
|
+
continue;
|
|
36824
|
+
}
|
|
36825
|
+
const parentGroup = groupNodesById.get(binding.groupId.toLowerCase());
|
|
36826
|
+
if (!parentGroup) {
|
|
36827
|
+
continue;
|
|
36828
|
+
}
|
|
36829
|
+
parentGroup.children ??= [];
|
|
36830
|
+
if (parentGroup.children.length < this.maxItemsPerLevel) {
|
|
36831
|
+
parentGroup.children.push({
|
|
36832
|
+
id: binding.id.toLowerCase(),
|
|
36833
|
+
kind: 'page',
|
|
36834
|
+
label: binding.label,
|
|
36835
|
+
icon: binding.icon,
|
|
36836
|
+
order: binding.order,
|
|
36837
|
+
pageId,
|
|
36838
|
+
routePath,
|
|
36839
|
+
});
|
|
36840
|
+
}
|
|
36841
|
+
}
|
|
36842
|
+
}
|
|
36843
|
+
const rootNodes = [];
|
|
36844
|
+
for (const group of groups) {
|
|
36845
|
+
const node = groupNodesById.get(group.id.toLowerCase());
|
|
36846
|
+
if (!node) {
|
|
36847
|
+
continue;
|
|
36848
|
+
}
|
|
36849
|
+
rootNodes.push(node);
|
|
36850
|
+
}
|
|
36851
|
+
const sortNodes = (nodes) => {
|
|
36852
|
+
nodes.sort((left, right) => left.order !== right.order
|
|
36853
|
+
? left.order - right.order
|
|
36854
|
+
: left.label.localeCompare(right.label));
|
|
36855
|
+
for (const item of nodes) {
|
|
36856
|
+
if (item.children?.length) {
|
|
36857
|
+
sortNodes(item.children);
|
|
36858
|
+
}
|
|
36859
|
+
}
|
|
36860
|
+
};
|
|
36861
|
+
sortNodes(rootNodes);
|
|
36862
|
+
const validation = validateRuntimeNavigationMenuNodes(rootNodes, {
|
|
36863
|
+
allowedIcons: this.allowedIcons,
|
|
36864
|
+
});
|
|
36865
|
+
if (!validation.ok) {
|
|
36866
|
+
diagnostics.push(...validation.diagnostics);
|
|
36867
|
+
}
|
|
36868
|
+
this.publishSnapshot({
|
|
36869
|
+
epoch: activeEpoch,
|
|
36870
|
+
contextKey: activeContextKey,
|
|
36871
|
+
catalogEntries: [...this.latestCatalogEntries],
|
|
36872
|
+
groupsDocument: this.latestGroupsDoc,
|
|
36873
|
+
menuNodes: validation.ok ? validation.value : rootNodes,
|
|
36874
|
+
expandedGroupIds: Array.from(this.expandedGroups),
|
|
36875
|
+
loading,
|
|
36876
|
+
diagnostics,
|
|
36877
|
+
fencedAttempts: Object.fromEntries(this.fencedAttempts),
|
|
36878
|
+
lastError: null,
|
|
36879
|
+
});
|
|
36880
|
+
}
|
|
36881
|
+
isStale(epoch, contextKey) {
|
|
36882
|
+
return epoch !== this.epoch || contextKey !== this.contextKey;
|
|
36883
|
+
}
|
|
36884
|
+
teardownSubscriptions() {
|
|
36885
|
+
this.groupsSub?.unsubscribe();
|
|
36886
|
+
this.groupsSub = null;
|
|
36887
|
+
for (const sub of this.pageSubs.values()) {
|
|
36888
|
+
sub.unsubscribe();
|
|
36889
|
+
}
|
|
36890
|
+
this.pageSubs.clear();
|
|
36891
|
+
}
|
|
36892
|
+
publishSnapshot(next) {
|
|
36893
|
+
this.stateSubject.next(next);
|
|
36894
|
+
}
|
|
36895
|
+
}
|
|
36896
|
+
|
|
36897
|
+
const DEFAULT_GOVERNED_PAGE_CHILD_VALIDATORS = Object.freeze({
|
|
36898
|
+
'praxis-dynamic-form': validateDynamicFormPageWidget,
|
|
36899
|
+
'praxis-table': validateTablePageWidget,
|
|
36900
|
+
});
|
|
36901
|
+
const DEFAULT_GOVERNED_PAGE_PREVIEW_ADAPTERS = Object.freeze({
|
|
36902
|
+
'praxis-dynamic-form': DYNAMIC_FORM_GOVERNED_PAGE_PREVIEW_ADAPTER,
|
|
36903
|
+
'praxis-table': TABLE_GOVERNED_PAGE_PREVIEW_ADAPTER,
|
|
36904
|
+
});
|
|
36905
|
+
const CANONICAL_UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
36906
|
+
function isPlainRecord(value) {
|
|
36907
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
36908
|
+
}
|
|
36909
|
+
function parseLegacySourceReference(value) {
|
|
36910
|
+
if (!isPlainRecord(value) || typeof value['kind'] !== 'string') {
|
|
36911
|
+
return null;
|
|
36912
|
+
}
|
|
36913
|
+
if (value['kind'] === 'registered-native') {
|
|
36914
|
+
const sourceId = typeof value['sourceId'] === 'string' ? value['sourceId'].trim() : '';
|
|
36915
|
+
const revision = typeof value['revision'] === 'string' ? value['revision'].trim() : '';
|
|
36916
|
+
if (!sourceId || !revision) {
|
|
36917
|
+
return null;
|
|
36918
|
+
}
|
|
36919
|
+
return {
|
|
36920
|
+
kind: 'registered-native',
|
|
36921
|
+
sourceId,
|
|
36922
|
+
revision,
|
|
36923
|
+
};
|
|
36924
|
+
}
|
|
36925
|
+
if (value['kind'] === 'published-page' || value['kind'] === 'published-page-copy') {
|
|
36926
|
+
const pageId = typeof value['pageId'] === 'string' ? value['pageId'].trim().toLowerCase() : '';
|
|
36927
|
+
const releaseId = typeof value['releaseId'] === 'string' ? value['releaseId'].trim().toLowerCase() : '';
|
|
36928
|
+
const revisionId = typeof value['revisionId'] === 'string' ? value['revisionId'].trim().toLowerCase() : '';
|
|
36929
|
+
if (!CANONICAL_UUID_PATTERN.test(pageId) ||
|
|
36930
|
+
!CANONICAL_UUID_PATTERN.test(releaseId) ||
|
|
36931
|
+
!CANONICAL_UUID_PATTERN.test(revisionId)) {
|
|
36932
|
+
return null;
|
|
36933
|
+
}
|
|
36934
|
+
return {
|
|
36935
|
+
kind: 'published-page',
|
|
36936
|
+
pageId,
|
|
36937
|
+
releaseId,
|
|
36938
|
+
revisionId,
|
|
36939
|
+
};
|
|
36940
|
+
}
|
|
36941
|
+
return null;
|
|
36942
|
+
}
|
|
36943
|
+
function normalizeLegacyCandidateDocument(rawPayload) {
|
|
36944
|
+
if (!isPlainRecord(rawPayload)) {
|
|
36945
|
+
return rawPayload;
|
|
36946
|
+
}
|
|
36947
|
+
if (isPlainRecord(rawPayload['document'])) {
|
|
36948
|
+
return normalizeLegacyCandidateDocument(rawPayload['document']);
|
|
36949
|
+
}
|
|
36950
|
+
if (rawPayload['schemaVersion'] !== undefined || rawPayload['kind'] !== undefined) {
|
|
36951
|
+
return rawPayload;
|
|
36952
|
+
}
|
|
36953
|
+
if (isPlainRecord(rawPayload['config'])) {
|
|
36954
|
+
return {
|
|
36955
|
+
kind: 'praxis.dynamic-page.editor',
|
|
36956
|
+
version: 1,
|
|
36957
|
+
config: rawPayload['config'],
|
|
36958
|
+
};
|
|
36959
|
+
}
|
|
36960
|
+
const presentation = isPlainRecord(rawPayload['presentation'])
|
|
36961
|
+
? rawPayload['presentation']
|
|
36962
|
+
: {
|
|
36963
|
+
title: typeof rawPayload['title'] === 'string' ? rawPayload['title'] : '',
|
|
36964
|
+
icon: typeof rawPayload['icon'] === 'string' ? rawPayload['icon'] : 'article',
|
|
36965
|
+
...(typeof rawPayload['description'] === 'string'
|
|
36966
|
+
? { description: rawPayload['description'] }
|
|
36967
|
+
: {}),
|
|
36968
|
+
};
|
|
36969
|
+
const navigation = Array.isArray(rawPayload['navigation'])
|
|
36970
|
+
? rawPayload['navigation']
|
|
36971
|
+
: [];
|
|
36972
|
+
const definition = isPlainRecord(rawPayload['definition'])
|
|
36973
|
+
? rawPayload['definition']
|
|
36974
|
+
: Object.fromEntries(Object.entries(rawPayload).filter(([key]) => key !== 'presentation' &&
|
|
36975
|
+
key !== 'navigation' &&
|
|
36976
|
+
key !== 'title' &&
|
|
36977
|
+
key !== 'icon' &&
|
|
36978
|
+
key !== 'description' &&
|
|
36979
|
+
key !== 'source' &&
|
|
36980
|
+
key !== 'childSources'));
|
|
36981
|
+
return {
|
|
36982
|
+
kind: 'praxis.dynamic-page.editor',
|
|
36983
|
+
version: 1,
|
|
36984
|
+
config: {
|
|
36985
|
+
presentation,
|
|
36986
|
+
navigation,
|
|
36987
|
+
definition,
|
|
36988
|
+
},
|
|
36989
|
+
};
|
|
36990
|
+
}
|
|
36991
|
+
/**
|
|
36992
|
+
* Inspects a legacy `published-page:<uuid>` payload against the closed P0 governed page profile.
|
|
36993
|
+
* Validates page source, authoring document structure, and child widget sources while preserving
|
|
36994
|
+
* an exportable artifact for incompatible legacy records.
|
|
36995
|
+
*/
|
|
36996
|
+
function inspectLegacyPublishedPagePayload(request, childValidators = DEFAULT_GOVERNED_PAGE_CHILD_VALIDATORS) {
|
|
36997
|
+
const rawId = String(request.pageId ?? '').trim();
|
|
36998
|
+
const normalizedPageId = rawId
|
|
36999
|
+
.replace(new RegExp(`^${LEGACY_PUBLISHED_PAGE_STORAGE_PREFIX}`, 'i'), '')
|
|
37000
|
+
.trim()
|
|
37001
|
+
.toLowerCase();
|
|
37002
|
+
const storageKey = `${LEGACY_PUBLISHED_PAGE_STORAGE_PREFIX}${normalizedPageId}`;
|
|
37003
|
+
const routePath = `/pages/${normalizedPageId}`;
|
|
37004
|
+
const diagnostics = [];
|
|
37005
|
+
let target = {
|
|
37006
|
+
componentType: 'praxis-dynamic-page',
|
|
37007
|
+
componentId: normalizedPageId,
|
|
37008
|
+
};
|
|
37009
|
+
try {
|
|
37010
|
+
target = buildGovernedPageTarget(normalizedPageId);
|
|
37011
|
+
}
|
|
37012
|
+
catch {
|
|
37013
|
+
diagnostics.push({
|
|
37014
|
+
code: 'PAGE_ID_INVALID',
|
|
37015
|
+
path: '/pageId',
|
|
37016
|
+
});
|
|
37017
|
+
}
|
|
37018
|
+
const rawRecord = isPlainRecord(request.rawPayload) ? request.rawPayload : null;
|
|
37019
|
+
const resolvedSource = parseLegacySourceReference(request.source) ??
|
|
37020
|
+
parseLegacySourceReference(rawRecord?.['source']);
|
|
37021
|
+
if (!resolvedSource) {
|
|
37022
|
+
diagnostics.push({
|
|
37023
|
+
code: 'LEGACY_PAGE_SOURCE_INVALID',
|
|
37024
|
+
path: '/source',
|
|
37025
|
+
});
|
|
37026
|
+
}
|
|
37027
|
+
const candidateDoc = normalizeLegacyCandidateDocument(request.rawPayload);
|
|
37028
|
+
const validation = validatePageAuthoringDocument(candidateDoc, childValidators);
|
|
37029
|
+
if (!validation.ok) {
|
|
37030
|
+
diagnostics.push(...validation.diagnostics);
|
|
37031
|
+
}
|
|
37032
|
+
const rawChildSources = Array.isArray(request.childSources)
|
|
37033
|
+
? request.childSources
|
|
37034
|
+
: Array.isArray(rawRecord?.['childSources'])
|
|
37035
|
+
? rawRecord['childSources']
|
|
37036
|
+
: [];
|
|
37037
|
+
const childSourceMap = new Map();
|
|
37038
|
+
rawChildSources.forEach((entry, index) => {
|
|
37039
|
+
if (!isPlainRecord(entry)) {
|
|
37040
|
+
diagnostics.push({
|
|
37041
|
+
code: 'LEGACY_WIDGET_SOURCE_INVALID',
|
|
37042
|
+
path: `/childSources/${index}`,
|
|
37043
|
+
});
|
|
37044
|
+
return;
|
|
37045
|
+
}
|
|
37046
|
+
const widgetKey = typeof entry['widgetKey'] === 'string' ? entry['widgetKey'].trim().toLowerCase() : '';
|
|
37047
|
+
const parsedSource = parseLegacySourceReference(entry['source']);
|
|
37048
|
+
if (!widgetKey || !parsedSource || parsedSource.kind !== 'registered-native') {
|
|
37049
|
+
diagnostics.push({
|
|
37050
|
+
code: 'LEGACY_WIDGET_SOURCE_INVALID',
|
|
37051
|
+
path: `/childSources/${index}`,
|
|
37052
|
+
});
|
|
37053
|
+
return;
|
|
37054
|
+
}
|
|
37055
|
+
childSourceMap.set(widgetKey, {
|
|
37056
|
+
widgetKey,
|
|
37057
|
+
source: parsedSource,
|
|
37058
|
+
});
|
|
37059
|
+
});
|
|
37060
|
+
if (validation.ok) {
|
|
37061
|
+
validation.value.config.definition.widgets.forEach((widget, index) => {
|
|
37062
|
+
const normalizedKey = widget.key.toLowerCase();
|
|
37063
|
+
if (!childSourceMap.has(normalizedKey)) {
|
|
37064
|
+
diagnostics.push({
|
|
37065
|
+
code: 'LEGACY_WIDGET_SOURCE_MISSING',
|
|
37066
|
+
path: `/config/definition/widgets/${index}/key`,
|
|
37067
|
+
});
|
|
37068
|
+
}
|
|
37069
|
+
});
|
|
37070
|
+
}
|
|
37071
|
+
const compatible = diagnostics.length === 0 && validation.ok && resolvedSource !== null;
|
|
37072
|
+
const authoringDocument = compatible && validation.ok ? validation.value : null;
|
|
37073
|
+
const childSources = Array.from(childSourceMap.values());
|
|
37074
|
+
return {
|
|
37075
|
+
storageKey,
|
|
37076
|
+
pageId: normalizedPageId,
|
|
37077
|
+
routePath,
|
|
37078
|
+
target,
|
|
37079
|
+
compatible,
|
|
37080
|
+
source: resolvedSource,
|
|
37081
|
+
childSources,
|
|
37082
|
+
authoringDocument,
|
|
37083
|
+
diagnostics,
|
|
37084
|
+
exportableArtifact: {
|
|
37085
|
+
schemaVersion: '1.0.0',
|
|
37086
|
+
kind: 'legacy-published-page-export',
|
|
37087
|
+
storageKey,
|
|
37088
|
+
pageId: normalizedPageId,
|
|
37089
|
+
routePath,
|
|
37090
|
+
compatible,
|
|
37091
|
+
diagnostics,
|
|
37092
|
+
rawPayload: request.rawPayload,
|
|
37093
|
+
},
|
|
37094
|
+
};
|
|
37095
|
+
}
|
|
37096
|
+
const DEFAULT_CAPABILITIES = Object.freeze({
|
|
37097
|
+
canAuthorPages: true,
|
|
37098
|
+
canSubmitForReview: true,
|
|
37099
|
+
canApproveReleases: false,
|
|
37100
|
+
canPublishReleases: false,
|
|
37101
|
+
canManageNavigationGroups: true,
|
|
37102
|
+
});
|
|
37103
|
+
class GovernedPageWorkflowController {
|
|
37104
|
+
persistence;
|
|
37105
|
+
childValidators;
|
|
37106
|
+
previewAdapters;
|
|
37107
|
+
idGenerator;
|
|
37108
|
+
contextGeneration = 0;
|
|
37109
|
+
contextKey;
|
|
37110
|
+
fencedOperations = new Map();
|
|
37111
|
+
childSourcesByWidgetId = new Map();
|
|
37112
|
+
stateSubject;
|
|
37113
|
+
snapshot$;
|
|
37114
|
+
constructor(config) {
|
|
37115
|
+
this.persistence = config.persistence;
|
|
37116
|
+
this.childValidators = config.childValidators ?? DEFAULT_GOVERNED_PAGE_CHILD_VALIDATORS;
|
|
37117
|
+
this.previewAdapters = config.previewAdapters ?? DEFAULT_GOVERNED_PAGE_PREVIEW_ADAPTERS;
|
|
37118
|
+
this.contextKey = config.initialContextKey ?? 'default';
|
|
37119
|
+
this.idGenerator =
|
|
37120
|
+
config.idGenerator ??
|
|
37121
|
+
(() => typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function'
|
|
37122
|
+
? crypto.randomUUID()
|
|
37123
|
+
: `00000000-0000-4000-8000-${Date.now().toString(16).padStart(12, '0').slice(-12)}`);
|
|
37124
|
+
this.stateSubject = new BehaviorSubject({
|
|
37125
|
+
contextGeneration: this.contextGeneration,
|
|
37126
|
+
contextKey: this.contextKey,
|
|
37127
|
+
pageId: null,
|
|
37128
|
+
rootTarget: null,
|
|
37129
|
+
source: null,
|
|
37130
|
+
draftWorkspace: null,
|
|
37131
|
+
draftEtag: null,
|
|
37132
|
+
workingDocument: null,
|
|
37133
|
+
baselineDocument: null,
|
|
37134
|
+
publishedSnapshot: null,
|
|
37135
|
+
publishedEtag: null,
|
|
37136
|
+
childSources: [],
|
|
37137
|
+
reviewReceipt: null,
|
|
37138
|
+
reviewEtag: null,
|
|
37139
|
+
headReceipt: null,
|
|
37140
|
+
headEtag: null,
|
|
37141
|
+
preview: null,
|
|
37142
|
+
dirty: false,
|
|
37143
|
+
busy: false,
|
|
37144
|
+
capabilities: config.initialCapabilities ?? DEFAULT_CAPABILITIES,
|
|
37145
|
+
fencedOperations: {},
|
|
37146
|
+
lastError: null,
|
|
37147
|
+
});
|
|
37148
|
+
this.snapshot$ = this.stateSubject.asObservable();
|
|
37149
|
+
}
|
|
37150
|
+
get snapshot() {
|
|
37151
|
+
return this.stateSubject.value;
|
|
37152
|
+
}
|
|
37153
|
+
setCapabilities(capabilities) {
|
|
37154
|
+
this.publishSnapshot({
|
|
37155
|
+
...this.snapshot,
|
|
37156
|
+
capabilities: { ...capabilities },
|
|
37157
|
+
});
|
|
37158
|
+
}
|
|
37159
|
+
/**
|
|
37160
|
+
* Advances monotonic context generation on context switch (including A -> B -> A),
|
|
37161
|
+
* invalidating any in-flight callbacks from previous generations.
|
|
37162
|
+
*/
|
|
37163
|
+
setContextKey(nextContextKey) {
|
|
37164
|
+
const normalized = String(nextContextKey ?? '').trim() || 'default';
|
|
37165
|
+
this.contextGeneration += 1;
|
|
37166
|
+
this.contextKey = normalized;
|
|
37167
|
+
this.fencedOperations.clear();
|
|
37168
|
+
this.childSourcesByWidgetId.clear();
|
|
37169
|
+
this.publishSnapshot({
|
|
37170
|
+
contextGeneration: this.contextGeneration,
|
|
37171
|
+
contextKey: this.contextKey,
|
|
37172
|
+
pageId: null,
|
|
37173
|
+
rootTarget: null,
|
|
37174
|
+
source: null,
|
|
37175
|
+
draftWorkspace: null,
|
|
37176
|
+
draftEtag: null,
|
|
37177
|
+
workingDocument: null,
|
|
37178
|
+
baselineDocument: null,
|
|
37179
|
+
publishedSnapshot: null,
|
|
37180
|
+
publishedEtag: null,
|
|
37181
|
+
childSources: [],
|
|
37182
|
+
reviewReceipt: null,
|
|
37183
|
+
reviewEtag: null,
|
|
37184
|
+
headReceipt: null,
|
|
37185
|
+
headEtag: null,
|
|
37186
|
+
preview: null,
|
|
37187
|
+
dirty: false,
|
|
37188
|
+
busy: false,
|
|
37189
|
+
capabilities: this.snapshot.capabilities,
|
|
37190
|
+
fencedOperations: {},
|
|
37191
|
+
lastError: null,
|
|
37192
|
+
});
|
|
37193
|
+
}
|
|
37194
|
+
destroy() {
|
|
37195
|
+
this.contextGeneration += 1;
|
|
37196
|
+
this.stateSubject.complete();
|
|
37197
|
+
}
|
|
37198
|
+
async createNativePage(request) {
|
|
37199
|
+
this.requireCapability('canAuthorPages', 'Creating a governed page requires authoring capability.');
|
|
37200
|
+
const creationKey = request.creationKey?.trim() || this.idGenerator();
|
|
37201
|
+
return this.executePageCreation({
|
|
37202
|
+
creationKey,
|
|
37203
|
+
title: request.title,
|
|
37204
|
+
source: {
|
|
37205
|
+
kind: 'registered-native',
|
|
37206
|
+
sourceId: request.sourceId,
|
|
37207
|
+
revision: request.revision,
|
|
37208
|
+
},
|
|
37209
|
+
});
|
|
37210
|
+
}
|
|
37211
|
+
async copyPublishedPage(request) {
|
|
37212
|
+
this.requireCapability('canAuthorPages', 'Copying a published page requires authoring capability.');
|
|
37213
|
+
const activeGen = this.contextGeneration;
|
|
37214
|
+
const activeCtx = this.contextKey;
|
|
37215
|
+
const creationKey = request.creationKey?.trim() || this.idGenerator();
|
|
37216
|
+
this.setBusy(true);
|
|
37217
|
+
try {
|
|
37218
|
+
const publishedRead = await this.persistence.readPublishedPage(request.sourcePageId);
|
|
37219
|
+
this.assertActiveContext(activeGen, activeCtx);
|
|
37220
|
+
if ('status' in publishedRead) {
|
|
37221
|
+
throw this.fail('TRANSPORT_ERROR', 'Unexpected 304 when reading published source page.');
|
|
37222
|
+
}
|
|
37223
|
+
const sourceRef = publishedRead.body.copySource;
|
|
37224
|
+
return await this.executePageCreation({
|
|
37225
|
+
creationKey,
|
|
37226
|
+
title: request.title,
|
|
37227
|
+
source: sourceRef,
|
|
37228
|
+
}, activeGen, activeCtx);
|
|
37229
|
+
}
|
|
37230
|
+
catch (error) {
|
|
37231
|
+
throw this.handleOperationError(error, activeGen, activeCtx);
|
|
37232
|
+
}
|
|
37233
|
+
finally {
|
|
37234
|
+
if (!this.isStale(activeGen, activeCtx)) {
|
|
37235
|
+
this.setBusy(false);
|
|
37236
|
+
}
|
|
37237
|
+
}
|
|
37238
|
+
}
|
|
37239
|
+
/**
|
|
37240
|
+
* Explicitly imports a compatible legacy `published-page:<uuid>` record into a governed draft
|
|
37241
|
+
* preserving the `/pages/<uuid>` address (`{ componentType: 'praxis-dynamic-page', componentId: pageId }`).
|
|
37242
|
+
* Never fabricates approval or publication state and never deletes legacy storage keys.
|
|
37243
|
+
*/
|
|
37244
|
+
async importLegacyPublishedPage(request) {
|
|
37245
|
+
this.requireCapability('canAuthorPages', 'Importing a legacy published page into a governed draft requires authoring capability.');
|
|
37246
|
+
const inspection = inspectLegacyPublishedPagePayload(request, this.childValidators);
|
|
37247
|
+
if (!inspection.compatible || !inspection.authoringDocument || !inspection.source) {
|
|
37248
|
+
throw this.fail('VALIDATION_FAILED', 'Legacy published page cannot be imported into the closed P0 governed profile.', inspection.diagnostics);
|
|
37249
|
+
}
|
|
37250
|
+
const activeGen = this.contextGeneration;
|
|
37251
|
+
const activeCtx = this.contextKey;
|
|
37252
|
+
const idempotencyKey = request.idempotencyKey?.trim() || this.idGenerator();
|
|
37253
|
+
const attemptKey = `import-legacy-page:${inspection.pageId}`;
|
|
37254
|
+
this.assertNotFenced(attemptKey);
|
|
37255
|
+
this.setBusy(true);
|
|
37256
|
+
try {
|
|
37257
|
+
const draftReceipt = await this.persistence.createDraft(inspection.target, idempotencyKey);
|
|
37258
|
+
this.assertActiveContext(activeGen, activeCtx);
|
|
37259
|
+
const commandRef = this.idGenerator();
|
|
37260
|
+
const revisionReceipt = await this.persistence.createRevision(inspection.target, draftReceipt.body.draft.draftRef, draftReceipt.etag, {
|
|
37261
|
+
commandRef,
|
|
37262
|
+
target: inspection.target,
|
|
37263
|
+
authoringDocument: inspection.authoringDocument,
|
|
37264
|
+
reason: request.reason?.trim() ||
|
|
37265
|
+
`Import legacy ${inspection.storageKey} into governed draft workspace`,
|
|
37266
|
+
...(inspection.childSources.length > 0
|
|
37267
|
+
? { childSources: [...inspection.childSources] }
|
|
37268
|
+
: {}),
|
|
37269
|
+
});
|
|
37270
|
+
this.assertActiveContext(activeGen, activeCtx);
|
|
37271
|
+
if (revisionReceipt.etag === draftReceipt.etag) {
|
|
37272
|
+
throw this.fail('ETAG_NOT_PROGRESSED', 'Draft ETag did not progress after importing legacy page revision.');
|
|
37273
|
+
}
|
|
37274
|
+
this.childSourcesByWidgetId.clear();
|
|
37275
|
+
for (const childSource of inspection.childSources) {
|
|
37276
|
+
this.childSourcesByWidgetId.set(childSource.widgetKey.toLowerCase(), childSource);
|
|
37277
|
+
}
|
|
37278
|
+
this.applyDraftWorkspaceReceipt(inspection.pageId, inspection.target, revisionReceipt.body.nextDraft, revisionReceipt.etag, inspection.source);
|
|
37279
|
+
await this.refreshPublishedAndLifecycleMetadata(inspection.pageId, inspection.target, revisionReceipt.body.nextDraft.frozenReleaseRef, activeGen, activeCtx);
|
|
37280
|
+
return this.snapshot;
|
|
37281
|
+
}
|
|
37282
|
+
catch (error) {
|
|
37283
|
+
throw this.handleOperationError(error, activeGen, activeCtx, {
|
|
37284
|
+
operation: 'import-legacy-page',
|
|
37285
|
+
attemptKey,
|
|
37286
|
+
creationKey: idempotencyKey,
|
|
37287
|
+
source: inspection.source,
|
|
37288
|
+
childSources: [...inspection.childSources],
|
|
37289
|
+
});
|
|
37290
|
+
}
|
|
37291
|
+
finally {
|
|
37292
|
+
if (!this.isStale(activeGen, activeCtx)) {
|
|
37293
|
+
this.setBusy(false);
|
|
37294
|
+
}
|
|
37295
|
+
}
|
|
37296
|
+
}
|
|
37297
|
+
async openDraftWorkspace(pageId, draftId, options) {
|
|
37298
|
+
const activeGen = this.contextGeneration;
|
|
37299
|
+
const activeCtx = this.contextKey;
|
|
37300
|
+
const rootTarget = buildGovernedPageTarget(pageId);
|
|
37301
|
+
this.setBusy(true);
|
|
37302
|
+
try {
|
|
37303
|
+
const draftResult = await this.persistence.readDraft(rootTarget, draftId);
|
|
37304
|
+
this.assertActiveContext(activeGen, activeCtx);
|
|
37305
|
+
if ('status' in draftResult) {
|
|
37306
|
+
return this.snapshot;
|
|
37307
|
+
}
|
|
37308
|
+
this.applyDraftWorkspaceReceipt(pageId.trim().toLowerCase(), rootTarget, draftResult.body, draftResult.etag, options?.source ?? this.snapshot.source);
|
|
37309
|
+
await this.refreshPublishedAndLifecycleMetadata(pageId.trim().toLowerCase(), rootTarget, draftResult.body.frozenReleaseRef, activeGen, activeCtx);
|
|
37310
|
+
return this.snapshot;
|
|
37311
|
+
}
|
|
37312
|
+
catch (error) {
|
|
37313
|
+
throw this.handleOperationError(error, activeGen, activeCtx);
|
|
37314
|
+
}
|
|
37315
|
+
finally {
|
|
37316
|
+
if (!this.isStale(activeGen, activeCtx)) {
|
|
37317
|
+
this.setBusy(false);
|
|
37318
|
+
}
|
|
37319
|
+
}
|
|
37320
|
+
}
|
|
37321
|
+
updateWorkingDocument(nextDocument) {
|
|
37322
|
+
this.requireCapability('canAuthorPages', 'Editing a governed page draft requires authoring capability.');
|
|
37323
|
+
const decoded = decodePageAuthoringDocument(nextDocument);
|
|
37324
|
+
if (!decoded.ok) {
|
|
37325
|
+
throw this.fail('VALIDATION_FAILED', 'Invalid governed page authoring document.', decoded.diagnostics);
|
|
37326
|
+
}
|
|
37327
|
+
const baseline = this.snapshot.baselineDocument;
|
|
37328
|
+
const dirty = !baseline || uiLayoutJsonIdentity(baseline) !== uiLayoutJsonIdentity(decoded.value);
|
|
37329
|
+
this.publishSnapshot({
|
|
37330
|
+
...this.snapshot,
|
|
37331
|
+
workingDocument: decoded.value,
|
|
37332
|
+
dirty,
|
|
37333
|
+
lastError: null,
|
|
37334
|
+
});
|
|
37335
|
+
return decoded.value;
|
|
37336
|
+
}
|
|
37337
|
+
updatePresentation(presentation) {
|
|
37338
|
+
const current = this.requireWorkingDocument();
|
|
37339
|
+
return this.updateWorkingDocument({
|
|
37340
|
+
...current,
|
|
37341
|
+
config: {
|
|
37342
|
+
...current.config,
|
|
37343
|
+
presentation: {
|
|
37344
|
+
title: presentation.title,
|
|
37345
|
+
icon: presentation.icon,
|
|
37346
|
+
...(presentation.description !== undefined
|
|
37347
|
+
? { description: presentation.description }
|
|
37348
|
+
: {}),
|
|
37349
|
+
},
|
|
37350
|
+
},
|
|
37351
|
+
});
|
|
37352
|
+
}
|
|
37353
|
+
upsertNavigationBinding(binding, options) {
|
|
37354
|
+
this.requireCapability('canAuthorPages', 'Binding page navigation requires authoring capability.');
|
|
37355
|
+
const current = this.requireWorkingDocument();
|
|
37356
|
+
const updated = upsertPageNavigationBinding(current, binding, options);
|
|
37357
|
+
if (!updated.ok) {
|
|
37358
|
+
throw this.fail('VALIDATION_FAILED', 'Invalid governed page navigation binding.', updated.diagnostics);
|
|
37359
|
+
}
|
|
37360
|
+
return this.updateWorkingDocument(updated.value);
|
|
37361
|
+
}
|
|
37362
|
+
/**
|
|
37363
|
+
* Updates a child widget's inputs inside the page's authoring document and
|
|
37364
|
+
* records its governed child source so child widgets remain under page authorship.
|
|
37365
|
+
*/
|
|
37366
|
+
updateChildWidgetInputs(widgetKey, inputs, childSource) {
|
|
37367
|
+
this.requireCapability('canAuthorPages', 'Editing child widgets requires page authoring capability.');
|
|
37368
|
+
const current = this.requireWorkingDocument();
|
|
37369
|
+
const normalizedKey = String(widgetKey ?? '').trim().toLowerCase();
|
|
37370
|
+
const widgetIndex = current.config.definition.widgets.findIndex(widget => widget.key.toLowerCase() === normalizedKey);
|
|
37371
|
+
if (widgetIndex < 0) {
|
|
37372
|
+
throw this.fail('VALIDATION_FAILED', `Widget "${widgetKey}" does not exist on the governed page.`);
|
|
37373
|
+
}
|
|
37374
|
+
const nextWidgets = current.config.definition.widgets.map((widget, idx) => idx === widgetIndex
|
|
37375
|
+
? {
|
|
37376
|
+
...widget,
|
|
37377
|
+
definition: {
|
|
37378
|
+
...widget.definition,
|
|
37379
|
+
inputs: JSON.parse(JSON.stringify(inputs)),
|
|
37380
|
+
},
|
|
37381
|
+
}
|
|
37382
|
+
: widget);
|
|
37383
|
+
if (childSource) {
|
|
37384
|
+
this.childSourcesByWidgetId.set(normalizedKey, {
|
|
37385
|
+
widgetKey: normalizedKey,
|
|
37386
|
+
source: childSource.source,
|
|
37387
|
+
});
|
|
37388
|
+
}
|
|
37389
|
+
const updated = this.updateWorkingDocument({
|
|
37390
|
+
...current,
|
|
37391
|
+
config: {
|
|
37392
|
+
...current.config,
|
|
37393
|
+
definition: {
|
|
37394
|
+
...current.config.definition,
|
|
37395
|
+
widgets: nextWidgets,
|
|
37396
|
+
},
|
|
37397
|
+
},
|
|
37398
|
+
});
|
|
37399
|
+
this.publishSnapshot({
|
|
37400
|
+
...this.snapshot,
|
|
37401
|
+
childSources: Array.from(this.childSourcesByWidgetId.values()),
|
|
37402
|
+
});
|
|
37403
|
+
return updated;
|
|
37404
|
+
}
|
|
37405
|
+
buildInteractivePreview(baseContext = {}) {
|
|
37406
|
+
const working = this.requireWorkingDocument();
|
|
37407
|
+
const pageId = this.snapshot.pageId;
|
|
37408
|
+
if (!pageId) {
|
|
37409
|
+
throw this.fail('DRAFT_NOT_LOADED', 'Cannot materialize preview before loading a governed page.');
|
|
37410
|
+
}
|
|
37411
|
+
const previewResult = materializeGovernedPagePreview(working, pageId, this.previewAdapters, baseContext);
|
|
37412
|
+
if (!previewResult.ok) {
|
|
37413
|
+
const hasUncertified = previewResult.diagnostics.some(diagnostic => diagnostic.code === 'PAGE_PREVIEW_WIDGET_UNCERTIFIED');
|
|
37414
|
+
throw this.fail(hasUncertified ? 'PREVIEW_UNCERTIFIED' : 'VALIDATION_FAILED', hasUncertified
|
|
37415
|
+
? 'Interactive preview blocked: one or more widgets do not have a certified preview adapter.'
|
|
37416
|
+
: 'Interactive preview failed validation.', previewResult.diagnostics);
|
|
37417
|
+
}
|
|
37418
|
+
this.publishSnapshot({
|
|
37419
|
+
...this.snapshot,
|
|
37420
|
+
preview: previewResult.value,
|
|
37421
|
+
lastError: null,
|
|
37422
|
+
});
|
|
37423
|
+
return previewResult.value;
|
|
37424
|
+
}
|
|
37425
|
+
async saveDraftRevision(reason = 'Save governed page draft revision') {
|
|
37426
|
+
this.requireCapability('canAuthorPages', 'Saving a draft revision requires authoring capability.');
|
|
37427
|
+
const activeGen = this.contextGeneration;
|
|
37428
|
+
const activeCtx = this.contextKey;
|
|
37429
|
+
const { rootTarget, draftWorkspace, draftEtag, pageId } = this.requireLoadedDraft();
|
|
37430
|
+
const working = this.requireWorkingDocument();
|
|
37431
|
+
const attemptKey = `save-revision:${draftWorkspace.draft.draftRef}`;
|
|
37432
|
+
this.assertNotFenced(attemptKey);
|
|
37433
|
+
const validated = validatePageAuthoringDocument(working, this.childValidators);
|
|
37434
|
+
if (!validated.ok) {
|
|
37435
|
+
throw this.fail('VALIDATION_FAILED', 'Governed page authoring document failed validation.', validated.diagnostics);
|
|
37436
|
+
}
|
|
37437
|
+
const commandRef = this.idGenerator();
|
|
37438
|
+
const childSources = Array.from(this.childSourcesByWidgetId.values());
|
|
37439
|
+
let activeDraftRef = draftWorkspace.draft.draftRef;
|
|
37440
|
+
let activeDraftEtag = draftEtag;
|
|
37441
|
+
this.setBusy(true);
|
|
37442
|
+
try {
|
|
37443
|
+
// A frozen release is immutable: editing after freeze opens a fresh draft workspace
|
|
37444
|
+
// and never carries over a previous release's approval or submission state.
|
|
37445
|
+
if (draftWorkspace.frozenReleaseRef !== null ||
|
|
37446
|
+
draftWorkspace.draft.state === 'RELEASED') {
|
|
37447
|
+
const reopened = await this.persistence.createDraft(rootTarget, this.idGenerator());
|
|
37448
|
+
this.assertActiveContext(activeGen, activeCtx);
|
|
37449
|
+
activeDraftRef = reopened.body.draft.draftRef;
|
|
37450
|
+
activeDraftEtag = reopened.etag;
|
|
37451
|
+
}
|
|
37452
|
+
const receipt = await this.persistence.createRevision(rootTarget, activeDraftRef, activeDraftEtag, {
|
|
37453
|
+
commandRef,
|
|
37454
|
+
target: rootTarget,
|
|
37455
|
+
authoringDocument: validated.value,
|
|
37456
|
+
reason,
|
|
37457
|
+
...(childSources.length > 0 ? { childSources } : {}),
|
|
37458
|
+
});
|
|
37459
|
+
this.assertActiveContext(activeGen, activeCtx);
|
|
37460
|
+
if (receipt.etag === activeDraftEtag) {
|
|
37461
|
+
throw this.fail('ETAG_NOT_PROGRESSED', 'Draft ETag did not progress after saving revision.');
|
|
37462
|
+
}
|
|
37463
|
+
this.applyDraftWorkspaceReceipt(pageId, rootTarget, receipt.body.nextDraft, receipt.etag, this.snapshot.source);
|
|
37464
|
+
return this.snapshot;
|
|
37465
|
+
}
|
|
37466
|
+
catch (error) {
|
|
37467
|
+
throw this.handleOperationError(error, activeGen, activeCtx, {
|
|
37468
|
+
operation: 'save-revision',
|
|
37469
|
+
attemptKey,
|
|
37470
|
+
commandRef,
|
|
37471
|
+
draftId: activeDraftRef,
|
|
37472
|
+
});
|
|
37473
|
+
}
|
|
37474
|
+
finally {
|
|
37475
|
+
if (!this.isStale(activeGen, activeCtx)) {
|
|
37476
|
+
this.setBusy(false);
|
|
37477
|
+
}
|
|
37478
|
+
}
|
|
37479
|
+
}
|
|
37480
|
+
async assignScope(input) {
|
|
37481
|
+
this.requireCapability('canAuthorPages', 'Assigning governed page scope requires authoring capability.');
|
|
37482
|
+
const activeGen = this.contextGeneration;
|
|
37483
|
+
const activeCtx = this.contextKey;
|
|
37484
|
+
const { rootTarget, draftWorkspace, draftEtag, pageId } = this.requireLoadedDraft();
|
|
37485
|
+
const attemptKey = `assign-scope:${draftWorkspace.draft.draftRef}`;
|
|
37486
|
+
this.assertNotFenced(attemptKey);
|
|
37487
|
+
const commandRef = this.idGenerator();
|
|
37488
|
+
let activeDraftRef = draftWorkspace.draft.draftRef;
|
|
37489
|
+
let activeDraftEtag = draftEtag;
|
|
37490
|
+
this.setBusy(true);
|
|
37491
|
+
try {
|
|
37492
|
+
if (draftWorkspace.frozenReleaseRef !== null ||
|
|
37493
|
+
draftWorkspace.draft.state === 'RELEASED') {
|
|
37494
|
+
const reopened = await this.persistence.createDraft(rootTarget, this.idGenerator());
|
|
37495
|
+
this.assertActiveContext(activeGen, activeCtx);
|
|
37496
|
+
activeDraftRef = reopened.body.draft.draftRef;
|
|
37497
|
+
activeDraftEtag = reopened.etag;
|
|
37498
|
+
const working = this.snapshot.workingDocument;
|
|
37499
|
+
if (working) {
|
|
37500
|
+
const childSources = Array.from(this.childSourcesByWidgetId.values());
|
|
37501
|
+
const revReceipt = await this.persistence.createRevision(rootTarget, activeDraftRef, activeDraftEtag, {
|
|
37502
|
+
commandRef: this.idGenerator(),
|
|
37503
|
+
target: rootTarget,
|
|
37504
|
+
authoringDocument: working,
|
|
37505
|
+
reason: 'Reopen draft revision before updating scope assignment',
|
|
37506
|
+
...(childSources.length > 0 ? { childSources } : {}),
|
|
37507
|
+
});
|
|
37508
|
+
this.assertActiveContext(activeGen, activeCtx);
|
|
37509
|
+
if (revReceipt.etag === activeDraftEtag) {
|
|
37510
|
+
throw this.fail('ETAG_NOT_PROGRESSED', 'Draft ETag did not progress after saving revision.');
|
|
37511
|
+
}
|
|
37512
|
+
activeDraftEtag = revReceipt.etag;
|
|
37513
|
+
}
|
|
37514
|
+
}
|
|
37515
|
+
const receipt = await this.persistence.createAssignment(rootTarget, activeDraftRef, activeDraftEtag, {
|
|
37516
|
+
commandRef,
|
|
37517
|
+
target: rootTarget,
|
|
37518
|
+
layerClass: input.layerClass,
|
|
37519
|
+
selector: input.selector,
|
|
37520
|
+
priority: input.priority ?? 100,
|
|
37521
|
+
contributionKey: input.contributionKey ?? rootTarget.componentId,
|
|
37522
|
+
});
|
|
37523
|
+
this.assertActiveContext(activeGen, activeCtx);
|
|
37524
|
+
if (receipt.etag === activeDraftEtag) {
|
|
37525
|
+
throw this.fail('ETAG_NOT_PROGRESSED', 'Draft ETag did not progress after scope assignment.');
|
|
37526
|
+
}
|
|
37527
|
+
this.applyDraftWorkspaceReceipt(pageId, rootTarget, receipt.body.nextDraft, receipt.etag, this.snapshot.source);
|
|
37528
|
+
return this.snapshot;
|
|
37529
|
+
}
|
|
37530
|
+
catch (error) {
|
|
37531
|
+
throw this.handleOperationError(error, activeGen, activeCtx, {
|
|
37532
|
+
operation: 'assign-scope',
|
|
37533
|
+
attemptKey,
|
|
37534
|
+
commandRef,
|
|
37535
|
+
draftId: activeDraftRef,
|
|
37536
|
+
});
|
|
37537
|
+
}
|
|
37538
|
+
finally {
|
|
37539
|
+
if (!this.isStale(activeGen, activeCtx)) {
|
|
37540
|
+
this.setBusy(false);
|
|
37541
|
+
}
|
|
37542
|
+
}
|
|
37543
|
+
}
|
|
37544
|
+
async saveAndSubmitForReview(options = {}) {
|
|
37545
|
+
this.requireCapability('canAuthorPages', 'Submitting a governed page requires authoring capability.');
|
|
37546
|
+
this.requireCapability('canSubmitForReview', 'Submitting a governed page for review requires submit capability.');
|
|
37547
|
+
const activeGen = this.contextGeneration;
|
|
37548
|
+
const activeCtx = this.contextKey;
|
|
37549
|
+
const reason = options.reason?.trim() || 'Submit governed page for review';
|
|
37550
|
+
const rootTargetEntry = this.snapshot.draftWorkspace?.targets[0];
|
|
37551
|
+
if (this.snapshot.dirty || !rootTargetEntry?.selectedRevision) {
|
|
37552
|
+
await this.saveDraftRevision(reason);
|
|
37553
|
+
this.assertActiveContext(activeGen, activeCtx);
|
|
37554
|
+
}
|
|
37555
|
+
const updatedTargetEntry = this.snapshot.draftWorkspace?.targets[0];
|
|
37556
|
+
const existingAssignment = updatedTargetEntry?.selectedAssignment;
|
|
37557
|
+
const isSameAssignment = Boolean(existingAssignment) &&
|
|
37558
|
+
Boolean(options.assignment) &&
|
|
37559
|
+
existingAssignment?.layerClass === options.assignment?.layerClass &&
|
|
37560
|
+
existingAssignment?.priority === (options.assignment?.priority ?? 100) &&
|
|
37561
|
+
uiLayoutJsonIdentity(existingAssignment?.selector ?? null) ===
|
|
37562
|
+
uiLayoutJsonIdentity(options.assignment?.selector ?? null);
|
|
37563
|
+
if (!existingAssignment || (options.assignment && !isSameAssignment)) {
|
|
37564
|
+
if (!options.assignment) {
|
|
37565
|
+
throw this.fail('VALIDATION_FAILED', 'Scope assignment selector is required before freezing and submitting a release.');
|
|
37566
|
+
}
|
|
37567
|
+
await this.assignScope(options.assignment);
|
|
37568
|
+
this.assertActiveContext(activeGen, activeCtx);
|
|
37569
|
+
}
|
|
37570
|
+
const { rootTarget, draftWorkspace, draftEtag, pageId } = this.requireLoadedDraft();
|
|
37571
|
+
const freezeAttemptKey = `freeze-release:${draftWorkspace.draft.draftRef}`;
|
|
37572
|
+
this.assertNotFenced(freezeAttemptKey);
|
|
37573
|
+
let releaseId = draftWorkspace.frozenReleaseRef;
|
|
37574
|
+
let reviewEtag = this.snapshot.reviewEtag;
|
|
37575
|
+
this.setBusy(true);
|
|
37576
|
+
try {
|
|
37577
|
+
if (!releaseId || !reviewEtag) {
|
|
37578
|
+
const freezeCommandRef = this.idGenerator();
|
|
37579
|
+
try {
|
|
37580
|
+
const freezeReceipt = await this.persistence.freezeRelease(rootTarget, draftWorkspace.draft.draftRef, draftEtag, { commandRef: freezeCommandRef });
|
|
37581
|
+
this.assertActiveContext(activeGen, activeCtx);
|
|
37582
|
+
if (freezeReceipt.etag === draftEtag) {
|
|
37583
|
+
throw this.fail('ETAG_NOT_PROGRESSED', 'Draft ETag did not progress after freezing release.');
|
|
37584
|
+
}
|
|
37585
|
+
this.applyDraftWorkspaceReceipt(pageId, rootTarget, freezeReceipt.body.nextDraft, freezeReceipt.etag, this.snapshot.source);
|
|
37586
|
+
releaseId = freezeReceipt.body.release.releaseRef;
|
|
37587
|
+
reviewEtag = freezeReceipt.body.review.reviewEtag;
|
|
37588
|
+
this.publishSnapshot({
|
|
37589
|
+
...this.snapshot,
|
|
37590
|
+
reviewReceipt: freezeReceipt.body.review,
|
|
37591
|
+
reviewEtag,
|
|
37592
|
+
});
|
|
37593
|
+
}
|
|
37594
|
+
catch (error) {
|
|
37595
|
+
throw this.handleOperationError(error, activeGen, activeCtx, {
|
|
37596
|
+
operation: 'freeze-release',
|
|
37597
|
+
attemptKey: freezeAttemptKey,
|
|
37598
|
+
commandRef: freezeCommandRef,
|
|
37599
|
+
draftId: draftWorkspace.draft.draftRef,
|
|
37600
|
+
});
|
|
37601
|
+
}
|
|
37602
|
+
}
|
|
37603
|
+
const submitAttemptKey = `submit-review:${releaseId}`;
|
|
37604
|
+
this.assertNotFenced(submitAttemptKey);
|
|
37605
|
+
try {
|
|
37606
|
+
const submitReceipt = await this.persistence.submit(rootTarget, releaseId, reviewEtag);
|
|
37607
|
+
this.assertActiveContext(activeGen, activeCtx);
|
|
37608
|
+
if (submitReceipt.etag === reviewEtag) {
|
|
37609
|
+
throw this.fail('ETAG_NOT_PROGRESSED', 'Review ETag did not progress after submitting release for review.');
|
|
37610
|
+
}
|
|
37611
|
+
this.publishSnapshot({
|
|
37612
|
+
...this.snapshot,
|
|
37613
|
+
reviewReceipt: submitReceipt.body,
|
|
37614
|
+
reviewEtag: submitReceipt.etag,
|
|
37615
|
+
lastError: null,
|
|
37616
|
+
});
|
|
37617
|
+
return this.snapshot;
|
|
37618
|
+
}
|
|
37619
|
+
catch (error) {
|
|
37620
|
+
throw this.handleOperationError(error, activeGen, activeCtx, {
|
|
37621
|
+
operation: 'submit-review',
|
|
37622
|
+
attemptKey: submitAttemptKey,
|
|
37623
|
+
releaseId,
|
|
37624
|
+
});
|
|
37625
|
+
}
|
|
37626
|
+
}
|
|
37627
|
+
finally {
|
|
37628
|
+
if (!this.isStale(activeGen, activeCtx)) {
|
|
37629
|
+
this.setBusy(false);
|
|
37630
|
+
}
|
|
37631
|
+
}
|
|
37632
|
+
}
|
|
37633
|
+
async approveRelease(reason = 'Approve governed page release') {
|
|
37634
|
+
this.requireCapability('canApproveReleases', 'Approving a governed page release requires approve capability.');
|
|
37635
|
+
const activeGen = this.contextGeneration;
|
|
37636
|
+
const activeCtx = this.contextKey;
|
|
37637
|
+
const { rootTarget } = this.requireLoadedDraft();
|
|
37638
|
+
const releaseId = this.snapshot.reviewReceipt?.releaseRef ?? this.snapshot.draftWorkspace?.frozenReleaseRef;
|
|
37639
|
+
const reviewEtag = this.snapshot.reviewEtag;
|
|
37640
|
+
if (!releaseId || !reviewEtag) {
|
|
37641
|
+
throw this.fail('RELEASE_NOT_FROZEN', 'Cannot approve before a release is frozen and submitted.');
|
|
37642
|
+
}
|
|
37643
|
+
if (this.snapshot.reviewReceipt?.state === 'APPROVED') {
|
|
37644
|
+
return this.snapshot;
|
|
37645
|
+
}
|
|
37646
|
+
const attemptKey = `approve-release:${releaseId}`;
|
|
37647
|
+
this.assertNotFenced(attemptKey);
|
|
37648
|
+
this.setBusy(true);
|
|
37649
|
+
try {
|
|
37650
|
+
const receipt = await this.persistence.approve(rootTarget, releaseId, reviewEtag, {
|
|
37651
|
+
reason: reason.trim() || 'Approve governed page release',
|
|
37652
|
+
});
|
|
37653
|
+
this.assertActiveContext(activeGen, activeCtx);
|
|
37654
|
+
if (receipt.etag === reviewEtag) {
|
|
37655
|
+
throw this.fail('ETAG_NOT_PROGRESSED', 'Review ETag did not progress after approving release.');
|
|
37656
|
+
}
|
|
37657
|
+
this.publishSnapshot({
|
|
37658
|
+
...this.snapshot,
|
|
37659
|
+
reviewReceipt: receipt.body,
|
|
37660
|
+
reviewEtag: receipt.etag,
|
|
37661
|
+
lastError: null,
|
|
37662
|
+
});
|
|
37663
|
+
return this.snapshot;
|
|
37664
|
+
}
|
|
37665
|
+
catch (error) {
|
|
37666
|
+
throw this.handleOperationError(error, activeGen, activeCtx, {
|
|
37667
|
+
operation: 'approve-release',
|
|
37668
|
+
attemptKey,
|
|
37669
|
+
releaseId,
|
|
37670
|
+
});
|
|
37671
|
+
}
|
|
37672
|
+
finally {
|
|
37673
|
+
if (!this.isStale(activeGen, activeCtx)) {
|
|
37674
|
+
this.setBusy(false);
|
|
37675
|
+
}
|
|
37676
|
+
}
|
|
37677
|
+
}
|
|
37678
|
+
async publishRelease(reason = 'Publish governed page release') {
|
|
37679
|
+
this.requireCapability('canPublishReleases', 'Publishing a governed page release requires publish capability.');
|
|
37680
|
+
const activeGen = this.contextGeneration;
|
|
37681
|
+
const activeCtx = this.contextKey;
|
|
37682
|
+
const { rootTarget, pageId } = this.requireLoadedDraft();
|
|
37683
|
+
const releaseId = this.snapshot.reviewReceipt?.releaseRef ?? this.snapshot.draftWorkspace?.frozenReleaseRef;
|
|
37684
|
+
if (!releaseId) {
|
|
37685
|
+
throw this.fail('RELEASE_NOT_FROZEN', 'Cannot publish before a release is frozen and approved.');
|
|
37686
|
+
}
|
|
37687
|
+
const attemptKey = `publish-release:${releaseId}`;
|
|
37688
|
+
this.assertNotFenced(attemptKey);
|
|
37689
|
+
this.setBusy(true);
|
|
37690
|
+
try {
|
|
37691
|
+
let currentHead = this.snapshot.headReceipt;
|
|
37692
|
+
let currentHeadEtag = this.snapshot.headEtag;
|
|
37693
|
+
try {
|
|
37694
|
+
const headRead = await this.persistence.readHead(rootTarget);
|
|
37695
|
+
this.assertActiveContext(activeGen, activeCtx);
|
|
37696
|
+
if ('body' in headRead) {
|
|
37697
|
+
currentHead = headRead.body;
|
|
37698
|
+
currentHeadEtag = headRead.etag;
|
|
37699
|
+
}
|
|
37700
|
+
}
|
|
37701
|
+
catch (headError) {
|
|
37702
|
+
this.assertActiveContext(activeGen, activeCtx);
|
|
37703
|
+
if (headError instanceof UiLayoutLifecycleHttpError && headError.status === 404) {
|
|
37704
|
+
currentHead = null;
|
|
37705
|
+
currentHeadEtag = null;
|
|
37706
|
+
}
|
|
37707
|
+
else {
|
|
37708
|
+
throw headError;
|
|
37709
|
+
}
|
|
37710
|
+
}
|
|
37711
|
+
if (currentHead?.activeReleaseRef === releaseId) {
|
|
37712
|
+
this.publishSnapshot({
|
|
37713
|
+
...this.snapshot,
|
|
37714
|
+
headReceipt: currentHead,
|
|
37715
|
+
headEtag: currentHeadEtag,
|
|
37716
|
+
lastError: null,
|
|
37717
|
+
});
|
|
37718
|
+
return this.snapshot;
|
|
37719
|
+
}
|
|
37720
|
+
const condition = currentHeadEtag
|
|
37721
|
+
? { kind: 'match', etag: currentHeadEtag }
|
|
37722
|
+
: { kind: 'first-publish' };
|
|
37723
|
+
const publishReceipt = await this.persistence.publish(rootTarget, condition, {
|
|
37724
|
+
releaseId,
|
|
37725
|
+
reason: reason.trim() || 'Publish governed page release',
|
|
37726
|
+
});
|
|
37727
|
+
this.assertActiveContext(activeGen, activeCtx);
|
|
37728
|
+
let publishedSnapshot = this.snapshot.publishedSnapshot;
|
|
37729
|
+
let publishedEtag = this.snapshot.publishedEtag;
|
|
37730
|
+
try {
|
|
37731
|
+
const publishedRead = await this.persistence.readPublishedPage(pageId);
|
|
37732
|
+
this.assertActiveContext(activeGen, activeCtx);
|
|
37733
|
+
if ('body' in publishedRead) {
|
|
37734
|
+
publishedSnapshot = publishedRead.body;
|
|
37735
|
+
publishedEtag = publishedRead.etag;
|
|
37736
|
+
}
|
|
37737
|
+
}
|
|
37738
|
+
catch {
|
|
37739
|
+
// Published snapshot refresh is best-effort after head advancement.
|
|
37740
|
+
}
|
|
37741
|
+
this.publishSnapshot({
|
|
37742
|
+
...this.snapshot,
|
|
37743
|
+
headReceipt: publishReceipt.body,
|
|
37744
|
+
headEtag: publishReceipt.etag,
|
|
37745
|
+
publishedSnapshot,
|
|
37746
|
+
publishedEtag,
|
|
37747
|
+
lastError: null,
|
|
37748
|
+
});
|
|
37749
|
+
return this.snapshot;
|
|
37750
|
+
}
|
|
37751
|
+
catch (error) {
|
|
37752
|
+
throw this.handleOperationError(error, activeGen, activeCtx, {
|
|
37753
|
+
operation: 'publish-release',
|
|
37754
|
+
attemptKey,
|
|
37755
|
+
releaseId,
|
|
37756
|
+
});
|
|
37757
|
+
}
|
|
37758
|
+
finally {
|
|
37759
|
+
if (!this.isStale(activeGen, activeCtx)) {
|
|
37760
|
+
this.setBusy(false);
|
|
37761
|
+
}
|
|
37762
|
+
}
|
|
37763
|
+
}
|
|
37764
|
+
async approveAndPublish(reason = 'Approve and publish governed page release') {
|
|
37765
|
+
this.requireCapability('canApproveReleases', 'Approve capability is required.');
|
|
37766
|
+
this.requireCapability('canPublishReleases', 'Publish capability is required.');
|
|
37767
|
+
await this.approveRelease(reason);
|
|
37768
|
+
return this.publishRelease(reason);
|
|
37769
|
+
}
|
|
37770
|
+
/**
|
|
37771
|
+
* Reconciles a fenced operation after an ambiguous network failure (`COMMAND_OUTCOME_UNCERTAIN`)
|
|
37772
|
+
* using the canonical read endpoints before unlocking retries.
|
|
37773
|
+
*/
|
|
37774
|
+
async reconcileUncertainOperation(attemptKey) {
|
|
37775
|
+
const fenced = this.fencedOperations.get(attemptKey);
|
|
37776
|
+
if (!fenced) {
|
|
37777
|
+
return this.snapshot;
|
|
37778
|
+
}
|
|
37779
|
+
const activeGen = this.contextGeneration;
|
|
37780
|
+
const activeCtx = this.contextKey;
|
|
37781
|
+
this.setBusy(true);
|
|
37782
|
+
try {
|
|
37783
|
+
if (fenced.operation === 'create-page' && fenced.creationKey) {
|
|
37784
|
+
const receipt = await this.persistence.readPageByCreationKey(fenced.creationKey);
|
|
37785
|
+
this.assertActiveContext(activeGen, activeCtx);
|
|
37786
|
+
const draftRead = await this.persistence.readDraft(receipt.body.target, receipt.body.draftId);
|
|
37787
|
+
this.assertActiveContext(activeGen, activeCtx);
|
|
37788
|
+
if ('status' in draftRead) {
|
|
37789
|
+
throw this.fail('TRANSPORT_ERROR', 'Unexpected 304 when reading created page draft workspace.');
|
|
37790
|
+
}
|
|
37791
|
+
this.fencedOperations.delete(attemptKey);
|
|
37792
|
+
this.applyDraftWorkspaceReceipt(receipt.body.pageId, receipt.body.target, draftRead.body, draftRead.etag, receipt.body.source);
|
|
37793
|
+
await this.refreshPublishedAndLifecycleMetadata(receipt.body.pageId, receipt.body.target, draftRead.body.frozenReleaseRef, activeGen, activeCtx);
|
|
37794
|
+
return this.snapshot;
|
|
37795
|
+
}
|
|
37796
|
+
if (fenced.operation === 'import-legacy-page' && fenced.creationKey) {
|
|
37797
|
+
const legacyPageId = fenced.attemptKey.replace(/^import-legacy-page:/, '');
|
|
37798
|
+
const legacyTarget = buildGovernedPageTarget(legacyPageId);
|
|
37799
|
+
const draftLookup = await this.persistence.readDraftByCreationKey(legacyTarget, fenced.creationKey);
|
|
37800
|
+
this.assertActiveContext(activeGen, activeCtx);
|
|
37801
|
+
const draftRead = await this.persistence.readDraft(legacyTarget, draftLookup.body.draftRef);
|
|
37802
|
+
this.assertActiveContext(activeGen, activeCtx);
|
|
37803
|
+
if ('status' in draftRead) {
|
|
37804
|
+
throw this.fail('TRANSPORT_ERROR', 'Unexpected 304 when reading imported legacy draft workspace.');
|
|
37805
|
+
}
|
|
37806
|
+
if (fenced.childSources) {
|
|
37807
|
+
this.childSourcesByWidgetId.clear();
|
|
37808
|
+
for (const childSource of fenced.childSources) {
|
|
37809
|
+
this.childSourcesByWidgetId.set(childSource.widgetKey.toLowerCase(), childSource);
|
|
37810
|
+
}
|
|
37811
|
+
}
|
|
37812
|
+
this.fencedOperations.delete(attemptKey);
|
|
37813
|
+
this.applyDraftWorkspaceReceipt(legacyPageId, legacyTarget, draftRead.body, draftRead.etag, fenced.source ?? this.snapshot.source);
|
|
37814
|
+
await this.refreshPublishedAndLifecycleMetadata(legacyPageId, legacyTarget, draftRead.body.frozenReleaseRef, activeGen, activeCtx);
|
|
37815
|
+
return this.snapshot;
|
|
37816
|
+
}
|
|
37817
|
+
const { rootTarget, pageId } = this.requireLoadedDraft();
|
|
37818
|
+
if ((fenced.operation === 'save-revision' ||
|
|
37819
|
+
fenced.operation === 'assign-scope' ||
|
|
37820
|
+
fenced.operation === 'freeze-release') &&
|
|
37821
|
+
fenced.draftId) {
|
|
37822
|
+
const draftRead = await this.persistence.readDraft(rootTarget, fenced.draftId);
|
|
37823
|
+
this.assertActiveContext(activeGen, activeCtx);
|
|
37824
|
+
if ('body' in draftRead) {
|
|
37825
|
+
this.fencedOperations.delete(attemptKey);
|
|
37826
|
+
this.applyDraftWorkspaceReceipt(pageId, rootTarget, draftRead.body, draftRead.etag, this.snapshot.source);
|
|
37827
|
+
if (draftRead.body.frozenReleaseRef) {
|
|
37828
|
+
await this.refreshPublishedAndLifecycleMetadata(pageId, rootTarget, draftRead.body.frozenReleaseRef, activeGen, activeCtx);
|
|
37829
|
+
}
|
|
37830
|
+
}
|
|
37831
|
+
return this.snapshot;
|
|
37832
|
+
}
|
|
37833
|
+
if ((fenced.operation === 'submit-review' || fenced.operation === 'approve-release') &&
|
|
37834
|
+
fenced.releaseId) {
|
|
37835
|
+
const reviewRead = await this.persistence.readReview(rootTarget, fenced.releaseId);
|
|
37836
|
+
this.assertActiveContext(activeGen, activeCtx);
|
|
37837
|
+
if ('body' in reviewRead) {
|
|
37838
|
+
this.fencedOperations.delete(attemptKey);
|
|
37839
|
+
this.publishSnapshot({
|
|
37840
|
+
...this.snapshot,
|
|
37841
|
+
reviewReceipt: reviewRead.body,
|
|
37842
|
+
reviewEtag: reviewRead.etag,
|
|
37843
|
+
fencedOperations: Object.fromEntries(this.fencedOperations),
|
|
37844
|
+
lastError: null,
|
|
37845
|
+
});
|
|
37846
|
+
}
|
|
37847
|
+
return this.snapshot;
|
|
37848
|
+
}
|
|
37849
|
+
if (fenced.operation === 'publish-release') {
|
|
37850
|
+
const headRead = await this.persistence.readHead(rootTarget);
|
|
37851
|
+
this.assertActiveContext(activeGen, activeCtx);
|
|
37852
|
+
if ('body' in headRead) {
|
|
37853
|
+
this.fencedOperations.delete(attemptKey);
|
|
37854
|
+
this.publishSnapshot({
|
|
37855
|
+
...this.snapshot,
|
|
37856
|
+
headReceipt: headRead.body,
|
|
37857
|
+
headEtag: headRead.etag,
|
|
37858
|
+
fencedOperations: Object.fromEntries(this.fencedOperations),
|
|
37859
|
+
lastError: null,
|
|
37860
|
+
});
|
|
37861
|
+
}
|
|
37862
|
+
return this.snapshot;
|
|
37863
|
+
}
|
|
37864
|
+
return this.snapshot;
|
|
37865
|
+
}
|
|
37866
|
+
catch (error) {
|
|
37867
|
+
throw this.handleOperationError(error, activeGen, activeCtx);
|
|
37868
|
+
}
|
|
37869
|
+
finally {
|
|
37870
|
+
if (!this.isStale(activeGen, activeCtx)) {
|
|
37871
|
+
this.setBusy(false);
|
|
37872
|
+
}
|
|
37873
|
+
}
|
|
37874
|
+
}
|
|
37875
|
+
/**
|
|
37876
|
+
* Reloads server state after a 409 Conflict or 412 Precondition Failed response.
|
|
37877
|
+
*/
|
|
37878
|
+
async reloadAfterConflict() {
|
|
37879
|
+
const { pageId, draftWorkspace } = this.requireLoadedDraft();
|
|
37880
|
+
return this.openDraftWorkspace(pageId, draftWorkspace.draft.draftRef, {
|
|
37881
|
+
source: this.snapshot.source,
|
|
37882
|
+
});
|
|
37883
|
+
}
|
|
37884
|
+
async executePageCreation(command, activeGen = this.contextGeneration, activeCtx = this.contextKey) {
|
|
37885
|
+
const attemptKey = `create-page:${command.creationKey}`;
|
|
37886
|
+
this.assertNotFenced(attemptKey);
|
|
37887
|
+
this.setBusy(true);
|
|
37888
|
+
try {
|
|
37889
|
+
const receipt = await this.persistence.createPage(command);
|
|
37890
|
+
this.assertActiveContext(activeGen, activeCtx);
|
|
37891
|
+
const draftRead = await this.persistence.readDraft(receipt.body.target, receipt.body.draftId);
|
|
37892
|
+
this.assertActiveContext(activeGen, activeCtx);
|
|
37893
|
+
if ('status' in draftRead) {
|
|
37894
|
+
throw this.fail('TRANSPORT_ERROR', 'Unexpected 304 when reading created page draft workspace.');
|
|
37895
|
+
}
|
|
37896
|
+
this.childSourcesByWidgetId.clear();
|
|
37897
|
+
this.applyDraftWorkspaceReceipt(receipt.body.pageId, receipt.body.target, draftRead.body, draftRead.etag, receipt.body.source);
|
|
37898
|
+
await this.refreshPublishedAndLifecycleMetadata(receipt.body.pageId, receipt.body.target, draftRead.body.frozenReleaseRef, activeGen, activeCtx);
|
|
37899
|
+
return receipt.body;
|
|
37900
|
+
}
|
|
37901
|
+
catch (error) {
|
|
37902
|
+
throw this.handleOperationError(error, activeGen, activeCtx, {
|
|
37903
|
+
operation: 'create-page',
|
|
37904
|
+
attemptKey,
|
|
37905
|
+
creationKey: command.creationKey,
|
|
37906
|
+
});
|
|
37907
|
+
}
|
|
37908
|
+
finally {
|
|
37909
|
+
if (!this.isStale(activeGen, activeCtx)) {
|
|
37910
|
+
this.setBusy(false);
|
|
37911
|
+
}
|
|
37912
|
+
}
|
|
37913
|
+
}
|
|
37914
|
+
applyDraftWorkspaceReceipt(pageId, rootTarget, draftWorkspace, draftEtag, source) {
|
|
37915
|
+
const primaryTarget = draftWorkspace.targets.find(target => target.target.componentType === rootTarget.componentType &&
|
|
37916
|
+
target.target.componentId === rootTarget.componentId) ?? draftWorkspace.targets[0];
|
|
37917
|
+
const workingDecoded = primaryTarget
|
|
37918
|
+
? decodePageAuthoringDocument(primaryTarget.working.document)
|
|
37919
|
+
: null;
|
|
37920
|
+
const baselineDecoded = primaryTarget
|
|
37921
|
+
? decodePageAuthoringDocument(primaryTarget.baseline.document)
|
|
37922
|
+
: null;
|
|
37923
|
+
const workingDocument = workingDecoded?.ok ? workingDecoded.value : this.snapshot.workingDocument;
|
|
37924
|
+
const baselineDocument = baselineDecoded?.ok
|
|
37925
|
+
? baselineDecoded.value
|
|
37926
|
+
: this.snapshot.baselineDocument;
|
|
37927
|
+
const hasFrozenRelease = Boolean(draftWorkspace.frozenReleaseRef);
|
|
37928
|
+
const keepReviewReceipt = hasFrozenRelease &&
|
|
37929
|
+
this.snapshot.reviewReceipt?.releaseRef === draftWorkspace.frozenReleaseRef;
|
|
37930
|
+
this.publishSnapshot({
|
|
37931
|
+
...this.snapshot,
|
|
37932
|
+
pageId,
|
|
37933
|
+
rootTarget,
|
|
37934
|
+
source,
|
|
37935
|
+
draftWorkspace,
|
|
37936
|
+
draftEtag,
|
|
37937
|
+
workingDocument,
|
|
37938
|
+
baselineDocument,
|
|
37939
|
+
reviewReceipt: keepReviewReceipt ? this.snapshot.reviewReceipt : null,
|
|
37940
|
+
reviewEtag: keepReviewReceipt ? this.snapshot.reviewEtag : null,
|
|
37941
|
+
childSources: Array.from(this.childSourcesByWidgetId.values()),
|
|
37942
|
+
dirty: false,
|
|
37943
|
+
fencedOperations: Object.fromEntries(this.fencedOperations),
|
|
37944
|
+
lastError: null,
|
|
37945
|
+
});
|
|
37946
|
+
}
|
|
37947
|
+
async refreshPublishedAndLifecycleMetadata(pageId, rootTarget, frozenReleaseRef, activeGen, activeCtx) {
|
|
37948
|
+
let publishedSnapshot = this.snapshot.publishedSnapshot;
|
|
37949
|
+
let publishedEtag = this.snapshot.publishedEtag;
|
|
37950
|
+
try {
|
|
37951
|
+
const publishedRead = await this.persistence.readPublishedPage(pageId);
|
|
37952
|
+
if (this.isStale(activeGen, activeCtx)) {
|
|
37953
|
+
return;
|
|
37954
|
+
}
|
|
37955
|
+
if ('body' in publishedRead) {
|
|
37956
|
+
publishedSnapshot = publishedRead.body;
|
|
37957
|
+
publishedEtag = publishedRead.etag;
|
|
37958
|
+
}
|
|
37959
|
+
}
|
|
37960
|
+
catch {
|
|
37961
|
+
// Newly created pages may not have a published release yet (404).
|
|
37962
|
+
}
|
|
37963
|
+
let headReceipt = this.snapshot.headReceipt;
|
|
37964
|
+
let headEtag = this.snapshot.headEtag;
|
|
37965
|
+
try {
|
|
37966
|
+
const headRead = await this.persistence.readHead(rootTarget);
|
|
37967
|
+
if (this.isStale(activeGen, activeCtx)) {
|
|
37968
|
+
return;
|
|
37969
|
+
}
|
|
37970
|
+
if ('body' in headRead) {
|
|
37971
|
+
headReceipt = headRead.body;
|
|
37972
|
+
headEtag = headRead.etag;
|
|
37973
|
+
}
|
|
37974
|
+
}
|
|
37975
|
+
catch {
|
|
37976
|
+
// Unreleased pages may not have a head receipt yet.
|
|
37977
|
+
}
|
|
37978
|
+
let reviewReceipt = this.snapshot.reviewReceipt;
|
|
37979
|
+
let reviewEtag = this.snapshot.reviewEtag;
|
|
37980
|
+
if (frozenReleaseRef) {
|
|
37981
|
+
try {
|
|
37982
|
+
const reviewRead = await this.persistence.readReview(rootTarget, frozenReleaseRef);
|
|
37983
|
+
if (this.isStale(activeGen, activeCtx)) {
|
|
37984
|
+
return;
|
|
37985
|
+
}
|
|
37986
|
+
if ('body' in reviewRead) {
|
|
37987
|
+
reviewReceipt = reviewRead.body;
|
|
37988
|
+
reviewEtag = reviewRead.etag;
|
|
37989
|
+
}
|
|
37990
|
+
}
|
|
37991
|
+
catch {
|
|
37992
|
+
// Review receipt may be unavailable if not yet created.
|
|
37993
|
+
}
|
|
37994
|
+
}
|
|
37995
|
+
this.publishSnapshot({
|
|
37996
|
+
...this.snapshot,
|
|
37997
|
+
publishedSnapshot,
|
|
37998
|
+
publishedEtag,
|
|
37999
|
+
headReceipt,
|
|
38000
|
+
headEtag,
|
|
38001
|
+
reviewReceipt,
|
|
38002
|
+
reviewEtag,
|
|
38003
|
+
});
|
|
38004
|
+
}
|
|
38005
|
+
requireCapability(key, message) {
|
|
38006
|
+
if (!this.snapshot.capabilities[key]) {
|
|
38007
|
+
throw this.fail('CAPABILITY_DENIED', message);
|
|
38008
|
+
}
|
|
38009
|
+
}
|
|
38010
|
+
requireWorkingDocument() {
|
|
38011
|
+
const doc = this.snapshot.workingDocument;
|
|
38012
|
+
if (!doc) {
|
|
38013
|
+
throw this.fail('DRAFT_NOT_LOADED', 'No governed page authoring document is currently loaded.');
|
|
38014
|
+
}
|
|
38015
|
+
return doc;
|
|
38016
|
+
}
|
|
38017
|
+
requireLoadedDraft() {
|
|
38018
|
+
const { pageId, rootTarget, draftWorkspace, draftEtag } = this.snapshot;
|
|
38019
|
+
if (!pageId || !rootTarget || !draftWorkspace || !draftEtag) {
|
|
38020
|
+
throw this.fail('DRAFT_NOT_LOADED', 'No governed page draft workspace is currently loaded.');
|
|
38021
|
+
}
|
|
38022
|
+
return { pageId, rootTarget, draftWorkspace, draftEtag };
|
|
38023
|
+
}
|
|
38024
|
+
assertNotFenced(attemptKey) {
|
|
38025
|
+
if (this.fencedOperations.has(attemptKey)) {
|
|
38026
|
+
throw this.fail('OPERATION_FENCED', `Operation "${attemptKey}" is fenced due to an uncertain command outcome. Reconcile before retrying.`);
|
|
38027
|
+
}
|
|
38028
|
+
}
|
|
38029
|
+
isStale(generation, contextKey) {
|
|
38030
|
+
return generation !== this.contextGeneration || contextKey !== this.contextKey;
|
|
38031
|
+
}
|
|
38032
|
+
assertActiveContext(generation, contextKey) {
|
|
38033
|
+
if (this.isStale(generation, contextKey)) {
|
|
38034
|
+
throw new UiLayoutLifecycleContractError('CONTEXT_OBSOLETE');
|
|
38035
|
+
}
|
|
38036
|
+
}
|
|
38037
|
+
handleOperationError(error, generation, contextKey, fenceContext) {
|
|
38038
|
+
if (this.isStale(generation, contextKey)) {
|
|
38039
|
+
return {
|
|
38040
|
+
code: 'CONTEXT_OBSOLETE',
|
|
38041
|
+
message: 'Operation response discarded because the runtime context changed.',
|
|
38042
|
+
};
|
|
38043
|
+
}
|
|
38044
|
+
if (error instanceof UiLayoutLifecycleContractError ||
|
|
38045
|
+
this.isLifecycleContractErrorLike(error)) {
|
|
38046
|
+
if (error.code === 'COMMAND_OUTCOME_UNCERTAIN' && fenceContext) {
|
|
38047
|
+
this.fencedOperations.set(fenceContext.attemptKey, {
|
|
38048
|
+
...fenceContext,
|
|
38049
|
+
occurredAt: new Date().toISOString(),
|
|
38050
|
+
});
|
|
38051
|
+
}
|
|
38052
|
+
const mapped = {
|
|
38053
|
+
code: error.code === 'COMMAND_OUTCOME_UNCERTAIN'
|
|
38054
|
+
? 'COMMAND_OUTCOME_UNCERTAIN'
|
|
38055
|
+
: error.code === 'CONTEXT_OBSOLETE'
|
|
38056
|
+
? 'CONTEXT_OBSOLETE'
|
|
38057
|
+
: 'TRANSPORT_ERROR',
|
|
38058
|
+
message: error.message,
|
|
38059
|
+
};
|
|
38060
|
+
this.publishSnapshot({
|
|
38061
|
+
...this.snapshot,
|
|
38062
|
+
fencedOperations: Object.fromEntries(this.fencedOperations),
|
|
38063
|
+
lastError: mapped,
|
|
38064
|
+
});
|
|
38065
|
+
return mapped;
|
|
38066
|
+
}
|
|
38067
|
+
if (error instanceof UiLayoutLifecycleHttpError) {
|
|
38068
|
+
const code = error.status === 403
|
|
38069
|
+
? 'CAPABILITY_DENIED'
|
|
38070
|
+
: error.status === 409
|
|
38071
|
+
? 'CONFLICT'
|
|
38072
|
+
: error.status === 412
|
|
38073
|
+
? 'PRECONDITION_FAILED'
|
|
38074
|
+
: 'TRANSPORT_ERROR';
|
|
38075
|
+
const mapped = {
|
|
38076
|
+
code,
|
|
38077
|
+
message: error.message,
|
|
38078
|
+
status: error.status,
|
|
38079
|
+
};
|
|
38080
|
+
this.publishSnapshot({ ...this.snapshot, lastError: mapped });
|
|
38081
|
+
return mapped;
|
|
38082
|
+
}
|
|
38083
|
+
if (this.isWorkflowError(error)) {
|
|
38084
|
+
this.publishSnapshot({ ...this.snapshot, lastError: error });
|
|
38085
|
+
return error;
|
|
38086
|
+
}
|
|
38087
|
+
const fallback = {
|
|
38088
|
+
code: 'TRANSPORT_ERROR',
|
|
38089
|
+
message: error instanceof Error ? error.message : String(error),
|
|
38090
|
+
};
|
|
38091
|
+
this.publishSnapshot({ ...this.snapshot, lastError: fallback });
|
|
38092
|
+
return fallback;
|
|
38093
|
+
}
|
|
38094
|
+
fail(code, message, diagnostics) {
|
|
38095
|
+
const err = {
|
|
38096
|
+
code,
|
|
38097
|
+
message,
|
|
38098
|
+
...(diagnostics ? { diagnostics } : {}),
|
|
38099
|
+
};
|
|
38100
|
+
this.publishSnapshot({ ...this.snapshot, lastError: err });
|
|
38101
|
+
return err;
|
|
38102
|
+
}
|
|
38103
|
+
isLifecycleContractErrorLike(value) {
|
|
38104
|
+
return (value instanceof Error &&
|
|
38105
|
+
value.name === 'UiLayoutLifecycleContractError' &&
|
|
38106
|
+
'code' in value &&
|
|
38107
|
+
typeof value.code === 'string');
|
|
38108
|
+
}
|
|
38109
|
+
isWorkflowError(value) {
|
|
38110
|
+
return (value !== null &&
|
|
38111
|
+
typeof value === 'object' &&
|
|
38112
|
+
!(value instanceof Error) &&
|
|
38113
|
+
'code' in value &&
|
|
38114
|
+
'message' in value &&
|
|
38115
|
+
typeof value.code === 'string');
|
|
38116
|
+
}
|
|
38117
|
+
setBusy(busy) {
|
|
38118
|
+
this.publishSnapshot({ ...this.snapshot, busy });
|
|
38119
|
+
}
|
|
38120
|
+
publishSnapshot(next) {
|
|
38121
|
+
this.stateSubject.next(next);
|
|
38122
|
+
}
|
|
38123
|
+
}
|
|
38124
|
+
|
|
36308
38125
|
/*
|
|
36309
38126
|
* Public API Surface of @praxisui/page-builder
|
|
36310
38127
|
*/
|
|
@@ -36314,4 +38131,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
36314
38131
|
* Generated bundle index. Do not edit.
|
|
36315
38132
|
*/
|
|
36316
38133
|
|
|
36317
|
-
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, PraxisPageCopyActionComponent, PraxisPageSaveAsComponent, TileToolbarComponent, UiCompositionResourcePreflightService, WidgetShellEditorComponent, clearWidgetAiCatalogs, compileUiCompositionPlan, getPageAiCatalog, getWidgetAiCapabilities, isComponentDocMeta, preflightUiCompositionPlan, preflightUiCompositionPlanResources, providePageBuilderWidgetAiCatalogs, providePraxisPageBuilderMetadata, providePraxisPageBuilderWidgets, registerWidgetAiCatalog, registerWidgetAiCatalogs, resolveUiCompositionPlanTemplate, validateUiCompositionPlan, verifyUiCompositionAuthoringSource };
|
|
38134
|
+
export { ComponentPaletteDialogComponent, ConfirmDialogComponent, ConnectionEditorComponent, DEFAULT_CORPORATE_NAVIGATION_GROUPS_TARGET, DEFAULT_GOVERNED_PAGE_CHILD_VALIDATORS, DEFAULT_GOVERNED_PAGE_PREVIEW_ADAPTERS, DynamicPageBuilderComponent, DynamicPageConfigEditorComponent, FloatingToolbarComponent, GOVERNED_PAGE_CAPABILITY_KEYS, GovernedPageNavigationController, GovernedPageWorkflowController, LEGACY_PUBLISHED_PAGE_STORAGE_PREFIX, 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, PraxisPageCopyActionComponent, PraxisPageSaveAsComponent, TileToolbarComponent, UiCompositionResourcePreflightService, WidgetShellEditorComponent, clearWidgetAiCatalogs, compileUiCompositionPlan, createCorporateGovernedPagePersistenceAdapter, createLocalPageSaveAsAdapter, deriveGovernedPageCapabilities, getPageAiCatalog, getWidgetAiCapabilities, inspectLegacyPublishedPagePayload, isComponentDocMeta, preflightUiCompositionPlan, preflightUiCompositionPlanResources, providePageBuilderWidgetAiCatalogs, providePraxisPageBuilderMetadata, providePraxisPageBuilderWidgets, registerWidgetAiCatalog, registerWidgetAiCatalogs, resolveUiCompositionPlanTemplate, validateUiCompositionPlan, verifyUiCompositionAuthoringSource };
|