From 0381ec7e538ecc5e17e1fae7b722240e3d033ad0 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 17 Jul 2026 16:47:42 +0000 Subject: [PATCH] perf(web): avoid visit writes during thread streaming Co-authored-by: Julius Marminge --- apps/web/src/components/ChatView.tsx | 27 +++---------------- .../src/hooks/useMarkActiveThreadVisited.ts | 17 ++++++++++++ 2 files changed, 21 insertions(+), 23 deletions(-) create mode 100644 apps/web/src/hooks/useMarkActiveThreadVisited.ts diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx index f5ea5bb1eba..439475bac90 100644 --- a/apps/web/src/components/ChatView.tsx +++ b/apps/web/src/components/ChatView.tsx @@ -93,7 +93,6 @@ import { togglePendingUserInputOptionSelection, type PendingUserInputDraftAnswer, } from "../pendingUserInput"; -import { useUiStateStore } from "../uiStateStore"; import { buildPlanImplementationThreadTitle, buildPlanImplementationPrompt, @@ -109,6 +108,7 @@ import { type Thread, type TurnDiffSummary, } from "../types"; +import { useMarkActiveThreadVisited } from "../hooks/useMarkActiveThreadVisited"; import { useTheme } from "../hooks/useTheme"; import { useTurnDiffSummaries } from "../hooks/useTurnDiffSummaries"; import { isCommandPaletteOpen } from "../commandPaletteContext"; @@ -1036,9 +1036,9 @@ function ChatViewContent(props: ChatViewProps) { const composerDraftTarget: ScopedThreadRef | DraftId = routeKind === "server" ? routeThreadRef : props.draftId; const serverThread = useThread(routeKind === "server" ? routeThreadRef : null); - const markThreadVisited = useUiStateStore((store) => store.markThreadVisited); - const activeThreadLastVisitedAt = useUiStateStore((store) => - routeKind === "server" ? store.threadLastVisitedAtById[routeThreadKey] : undefined, + useMarkActiveThreadVisited( + routeKind === "server" ? routeThreadKey : null, + serverThread?.latestTurn?.completedAt ?? null, ); const settings = useEnvironmentSettings(environmentId); const setStickyComposerModelSelection = useComposerDraftStore( @@ -1599,25 +1599,6 @@ function ChatViewContent(props: ChatViewProps) { [openOrReuseProjectDraftThread], ); - useEffect(() => { - if (!serverThread?.id) return; - const threadUpdatedAt = Date.parse(serverThread.updatedAt); - if (Number.isNaN(threadUpdatedAt)) return; - const lastVisitedAt = activeThreadLastVisitedAt ? Date.parse(activeThreadLastVisitedAt) : NaN; - if (!Number.isNaN(lastVisitedAt) && lastVisitedAt >= threadUpdatedAt) return; - - markThreadVisited( - scopedThreadKey(scopeThreadRef(serverThread.environmentId, serverThread.id)), - serverThread.updatedAt, - ); - }, [ - activeThreadLastVisitedAt, - markThreadVisited, - serverThread?.environmentId, - serverThread?.id, - serverThread?.updatedAt, - ]); - const selectedProviderByThreadId = composerActiveProvider ?? null; const threadProvider = activeThread?.modelSelection.instanceId ?? diff --git a/apps/web/src/hooks/useMarkActiveThreadVisited.ts b/apps/web/src/hooks/useMarkActiveThreadVisited.ts new file mode 100644 index 00000000000..408166c9636 --- /dev/null +++ b/apps/web/src/hooks/useMarkActiveThreadVisited.ts @@ -0,0 +1,17 @@ +import { useEffect } from "react"; + +import { useUiStateStore } from "../uiStateStore"; + +export function useMarkActiveThreadVisited( + threadKey: string | null, + latestTurnCompletedAt: string | null, +): void { + const markThreadVisited = useUiStateStore((store) => store.markThreadVisited); + + useEffect(() => { + if (threadKey === null || latestTurnCompletedAt === null) { + return; + } + markThreadVisited(threadKey, latestTurnCompletedAt); + }, [latestTurnCompletedAt, markThreadVisited, threadKey]); +}