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", "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 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, }); return { pages: entries.map(({ id, href, title }) => ({ id, href, title, })), launcherLinks: createIniPanelLauncherLinkViewModel(entries), shellIntegrationSchema: createIniPanelShellIntegrationSchema(), shellIntegrationManifest, shellIntegrationReadiness, shellIntegrationWorkflowPlan, shellSessionHandoffSummarySchema: createIniPanelShellSessionHandoffSummarySchema(), shellSessionHandoffSummary, shellSessionHandoffCompatibilityReport, shellSessionHandoffDisplayViewModel, shellSessionHandoffSnapshot, launchApiSchema: createIniPanelLaunchApiSchema(), launchApiManifest: createIniPanelLaunchApiManifest(entries), isSupportedShellIntegrationManifest: isSupportedIniPanelShellIntegrationManifest, createShellIntegrationReadiness: createIniPanelShellIntegrationReadiness, createShellIntegrationWorkflowPlan: createIniPanelShellIntegrationWorkflowPlan, isSupportedShellSessionHandoffSummary: isSupportedIniPanelShellSessionHandoffSummary, createShellSessionHandoffSummarySchema: createIniPanelShellSessionHandoffSummarySchema, createShellSessionHandoffSummary: createIniPanelShellSessionHandoffSummary, createShellSessionHandoffCompatibilityReport: createIniPanelShellSessionHandoffCompatibilityReport, createShellSessionHandoffDisplayViewModel: createIniPanelShellSessionHandoffDisplayViewModel, createShellSessionHandoffSnapshot: createIniPanelShellSessionHandoffSnapshot, controlPage: { browserApiSchema: createControlPageBrowserApiSchema(), browserApiManifest: createControlPageBrowserApiManifest(), browserApiMethods: createControlPageBrowserApiMethods(), contract: createIniPanelShellControlPageContract(), createController: createMachineSessionControlPageController, isSupportedBrowserApiManifest: isSupportedIniPanelControlPageApiManifest, loadSessionState: loadMachineSessionForControlPageWorkflow, readonlyStatusApiManifest: createMachineSessionReadonlyStatusApiManifest(), readStatus: readMachineSessionReadonlyStatus, refresh: refreshMachineSessionControlPage, renderView: renderMachineSessionControlView, }, }; }