import { INI_PANEL_ENTRIES, getIniPanelEntryByHref, getIniPanelEntryManifest, } from "./entry-manifest.js"; import { createMachineSessionControlPageController, } from "./control-page-controller.js"; import { renderMachineSessionControlView, } from "./control-view-renderer.js"; import { controlPageStatusText, createControlPageRefreshState, refreshMachineSessionControlPage, } from "./control-page-refresh-workflow.js"; import { createControlPageSessionLoadState, loadMachineSessionForControlPageWorkflow, } from "./control-page-session-workflow.js"; import { createMachineSessionReadonlyStatus, createMachineSessionReadonlyStatusApiManifest, readMachineSessionReadonlyStatus, } from "./readonly-status-api.js"; export { controlPageStatusText, createControlPageRefreshState, refreshMachineSessionControlPage, } from "./control-page-refresh-workflow.js"; export { createControlPageSessionLoadState, loadMachineSessionForControlPageWorkflow, } from "./control-page-session-workflow.js"; export { createMachineSessionReadonlyStatus, createMachineSessionReadonlyStatusApiManifest, readMachineSessionReadonlyStatus, } from "./readonly-status-api.js"; export { INI_PANEL_ENTRIES, getIniPanelEntryByHref, getIniPanelEntryManifest, } from "./entry-manifest.js"; export { createMachineSessionControlPageController, } from "./control-page-controller.js"; export { renderMachineSessionControlView, } from "./control-view-renderer.js"; export function getVisibleIniPanelEntries() { return getIniPanelEntryManifest(); } export function createIniPanelLauncherLinkViewModel(entries = getVisibleIniPanelEntries()) { return entries.map(({ id, href, title }) => ({ entryId: id, href, label: title, })); } export function createIniPanelLaunchApiSchema() { return { apiName: "ini-panel-launch", manifestVersion: 1, fields: [ "apiName", "manifestVersion", "methods", "visibleEntries", "targetPages", "controlPageApiMethods", "persistenceCapabilities", ], }; } export function isSupportedIniPanelLaunchApiManifest(manifest, schema = createIniPanelLaunchApiSchema()) { if (!manifest || typeof manifest !== "object") { return false; } if (manifest.apiName !== schema.apiName || manifest.manifestVersion !== schema.manifestVersion) { return false; } return schema.fields.every((fieldName) => Object.hasOwn(manifest, fieldName)); } export function createControlPageBrowserApiSchema() { return { apiName: "ini-panel-control-page", manifestVersion: 1, fields: [ "apiName", "manifestVersion", "methods", "contract", "readonlyStatusApiManifest", ], }; } export function isSupportedIniPanelControlPageApiManifest( manifest, schema = createControlPageBrowserApiSchema(), ) { if (!manifest || typeof manifest !== "object") { return false; } if (manifest.apiName !== schema.apiName || manifest.manifestVersion !== schema.manifestVersion) { return false; } return schema.fields.every((fieldName) => Object.hasOwn(manifest, fieldName)); } export function createIniPanelLaunchApiManifest(entries = getVisibleIniPanelEntries()) { const schema = createIniPanelLaunchApiSchema(); return { apiName: schema.apiName, manifestVersion: schema.manifestVersion, methods: [ "isSupportedLaunchApiManifest", "getLaunchApiSchema", "getLaunchApiManifest", "getShellIntegrationSchema", "getShellIntegrationManifest", "getShellIntegrationReadiness", "getShellSessionHandoffSummarySchema", "getShellSessionHandoffSummary", "isSupportedShellSessionHandoffSummary", "getShellSessionHandoffCompatibilityReport", "getShellSessionHandoffDisplayViewModel", "getShellSessionHandoffSnapshot", "getShellSessionHandoffRenderState", "getShellSessionHandoffActionPlan", "getShellSessionHandoffChecklist", "getShellSessionHandoffPackageSchema", "getShellSessionHandoffPackage", "getShellSessionHandoffPackageReport", "getShellSessionHandoffPackageDisplayViewModel", "getShellSessionHandoffPackageRenderState", "getShellSessionHandoffPackageMountState", "getShellSessionHandoffPackageMountDisplayViewModel", "getShellSessionHandoffPackageMountRenderState", "getShellSessionHandoffMountDomContract", "getShellSessionHandoffMountDomReadiness", "renderShellSessionHandoffMountState", "mountShellSessionHandoff", "mountShellSessionHandoffFromControlPage", "readShellSessionHandoffStatusFromControlPage", "getShellSessionHandoffWorkflowSummary", "getShellSessionHandoffWorkflowDisplayViewModel", "getShellSessionHandoffWorkflowRenderState", "getShellSessionHandoffWorkflowDomContract", "getShellSessionHandoffWorkflowDomReadiness", "renderShellSessionHandoffWorkflowState", "mountShellSessionHandoffWorkflow", "getWorkflowOverviewContract", "getWorkflowOverviewReadiness", "mountWorkflowOverviewEmbedding", "getWorkflowOverviewEmbeddingReport", "getWorkflowOverviewEmbeddingDisplayViewModel", "getWorkflowOverviewEmbeddingRenderState", "getWorkflowOverviewEmbeddingDomContract", "getWorkflowOverviewEmbeddingDomReadiness", "renderWorkflowOverviewEmbeddingState", "mountWorkflowOverviewEmbeddingState", "getWorkflowOverviewEmbeddingMountDisplayViewModel", "getWorkflowOverviewEmbeddingMountRenderState", "getWorkflowOverviewEmbeddingMountDomContract", "getWorkflowOverviewEmbeddingMountDomReadiness", "renderWorkflowOverviewEmbeddingMountState", "mountWorkflowOverviewEmbeddingMountState", "isSupportedShellSessionHandoffPackage", "getLauncherLinks", "getControlPageApiMethods", ], visibleEntries: entries.map(({ id, href, title }) => ({ id, href, title, })), targetPages: entries.map(({ id, href }) => ({ entryId: id, href, })), controlPageApiMethods: createControlPageBrowserApiMethods(), persistenceCapabilities: [ { id: "machine-files", label: "Machine text files", scope: "opfs", }, { id: "session-snapshot", label: "Machine session snapshot", scope: "opfs", }, { id: "readonly-status", label: "Read-only machine session status", scope: "control-page", }, ], }; } export function createIniPanelShellControlPageContract() { const readonlyStatusApiManifest = createMachineSessionReadonlyStatusApiManifest(); return { methodNames: [ "createController", "loadSessionState", "readStatus", "refresh", "renderView", ], readonlyStatusApiManifest, readonlyStatusApiMethodNames: Object.keys(readonlyStatusApiManifest.methods), readonlyStatusApiStatusFields: readonlyStatusApiManifest.statusFields, relatedControlPageMethods: readonlyStatusApiManifest.relatedControlPageMethods, }; } export function createControlPageBrowserApiMethods() { return [ "isSupportedControlPageApiManifest", "getControlPageApiSchema", "getControlPageApiManifest", "getControlPageApiMethods", "getControlPageContract", "getControlView", "getPanelApi", "loadControlPageSessionState", "readControlPageStatus", "refreshControlPage", "renderControlPage", ]; } export function createControlPageBrowserApiManifest() { const schema = createControlPageBrowserApiSchema(); const contract = createIniPanelShellControlPageContract(); return { apiName: schema.apiName, manifestVersion: schema.manifestVersion, methods: createControlPageBrowserApiMethods(), contract, readonlyStatusApiManifest: contract.readonlyStatusApiManifest, }; } export function createIniPanelShellIntegrationSchema() { return { apiName: "ini-panel-shell-integration", manifestVersion: 1, fields: [ "apiName", "manifestVersion", "pages", "launcherLinks", "launchApiSchema", "launchApiManifest", "controlPageApiSchema", "controlPageApiManifest", "compatibility", "persistenceCapabilities", ], }; } export function isSupportedIniPanelShellIntegrationManifest( manifest, schema = createIniPanelShellIntegrationSchema(), ) { if (!manifest || typeof manifest !== "object") { return false; } if (manifest.apiName !== schema.apiName || manifest.manifestVersion !== schema.manifestVersion) { return false; } return schema.fields.every((fieldName) => Object.hasOwn(manifest, fieldName)); } export function createIniPanelShellIntegrationManifest(entries = getVisibleIniPanelEntries()) { const schema = createIniPanelShellIntegrationSchema(); const launchApiSchema = createIniPanelLaunchApiSchema(); const launchApiManifest = createIniPanelLaunchApiManifest(entries); const controlPageApiSchema = createControlPageBrowserApiSchema(); const controlPageApiManifest = createControlPageBrowserApiManifest(); return { apiName: schema.apiName, manifestVersion: schema.manifestVersion, pages: entries.map(({ id, href, title }) => ({ id, href, title, })), launcherLinks: createIniPanelLauncherLinkViewModel(entries), launchApiSchema, launchApiManifest, controlPageApiSchema, controlPageApiManifest, compatibility: { launchApiManifest: isSupportedIniPanelLaunchApiManifest(launchApiManifest, launchApiSchema), controlPageApiManifest: isSupportedIniPanelControlPageApiManifest( controlPageApiManifest, controlPageApiSchema, ), }, persistenceCapabilities: launchApiManifest.persistenceCapabilities, }; } export function createIniPanelShellIntegrationReadiness( manifest = createIniPanelShellIntegrationManifest(), ) { const integrationManifestSupported = isSupportedIniPanelShellIntegrationManifest(manifest); const launchApiManifestSupported = integrationManifestSupported && isSupportedIniPanelLaunchApiManifest(manifest.launchApiManifest, manifest.launchApiSchema); const controlPageApiManifestSupported = integrationManifestSupported && isSupportedIniPanelControlPageApiManifest( manifest.controlPageApiManifest, manifest.controlPageApiSchema, ); const compatibility = integrationManifestSupported && manifest.compatibility ? { launchApiManifest: Boolean(manifest.compatibility.launchApiManifest), controlPageApiManifest: Boolean(manifest.compatibility.controlPageApiManifest), } : { launchApiManifest: false, controlPageApiManifest: false, }; const checks = { integrationManifestSupported, launchApiManifestSupported, controlPageApiManifestSupported, launchCompatibility: compatibility.launchApiManifest, controlPageCompatibility: compatibility.controlPageApiManifest, }; const missing = Object.entries(checks) .filter(([, passed]) => !passed) .map(([name]) => name); const ready = missing.length === 0; return { phase: ready ? "ready" : "blocked", ready, checks, missing, counts: { pages: Array.isArray(manifest?.pages) ? manifest.pages.length : 0, launcherLinks: Array.isArray(manifest?.launcherLinks) ? manifest.launcherLinks.length : 0, launchMethods: Array.isArray(manifest?.launchApiManifest?.methods) ? manifest.launchApiManifest.methods.length : 0, controlPageMethods: Array.isArray(manifest?.controlPageApiManifest?.methods) ? manifest.controlPageApiManifest.methods.length : 0, persistenceCapabilities: Array.isArray(manifest?.persistenceCapabilities) ? manifest.persistenceCapabilities.length : 0, }, apiNames: { integration: manifest?.apiName ?? null, launch: manifest?.launchApiManifest?.apiName ?? null, controlPage: manifest?.controlPageApiManifest?.apiName ?? null, }, }; } export function createIniPanelShellIntegrationWorkflowPlan({ manifest = createIniPanelShellIntegrationManifest(), readiness = createIniPanelShellIntegrationReadiness(manifest), controlEntryId = "control-view", } = {}) { const targetPage = manifest?.launchApiManifest?.targetPages?.find( (page) => page.entryId === controlEntryId, ) ?? null; const requiredLaunchApiMethods = [ "getShellIntegrationManifest", "getShellIntegrationReadiness", ]; const requiredControlPageApiMethods = [ "getControlPageApiManifest", "loadControlPageSessionState", "readControlPageStatus", ]; const launchMethods = Array.isArray(manifest?.launchApiManifest?.methods) ? manifest.launchApiManifest.methods : []; const controlPageMethods = Array.isArray(manifest?.controlPageApiManifest?.methods) ? manifest.controlPageApiManifest.methods : []; const missingLaunchApiMethods = requiredLaunchApiMethods.filter( (methodName) => !launchMethods.includes(methodName), ); const missingControlPageApiMethods = requiredControlPageApiMethods.filter( (methodName) => !controlPageMethods.includes(methodName), ); const missing = [ ...readiness.missing, ...(targetPage ? [] : ["controlTargetPage"]), ...missingLaunchApiMethods.map((methodName) => `launchApi.${methodName}`), ...missingControlPageApiMethods.map((methodName) => `controlPageApi.${methodName}`), ]; const ready = readiness.ready && missing.length === 0; return { phase: ready ? "ready" : "blocked", ready, controlEntryId, targetPage, readiness, requiredLaunchApiMethods, requiredControlPageApiMethods, missingLaunchApiMethods, missingControlPageApiMethods, missing, readonlyStatusHandoff: { loadSessionStateMethod: "loadControlPageSessionState", readStatusMethod: "readControlPageStatus", expectedStatusFields: manifest?.controlPageApiManifest?.readonlyStatusApiManifest?.statusFields ?? [], }, }; } export function createIniPanelShellSessionHandoffSummarySchema() { return { apiName: "ini-panel-shell-session-handoff-summary", summaryVersion: 1, fields: [ "apiName", "summaryVersion", "phase", "ready", "targetPage", "readiness", "persistenceCapabilities", "readonlyStatusFields", "handoffMethods", "blockingReasons", ], }; } export function isSupportedIniPanelShellSessionHandoffSummary( summary, schema = createIniPanelShellSessionHandoffSummarySchema(), ) { if (!summary || typeof summary !== "object") { return false; } if (summary.apiName !== schema.apiName || summary.summaryVersion !== schema.summaryVersion) { return false; } return schema.fields.every((fieldName) => Object.hasOwn(summary, fieldName)); } export function createIniPanelShellSessionHandoffSummary({ manifest = createIniPanelShellIntegrationManifest(), readiness = createIniPanelShellIntegrationReadiness(manifest), workflowPlan = createIniPanelShellIntegrationWorkflowPlan({ manifest, readiness }), } = {}) { const schema = createIniPanelShellSessionHandoffSummarySchema(); const persistenceCapabilities = Array.isArray(manifest?.persistenceCapabilities) ? manifest.persistenceCapabilities.map(({ id, label, scope }) => ({ id, label, scope })) : []; const readonlyStatusFields = Array.isArray(workflowPlan?.readonlyStatusHandoff?.expectedStatusFields) ? [...workflowPlan.readonlyStatusHandoff.expectedStatusFields] : []; return { apiName: schema.apiName, summaryVersion: schema.summaryVersion, phase: workflowPlan.ready ? "ready" : "blocked", ready: Boolean(workflowPlan.ready), targetPage: workflowPlan.targetPage, readiness: { phase: readiness.phase, missing: [...readiness.missing], counts: { ...readiness.counts }, apiNames: { ...readiness.apiNames }, }, persistenceCapabilities, readonlyStatusFields, handoffMethods: { loadSessionState: workflowPlan.readonlyStatusHandoff.loadSessionStateMethod, readStatus: workflowPlan.readonlyStatusHandoff.readStatusMethod, }, blockingReasons: [...workflowPlan.missing], }; } export function createIniPanelShellSessionHandoffCompatibilityReport( summary = createIniPanelShellSessionHandoffSummary(), schema = createIniPanelShellSessionHandoffSummarySchema(), ) { const summaryObject = summary && typeof summary === "object" ? summary : {}; const missingFields = schema.fields.filter((fieldName) => !Object.hasOwn(summaryObject, fieldName)); const mismatchFields = [ ...(summaryObject.apiName === schema.apiName ? [] : ["apiName"]), ...(summaryObject.summaryVersion === schema.summaryVersion ? [] : ["summaryVersion"]), ]; const summarySupported = missingFields.length === 0 && mismatchFields.length === 0; const blockingReasons = Array.isArray(summaryObject.blockingReasons) ? [...summaryObject.blockingReasons] : []; const handoffReady = summarySupported && summaryObject.ready === true && blockingReasons.length === 0; return { apiName: "ini-panel-shell-session-handoff-compatibility-report", reportVersion: 1, phase: handoffReady ? "ready" : "blocked", compatible: handoffReady, summarySupported, handoffReady, schema: { apiName: schema.apiName, summaryVersion: schema.summaryVersion, fields: [...schema.fields], }, summary: { apiName: summaryObject.apiName ?? null, summaryVersion: summaryObject.summaryVersion ?? null, phase: summaryObject.phase ?? null, ready: summaryObject.ready ?? false, }, missingFields, mismatchFields, blockingReasons, }; } export function createIniPanelShellSessionHandoffDisplayViewModel( report = createIniPanelShellSessionHandoffCompatibilityReport(), ) { const missingFields = Array.isArray(report?.missingFields) ? report.missingFields : []; const mismatchFields = Array.isArray(report?.mismatchFields) ? report.mismatchFields : []; const blockingReasons = Array.isArray(report?.blockingReasons) ? report.blockingReasons : []; const problems = [...missingFields, ...mismatchFields, ...blockingReasons]; return { phase: report?.phase === "ready" ? "ready" : "blocked", title: "Shell handoff compatibility", statusText: report?.compatible ? "Ready" : "Blocked", detailText: problems.length === 0 ? "No blocking reasons" : problems.join(", "), rows: [ { id: "summary-supported", label: "Summary schema", value: report?.summarySupported ? "supported" : "blocked", }, { id: "handoff-ready", label: "Handoff readiness", value: report?.handoffReady ? "ready" : "blocked", }, { id: "missing-fields", label: "Missing fields", value: missingFields.length === 0 ? "none" : missingFields.join(", "), }, { id: "mismatch-fields", label: "Mismatched fields", value: mismatchFields.length === 0 ? "none" : mismatchFields.join(", "), }, { id: "blocking-reasons", label: "Blocking reasons", value: blockingReasons.length === 0 ? "none" : blockingReasons.join(", "), }, ], }; } export function createIniPanelShellSessionHandoffSnapshot({ readiness = createIniPanelShellIntegrationReadiness(), summary = createIniPanelShellSessionHandoffSummary(), compatibilityReport = createIniPanelShellSessionHandoffCompatibilityReport(summary), displayViewModel = createIniPanelShellSessionHandoffDisplayViewModel(compatibilityReport), } = {}) { return { apiName: "ini-panel-shell-session-handoff-snapshot", snapshotVersion: 1, phase: compatibilityReport.phase, ready: Boolean(compatibilityReport.compatible), readiness, summary, compatibilityReport, displayViewModel, }; } export function createIniPanelShellSessionHandoffRenderState( snapshot = createIniPanelShellSessionHandoffSnapshot(), ) { const displayViewModel = snapshot?.displayViewModel && typeof snapshot.displayViewModel === "object" ? snapshot.displayViewModel : createIniPanelShellSessionHandoffDisplayViewModel(snapshot?.compatibilityReport); const rows = Array.isArray(displayViewModel.rows) ? displayViewModel.rows.map(({ id, label, value }) => ({ id, label, value: value == null ? "" : String(value), })) : []; const ready = snapshot?.ready === true && displayViewModel.phase === "ready"; const statusText = displayViewModel.statusText ?? (ready ? "Ready" : "Blocked"); const detailText = displayViewModel.detailText ?? "No blocking reasons"; return { apiName: "ini-panel-shell-session-handoff-render-state", renderStateVersion: 1, phase: ready ? "ready" : "blocked", ready, title: displayViewModel.title ?? "Shell handoff compatibility", statusText, detailText, statusLine: `${statusText}: ${detailText}`, rows, dataset: { handoffPhase: ready ? "ready" : "blocked", handoffReady: ready ? "true" : "false", }, }; } export function createIniPanelShellSessionHandoffActionPlan({ snapshot = createIniPanelShellSessionHandoffSnapshot(), renderState = createIniPanelShellSessionHandoffRenderState(snapshot), } = {}) { const summary = snapshot?.summary && typeof snapshot.summary === "object" ? snapshot.summary : {}; const handoffMethods = summary.handoffMethods && typeof summary.handoffMethods === "object" ? summary.handoffMethods : {}; const blockingReasons = Array.isArray(summary.blockingReasons) ? [...summary.blockingReasons] : []; const displayRows = Array.isArray(renderState?.rows) ? renderState.rows.map(({ id, label, value }) => ({ id, label, value, })) : []; const ready = snapshot?.ready === true && renderState?.ready === true && blockingReasons.length === 0; return { apiName: "ini-panel-shell-session-handoff-action-plan", planVersion: 1, phase: ready ? "ready" : "blocked", ready, statusLine: renderState?.statusLine ?? "Blocked: No handoff render state", targetPage: summary.targetPage ?? null, requiredMethods: { loadSessionState: handoffMethods.loadSessionState ?? null, readStatus: handoffMethods.readStatus ?? null, }, blockingReasons, displayRows, nextSteps: ready ? [ { id: "open-control-page", label: "Open control page target", targetPage: summary.targetPage ?? null, }, { id: "read-readonly-status", label: "Read readonly status", methods: { loadSessionState: handoffMethods.loadSessionState ?? null, readStatus: handoffMethods.readStatus ?? null, }, }, ] : [ { id: "resolve-blockers", label: "Resolve handoff blockers", blockingReasons, displayRows, }, ], }; } export function createIniPanelShellSessionHandoffChecklist({ actionPlan = createIniPanelShellSessionHandoffActionPlan(), renderState = createIniPanelShellSessionHandoffRenderState(), } = {}) { const displayRows = Array.isArray(actionPlan?.displayRows) ? actionPlan.displayRows.map(({ id, label, value }) => ({ id, label, value })) : []; const blockingReasons = Array.isArray(actionPlan?.blockingReasons) ? [...actionPlan.blockingReasons] : []; const targetPageReady = Boolean(actionPlan?.targetPage?.href); const loadMethodReady = Boolean(actionPlan?.requiredMethods?.loadSessionState); const readMethodReady = Boolean(actionPlan?.requiredMethods?.readStatus); const ready = actionPlan?.ready === true && renderState?.ready === true && targetPageReady && loadMethodReady && readMethodReady && blockingReasons.length === 0; const checks = [ { id: "render-state", label: "Render state", status: renderState?.ready ? "pass" : "blocked", value: renderState?.statusLine ?? "missing", }, { id: "target-page", label: "Control target", status: targetPageReady ? "pass" : "blocked", value: actionPlan?.targetPage?.href ?? "missing", }, { id: "load-session-method", label: "Load session method", status: loadMethodReady ? "pass" : "blocked", value: actionPlan?.requiredMethods?.loadSessionState ?? "missing", }, { id: "read-status-method", label: "Read status method", status: readMethodReady ? "pass" : "blocked", value: actionPlan?.requiredMethods?.readStatus ?? "missing", }, { id: "blocking-reasons", label: "Blocking reasons", status: blockingReasons.length === 0 ? "pass" : "blocked", value: blockingReasons.length === 0 ? "none" : blockingReasons.join(", "), }, ]; return { apiName: "ini-panel-shell-session-handoff-checklist", checklistVersion: 1, phase: ready ? "ready" : "blocked", ready, statusLine: actionPlan?.statusLine ?? renderState?.statusLine ?? "Blocked: No handoff action plan", checks, displayRows, nextStepIds: Array.isArray(actionPlan?.nextSteps) ? actionPlan.nextSteps.map(({ id }) => id) : [], blockingReasons, }; } export function createIniPanelShellSessionHandoffPackage({ shellIntegrationManifest = createIniPanelShellIntegrationManifest(), launchApiManifest = shellIntegrationManifest.launchApiManifest ?? createIniPanelLaunchApiManifest(), controlPageApiManifest = shellIntegrationManifest.controlPageApiManifest ?? createControlPageBrowserApiManifest(), snapshot = createIniPanelShellSessionHandoffSnapshot(), renderState = createIniPanelShellSessionHandoffRenderState(snapshot), actionPlan = createIniPanelShellSessionHandoffActionPlan({ snapshot, renderState }), checklist = createIniPanelShellSessionHandoffChecklist({ actionPlan, renderState }), } = {}) { return { apiName: "ini-panel-shell-session-handoff-package", packageVersion: 1, phase: checklist.phase, ready: Boolean(checklist.ready), statusLine: checklist.statusLine, manifests: { shellIntegration: shellIntegrationManifest, launchApi: launchApiManifest, controlPageApi: controlPageApiManifest, launchMethods: Array.isArray(launchApiManifest?.methods) ? [...launchApiManifest.methods] : [], controlPageMethods: Array.isArray(controlPageApiManifest?.methods) ? [...controlPageApiManifest.methods] : [], }, snapshot, renderState, actionPlan, checklist, }; } export function createIniPanelShellSessionHandoffPackageSchema() { return { apiName: "ini-panel-shell-session-handoff-package", packageVersion: 1, fields: [ "apiName", "packageVersion", "phase", "ready", "statusLine", "manifests", "snapshot", "renderState", "actionPlan", "checklist", ], manifestFields: [ "shellIntegration", "launchApi", "controlPageApi", "launchMethods", "controlPageMethods", ], }; } export function isSupportedIniPanelShellSessionHandoffPackage( handoffPackage, schema = createIniPanelShellSessionHandoffPackageSchema(), ) { if (!handoffPackage || typeof handoffPackage !== "object") { return false; } if (handoffPackage.apiName !== schema.apiName || handoffPackage.packageVersion !== schema.packageVersion) { return false; } if (!schema.fields.every((fieldName) => Object.hasOwn(handoffPackage, fieldName))) { return false; } const manifests = handoffPackage.manifests; if (!manifests || typeof manifests !== "object") { return false; } return schema.manifestFields.every((fieldName) => Object.hasOwn(manifests, fieldName)); } export function createIniPanelShellSessionHandoffPackageReport( handoffPackage = createIniPanelShellSessionHandoffPackage(), schema = createIniPanelShellSessionHandoffPackageSchema(), ) { const packageObject = handoffPackage && typeof handoffPackage === "object" ? handoffPackage : {}; const manifests = packageObject.manifests && typeof packageObject.manifests === "object" ? packageObject.manifests : {}; const missingPackageFields = schema.fields.filter((fieldName) => !Object.hasOwn(packageObject, fieldName)); const missingManifestFields = schema.manifestFields.filter((fieldName) => !Object.hasOwn(manifests, fieldName)); const mismatchFields = [ ...(packageObject.apiName === schema.apiName ? [] : ["apiName"]), ...(packageObject.packageVersion === schema.packageVersion ? [] : ["packageVersion"]), ]; const blockingReasons = Array.isArray(packageObject?.checklist?.blockingReasons) ? [...packageObject.checklist.blockingReasons] : []; const packageSupported = missingPackageFields.length === 0 && missingManifestFields.length === 0 && mismatchFields.length === 0; const ready = packageSupported && packageObject.ready === true && blockingReasons.length === 0; return { apiName: "ini-panel-shell-session-handoff-package-report", reportVersion: 1, phase: ready ? "ready" : "blocked", ready, packageSupported, packageReady: ready, statusLine: packageObject.statusLine ?? (ready ? "Ready: No blocking reasons" : "Blocked: Package unsupported"), schema: { apiName: schema.apiName, packageVersion: schema.packageVersion, fields: [...schema.fields], manifestFields: [...schema.manifestFields], }, package: { apiName: packageObject.apiName ?? null, packageVersion: packageObject.packageVersion ?? null, phase: packageObject.phase ?? null, ready: packageObject.ready ?? false, }, missingPackageFields, missingManifestFields, mismatchFields, blockingReasons, rows: [ { id: "package-supported", label: "Package schema", value: packageSupported ? "supported" : "blocked", }, { id: "package-ready", label: "Package readiness", value: ready ? "ready" : "blocked", }, { id: "missing-package-fields", label: "Missing package fields", value: missingPackageFields.length === 0 ? "none" : missingPackageFields.join(", "), }, { id: "missing-manifest-fields", label: "Missing manifest fields", value: missingManifestFields.length === 0 ? "none" : missingManifestFields.join(", "), }, { id: "mismatch-fields", label: "Mismatched fields", value: mismatchFields.length === 0 ? "none" : mismatchFields.join(", "), }, { id: "blocking-reasons", label: "Blocking reasons", value: blockingReasons.length === 0 ? "none" : blockingReasons.join(", "), }, ], }; } export function createIniPanelShellSessionHandoffPackageDisplayViewModel( report = createIniPanelShellSessionHandoffPackageReport(), ) { const missingPackageFields = Array.isArray(report?.missingPackageFields) ? report.missingPackageFields : []; const missingManifestFields = Array.isArray(report?.missingManifestFields) ? report.missingManifestFields : []; const mismatchFields = Array.isArray(report?.mismatchFields) ? report.mismatchFields : []; const blockingReasons = Array.isArray(report?.blockingReasons) ? report.blockingReasons : []; const problems = [ ...missingPackageFields, ...missingManifestFields, ...mismatchFields, ...blockingReasons, ]; return { phase: report?.phase === "ready" ? "ready" : "blocked", title: "Shell handoff package", statusText: report?.ready ? "Ready" : "Blocked", detailText: problems.length === 0 ? "No blocking reasons" : problems.join(", "), rows: Array.isArray(report?.rows) ? report.rows.map(({ id, label, value }) => ({ id, label, value, })) : [], }; } export function createIniPanelShellSessionHandoffPackageRenderState( displayViewModel = createIniPanelShellSessionHandoffPackageDisplayViewModel(), ) { const rows = Array.isArray(displayViewModel?.rows) ? displayViewModel.rows.map(({ id, label, value }) => ({ id, label, value: value == null ? "" : String(value), })) : []; const ready = displayViewModel?.phase === "ready" && displayViewModel?.statusText === "Ready"; const statusText = displayViewModel?.statusText ?? (ready ? "Ready" : "Blocked"); const detailText = displayViewModel?.detailText ?? "No blocking reasons"; return { apiName: "ini-panel-shell-session-handoff-package-render-state", renderStateVersion: 1, phase: ready ? "ready" : "blocked", ready, title: displayViewModel?.title ?? "Shell handoff package", statusText, detailText, statusLine: `${statusText}: ${detailText}`, rows, dataset: { handoffPhase: ready ? "ready" : "blocked", handoffReady: ready ? "true" : "false", handoffScope: "package", }, }; } export function createIniPanelShellSessionHandoffPackageMountState({ handoffPackage = createIniPanelShellSessionHandoffPackage(), packageRenderState = createIniPanelShellSessionHandoffPackageRenderState(), sessionLoadState = createControlPageSessionLoadState({ phase: "waiting-panel" }), } = {}) { const actionPlan = handoffPackage?.actionPlan && typeof handoffPackage.actionPlan === "object" ? handoffPackage.actionPlan : {}; const requiredMethods = actionPlan.requiredMethods && typeof actionPlan.requiredMethods === "object" ? actionPlan.requiredMethods : {}; const targetPage = actionPlan.targetPage ?? null; const blockingReasons = [ ...(Array.isArray(actionPlan.blockingReasons) ? actionPlan.blockingReasons : []), ...(sessionLoadState?.phase === "error" ? ["controlPageSessionError"] : []), ]; const packageReady = handoffPackage?.ready === true && packageRenderState?.ready === true; const targetReady = Boolean(targetPage?.href); const methodsReady = Boolean(requiredMethods.loadSessionState && requiredMethods.readStatus); const sessionReady = sessionLoadState?.phase === "ready"; const ready = packageReady && targetReady && methodsReady && sessionReady && blockingReasons.length === 0; return { apiName: "ini-panel-shell-session-handoff-package-mount-state", mountStateVersion: 1, phase: ready ? "ready" : "blocked", ready, packageReady, targetReady, methodsReady, sessionReady, statusLine: ready ? "Ready: Control page session mounted" : `Blocked: ${blockingReasons.length === 0 ? sessionLoadState?.phase ?? "waiting-panel" : blockingReasons.join(", ")}`, packageRenderState, targetPage, requiredMethods: { loadSessionState: requiredMethods.loadSessionState ?? null, readStatus: requiredMethods.readStatus ?? null, }, sessionLoadState, blockingReasons, checks: [ { id: "package-render-state", label: "Package render state", status: packageReady ? "pass" : "blocked", value: packageRenderState?.statusLine ?? "missing", }, { id: "target-page", label: "Control target", status: targetReady ? "pass" : "blocked", value: targetPage?.href ?? "missing", }, { id: "required-methods", label: "Readonly methods", status: methodsReady ? "pass" : "blocked", value: methodsReady ? `${requiredMethods.loadSessionState}, ${requiredMethods.readStatus}` : "missing", }, { id: "session-load-state", label: "Session load state", status: sessionReady ? "pass" : "blocked", value: sessionLoadState?.phase ?? "missing", }, { id: "blocking-reasons", label: "Blocking reasons", status: blockingReasons.length === 0 ? "pass" : "blocked", value: blockingReasons.length === 0 ? "none" : blockingReasons.join(", "), }, ], }; } export function createIniPanelShellSessionHandoffPackageMountDisplayViewModel( mountState = createIniPanelShellSessionHandoffPackageMountState(), ) { const checks = Array.isArray(mountState?.checks) ? mountState.checks : []; const blockingChecks = checks.filter(({ status }) => status !== "pass"); const blockingReasons = Array.isArray(mountState?.blockingReasons) ? mountState.blockingReasons : []; const problems = [ ...blockingReasons, ...blockingChecks.map(({ id }) => id), ]; return { phase: mountState?.phase === "ready" ? "ready" : "blocked", title: "Shell handoff mount", statusText: mountState?.ready ? "Ready" : "Blocked", detailText: problems.length === 0 ? "No blocking reasons" : problems.join(", "), rows: checks.map(({ id, label, status, value }) => ({ id, label, value, status, })), }; } export function createIniPanelShellSessionHandoffPackageMountRenderState( displayViewModel = createIniPanelShellSessionHandoffPackageMountDisplayViewModel(), ) { const rows = Array.isArray(displayViewModel?.rows) ? displayViewModel.rows.map(({ id, label, value, status }) => ({ id, label, value: value == null ? "" : String(value), status: status ?? "blocked", })) : []; const ready = displayViewModel?.phase === "ready" && displayViewModel?.statusText === "Ready"; const statusText = displayViewModel?.statusText ?? (ready ? "Ready" : "Blocked"); const detailText = displayViewModel?.detailText ?? "No blocking reasons"; return { apiName: "ini-panel-shell-session-handoff-package-mount-render-state", renderStateVersion: 1, phase: ready ? "ready" : "blocked", ready, title: displayViewModel?.title ?? "Shell handoff mount", statusText, detailText, statusLine: `${statusText}: ${detailText}`, rows, dataset: { handoffPhase: ready ? "ready" : "blocked", handoffReady: ready ? "true" : "false", handoffScope: "mount", }, }; } export function createIniPanelShellSessionHandoffMountDomContract({ rowDatasetPrefix = "handoffMount", selectors = {}, } = {}) { const mountSelector = selectors.mount ?? "[data-handoff-mount]"; const statusSelector = selectors.status ?? "[data-handoff-mount-status]"; const rowsSelector = selectors.rows ?? "[data-handoff-mount-rows]"; return { apiName: "ini-panel-shell-session-handoff-mount-dom-contract", contractVersion: 1, requiredNodeIds: ["mount", "status", "rows"], selectors: { mount: mountSelector, status: statusSelector, rows: rowsSelector, }, mountDatasetKeys: ["handoffPhase", "handoffReady", "handoffScope"], rowDatasetPrefix, rowDatasetKeys: { term: [`${rowDatasetPrefix}Row`, `${rowDatasetPrefix}Status`], detail: [`${rowDatasetPrefix}Value`, `${rowDatasetPrefix}Status`], }, renderResultFields: ["rendered", "statusLine", "rowCount", "rowIds", "dataset"], }; } export function createIniPanelShellSessionHandoffMountDomReadiness({ rowDatasetPrefix = "handoffMount", contract = createIniPanelShellSessionHandoffMountDomContract({ rowDatasetPrefix }), documentRef = globalThis.document, mountNode = documentRef?.querySelector?.(contract.selectors.mount), statusNode = documentRef?.querySelector?.(contract.selectors.status), rowsNode = documentRef?.querySelector?.(contract.selectors.rows), } = {}) { const checks = { document: Boolean(documentRef?.createElement), mount: Boolean(mountNode), status: Boolean(statusNode), rows: Boolean(rowsNode), rowDatasetPrefix: Boolean(contract.rowDatasetPrefix), }; const missing = Object.entries(checks) .filter(([, passed]) => !passed) .map(([name]) => name); const ready = missing.length === 0; return { apiName: "ini-panel-shell-session-handoff-mount-dom-readiness", readinessVersion: 1, phase: ready ? "ready" : "blocked", ready, missing, checks, contract, }; } export function renderIniPanelShellSessionHandoffMountState( renderState = createIniPanelShellSessionHandoffPackageMountRenderState(), { rowDatasetPrefix = "handoffMount", contract = createIniPanelShellSessionHandoffMountDomContract({ rowDatasetPrefix }), documentRef = globalThis.document, mountNode = documentRef?.querySelector?.(contract.selectors.mount), statusNode = documentRef?.querySelector?.(contract.selectors.status), rowsNode = documentRef?.querySelector?.(contract.selectors.rows), } = {}, ) { const readiness = createIniPanelShellSessionHandoffMountDomReadiness({ contract, documentRef, mountNode, statusNode, rowsNode, }); if (!readiness.ready) { throw new Error( `Shell handoff mount DOM is not ready: ${readiness.missing.join(", ")}`, ); } mountNode.dataset.handoffPhase = renderState?.dataset?.handoffPhase ?? "blocked"; mountNode.dataset.handoffReady = renderState?.dataset?.handoffReady ?? "false"; mountNode.dataset.handoffScope = renderState?.dataset?.handoffScope ?? "mount"; statusNode.textContent = renderState?.statusLine ?? "Blocked: No mount render state"; rowsNode.replaceChildren(); const rows = Array.isArray(renderState?.rows) ? renderState.rows : []; const effectiveRowDatasetPrefix = contract.rowDatasetPrefix; for (const row of rows) { const term = documentRef.createElement("dt"); term.dataset[`${effectiveRowDatasetPrefix}Row`] = row.id; term.dataset[`${effectiveRowDatasetPrefix}Status`] = row.status; term.textContent = row.label; const detail = documentRef.createElement("dd"); detail.dataset[`${effectiveRowDatasetPrefix}Value`] = row.id; detail.dataset[`${effectiveRowDatasetPrefix}Status`] = row.status; detail.textContent = row.value; rowsNode.append(term, detail); } return { rendered: true, statusLine: statusNode.textContent, rowCount: rows.length, rowIds: rows.map(({ id }) => id), dataset: { handoffPhase: mountNode.dataset.handoffPhase, handoffReady: mountNode.dataset.handoffReady, handoffScope: mountNode.dataset.handoffScope, }, }; } export function mountIniPanelShellSessionHandoff({ renderState = createIniPanelShellSessionHandoffPackageMountRenderState(), rowDatasetPrefix = "handoffMount", contract = createIniPanelShellSessionHandoffMountDomContract({ rowDatasetPrefix }), documentRef = globalThis.document, mountNode = documentRef?.querySelector?.(contract.selectors.mount), statusNode = documentRef?.querySelector?.(contract.selectors.status), rowsNode = documentRef?.querySelector?.(contract.selectors.rows), } = {}) { const domReadiness = createIniPanelShellSessionHandoffMountDomReadiness({ contract, documentRef, mountNode, statusNode, rowsNode, }); if (!domReadiness.ready) { return { apiName: "ini-panel-shell-session-handoff-mount-workflow", workflowVersion: 1, phase: "blocked", ready: false, statusLine: `Blocked: ${domReadiness.missing.join(", ")}`, contract, domReadiness, renderState, renderResult: null, error: null, }; } try { const renderResult = renderIniPanelShellSessionHandoffMountState(renderState, { contract, documentRef, mountNode, statusNode, rowsNode, }); return { apiName: "ini-panel-shell-session-handoff-mount-workflow", workflowVersion: 1, phase: renderState?.ready ? "ready" : "blocked", ready: renderState?.ready === true, statusLine: renderResult.statusLine, contract, domReadiness, renderState, renderResult, error: null, }; } catch (error) { return { apiName: "ini-panel-shell-session-handoff-mount-workflow", workflowVersion: 1, phase: "blocked", ready: false, statusLine: `Blocked: ${error?.message ?? "mount workflow error"}`, contract, domReadiness, renderState, renderResult: null, error: error?.message ?? String(error), }; } } export function mountIniPanelShellSessionHandoffFromControlPage({ controlPageApi = null, sessionLoadState = null, loadSessionStateMethod = "loadControlPageSessionState", handoffPackage = createIniPanelShellSessionHandoffPackage(), packageRenderState = createIniPanelShellSessionHandoffPackageRenderState(), rowDatasetPrefix = "handoffMount", contract = createIniPanelShellSessionHandoffMountDomContract({ rowDatasetPrefix }), documentRef = globalThis.document, mountNode = documentRef?.querySelector?.(contract.selectors.mount), statusNode = documentRef?.querySelector?.(contract.selectors.status), rowsNode = documentRef?.querySelector?.(contract.selectors.rows), } = {}) { let observedSessionLoadState = sessionLoadState; let sessionLoadError = null; if (!observedSessionLoadState) { const loadSessionState = controlPageApi?.[loadSessionStateMethod]; if (typeof loadSessionState === "function") { try { observedSessionLoadState = loadSessionState.call(controlPageApi); } catch (error) { sessionLoadError = error?.message ?? String(error); observedSessionLoadState = createControlPageSessionLoadState({ phase: "error", error: sessionLoadError, }); } } else { observedSessionLoadState = createControlPageSessionLoadState({ phase: "waiting-api" }); } } const mountState = createIniPanelShellSessionHandoffPackageMountState({ handoffPackage, packageRenderState, sessionLoadState: observedSessionLoadState, }); const mountDisplayViewModel = createIniPanelShellSessionHandoffPackageMountDisplayViewModel(mountState); const mountRenderState = createIniPanelShellSessionHandoffPackageMountRenderState(mountDisplayViewModel); const mountWorkflow = mountIniPanelShellSessionHandoff({ renderState: mountRenderState, contract, documentRef, mountNode, statusNode, rowsNode, }); return { apiName: "ini-panel-shell-session-handoff-control-page-mount-workflow", workflowVersion: 1, phase: mountWorkflow.phase, ready: mountWorkflow.ready, statusLine: mountWorkflow.statusLine, loadSessionStateMethod, sessionLoadState: observedSessionLoadState, sessionLoadError, mountState, mountDisplayViewModel, mountRenderState, mountWorkflow, }; } export function readIniPanelShellSessionHandoffStatusFromControlPage({ controlPageApi = null, readonlyStatus = null, readStatusMethod = "readControlPageStatus", handoffSummary = createIniPanelShellSessionHandoffSummary(), expectedStatusFields = handoffSummary?.readonlyStatusFields ?? [], } = {}) { let observedStatus = readonlyStatus; let readError = null; if (!observedStatus) { const readStatus = controlPageApi?.[readStatusMethod]; if (typeof readStatus === "function") { try { observedStatus = readStatus.call(controlPageApi); } catch (error) { readError = error?.message ?? String(error); observedStatus = null; } } else { readError = `Missing ${readStatusMethod}`; observedStatus = null; } } const statusObject = observedStatus && typeof observedStatus === "object" ? observedStatus : {}; const expectedFields = Array.isArray(expectedStatusFields) ? [...expectedStatusFields] : []; const presentStatusFields = expectedFields.filter((fieldName) => Object.hasOwn(statusObject, fieldName)); const missingStatusFields = expectedFields.filter((fieldName) => !Object.hasOwn(statusObject, fieldName)); const ready = !readError && missingStatusFields.length === 0; return { apiName: "ini-panel-shell-session-handoff-readonly-status-result", resultVersion: 1, phase: ready ? "ready" : "blocked", ready, statusLine: ready ? "Ready: readonly status fields available" : `Blocked: ${readError ?? missingStatusFields.join(", ")}`, readStatusMethod, expectedStatusFields: expectedFields, presentStatusFields, missingStatusFields, readonlyStatus: observedStatus, readError, }; } export function createIniPanelShellSessionHandoffWorkflowSummary({ mountWorkflow = { phase: "blocked", ready: false, statusLine: "Blocked: mount workflow unavailable", }, controlPageMountWorkflow = { phase: "blocked", ready: false, statusLine: "Blocked: control-page mount workflow unavailable", }, readonlyStatusResult = { phase: "blocked", ready: false, statusLine: "Blocked: readonly status unavailable", missingStatusFields: [], }, } = {}) { const blockingReasons = []; if (mountWorkflow?.ready !== true) { blockingReasons.push(`mount:${mountWorkflow?.phase ?? "blocked"}`); } if (controlPageMountWorkflow?.ready !== true) { blockingReasons.push(`control-page-mount:${controlPageMountWorkflow?.phase ?? "blocked"}`); } if (readonlyStatusResult?.ready !== true) { const readonlyDetail = readonlyStatusResult?.readError ?? ( Array.isArray(readonlyStatusResult?.missingStatusFields) && readonlyStatusResult.missingStatusFields.length > 0 ? readonlyStatusResult.missingStatusFields.join(", ") : readonlyStatusResult?.phase ?? "blocked" ); blockingReasons.push(`readonly-status:${readonlyDetail}`); } const ready = blockingReasons.length === 0; return { apiName: "ini-panel-shell-session-handoff-workflow-summary", summaryVersion: 1, phase: ready ? "ready" : "blocked", ready, statusLine: ready ? "Ready: shell handoff end-to-end workflow available" : `Blocked: ${blockingReasons.join("; ")}`, mountWorkflow: { phase: mountWorkflow?.phase ?? "blocked", ready: mountWorkflow?.ready === true, statusLine: mountWorkflow?.statusLine ?? null, }, controlPageMountWorkflow: { phase: controlPageMountWorkflow?.phase ?? "blocked", ready: controlPageMountWorkflow?.ready === true, statusLine: controlPageMountWorkflow?.statusLine ?? null, }, readonlyStatusResult: { phase: readonlyStatusResult?.phase ?? "blocked", ready: readonlyStatusResult?.ready === true, statusLine: readonlyStatusResult?.statusLine ?? null, missingStatusFields: Array.isArray(readonlyStatusResult?.missingStatusFields) ? [...readonlyStatusResult.missingStatusFields] : [], }, blockingReasons, rows: [ { id: "mount-workflow", label: "Mount workflow", value: mountWorkflow?.statusLine ?? "missing", }, { id: "control-page-mount-workflow", label: "Control-page mount workflow", value: controlPageMountWorkflow?.statusLine ?? "missing", }, { id: "readonly-status-workflow", label: "Readonly status workflow", value: readonlyStatusResult?.statusLine ?? "missing", }, { id: "readonly-status-missing-fields", label: "Readonly missing fields", value: Array.isArray(readonlyStatusResult?.missingStatusFields) && readonlyStatusResult.missingStatusFields.length > 0 ? readonlyStatusResult.missingStatusFields.join(", ") : "none", }, { id: "blocking-reasons", label: "Blocking reasons", value: blockingReasons.length > 0 ? blockingReasons.join("; ") : "none", }, ], }; } export function createIniPanelShellSessionHandoffWorkflowDisplayViewModel( workflowSummary = createIniPanelShellSessionHandoffWorkflowSummary(), ) { const blockingReasons = Array.isArray(workflowSummary?.blockingReasons) ? workflowSummary.blockingReasons : []; return { phase: workflowSummary?.phase === "ready" ? "ready" : "blocked", title: "Shell handoff workflow", statusText: workflowSummary?.ready ? "Ready" : "Blocked", detailText: blockingReasons.length > 0 ? blockingReasons.join(", ") : "No blocking reasons", rows: Array.isArray(workflowSummary?.rows) ? workflowSummary.rows.map(({ id, label, value }) => ({ id, label, value, })) : [], }; } export function createIniPanelShellSessionHandoffWorkflowRenderState( displayViewModel = createIniPanelShellSessionHandoffWorkflowDisplayViewModel(), ) { const rows = Array.isArray(displayViewModel?.rows) ? displayViewModel.rows.map(({ id, label, value }) => ({ id, label, value: value == null ? "" : String(value), })) : []; const ready = displayViewModel?.phase === "ready" && displayViewModel?.statusText === "Ready"; const statusText = displayViewModel?.statusText ?? (ready ? "Ready" : "Blocked"); const detailText = displayViewModel?.detailText ?? "No blocking reasons"; return { apiName: "ini-panel-shell-session-handoff-workflow-render-state", renderStateVersion: 1, phase: ready ? "ready" : "blocked", ready, title: displayViewModel?.title ?? "Shell handoff workflow", statusText, detailText, statusLine: `${statusText}: ${detailText}`, rows, dataset: { handoffPhase: ready ? "ready" : "blocked", handoffReady: ready ? "true" : "false", handoffScope: "workflow", }, }; } export function createIniPanelShellSessionHandoffWorkflowDomContract({ rowDatasetPrefix = "handoffWorkflow", selectors = {}, } = {}) { const mountSelector = selectors.mount ?? "[data-handoff-workflow]"; const statusSelector = selectors.status ?? "[data-handoff-workflow-status]"; const rowsSelector = selectors.rows ?? "[data-handoff-workflow-rows]"; return { apiName: "ini-panel-shell-session-handoff-workflow-dom-contract", contractVersion: 1, requiredNodeIds: ["mount", "status", "rows"], selectors: { mount: mountSelector, status: statusSelector, rows: rowsSelector, }, mountDatasetKeys: ["handoffPhase", "handoffReady", "handoffScope"], rowDatasetPrefix, rowDatasetKeys: { term: [`${rowDatasetPrefix}Row`, `${rowDatasetPrefix}Status`], detail: [`${rowDatasetPrefix}Value`, `${rowDatasetPrefix}Status`], }, renderResultFields: ["rendered", "statusLine", "rowCount", "rowIds", "dataset"], }; } export function createIniPanelShellWorkflowOverviewContract() { return { pageHref: "./workflow-overview.html", entryId: "workflow-overview", apiName: "linuxCncIniPanelWorkflowOverviewApi", methodNames: [ "getWorkflowOverviewRenderState", "getWorkflowOverviewDisplayViewModel", "getWorkflowOverviewSummary", "getWorkflowOverviewDomContract", "getWorkflowOverviewDomReadiness", "renderWorkflowOverview", "mountWorkflowOverview", "createWorkflowOverviewRenderState", ], }; } export function createIniPanelShellWorkflowOverviewReadiness({ overviewApi = null, contract = createIniPanelShellWorkflowOverviewContract(), } = {}) { const methodNames = Array.isArray(contract?.methodNames) ? contract.methodNames : []; const missing = methodNames.filter((methodName) => typeof overviewApi?.[methodName] !== "function"); const ready = missing.length === 0; return { apiName: "ini-panel-shell-workflow-overview-readiness", readinessVersion: 1, phase: ready ? "ready" : "blocked", ready, missing, contract, }; } export function createIniPanelShellSessionHandoffWorkflowDomReadiness({ rowDatasetPrefix = "handoffWorkflow", contract = createIniPanelShellSessionHandoffWorkflowDomContract({ rowDatasetPrefix }), documentRef = globalThis.document, mountNode = documentRef?.querySelector?.(contract.selectors.mount), statusNode = documentRef?.querySelector?.(contract.selectors.status), rowsNode = documentRef?.querySelector?.(contract.selectors.rows), } = {}) { const checks = { document: Boolean(documentRef?.createElement), mount: Boolean(mountNode), status: Boolean(statusNode), rows: Boolean(rowsNode), rowDatasetPrefix: Boolean(contract.rowDatasetPrefix), }; const missing = Object.entries(checks) .filter(([, passed]) => !passed) .map(([name]) => name); const ready = missing.length === 0; return { apiName: "ini-panel-shell-session-handoff-workflow-dom-readiness", readinessVersion: 1, phase: ready ? "ready" : "blocked", ready, missing, checks, contract, }; } export function renderIniPanelShellSessionHandoffWorkflowState( renderState = createIniPanelShellSessionHandoffWorkflowRenderState(), { rowDatasetPrefix = "handoffWorkflow", contract = createIniPanelShellSessionHandoffWorkflowDomContract({ rowDatasetPrefix }), documentRef = globalThis.document, mountNode = documentRef?.querySelector?.(contract.selectors.mount), statusNode = documentRef?.querySelector?.(contract.selectors.status), rowsNode = documentRef?.querySelector?.(contract.selectors.rows), } = {}, ) { const readiness = createIniPanelShellSessionHandoffWorkflowDomReadiness({ contract, documentRef, mountNode, statusNode, rowsNode, }); if (!readiness.ready) { throw new Error( `Shell handoff workflow DOM is not ready: ${readiness.missing.join(", ")}`, ); } mountNode.dataset.handoffPhase = renderState?.dataset?.handoffPhase ?? "blocked"; mountNode.dataset.handoffReady = renderState?.dataset?.handoffReady ?? "false"; mountNode.dataset.handoffScope = renderState?.dataset?.handoffScope ?? "workflow"; statusNode.textContent = renderState?.statusLine ?? "Blocked: No workflow render state"; rowsNode.replaceChildren(); const rows = Array.isArray(renderState?.rows) ? renderState.rows : []; const effectiveRowDatasetPrefix = contract.rowDatasetPrefix; for (const row of rows) { const term = documentRef.createElement("dt"); term.dataset[`${effectiveRowDatasetPrefix}Row`] = row.id; term.dataset[`${effectiveRowDatasetPrefix}Status`] = renderState?.ready ? "pass" : "blocked"; term.textContent = row.label; const detail = documentRef.createElement("dd"); detail.dataset[`${effectiveRowDatasetPrefix}Value`] = row.id; detail.dataset[`${effectiveRowDatasetPrefix}Status`] = renderState?.ready ? "pass" : "blocked"; detail.textContent = row.value; rowsNode.append(term, detail); } return { rendered: true, statusLine: statusNode.textContent, rowCount: rows.length, rowIds: rows.map(({ id }) => id), dataset: { handoffPhase: mountNode.dataset.handoffPhase, handoffReady: mountNode.dataset.handoffReady, handoffScope: mountNode.dataset.handoffScope, }, }; } export function mountIniPanelShellSessionHandoffWorkflow({ renderState = createIniPanelShellSessionHandoffWorkflowRenderState(), rowDatasetPrefix = "handoffWorkflow", contract = createIniPanelShellSessionHandoffWorkflowDomContract({ rowDatasetPrefix }), documentRef = globalThis.document, mountNode = documentRef?.querySelector?.(contract.selectors.mount), statusNode = documentRef?.querySelector?.(contract.selectors.status), rowsNode = documentRef?.querySelector?.(contract.selectors.rows), } = {}) { const domReadiness = createIniPanelShellSessionHandoffWorkflowDomReadiness({ contract, documentRef, mountNode, statusNode, rowsNode, }); if (!domReadiness.ready) { return { apiName: "ini-panel-shell-session-handoff-workflow-dom-mount", workflowVersion: 1, phase: "blocked", ready: false, statusLine: `Blocked: ${domReadiness.missing.join(", ")}`, contract, domReadiness, renderState, renderResult: null, error: null, }; } try { const renderResult = renderIniPanelShellSessionHandoffWorkflowState(renderState, { contract, documentRef, mountNode, statusNode, rowsNode, }); return { apiName: "ini-panel-shell-session-handoff-workflow-dom-mount", workflowVersion: 1, phase: renderState?.phase === "ready" ? "ready" : "blocked", ready: renderState?.ready === true, statusLine: renderResult.statusLine, contract, domReadiness, renderState, renderResult, error: null, }; } catch (error) { return { apiName: "ini-panel-shell-session-handoff-workflow-dom-mount", workflowVersion: 1, phase: "blocked", ready: false, statusLine: `Blocked: ${error?.message ?? "workflow DOM mount error"}`, contract, domReadiness, renderState, renderResult: null, error: error?.message ?? String(error), }; } } export function mountIniPanelShellWorkflowOverviewEmbedding({ overviewApi = null, contract = createIniPanelShellWorkflowOverviewContract(), mountOptions = {}, } = {}) { const readiness = createIniPanelShellWorkflowOverviewReadiness({ overviewApi, contract, }); if (!readiness.ready) { return { apiName: "ini-panel-shell-workflow-overview-embedding", embeddingVersion: 1, phase: "blocked", ready: false, pageHref: contract.pageHref, contract, readiness, renderState: null, mountResult: null, error: null, }; } try { const renderState = overviewApi.getWorkflowOverviewRenderState(); const mountResult = overviewApi.mountWorkflowOverview(mountOptions); return { apiName: "ini-panel-shell-workflow-overview-embedding", embeddingVersion: 1, phase: mountResult?.phase ?? "blocked", ready: mountResult?.ready === true, pageHref: contract.pageHref, contract, readiness, renderState, mountResult, error: null, }; } catch (error) { return { apiName: "ini-panel-shell-workflow-overview-embedding", embeddingVersion: 1, phase: "blocked", ready: false, pageHref: contract.pageHref, contract, readiness, renderState: null, mountResult: null, error: error?.message ?? String(error), }; } } export function createIniPanelShellWorkflowOverviewEmbeddingReport( embeddingResult = mountIniPanelShellWorkflowOverviewEmbedding(), contract = createIniPanelShellWorkflowOverviewContract(), ) { const result = embeddingResult && typeof embeddingResult === "object" ? embeddingResult : {}; const readiness = result.readiness && typeof result.readiness === "object" ? result.readiness : {}; const resultContract = result.contract && typeof result.contract === "object" ? result.contract : contract; const mountResult = result.mountResult && typeof result.mountResult === "object" ? result.mountResult : {}; const renderResult = mountResult.renderResult && typeof mountResult.renderResult === "object" ? mountResult.renderResult : {}; const missingMethods = Array.isArray(readiness.missing) ? [...readiness.missing] : []; const error = result.error ?? mountResult.error ?? null; const overviewReady = readiness.ready === true; const embeddingReady = result.ready === true; const mountPhase = mountResult.phase ?? null; const mountReady = mountResult.ready === true; const renderRowCount = Number.isFinite(renderResult.rowCount) ? renderResult.rowCount : 0; const blockingReasons = [ ...missingMethods.map((methodName) => `missing:${methodName}`), ...(embeddingReady ? [] : [`embedding:${result.phase ?? "blocked"}`]), ...(mountPhase && !mountReady ? [`mount:${mountPhase}`] : []), ...(error ? [`error:${error}`] : []), ]; const ready = overviewReady && embeddingReady && mountReady && !error && blockingReasons.length === 0; const statusLine = ready ? "Ready: No blocking reasons" : `Blocked: ${blockingReasons.length > 0 ? blockingReasons.join(", ") : "workflow overview embedding unavailable"}`; return { apiName: "ini-panel-shell-workflow-overview-embedding-report", reportVersion: 1, phase: ready ? "ready" : "blocked", ready, overviewReady, embeddingReady, mountReady, statusLine, pageHref: result.pageHref ?? resultContract.pageHref ?? null, contract: resultContract, missingMethods, mountPhase, renderRowCount, error, blockingReasons, rows: [ { id: "overview-api", label: "Overview API", value: overviewReady ? "ready" : "blocked", }, { id: "missing-methods", label: "Missing methods", value: missingMethods.length === 0 ? "none" : missingMethods.join(", "), }, { id: "embedding-ready", label: "Embedding readiness", value: embeddingReady ? "ready" : "blocked", }, { id: "mount-phase", label: "Mount phase", value: mountPhase ?? "missing", }, { id: "render-row-count", label: "Render row count", value: String(renderRowCount), }, { id: "error", label: "Error", value: error ?? "none", }, ], }; } export function createIniPanelShellWorkflowOverviewEmbeddingDisplayViewModel( report = createIniPanelShellWorkflowOverviewEmbeddingReport(), ) { const blockingReasons = Array.isArray(report?.blockingReasons) ? report.blockingReasons : []; const rows = Array.isArray(report?.rows) ? report.rows.map(({ id, label, value }) => ({ id, label, value: value == null ? "" : String(value), })) : []; return { phase: report?.phase === "ready" ? "ready" : "blocked", title: "Workflow overview embedding", statusText: report?.ready ? "Ready" : "Blocked", detailText: blockingReasons.length === 0 ? "No blocking reasons" : blockingReasons.join(", "), rows, }; } export function createIniPanelShellWorkflowOverviewEmbeddingRenderState( displayViewModel = createIniPanelShellWorkflowOverviewEmbeddingDisplayViewModel(), ) { const rows = Array.isArray(displayViewModel?.rows) ? displayViewModel.rows.map(({ id, label, value }) => ({ id, label, value: value == null ? "" : String(value), })) : []; const ready = displayViewModel?.phase === "ready" && displayViewModel?.statusText === "Ready"; const statusText = displayViewModel?.statusText ?? (ready ? "Ready" : "Blocked"); const detailText = displayViewModel?.detailText ?? "No blocking reasons"; return { apiName: "ini-panel-shell-workflow-overview-embedding-render-state", renderStateVersion: 1, phase: ready ? "ready" : "blocked", ready, title: displayViewModel?.title ?? "Workflow overview embedding", statusText, detailText, statusLine: `${statusText}: ${detailText}`, rows, dataset: { handoffPhase: ready ? "ready" : "blocked", handoffReady: ready ? "true" : "false", handoffScope: "workflow-overview-embedding", }, }; } export function createIniPanelShellWorkflowOverviewEmbeddingDomContract({ rowDatasetPrefix = "workflowOverviewEmbedding", selectors = {}, } = {}) { const mountSelector = selectors.mount ?? "[data-workflow-overview-embedding]"; const statusSelector = selectors.status ?? "[data-workflow-overview-embedding-status]"; const rowsSelector = selectors.rows ?? "[data-workflow-overview-embedding-rows]"; return { apiName: "ini-panel-shell-workflow-overview-embedding-dom-contract", contractVersion: 1, requiredNodeIds: ["mount", "status", "rows"], selectors: { mount: mountSelector, status: statusSelector, rows: rowsSelector, }, mountDatasetKeys: ["handoffPhase", "handoffReady", "handoffScope"], rowDatasetPrefix, rowDatasetKeys: { term: [`${rowDatasetPrefix}Row`, `${rowDatasetPrefix}Status`], detail: [`${rowDatasetPrefix}Value`, `${rowDatasetPrefix}Status`], }, renderResultFields: ["rendered", "statusLine", "rowCount", "rowIds", "dataset"], }; } export function createIniPanelShellWorkflowOverviewEmbeddingDomReadiness({ rowDatasetPrefix = "workflowOverviewEmbedding", contract = createIniPanelShellWorkflowOverviewEmbeddingDomContract({ rowDatasetPrefix }), documentRef = globalThis.document, mountNode = documentRef?.querySelector?.(contract.selectors.mount), statusNode = documentRef?.querySelector?.(contract.selectors.status), rowsNode = documentRef?.querySelector?.(contract.selectors.rows), } = {}) { const checks = { document: Boolean(documentRef?.createElement), mount: Boolean(mountNode), status: Boolean(statusNode), rows: Boolean(rowsNode), rowDatasetPrefix: Boolean(contract.rowDatasetPrefix), }; const missing = Object.entries(checks) .filter(([, passed]) => !passed) .map(([name]) => name); const ready = missing.length === 0; return { apiName: "ini-panel-shell-workflow-overview-embedding-dom-readiness", readinessVersion: 1, phase: ready ? "ready" : "blocked", ready, missing, checks, contract, }; } export function renderIniPanelShellWorkflowOverviewEmbeddingState( renderState = createIniPanelShellWorkflowOverviewEmbeddingRenderState(), { rowDatasetPrefix = "workflowOverviewEmbedding", contract = createIniPanelShellWorkflowOverviewEmbeddingDomContract({ rowDatasetPrefix }), documentRef = globalThis.document, mountNode = documentRef?.querySelector?.(contract.selectors.mount), statusNode = documentRef?.querySelector?.(contract.selectors.status), rowsNode = documentRef?.querySelector?.(contract.selectors.rows), } = {}, ) { const readiness = createIniPanelShellWorkflowOverviewEmbeddingDomReadiness({ contract, documentRef, mountNode, statusNode, rowsNode, }); if (!readiness.ready) { throw new Error( `Workflow overview embedding DOM is not ready: ${readiness.missing.join(", ")}`, ); } mountNode.dataset.handoffPhase = renderState?.dataset?.handoffPhase ?? "blocked"; mountNode.dataset.handoffReady = renderState?.dataset?.handoffReady ?? "false"; mountNode.dataset.handoffScope = renderState?.dataset?.handoffScope ?? "workflow-overview-embedding"; statusNode.textContent = renderState?.statusLine ?? "Blocked: No workflow overview embedding render state"; rowsNode.replaceChildren(); const rows = Array.isArray(renderState?.rows) ? renderState.rows : []; const effectiveRowDatasetPrefix = contract.rowDatasetPrefix; for (const row of rows) { const term = documentRef.createElement("dt"); term.dataset[`${effectiveRowDatasetPrefix}Row`] = row.id; term.dataset[`${effectiveRowDatasetPrefix}Status`] = renderState?.ready ? "pass" : "blocked"; term.textContent = row.label; const detail = documentRef.createElement("dd"); detail.dataset[`${effectiveRowDatasetPrefix}Value`] = row.id; detail.dataset[`${effectiveRowDatasetPrefix}Status`] = renderState?.ready ? "pass" : "blocked"; detail.textContent = row.value; rowsNode.append(term, detail); } return { rendered: true, statusLine: statusNode.textContent, rowCount: rows.length, rowIds: rows.map(({ id }) => id), dataset: { handoffPhase: mountNode.dataset.handoffPhase, handoffReady: mountNode.dataset.handoffReady, handoffScope: mountNode.dataset.handoffScope, }, }; } export function mountIniPanelShellWorkflowOverviewEmbeddingState({ renderState = createIniPanelShellWorkflowOverviewEmbeddingRenderState(), rowDatasetPrefix = "workflowOverviewEmbedding", contract = createIniPanelShellWorkflowOverviewEmbeddingDomContract({ rowDatasetPrefix }), documentRef = globalThis.document, mountNode = documentRef?.querySelector?.(contract.selectors.mount), statusNode = documentRef?.querySelector?.(contract.selectors.status), rowsNode = documentRef?.querySelector?.(contract.selectors.rows), } = {}) { const domReadiness = createIniPanelShellWorkflowOverviewEmbeddingDomReadiness({ contract, documentRef, mountNode, statusNode, rowsNode, }); if (!domReadiness.ready) { return { apiName: "ini-panel-shell-workflow-overview-embedding-dom-mount", workflowVersion: 1, phase: "blocked", ready: false, statusLine: `Blocked: ${domReadiness.missing.join(", ")}`, contract, domReadiness, renderState, renderResult: null, error: null, }; } try { const renderResult = renderIniPanelShellWorkflowOverviewEmbeddingState(renderState, { contract, documentRef, mountNode, statusNode, rowsNode, }); return { apiName: "ini-panel-shell-workflow-overview-embedding-dom-mount", workflowVersion: 1, phase: renderState?.phase === "ready" ? "ready" : "blocked", ready: renderState?.ready === true, statusLine: renderResult.statusLine, contract, domReadiness, renderState, renderResult, error: null, }; } catch (error) { return { apiName: "ini-panel-shell-workflow-overview-embedding-dom-mount", workflowVersion: 1, phase: "blocked", ready: false, statusLine: `Blocked: ${error?.message ?? "workflow overview embedding DOM mount error"}`, contract, domReadiness, renderState, renderResult: null, error: error?.message ?? String(error), }; } } export function createIniPanelShellWorkflowOverviewEmbeddingMountDisplayViewModel( mountResult = mountIniPanelShellWorkflowOverviewEmbeddingState(), ) { const domMissing = Array.isArray(mountResult?.domReadiness?.missing) ? mountResult.domReadiness.missing : []; const renderResult = mountResult?.renderResult && typeof mountResult.renderResult === "object" ? mountResult.renderResult : {}; const renderState = mountResult?.renderState && typeof mountResult.renderState === "object" ? mountResult.renderState : {}; const error = mountResult?.error ?? null; const ready = mountResult?.ready === true; const problems = [ ...(ready ? [] : [`mount:${mountResult?.phase ?? "blocked"}`]), ...domMissing.map((name) => `dom:${name}`), ...(error ? [`error:${error}`] : []), ]; return { phase: ready ? "ready" : "blocked", title: "Workflow overview embedding mount", statusText: ready ? "Ready" : "Blocked", detailText: problems.length === 0 ? "No blocking reasons" : problems.join(", "), rows: [ { id: "mount-ready", label: "Mount readiness", value: ready ? "ready" : "blocked", }, { id: "dom-readiness", label: "DOM readiness", value: mountResult?.domReadiness?.ready ? "ready" : "blocked", }, { id: "missing-dom", label: "Missing DOM", value: domMissing.length === 0 ? "none" : domMissing.join(", "), }, { id: "render-state", label: "Render state", value: renderState.statusLine ?? "missing", }, { id: "render-result", label: "Render result", value: renderResult.rendered ? "rendered" : "missing", }, { id: "render-row-count", label: "Render row count", value: Number.isFinite(renderResult.rowCount) ? String(renderResult.rowCount) : "0", }, { id: "error", label: "Error", value: error ?? "none", }, ], }; } export function createIniPanelShellWorkflowOverviewEmbeddingMountRenderState( displayViewModel = createIniPanelShellWorkflowOverviewEmbeddingMountDisplayViewModel(), ) { const rows = Array.isArray(displayViewModel?.rows) ? displayViewModel.rows.map(({ id, label, value }) => ({ id, label, value: value == null ? "" : String(value), })) : []; const ready = displayViewModel?.phase === "ready" && displayViewModel?.statusText === "Ready"; const statusText = displayViewModel?.statusText ?? (ready ? "Ready" : "Blocked"); const detailText = displayViewModel?.detailText ?? "No blocking reasons"; return { apiName: "ini-panel-shell-workflow-overview-embedding-mount-render-state", renderStateVersion: 1, phase: ready ? "ready" : "blocked", ready, title: displayViewModel?.title ?? "Workflow overview embedding mount", statusText, detailText, statusLine: `${statusText}: ${detailText}`, rows, dataset: { handoffPhase: ready ? "ready" : "blocked", handoffReady: ready ? "true" : "false", handoffScope: "workflow-overview-embedding-mount", }, }; } export function createIniPanelShellWorkflowOverviewEmbeddingMountDomContract({ rowDatasetPrefix = "workflowOverviewEmbeddingMount", selectors = {}, } = {}) { const mountSelector = selectors.mount ?? "[data-workflow-overview-embedding-mount]"; const statusSelector = selectors.status ?? "[data-workflow-overview-embedding-mount-status]"; const rowsSelector = selectors.rows ?? "[data-workflow-overview-embedding-mount-rows]"; return { apiName: "ini-panel-shell-workflow-overview-embedding-mount-dom-contract", contractVersion: 1, requiredNodeIds: ["mount", "status", "rows"], selectors: { mount: mountSelector, status: statusSelector, rows: rowsSelector, }, mountDatasetKeys: ["handoffPhase", "handoffReady", "handoffScope"], rowDatasetPrefix, rowDatasetKeys: { term: [`${rowDatasetPrefix}Row`, `${rowDatasetPrefix}Status`], detail: [`${rowDatasetPrefix}Value`, `${rowDatasetPrefix}Status`], }, renderResultFields: ["rendered", "statusLine", "rowCount", "rowIds", "dataset"], }; } export function createIniPanelShellWorkflowOverviewEmbeddingMountDomReadiness({ rowDatasetPrefix = "workflowOverviewEmbeddingMount", contract = createIniPanelShellWorkflowOverviewEmbeddingMountDomContract({ rowDatasetPrefix }), documentRef = globalThis.document, mountNode = documentRef?.querySelector?.(contract.selectors.mount), statusNode = documentRef?.querySelector?.(contract.selectors.status), rowsNode = documentRef?.querySelector?.(contract.selectors.rows), } = {}) { const checks = { document: Boolean(documentRef?.createElement), mount: Boolean(mountNode), status: Boolean(statusNode), rows: Boolean(rowsNode), rowDatasetPrefix: Boolean(contract.rowDatasetPrefix), }; const missing = Object.entries(checks) .filter(([, passed]) => !passed) .map(([name]) => name); const ready = missing.length === 0; return { apiName: "ini-panel-shell-workflow-overview-embedding-mount-dom-readiness", readinessVersion: 1, phase: ready ? "ready" : "blocked", ready, missing, checks, contract, }; } export function renderIniPanelShellWorkflowOverviewEmbeddingMountState( renderState = createIniPanelShellWorkflowOverviewEmbeddingMountRenderState(), { rowDatasetPrefix = "workflowOverviewEmbeddingMount", contract = createIniPanelShellWorkflowOverviewEmbeddingMountDomContract({ rowDatasetPrefix }), documentRef = globalThis.document, mountNode = documentRef?.querySelector?.(contract.selectors.mount), statusNode = documentRef?.querySelector?.(contract.selectors.status), rowsNode = documentRef?.querySelector?.(contract.selectors.rows), } = {}, ) { const readiness = createIniPanelShellWorkflowOverviewEmbeddingMountDomReadiness({ contract, documentRef, mountNode, statusNode, rowsNode, }); if (!readiness.ready) { throw new Error( `Workflow overview embedding mount DOM is not ready: ${readiness.missing.join(", ")}`, ); } mountNode.dataset.handoffPhase = renderState?.dataset?.handoffPhase ?? "blocked"; mountNode.dataset.handoffReady = renderState?.dataset?.handoffReady ?? "false"; mountNode.dataset.handoffScope = renderState?.dataset?.handoffScope ?? "workflow-overview-embedding-mount"; statusNode.textContent = renderState?.statusLine ?? "Blocked: No workflow overview embedding mount render state"; rowsNode.replaceChildren(); const rows = Array.isArray(renderState?.rows) ? renderState.rows : []; const effectiveRowDatasetPrefix = contract.rowDatasetPrefix; for (const row of rows) { const term = documentRef.createElement("dt"); term.dataset[`${effectiveRowDatasetPrefix}Row`] = row.id; term.dataset[`${effectiveRowDatasetPrefix}Status`] = renderState?.ready ? "pass" : "blocked"; term.textContent = row.label; const detail = documentRef.createElement("dd"); detail.dataset[`${effectiveRowDatasetPrefix}Value`] = row.id; detail.dataset[`${effectiveRowDatasetPrefix}Status`] = renderState?.ready ? "pass" : "blocked"; detail.textContent = row.value; rowsNode.append(term, detail); } return { rendered: true, statusLine: statusNode.textContent, rowCount: rows.length, rowIds: rows.map(({ id }) => id), dataset: { handoffPhase: mountNode.dataset.handoffPhase, handoffReady: mountNode.dataset.handoffReady, handoffScope: mountNode.dataset.handoffScope, }, }; } export function mountIniPanelShellWorkflowOverviewEmbeddingMountState({ renderState = createIniPanelShellWorkflowOverviewEmbeddingMountRenderState(), rowDatasetPrefix = "workflowOverviewEmbeddingMount", contract = createIniPanelShellWorkflowOverviewEmbeddingMountDomContract({ rowDatasetPrefix }), documentRef = globalThis.document, mountNode = documentRef?.querySelector?.(contract.selectors.mount), statusNode = documentRef?.querySelector?.(contract.selectors.status), rowsNode = documentRef?.querySelector?.(contract.selectors.rows), } = {}) { const domReadiness = createIniPanelShellWorkflowOverviewEmbeddingMountDomReadiness({ contract, documentRef, mountNode, statusNode, rowsNode, }); if (!domReadiness.ready) { return { apiName: "ini-panel-shell-workflow-overview-embedding-mount-dom-mount", workflowVersion: 1, phase: "blocked", ready: false, statusLine: `Blocked: ${domReadiness.missing.join(", ")}`, contract, domReadiness, renderState, renderResult: null, error: null, }; } try { const renderResult = renderIniPanelShellWorkflowOverviewEmbeddingMountState(renderState, { contract, documentRef, mountNode, statusNode, rowsNode, }); return { apiName: "ini-panel-shell-workflow-overview-embedding-mount-dom-mount", workflowVersion: 1, phase: renderState?.phase === "ready" ? "ready" : "blocked", ready: renderState?.ready === true, statusLine: renderResult.statusLine, contract, domReadiness, renderState, renderResult, error: null, }; } catch (error) { return { apiName: "ini-panel-shell-workflow-overview-embedding-mount-dom-mount", workflowVersion: 1, phase: "blocked", ready: false, statusLine: `Blocked: ${error?.message ?? "workflow overview embedding mount DOM mount error"}`, contract, domReadiness, renderState, renderResult: null, error: error?.message ?? String(error), }; } } export function createIniPanelShellViewModel(entries = getVisibleIniPanelEntries()) { const shellIntegrationManifest = createIniPanelShellIntegrationManifest(entries); const shellIntegrationReadiness = createIniPanelShellIntegrationReadiness(shellIntegrationManifest); const shellIntegrationWorkflowPlan = createIniPanelShellIntegrationWorkflowPlan({ manifest: shellIntegrationManifest, readiness: shellIntegrationReadiness, }); const shellSessionHandoffSummary = createIniPanelShellSessionHandoffSummary({ manifest: shellIntegrationManifest, readiness: shellIntegrationReadiness, workflowPlan: shellIntegrationWorkflowPlan, }); const shellSessionHandoffCompatibilityReport = createIniPanelShellSessionHandoffCompatibilityReport( shellSessionHandoffSummary, ); const shellSessionHandoffDisplayViewModel = createIniPanelShellSessionHandoffDisplayViewModel( shellSessionHandoffCompatibilityReport, ); const shellSessionHandoffSnapshot = createIniPanelShellSessionHandoffSnapshot({ readiness: shellIntegrationReadiness, summary: shellSessionHandoffSummary, compatibilityReport: shellSessionHandoffCompatibilityReport, displayViewModel: shellSessionHandoffDisplayViewModel, }); const shellSessionHandoffRenderState = createIniPanelShellSessionHandoffRenderState( shellSessionHandoffSnapshot, ); const shellSessionHandoffActionPlan = createIniPanelShellSessionHandoffActionPlan({ snapshot: shellSessionHandoffSnapshot, renderState: shellSessionHandoffRenderState, }); const shellSessionHandoffChecklist = createIniPanelShellSessionHandoffChecklist({ actionPlan: shellSessionHandoffActionPlan, renderState: shellSessionHandoffRenderState, }); const shellSessionHandoffPackage = createIniPanelShellSessionHandoffPackage({ shellIntegrationManifest, launchApiManifest: shellIntegrationManifest.launchApiManifest, controlPageApiManifest: shellIntegrationManifest.controlPageApiManifest, snapshot: shellSessionHandoffSnapshot, renderState: shellSessionHandoffRenderState, actionPlan: shellSessionHandoffActionPlan, checklist: shellSessionHandoffChecklist, }); const shellSessionHandoffPackageReport = createIniPanelShellSessionHandoffPackageReport( shellSessionHandoffPackage, ); const shellSessionHandoffPackageDisplayViewModel = createIniPanelShellSessionHandoffPackageDisplayViewModel( shellSessionHandoffPackageReport, ); const shellSessionHandoffPackageRenderState = createIniPanelShellSessionHandoffPackageRenderState( shellSessionHandoffPackageDisplayViewModel, ); const shellSessionHandoffPackageMountState = createIniPanelShellSessionHandoffPackageMountState({ handoffPackage: shellSessionHandoffPackage, packageRenderState: shellSessionHandoffPackageRenderState, }); const shellSessionHandoffPackageMountDisplayViewModel = createIniPanelShellSessionHandoffPackageMountDisplayViewModel( shellSessionHandoffPackageMountState, ); const shellSessionHandoffPackageMountRenderState = createIniPanelShellSessionHandoffPackageMountRenderState( shellSessionHandoffPackageMountDisplayViewModel, ); const shellSessionHandoffMountDomContract = createIniPanelShellSessionHandoffMountDomContract(); const shellSessionHandoffWorkflowSummary = createIniPanelShellSessionHandoffWorkflowSummary({ mountWorkflow: { phase: shellSessionHandoffPackageMountRenderState.phase, ready: shellSessionHandoffPackageMountRenderState.ready, statusLine: shellSessionHandoffPackageMountRenderState.statusLine, }, controlPageMountWorkflow: { phase: shellSessionHandoffPackageMountState.phase, ready: shellSessionHandoffPackageMountState.ready, statusLine: shellSessionHandoffPackageMountState.statusLine, }, readonlyStatusResult: readIniPanelShellSessionHandoffStatusFromControlPage({ handoffSummary: shellSessionHandoffSummary, }), }); const shellSessionHandoffWorkflowDisplayViewModel = createIniPanelShellSessionHandoffWorkflowDisplayViewModel( shellSessionHandoffWorkflowSummary, ); const shellSessionHandoffWorkflowRenderState = createIniPanelShellSessionHandoffWorkflowRenderState( shellSessionHandoffWorkflowDisplayViewModel, ); const shellSessionHandoffWorkflowDomContract = createIniPanelShellSessionHandoffWorkflowDomContract(); const shellWorkflowOverviewContract = createIniPanelShellWorkflowOverviewContract(); return { pages: entries.map(({ id, href, title }) => ({ id, href, title, })), launcherLinks: createIniPanelLauncherLinkViewModel(entries), shellIntegrationSchema: createIniPanelShellIntegrationSchema(), shellIntegrationManifest, shellIntegrationReadiness, shellIntegrationWorkflowPlan, shellSessionHandoffSummarySchema: createIniPanelShellSessionHandoffSummarySchema(), shellSessionHandoffSummary, shellSessionHandoffCompatibilityReport, shellSessionHandoffDisplayViewModel, shellSessionHandoffSnapshot, shellSessionHandoffRenderState, shellSessionHandoffActionPlan, shellSessionHandoffChecklist, shellSessionHandoffPackage, shellSessionHandoffPackageSchema: createIniPanelShellSessionHandoffPackageSchema(), shellSessionHandoffPackageReport, shellSessionHandoffPackageDisplayViewModel, shellSessionHandoffPackageRenderState, shellSessionHandoffPackageMountState, shellSessionHandoffPackageMountDisplayViewModel, shellSessionHandoffPackageMountRenderState, shellSessionHandoffMountDomContract, shellSessionHandoffWorkflowSummary, shellSessionHandoffWorkflowDisplayViewModel, shellSessionHandoffWorkflowRenderState, shellSessionHandoffWorkflowDomContract, shellWorkflowOverviewContract, launchApiSchema: createIniPanelLaunchApiSchema(), launchApiManifest: createIniPanelLaunchApiManifest(entries), isSupportedShellIntegrationManifest: isSupportedIniPanelShellIntegrationManifest, createShellIntegrationReadiness: createIniPanelShellIntegrationReadiness, createShellIntegrationWorkflowPlan: createIniPanelShellIntegrationWorkflowPlan, isSupportedShellSessionHandoffSummary: isSupportedIniPanelShellSessionHandoffSummary, createShellSessionHandoffSummarySchema: createIniPanelShellSessionHandoffSummarySchema, createShellSessionHandoffSummary: createIniPanelShellSessionHandoffSummary, createShellSessionHandoffCompatibilityReport: createIniPanelShellSessionHandoffCompatibilityReport, createShellSessionHandoffDisplayViewModel: createIniPanelShellSessionHandoffDisplayViewModel, createShellSessionHandoffSnapshot: createIniPanelShellSessionHandoffSnapshot, createShellSessionHandoffRenderState: createIniPanelShellSessionHandoffRenderState, createShellSessionHandoffActionPlan: createIniPanelShellSessionHandoffActionPlan, createShellSessionHandoffChecklist: createIniPanelShellSessionHandoffChecklist, createShellSessionHandoffPackage: createIniPanelShellSessionHandoffPackage, createShellSessionHandoffPackageSchema: createIniPanelShellSessionHandoffPackageSchema, createShellSessionHandoffPackageReport: createIniPanelShellSessionHandoffPackageReport, createShellSessionHandoffPackageDisplayViewModel: createIniPanelShellSessionHandoffPackageDisplayViewModel, createShellSessionHandoffPackageRenderState: createIniPanelShellSessionHandoffPackageRenderState, createShellSessionHandoffPackageMountState: createIniPanelShellSessionHandoffPackageMountState, createShellSessionHandoffPackageMountDisplayViewModel: createIniPanelShellSessionHandoffPackageMountDisplayViewModel, createShellSessionHandoffPackageMountRenderState: createIniPanelShellSessionHandoffPackageMountRenderState, createShellSessionHandoffMountDomContract: createIniPanelShellSessionHandoffMountDomContract, createShellSessionHandoffMountDomReadiness: createIniPanelShellSessionHandoffMountDomReadiness, renderShellSessionHandoffMountState: renderIniPanelShellSessionHandoffMountState, mountShellSessionHandoff: mountIniPanelShellSessionHandoff, mountShellSessionHandoffFromControlPage: mountIniPanelShellSessionHandoffFromControlPage, readShellSessionHandoffStatusFromControlPage: readIniPanelShellSessionHandoffStatusFromControlPage, createShellSessionHandoffWorkflowSummary: createIniPanelShellSessionHandoffWorkflowSummary, createShellSessionHandoffWorkflowDisplayViewModel: createIniPanelShellSessionHandoffWorkflowDisplayViewModel, createShellSessionHandoffWorkflowRenderState: createIniPanelShellSessionHandoffWorkflowRenderState, createShellSessionHandoffWorkflowDomContract: createIniPanelShellSessionHandoffWorkflowDomContract, createShellSessionHandoffWorkflowDomReadiness: createIniPanelShellSessionHandoffWorkflowDomReadiness, renderShellSessionHandoffWorkflowState: renderIniPanelShellSessionHandoffWorkflowState, mountShellSessionHandoffWorkflow: mountIniPanelShellSessionHandoffWorkflow, createWorkflowOverviewContract: createIniPanelShellWorkflowOverviewContract, createWorkflowOverviewReadiness: createIniPanelShellWorkflowOverviewReadiness, mountWorkflowOverviewEmbedding: mountIniPanelShellWorkflowOverviewEmbedding, createWorkflowOverviewEmbeddingReport: createIniPanelShellWorkflowOverviewEmbeddingReport, createWorkflowOverviewEmbeddingDisplayViewModel: createIniPanelShellWorkflowOverviewEmbeddingDisplayViewModel, createWorkflowOverviewEmbeddingRenderState: createIniPanelShellWorkflowOverviewEmbeddingRenderState, createWorkflowOverviewEmbeddingDomContract: createIniPanelShellWorkflowOverviewEmbeddingDomContract, createWorkflowOverviewEmbeddingDomReadiness: createIniPanelShellWorkflowOverviewEmbeddingDomReadiness, renderWorkflowOverviewEmbeddingState: renderIniPanelShellWorkflowOverviewEmbeddingState, mountWorkflowOverviewEmbeddingState: mountIniPanelShellWorkflowOverviewEmbeddingState, createWorkflowOverviewEmbeddingMountDisplayViewModel: createIniPanelShellWorkflowOverviewEmbeddingMountDisplayViewModel, createWorkflowOverviewEmbeddingMountRenderState: createIniPanelShellWorkflowOverviewEmbeddingMountRenderState, createWorkflowOverviewEmbeddingMountDomContract: createIniPanelShellWorkflowOverviewEmbeddingMountDomContract, createWorkflowOverviewEmbeddingMountDomReadiness: createIniPanelShellWorkflowOverviewEmbeddingMountDomReadiness, renderWorkflowOverviewEmbeddingMountState: renderIniPanelShellWorkflowOverviewEmbeddingMountState, mountWorkflowOverviewEmbeddingMountState: mountIniPanelShellWorkflowOverviewEmbeddingMountState, isSupportedShellSessionHandoffPackage: isSupportedIniPanelShellSessionHandoffPackage, controlPage: { browserApiSchema: createControlPageBrowserApiSchema(), browserApiManifest: createControlPageBrowserApiManifest(), browserApiMethods: createControlPageBrowserApiMethods(), contract: createIniPanelShellControlPageContract(), createController: createMachineSessionControlPageController, isSupportedBrowserApiManifest: isSupportedIniPanelControlPageApiManifest, loadSessionState: loadMachineSessionForControlPageWorkflow, readonlyStatusApiManifest: createMachineSessionReadonlyStatusApiManifest(), readStatus: readMachineSessionReadonlyStatus, refresh: refreshMachineSessionControlPage, renderView: renderMachineSessionControlView, }, }; }