import { normalizeColorSchemePreference } from "./policies/color-scheme.js"; import type { ApplicationApi, ColorSchemePreference, RenderFailureReport, } from "./ports/in/application-api.js"; import type { ApplicationOutputPorts } from "./ports/out/application-output-ports.js"; import { decideChunkRecovery } from "./use-cases/decide-chunk-recovery.js"; export type { ApplicationApi, ApplicationOutputPorts }; /** * Builds the driving API consumed by inbound adapters. Concrete output ports * remain inside these closures and are never returned to React. */ export function createApplication( outputPorts: ApplicationOutputPorts, ): ApplicationApi { const session = Object.freeze({ getSnapshot: () => outputPorts.session.getState(), subscribe: (listener: () => void) => outputPorts.session.subscribe(listener), beginSignIn: (returnTo?: string) => outputPorts.session.beginSignIn(returnTo), signOut: () => outputPorts.session.signOut(), recover: () => outputPorts.session.recover(), }); const preferences = Object.freeze({ getColorScheme(): ColorSchemePreference { const result = outputPorts.preferences.read("COLOR_SCHEME"); return normalizeColorSchemePreference( result.ok ? result.value : undefined, ); }, setColorScheme(preference: ColorSchemePreference) { const normalized = normalizeColorSchemePreference(preference); return outputPorts.preferences.write("COLOR_SCHEME", normalized); }, }); const diagnostics = Object.freeze({ reportRenderFailure(report: RenderFailureReport) { try { outputPorts.diagnostics.emit("ui.render.failed", { route_id: report.routeId, build_id: report.buildId, component_boundary: report.boundaryName, }); } catch { // Diagnostics are best-effort and cannot become an application failure. } }, }); const runtime = Object.freeze({ async getReleaseSummary() { const release = await outputPorts.releaseInfo.getCurrent(); return Object.freeze({ buildId: release.buildId, releaseId: release.releaseId, configSchemaVersion: release.configSchemaVersion, apiContractVersion: release.apiContractVersion, }); }, }); const recovery = Object.freeze({ async recoverChunk(input: { chunkId: string; failureKind: "CHUNK_LOAD_FAILURE" | "DEPLOY_MISMATCH"; }) { try { const current = await outputPorts.releaseInfo.getCurrent(); const active = await outputPorts.releaseInfo.refresh(); if (!active.routeChunks[input.chunkId]) { return { action: "support" as const, reason: "active-chunk-unknown", }; } const decision = decideChunkRecovery({ failureKind: input.failureKind, manifestLoaded: true, currentBuildId: current.buildId, currentReleaseId: current.releaseId, activeBuildId: active.buildId, activeReleaseId: active.releaseId, storage: outputPorts.preferences, }); if (decision.action === "reload-once") { try { outputPorts.navigation.reload(); } catch { return { action: "support" as const, reason: "reload-failed", }; } } return decision; } catch { return { action: "support" as const, reason: "manifest-unavailable", }; } }, }); return Object.freeze({ session, preferences, diagnostics, runtime, recovery, }); }