From abce8fb08dd3c2c96375c3dd8b36229174917eec Mon Sep 17 00:00:00 2001 From: aucre-coder <70275106+aucre-coder@users.noreply.github.com> Date: Wed, 25 Mar 2026 01:31:32 +0100 Subject: [PATCH 1/4] fix(web): reset send phase when latest turn settles --- apps/web/src/components/ChatView.tsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx index 59ff4f73c8..935044e176 100644 --- a/apps/web/src/components/ChatView.tsx +++ b/apps/web/src/components/ChatView.tsx @@ -2092,6 +2092,16 @@ export default function ChatView({ threadId }: ChatViewProps) { sendPhase, ]); + useEffect(() => { + if (sendPhase === "idle") { + return; + } + if (!latestTurnSettled) { + return; + } + resetSendPhase(); + }, [latestTurnSettled, resetSendPhase, sendPhase]); + useEffect(() => { if (!activeThreadId) return; const previous = terminalOpenByThreadRef.current[activeThreadId] ?? false; From b067cbd9fa2e12c649d22acf9264d90ad25b93cb Mon Sep 17 00:00:00 2001 From: aucre-coder <70275106+aucre-coder@users.noreply.github.com> Date: Wed, 25 Mar 2026 02:04:13 +0100 Subject: [PATCH 2/4] fix(web): reset send phase after current turn settles Added checks for sendStartedAt and activeLatestTurn completion before resetting send phase. --- apps/web/src/components/ChatView.tsx | 30 ++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx index 935044e176..1296f96233 100644 --- a/apps/web/src/components/ChatView.tsx +++ b/apps/web/src/components/ChatView.tsx @@ -2093,14 +2093,28 @@ export default function ChatView({ threadId }: ChatViewProps) { ]); useEffect(() => { - if (sendPhase === "idle") { - return; - } - if (!latestTurnSettled) { - return; - } - resetSendPhase(); - }, [latestTurnSettled, resetSendPhase, sendPhase]); + if (sendPhase === "idle") { + return; + } + if (!latestTurnSettled) { + return; + } + if (!sendStartedAt || !activeLatestTurn?.completedAt) { + return; + } + + const sendStartedAtMs = Date.parse(sendStartedAt); + const turnCompletedAtMs = Date.parse(activeLatestTurn.completedAt); + if (Number.isNaN(sendStartedAtMs) || Number.isNaN(turnCompletedAtMs)) { + return; + } + if (turnCompletedAtMs < sendStartedAtMs) { + return; + } + + resetSendPhase(); +}, [activeLatestTurn?.completedAt, latestTurnSettled, resetSendPhase, sendPhase, sendStartedAt]); + useEffect(() => { if (!activeThreadId) return; From b59f3fcd53880128d80d3a8f91e9be466b121f57 Mon Sep 17 00:00:00 2001 From: aucre-coder <70275106+aucre-coder@users.noreply.github.com> Date: Wed, 25 Mar 2026 02:17:49 +0100 Subject: [PATCH 3/4] fix(web): reset send phase when latest turn settles --- apps/web/src/components/ChatView.tsx | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx index 935044e176..0fe3ebc58d 100644 --- a/apps/web/src/components/ChatView.tsx +++ b/apps/web/src/components/ChatView.tsx @@ -388,6 +388,7 @@ export default function ChatView({ threadId }: ChatViewProps) { const attachmentPreviewHandoffByMessageIdRef = useRef>({}); const attachmentPreviewHandoffTimeoutByMessageIdRef = useRef>({}); const sendInFlightRef = useRef(false); + const sendBaselineTurnIdRef = useRef(null); const dragDepthRef = useRef(0); const terminalOpenByThreadRef = useRef>({}); const setMessagesScrollContainerRef = useCallback((element: HTMLDivElement | null) => { @@ -2061,12 +2062,17 @@ export default function ChatView({ threadId }: ChatViewProps) { }; }, [phase]); - const beginSendPhase = useCallback((nextPhase: Exclude) => { - setSendStartedAt((current) => current ?? new Date().toISOString()); - setSendPhase(nextPhase); - }, []); + const beginSendPhase = useCallback( + (nextPhase: Exclude) => { + sendBaselineTurnIdRef.current = activeLatestTurn?.turnId ?? null; + setSendStartedAt((current) => current ?? new Date().toISOString()); + setSendPhase(nextPhase); + }, + [activeLatestTurn?.turnId], + ); const resetSendPhase = useCallback(() => { + sendBaselineTurnIdRef.current = null; setSendPhase("idle"); setSendStartedAt(null); }, []); @@ -2099,8 +2105,14 @@ export default function ChatView({ threadId }: ChatViewProps) { if (!latestTurnSettled) { return; } + + const latestTurnId = activeLatestTurn?.turnId ?? null; + if (latestTurnId === sendBaselineTurnIdRef.current) { + return; + } + resetSendPhase(); - }, [latestTurnSettled, resetSendPhase, sendPhase]); + }, [activeLatestTurn?.turnId, latestTurnSettled, resetSendPhase, sendPhase]); useEffect(() => { if (!activeThreadId) return; From b71f7b5b90b54c0da2f0ddb3c70d5243c31ef25c Mon Sep 17 00:00:00 2001 From: aucre-coder <70275106+aucre-coder@users.noreply.github.com> Date: Wed, 25 Mar 2026 02:34:12 +0100 Subject: [PATCH 4/4] fix: Reset send phase when latest turn settles Add a useEffect that calls resetSendPhase() once latestTurnSettled becomes true and sendPhase is not 'idle'. The effect returns early if sendPhase is 'idle' or latestTurnSettled is false, ensuring the sendPhase doesn't remain in a non-idle state after a turn finishes. --- apps/web/src/components/ChatView.tsx | 16 +++++++++------- apps/web/src/session-logic.ts | 11 +++++++++++ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx index 0fe3ebc58d..04d9976055 100644 --- a/apps/web/src/components/ChatView.tsx +++ b/apps/web/src/components/ChatView.tsx @@ -57,6 +57,7 @@ import { deriveWorkLogEntries, hasActionableProposedPlan, hasToolActivityForTurn, + hasSettledLatestTurnAdvanced, isLatestTurnSettled, formatElapsed, } from "../session-logic"; @@ -2102,17 +2103,18 @@ export default function ChatView({ threadId }: ChatViewProps) { if (sendPhase === "idle") { return; } - if (!latestTurnSettled) { - return; - } - - const latestTurnId = activeLatestTurn?.turnId ?? null; - if (latestTurnId === sendBaselineTurnIdRef.current) { + if ( + !hasSettledLatestTurnAdvanced( + activeLatestTurn, + activeThread?.session ?? null, + sendBaselineTurnIdRef.current, + ) + ) { return; } resetSendPhase(); - }, [activeLatestTurn?.turnId, latestTurnSettled, resetSendPhase, sendPhase]); + }, [activeLatestTurn, activeThread?.session, resetSendPhase, sendPhase]); useEffect(() => { if (!activeThreadId) return; diff --git a/apps/web/src/session-logic.ts b/apps/web/src/session-logic.ts index 83a95d6313..8a2d3e7913 100644 --- a/apps/web/src/session-logic.ts +++ b/apps/web/src/session-logic.ts @@ -139,6 +139,17 @@ export function isLatestTurnSettled( return true; } +export function hasSettledLatestTurnAdvanced( + latestTurn: LatestTurnTiming | null, + session: SessionActivityState | null, + baselineTurnId: TurnId | null, +): boolean { + if (!isLatestTurnSettled(latestTurn, session)) { + return false; + } + return (latestTurn?.turnId ?? null) !== baselineTurnId; +} + export function deriveActiveWorkStartedAt( latestTurn: LatestTurnTiming | null, session: SessionActivityState | null,