Skip to content
Merged
33 changes: 29 additions & 4 deletions apps/web/src/components/ChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ import {
useState,
} from "react";
import { flushSync } from "react-dom";
import { useLocation, useNavigate } from "@tanstack/react-router";
import { useLocation, useNavigate, useRouter } from "@tanstack/react-router";
import { assistantCitationFromLocation } from "../lib/assistantCitationNavigation";
import { isMacPlatform } from "../lib/utils";
import type { AssistantCitationSourceAnchor } from "~/lib/assistantTextSelection";
Expand Down Expand Up @@ -391,9 +391,10 @@ import {
useThreadRefs,
useThreadVisibleTurnItems,
useThreadCheckpointSummaries,
readThreadShell,
waitForThreadShell,
} from "../state/entities";
import { useEnvironmentShellHasSnapshot } from "../state/shell";
import { environmentShell, useEnvironmentShellHasSnapshot } from "../state/shell";
import { ChatComposer, type ChatComposerHandle } from "./chat/ChatComposer";
import { createPageScrollController, type PageScrollKey } from "./chat/pageScrollController";
import { isTimelineScrollTarget } from "./chat/timelineScrollTarget";
Expand Down Expand Up @@ -574,6 +575,7 @@ const EMPTY_FEEDBACK_SUBMISSIONS: ReadonlyArray<CodexFeedbackSubmission> = [];
// second, and every server-side visit is a full command dispatch plus a
// broadcast to all shell subscribers.
const VISIT_DISPATCH_THROTTLE_MS = 10_000;
const RELATED_THREAD_SHELL_WAIT_MS = 1_000;
const EMPTY_PROVIDER_SKILLS: ServerProvider["skills"] = [];
const EMPTY_AUTO_UPDATE_ENVIRONMENTS: ReadonlyArray<EnvironmentPresentation> = [];
function useDraftHeroLayoutTransition(
Expand Down Expand Up @@ -1198,6 +1200,7 @@ export default function ChatView(props: ChatViewProps) {
);
const timestampFormat = settings.timestampFormat;
const navigate = useNavigate();
const router = useRouter();
// Selects primitives: an object here is new on every router store update, which
// re-renders ChatView on unrelated navigation state changes.
const citationHref = useLocation({ select: (location) => location.href });
Expand Down Expand Up @@ -5033,10 +5036,32 @@ export default function ChatView(props: ChatViewProps) {
}
};
const handleStopBackgroundWorkStable = useStableCallback(handleStopBackgroundWork);
const onOpenRelatedThread = (threadId: ThreadId) => {
const openRelatedThreadGenerationRef = useRef(0);
const onOpenRelatedThread = async (threadId: ThreadId) => {
const generation = ++openRelatedThreadGenerationRef.current;
const threadRef = scopeThreadRef(environmentId, threadId);
const isEnvironmentLive = () =>
appAtomRegistry.get(environmentShell.stateValueAtom(environmentId)).status === "live";
// A just-launched child can reach this timeline before its shell syncs.
if (isEnvironmentLive() && readThreadShell(threadRef) === null) {
const location = router.latestLocation;
await waitForThreadShell(threadRef, RELATED_THREAD_SHELL_WAIT_MS);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
if (
generation !== openRelatedThreadGenerationRef.current ||
currentRouteThreadKeyRef.current !== routeThreadKey ||
router.latestLocation !== location
) {
return;
}
}
const shell = readThreadShell(threadRef);
if (shell === null ? isEnvironmentLive() : shell.deletedAt != null) {
toastManager.add({ type: "info", title: "This thread is no longer available" });
return;
}
void navigate({
to: "/$environmentId/$threadId",
params: buildThreadRouteParams(scopeThreadRef(environmentId, threadId)),
params: buildThreadRouteParams(threadRef),
});
};
const onOpenRelatedThreadStable = useStableCallback(onOpenRelatedThread);
Expand Down
Loading