import { OrchestrationEvent, ThreadId, type ServerLifecycleWelcomePayload, } from "@t3tools/contracts"; import { Outlet, createRootRouteWithContext, type ErrorComponentProps, useNavigate, useLocation, } from "@tanstack/react-router"; import { useEffect, useEffectEvent, useRef } from "react"; import { QueryClient, useQueryClient } from "@tanstack/react-query"; import { Throttler } from "@tanstack/react-pacer"; import { APP_DISPLAY_NAME } from "../branding"; import { AppSidebarLayout } from "../components/AppSidebarLayout"; import { Button } from "../components/ui/button"; import { AnchoredToastProvider, ToastProvider, toastManager } from "../components/ui/toast"; import { resolveAndPersistPreferredEditor } from "../editorPreferences"; import { readNativeApi } from "../nativeApi"; import { type ServerConfigUpdateSource, useServerConfig, useServerConfigUpdatedSubscription, useServerWelcomeSubscription, } from "../rpc/serverState"; import { ServerStateBootstrap } from "../rpc/serverStateBootstrap"; import { clearPromotedDraftThread, clearPromotedDraftThreads, useComposerDraftStore, } from "../composerDraftStore"; import { useStore } from "../store"; import { useUiStateStore } from "../uiStateStore"; import { useTerminalStateStore } from "../terminalStateStore"; import { terminalRunningSubprocessFromEvent } from "../terminalActivity"; import { migrateLocalSettingsToServer } from "../hooks/useSettings"; import { providerQueryKeys } from "../lib/providerReactQuery"; import { projectQueryKeys } from "../lib/projectReactQuery"; import { collectActiveTerminalThreadIds } from "../lib/terminalStateCleanup"; import { deriveOrchestrationBatchEffects } from "../orchestrationEventEffects"; import { createOrchestrationRecoveryCoordinator } from "../orchestrationRecovery"; export const Route = createRootRouteWithContext<{ queryClient: QueryClient; }>()({ component: RootRouteView, errorComponent: RootRouteErrorView, head: () => ({ meta: [{ name: "title", content: APP_DISPLAY_NAME }], }), }); function RootRouteView() { if (!readNativeApi()) { return (

Connecting to {APP_DISPLAY_NAME} server...

); } return ( <> ); } function RootRouteErrorView({ error, reset }: ErrorComponentProps) { const message = errorMessage(error); const details = errorDetails(error); return (

{APP_DISPLAY_NAME}

Something went wrong.

{message}

Show error details Hide error details
            {details}
          
); } function errorMessage(error: unknown): string { if (error instanceof Error && error.message.trim().length > 0) { return error.message; } if (typeof error === "string" && error.trim().length > 0) { return error; } return "An unexpected router error occurred."; } function errorDetails(error: unknown): string { if (error instanceof Error) { return error.stack ?? error.message; } if (typeof error === "string") { return error; } try { return JSON.stringify(error, null, 2); } catch { return "No additional error details are available."; } } function EventRouter() { const applyOrchestrationEvents = useStore((store) => store.applyOrchestrationEvents); const syncServerReadModel = useStore((store) => store.syncServerReadModel); const setProjectExpanded = useUiStateStore((store) => store.setProjectExpanded); const syncProjects = useUiStateStore((store) => store.syncProjects); const syncThreads = useUiStateStore((store) => store.syncThreads); const clearThreadUi = useUiStateStore((store) => store.clearThreadUi); const removeTerminalState = useTerminalStateStore((store) => store.removeTerminalState); const removeOrphanedTerminalStates = useTerminalStateStore( (store) => store.removeOrphanedTerminalStates, ); const queryClient = useQueryClient(); const navigate = useNavigate(); const pathname = useLocation({ select: (loc) => loc.pathname }); const pathnameRef = useRef(pathname); const handledBootstrapThreadIdRef = useRef(null); const handledConfigReplayRef = useRef(false); const disposedRef = useRef(false); const bootstrapFromSnapshotRef = useRef<() => Promise>(async () => undefined); const serverConfig = useServerConfig(); pathnameRef.current = pathname; const handleWelcome = useEffectEvent((payload: ServerLifecycleWelcomePayload) => { migrateLocalSettingsToServer(); void (async () => { await bootstrapFromSnapshotRef.current(); if (disposedRef.current) { return; } if (!payload.bootstrapProjectId || !payload.bootstrapThreadId) { return; } setProjectExpanded(payload.bootstrapProjectId, true); if (pathnameRef.current !== "/") { return; } if (handledBootstrapThreadIdRef.current === payload.bootstrapThreadId) { return; } await navigate({ to: "/$threadId", params: { threadId: payload.bootstrapThreadId }, replace: true, }); handledBootstrapThreadIdRef.current = payload.bootstrapThreadId; })().catch(() => undefined); }); const handleServerConfigUpdated = useEffectEvent( ({ payload, source, }: { readonly payload: import("@t3tools/contracts").ServerConfigUpdatedPayload; readonly source: ServerConfigUpdateSource; }) => { const isReplay = !handledConfigReplayRef.current; handledConfigReplayRef.current = true; if (isReplay || source !== "keybindingsUpdated") { return; } const issue = payload.issues.find((entry) => entry.kind.startsWith("keybindings.")); if (!issue) { toastManager.add({ type: "success", title: "Keybindings updated", description: "Keybindings configuration reloaded successfully.", }); return; } toastManager.add({ type: "warning", title: "Invalid keybindings configuration", description: issue.message, actionProps: { children: "Open keybindings.json", onClick: () => { const api = readNativeApi(); if (!api) { return; } void Promise.resolve(serverConfig ?? api.server.getConfig()) .then((config) => { const editor = resolveAndPersistPreferredEditor(config.availableEditors); if (!editor) { throw new Error("No available editors found."); } return api.shell.openInEditor(config.keybindingsConfigPath, editor); }) .catch((error) => { toastManager.add({ type: "error", title: "Unable to open keybindings file", description: error instanceof Error ? error.message : "Unknown error opening file.", }); }); }, }, }); }, ); useEffect(() => { const api = readNativeApi(); if (!api) return; let disposed = false; disposedRef.current = false; const recovery = createOrchestrationRecoveryCoordinator(); let needsProviderInvalidation = false; const reconcileSnapshotDerivedState = () => { const threads = useStore.getState().threads; const projects = useStore.getState().projects; syncProjects(projects.map((project) => ({ id: project.id, cwd: project.cwd }))); syncThreads( threads.map((thread) => ({ id: thread.id, seedVisitedAt: thread.updatedAt ?? thread.createdAt, })), ); clearPromotedDraftThreads(threads.map((thread) => thread.id)); const draftThreadIds = Object.keys( useComposerDraftStore.getState().draftThreadsByThreadId, ) as ThreadId[]; const activeThreadIds = collectActiveTerminalThreadIds({ snapshotThreads: threads.map((thread) => ({ id: thread.id, deletedAt: null })), draftThreadIds, }); removeOrphanedTerminalStates(activeThreadIds); }; const queryInvalidationThrottler = new Throttler( () => { if (!needsProviderInvalidation) { return; } needsProviderInvalidation = false; void queryClient.invalidateQueries({ queryKey: providerQueryKeys.all }); // Invalidate workspace entry queries so the @-mention file picker // reflects files created, deleted, or restored during this turn. void queryClient.invalidateQueries({ queryKey: projectQueryKeys.all }); }, { wait: 100, leading: false, trailing: true, }, ); const applyEventBatch = (events: ReadonlyArray) => { const nextEvents = recovery.markEventBatchApplied(events); if (nextEvents.length === 0) { return; } const batchEffects = deriveOrchestrationBatchEffects(nextEvents); const needsProjectUiSync = nextEvents.some( (event) => event.type === "project.created" || event.type === "project.meta-updated" || event.type === "project.deleted", ); if (batchEffects.needsProviderInvalidation) { needsProviderInvalidation = true; void queryInvalidationThrottler.maybeExecute(); } applyOrchestrationEvents(nextEvents); if (needsProjectUiSync) { const projects = useStore.getState().projects; syncProjects(projects.map((project) => ({ id: project.id, cwd: project.cwd }))); } const needsThreadUiSync = nextEvents.some( (event) => event.type === "thread.created" || event.type === "thread.deleted", ); if (needsThreadUiSync) { const threads = useStore.getState().threads; syncThreads( threads.map((thread) => ({ id: thread.id, seedVisitedAt: thread.updatedAt ?? thread.createdAt, })), ); } const draftStore = useComposerDraftStore.getState(); for (const threadId of batchEffects.clearPromotedDraftThreadIds) { clearPromotedDraftThread(threadId); } for (const threadId of batchEffects.clearDeletedThreadIds) { draftStore.clearDraftThread(threadId); clearThreadUi(threadId); } for (const threadId of batchEffects.removeTerminalStateThreadIds) { removeTerminalState(threadId); } }; const recoverFromSequenceGap = async (): Promise => { if (!recovery.beginReplayRecovery("sequence-gap")) { return; } try { const events = await api.orchestration.replayEvents(recovery.getState().latestSequence); if (!disposed) { applyEventBatch(events); } } catch { recovery.failReplayRecovery(); void fallbackToSnapshotRecovery(); return; } if (!disposed && recovery.completeReplayRecovery()) { void recoverFromSequenceGap(); } }; const runSnapshotRecovery = async (reason: "bootstrap" | "replay-failed"): Promise => { if (!recovery.beginSnapshotRecovery(reason)) { return; } try { const snapshot = await api.orchestration.getSnapshot(); if (!disposed) { syncServerReadModel(snapshot); reconcileSnapshotDerivedState(); if (recovery.completeSnapshotRecovery(snapshot.snapshotSequence)) { void recoverFromSequenceGap(); } } } catch { // Keep prior state and wait for welcome or a later replay attempt. recovery.failSnapshotRecovery(); } }; const bootstrapFromSnapshot = async (): Promise => { await runSnapshotRecovery("bootstrap"); }; bootstrapFromSnapshotRef.current = bootstrapFromSnapshot; const fallbackToSnapshotRecovery = async (): Promise => { await runSnapshotRecovery("replay-failed"); }; const unsubDomainEvent = api.orchestration.onDomainEvent((event) => { const action = recovery.classifyDomainEvent(event.sequence); if (action === "apply") { applyEventBatch([event]); return; } if (action === "recover") { void recoverFromSequenceGap(); } }); const unsubTerminalEvent = api.terminal.onEvent((event) => { const hasRunningSubprocess = terminalRunningSubprocessFromEvent(event); if (hasRunningSubprocess === null) { return; } useTerminalStateStore .getState() .setTerminalActivity( ThreadId.makeUnsafe(event.threadId), event.terminalId, hasRunningSubprocess, ); }); return () => { disposed = true; disposedRef.current = true; needsProviderInvalidation = false; queryInvalidationThrottler.cancel(); unsubDomainEvent(); unsubTerminalEvent(); }; }, [ applyOrchestrationEvents, navigate, queryClient, removeTerminalState, removeOrphanedTerminalStates, clearThreadUi, setProjectExpanded, syncProjects, syncServerReadModel, syncThreads, ]); useServerWelcomeSubscription(handleWelcome); useServerConfigUpdatedSubscription(handleServerConfigUpdated); return null; } function DesktopProjectBootstrap() { // Desktop hydration runs through EventRouter project + orchestration sync. return null; }