From 28f1590b7acdb672ed690b981218fc5bb6310982 Mon Sep 17 00:00:00 2001 From: kshitijk4poor <82637225+kshitijk4poor@users.noreply.github.com> Date: Wed, 3 Jun 2026 12:09:18 +0530 Subject: [PATCH] fix(desktop): stop background session messages bleeding into the active transcript MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A still-busy background session (one the user toggled away from) keeps emitting updateSessionState() heartbeats — stream deltas, and especially the 'session busy' prompt-rejection errors from auto-drained queued turns. Each call invoked syncSessionStateToView() unconditionally, staging that session's messages into the shared $messages view. flushPendingViewState() guarded against the wrong session reaching the view, but only one requestAnimationFrame is scheduled per frame and pendingViewStateRef holds just the latest writer. So within a single frame a background write could overwrite an already-pending foreground write, and the stale background transcript (e.g. the red 'session busy' rows) would render on top of whatever session the user switched to — appearing to 'bleed' into every session. Guard at the staging site: a session may only stage into the view when it is the currently-active session. Background sessions still update their own cache entry; they just never touch $messages. Pure render fix, no behavior change to queuing, interrupt, or drain. --- .../app/session/hooks/use-session-state-cache.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/apps/desktop/src/app/session/hooks/use-session-state-cache.ts b/apps/desktop/src/app/session/hooks/use-session-state-cache.ts index 683d342dd..b2597fea8 100644 --- a/apps/desktop/src/app/session/hooks/use-session-state-cache.ts +++ b/apps/desktop/src/app/session/hooks/use-session-state-cache.ts @@ -95,6 +95,19 @@ export function useSessionStateCache({ const syncSessionStateToView = useCallback( (sessionId: string, state: ClientSessionState) => { + // Only the currently-viewed session may stage into the shared `$messages` + // view. A background session (e.g. one still busy and emitting stream / + // error updates after the user toggled away) must update its own cache + // entry but never the view — otherwise its messages clobber the + // foreground transcript and appear to "bleed" into every other session. + // The flush below also re-checks the active id, but staging here is what + // prevents a background write from overwriting an already-pending + // foreground write within the same animation frame (only one RAF is + // scheduled, so the last `pendingViewStateRef` writer would otherwise win). + if (sessionId !== activeSessionIdRef.current) { + return + } + pendingViewStateRef.current = { sessionId, state } if (viewSyncRafRef.current !== null) {