1import { Outlet, createFileRoute, redirect } from "@tanstack/react-router";
2
3import { ThreadRouteView } from "../components/ThreadRouteView";
4import { isAppModalPage } from "../components/sidebar/mainAppLocation";
5import { SidebarInset } from "../components/ui/sidebar";
6import { resolveThreadRouteTarget } from "../threadRoutes";
7import { retainThreadFindForRoute } from "../components/chat/threadFindStore";
8import { ThreadLinkPrompt } from "../components/promptLinks/ThreadLinkPrompt";
9import { useShownThreadRouteTarget } from "../hooks/useShownThreadRouteTarget";
10
11function ChatRouteLayout() {
12 // Render both routes here so draft promotion keeps the same ChatView mounted.
13 const { shownTarget, modalOpen } = useShownThreadRouteTarget();
14 return (
15 <>
16 {shownTarget?.kind === "server" && !modalOpen ? (
17 <ThreadLinkPrompt threadRef={shownTarget.threadRef} />
18 ) : null}
19 {shownTarget ? (
20 <ThreadRouteView target={shownTarget} background={modalOpen} />
21 ) : modalOpen ? (
22 <SidebarInset className="h-dvh" />
23 ) : (
24 <Outlet />
25 )}
26 {modalOpen ? <Outlet /> : null}
27 </>
28 );
29}
30
31export const Route = createFileRoute("/_chat")({
32 beforeLoad: async ({ context, location }) => {
33 if (
34 context.authGateState.status !== "authenticated" &&
35 context.authGateState.status !== "hosted-static"
36 ) {
37 throw redirect({ to: "/pair", replace: true });
38 }
39 return { appModalPage: isAppModalPage(location.pathname) };
40 },
41 component: ChatRouteLayout,
42 // Every match carries the whole location's params, so this layout sees the thread route's too.
43 onEnter: ({ context, params }) => retainThreadFindForRouteMatch(context.appModalPage, params),
44 onStay: ({ context, params }) => retainThreadFindForRouteMatch(context.appModalPage, params),
45 onLeave: () => retainThreadFindForRoute({ modalPage: false, threadRef: null }),
46});
47
48function retainThreadFindForRouteMatch(
49 modalPage: boolean,
50 params: Partial<Record<"environmentId" | "threadId", string>>,
51) {
52 const target = resolveThreadRouteTarget(params);
53 retainThreadFindForRoute({
54 modalPage,
55 threadRef: target?.kind === "server" ? target.threadRef : null,
56 });
57}