History

fix: prevent fallback effect from immediately resetting sendPhase on follow-up messages

The fallback effect that catches missed 'running' phase transitions was incorrectly firing on follow-up messages. When beginSendPhase was called, latestTurnSettled was still true from the previous completed turn, causing an immediate resetSendPhase before the server could create the new turn. Fix: record the turnId at the time beginSendPhase is called (via a ref), and only allow the fallback reset when the latest turn differs from the one recorded — meaning a new turn was actually created and has settled. Applied via @cursor push command

Cursor Agent committed 7 months ago

Commit c91e8b21 parent 9a9be391 file · +19 −6Browse files

Unified · Split

apps/web/src/components/ChatView.tsx+19−6
@@ -2161,14 +2161,21 @@ export default function ChatView({ threadId }: ChatViewProps) { · 2160 unmodified lines
21612161 };
21622162 }, [phase, sendPhase]);
21632163
2164− const beginSendPhase = useCallback((nextPhase: Exclude<SendPhase, "idle">) => {
2165− setSendStartedAt((current) => current ?? new Date().toISOString());
2166− setSendPhase(nextPhase);
2167− }, []);
2164+ const turnIdAtSendRef = useRef<string | null>(null);
2165+
2166+ const beginSendPhase = useCallback(
2167+ (nextPhase: Exclude<SendPhase, "idle">) => {
2168+ setSendStartedAt((current) => current ?? new Date().toISOString());
2169+ turnIdAtSendRef.current = activeLatestTurn?.turnId ?? null;
2170+ setSendPhase(nextPhase);
2171+ },
2172+ [activeLatestTurn?.turnId],
2173+ );
21682174
21692175 const resetSendPhase = useCallback(() => {
21702176 setSendPhase("idle");
21712177 setSendStartedAt(null);
2178+ turnIdAtSendRef.current = null;
21722179 }, []);
21732180
21742181 useEffect(() => {
@@ -2198,12 +2205,18 @@ export default function ChatView({ threadId }: ChatViewProps) { · 23 unmodified lines
21982205 // this by checking whether the turn has settled while sendPhase is still
21992206 // non-idle — the turn completing is a definitive signal that "sending" is
22002207 // over.
2208+ //
2209+ // Guard: only reset when the latest turn differs from the one recorded at
2210+ // send time. Without this, a follow-up message on a thread whose previous
2211+ // turn already settled would be immediately reset before the server creates
2212+ // the new turn.
22012213 useEffect(() => {
22022214 if (sendPhase === "idle") return;
2203− if (phase !== "running" && phase !== "connecting" && latestTurnSettled) {
2215+ const turnChanged = activeLatestTurn?.turnId !== turnIdAtSendRef.current;
2216+ if (phase !== "running" && phase !== "connecting" && latestTurnSettled && turnChanged) {
22042217 resetSendPhase();
22052218 }
2206− }, [latestTurnSettled, phase, resetSendPhase, sendPhase]);
2219+ }, [activeLatestTurn?.turnId, latestTurnSettled, phase, resetSendPhase, sendPhase]);
22072220
22082221 useEffect(() => {
22092222 if (!activeThreadId) return;