From b92deff67e11f543af261b6162886761793c2bdd Mon Sep 17 00:00:00 2001 From: andyst-dev <150129844+andyst-dev@users.noreply.github.com> Date: Tue, 11 Aug 2026 13:53:40 +0200 Subject: [PATCH] fix(desktop): stop chat panes polling the transcript while inactive MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every v2 chat pane polls chat.getSnapshot on a timer (4fps / 250ms) with refetchIntervalInBackground: true, gated only on sessionId existing — not on pane visibility. An idle/background chat pane therefore refetches the whole conversation 4x/sec forever, and the serialize/parse cost scales with transcript length. With several projects each holding a chat pane open, that additive work on the single renderer thread degrades responsiveness (#6339). Gate the polling on the pane being the active tab: propagate ctx.isActive from the pane registry through ChatPane and ChatPaneInterface into useChatDisplay, which now sets refetchInterval to false (and disables background polling) while the pane is not active. The cached snapshot stays mounted, so returning to the pane resumes fresh. Defaults to true when the prop is omitted, so other call sites keep polling as before. Fixes #6339 --- .../ChatPaneInterface.tsx | 2 ++ .../WorkspaceChatInterface/types.ts | 6 +++++ .../useWorkspaceChatDisplay.ts | 25 ++++++++++++++++--- .../hooks/usePaneRegistry/usePaneRegistry.tsx | 1 + 4 files changed, 31 insertions(+), 3 deletions(-) diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatPane/components/WorkspaceChatInterface/ChatPaneInterface.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatPane/components/WorkspaceChatInterface/ChatPaneInterface.tsx index 604bccbc002..bb42ffaa1d0 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatPane/components/WorkspaceChatInterface/ChatPaneInterface.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatPane/components/WorkspaceChatInterface/ChatPaneInterface.tsx @@ -195,6 +195,7 @@ export function ChatPaneInterface({ organizationId, cwd, isFocused, + isActive, getOrCreateSession, onResetSession, onUserMessageSubmitted, @@ -294,6 +295,7 @@ export function ChatPaneInterface({ sessionId, workspaceId, enabled: Boolean(sessionId), + isActive, }); const { commands, diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatPane/components/WorkspaceChatInterface/types.ts b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatPane/components/WorkspaceChatInterface/types.ts index 6c4f6b99b53..b89e92ef1ed 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatPane/components/WorkspaceChatInterface/types.ts +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatPane/components/WorkspaceChatInterface/types.ts @@ -13,6 +13,12 @@ export interface ChatPaneInterfaceProps { organizationId: string | null; cwd: string; isFocused: boolean; + /** + * True while this chat pane is the focused/active tab. Gates background + * chat polling so idle/background panes stop refetching the full + * transcript on a timer (#6339). + */ + isActive?: boolean; getOrCreateSession: () => Promise; onResetSession: () => Promise; onUserMessageSubmitted?: (message: string) => void; diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatPane/hooks/useWorkspaceChatDisplay/useWorkspaceChatDisplay.ts b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatPane/hooks/useWorkspaceChatDisplay/useWorkspaceChatDisplay.ts index 55fcc63bdba..f8455fb4d8c 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatPane/hooks/useWorkspaceChatDisplay/useWorkspaceChatDisplay.ts +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatPane/hooks/useWorkspaceChatDisplay/useWorkspaceChatDisplay.ts @@ -9,6 +9,14 @@ interface UseChatDisplayOptions { workspaceId: string; enabled?: boolean; fps?: number; + /** + * True while this chat pane is the focused/active tab. Idle panes in the + * background keep polling the full transcript on a timer even when nothing + * is running — that serialize/parse work scales with transcript length and + * adds up across several open panes (#6339). Gate background polling on + * this so hidden panes don't refetch until they become active again. + */ + isActive?: boolean; } // Retention window for an inactive session's cached snapshot (full message @@ -117,7 +125,13 @@ function getLegacyImagePayload( } export function useChatDisplay(options: UseChatDisplayOptions) { - const { sessionId, workspaceId, enabled = true, fps = 4 } = options; + const { + sessionId, + workspaceId, + enabled = true, + fps = 4, + isActive = true, + } = options; const [commandError, setCommandError] = useState(null); const queryInput = sessionId === null ? undefined : { sessionId, workspaceId }; @@ -125,8 +139,13 @@ export function useChatDisplay(options: UseChatDisplayOptions) { const refetchIntervalMs = toRefetchIntervalMs(fps); const queryOptions = { enabled: isQueryEnabled && queryInput !== undefined, - refetchInterval: refetchIntervalMs, - refetchIntervalInBackground: true, + // Only poll while this pane is the active tab. Idle/background panes + // otherwise refetch the whole transcript on a timer forever, and that + // serialize/parse cost scales with conversation length — it adds up + // across several open panes and degrades responsiveness (#6339). The + // cached snapshot stays mounted, so returning to the pane resumes fresh. + refetchInterval: isActive ? refetchIntervalMs : false, + refetchIntervalInBackground: isActive, refetchOnWindowFocus: false, gcTime: CHAT_QUERY_GC_TIME_MS, } as const; diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/usePaneRegistry.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/usePaneRegistry.tsx index a02fcc75de2..3ecbc2b88f3 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/usePaneRegistry.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/usePaneRegistry.tsx @@ -518,6 +518,7 @@ export function usePaneRegistry({ ctx.actions.updateData({ ...data, sessionId: id }) }