import { ThreadId } from "@t3tools/contracts";
import {
Outlet,
createRootRouteWithContext,
type ErrorComponentProps,
useNavigate,
useRouterState,
} from "@tanstack/react-router";
import { useEffect, useRef } from "react";
import { QueryClient, useQueryClient } from "@tanstack/react-query";
import { Throttler } from "@tanstack/react-pacer";
import { APP_DISPLAY_NAME } from "../branding";
import { Button } from "../components/ui/button";
import { AnchoredToastProvider, ToastProvider, toastManager } from "../components/ui/toast";
import { resolveAndPersistPreferredEditor } from "../editorPreferences";
import { serverConfigQueryOptions, serverQueryKeys } from "../lib/serverReactQuery";
import { readNativeApi } from "../nativeApi";
import { clearPromotedDraftThreads, useComposerDraftStore } from "../composerDraftStore";
import { useStore } from "../store";
import { useTerminalStateStore } from "../terminalStateStore";
import { terminalRunningSubprocessFromEvent } from "../terminalActivity";
import { onServerConfigUpdated, onServerWelcome } from "../wsNativeApi";
import { providerQueryKeys } from "../lib/providerReactQuery";
import { projectQueryKeys } from "../lib/projectReactQuery";
import { collectActiveTerminalThreadIds } from "../lib/terminalStateCleanup";
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 syncServerReadModel = useStore((store) => store.syncServerReadModel);
const setProjectExpanded = useStore((store) => store.setProjectExpanded);
const removeOrphanedTerminalStates = useTerminalStateStore(
(store) => store.removeOrphanedTerminalStates,
);
const queryClient = useQueryClient();
const navigate = useNavigate();
const pathname = useRouterState({ select: (state) => state.location.pathname });
const pathnameRef = useRef(pathname);
const handledBootstrapThreadIdRef = useRef(null);
pathnameRef.current = pathname;
useEffect(() => {
const api = readNativeApi();
if (!api) return;
let disposed = false;
let latestSequence = 0;
let syncing = false;
let pending = false;
let needsProviderInvalidation = false;
const flushSnapshotSync = async (): Promise => {
const snapshot = await api.orchestration.getSnapshot();
if (disposed) return;
latestSequence = Math.max(latestSequence, snapshot.snapshotSequence);
syncServerReadModel(snapshot);
clearPromotedDraftThreads(new Set(snapshot.threads.map((t) => t.id)));
const draftThreadIds = Object.keys(
useComposerDraftStore.getState().draftThreadsByThreadId,
) as ThreadId[];
const activeThreadIds = collectActiveTerminalThreadIds({
snapshotThreads: snapshot.threads,
draftThreadIds,
});
removeOrphanedTerminalStates(activeThreadIds);
if (pending) {
pending = false;
await flushSnapshotSync();
}
};
const syncSnapshot = async () => {
if (syncing) {
pending = true;
return;
}
syncing = true;
pending = false;
try {
await flushSnapshotSync();
} catch {
// Keep prior state and wait for next domain event to trigger a resync.
}
syncing = false;
};
const domainEventFlushThrottler = new Throttler(
() => {
if (needsProviderInvalidation) {
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 });
}
void syncSnapshot();
},
{
wait: 100,
leading: false,
trailing: true,
},
);
const unsubDomainEvent = api.orchestration.onDomainEvent((event) => {
if (event.sequence <= latestSequence) {
return;
}
latestSequence = event.sequence;
if (event.type === "thread.turn-diff-completed" || event.type === "thread.reverted") {
needsProviderInvalidation = true;
}
domainEventFlushThrottler.maybeExecute();
});
const unsubTerminalEvent = api.terminal.onEvent((event) => {
const hasRunningSubprocess = terminalRunningSubprocessFromEvent(event);
if (hasRunningSubprocess === null) {
return;
}
useTerminalStateStore
.getState()
.setTerminalActivity(
ThreadId.makeUnsafe(event.threadId),
event.terminalId,
hasRunningSubprocess,
);
});
const unsubWelcome = onServerWelcome((payload) => {
void (async () => {
await syncSnapshot();
if (disposed) {
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);
});
// onServerConfigUpdated replays the latest cached value synchronously
// during subscribe. Skip the toast for that replay so effect re-runs
// don't produce duplicate toasts.
let subscribed = false;
const unsubServerConfigUpdated = onServerConfigUpdated((payload) => {
void queryClient.invalidateQueries({ queryKey: serverQueryKeys.config() });
if (!subscribed) 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: () => {
void queryClient
.ensureQueryData(serverConfigQueryOptions())
.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.",
});
});
},
},
});
});
subscribed = true;
return () => {
disposed = true;
needsProviderInvalidation = false;
domainEventFlushThrottler.cancel();
unsubDomainEvent();
unsubTerminalEvent();
unsubWelcome();
unsubServerConfigUpdated();
};
}, [
navigate,
queryClient,
removeOrphanedTerminalStates,
setProjectExpanded,
syncServerReadModel,
]);
return null;
}
function DesktopProjectBootstrap() {
// Desktop hydration runs through EventRouter project + orchestration sync.
return null;
}