fix(web): reset send phase when latest turn settles#1393
fix(web): reset send phase when latest turn settles#1393aucre-coder wants to merge 7 commits intopingdotgg:mainfrom
Conversation
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: abce8fb08d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
apps/web/src/components/ChatView.tsx
Outdated
| if (!latestTurnSettled) { | ||
| return; | ||
| } | ||
| resetSendPhase(); |
There was a problem hiding this comment.
Keep send phase active until new turn actually starts
This reset runs as soon as sendPhase flips from idle while latestTurnSettled is still true from the previous turn, so a normal follow-up send immediately clears the local busy state before thread.turn.start is dispatched. In ChatView, that re-enables controls that are only guarded by isSendBusy (for example onRevertToTurnCount at apps/web/src/components/ChatView.tsx:2323), creating a race window during long pre-send work (worktree creation/setup script/model persistence) where destructive actions can run concurrently with an in-flight send.
Useful? React with 👍 / 👎.
Added checks for sendStartedAt and activeLatestTurn completion before resetting send phase.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b067cbd9fa
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
apps/web/src/components/ChatView.tsx
Outdated
| if (turnCompletedAtMs < sendStartedAtMs) { | ||
| return; |
There was a problem hiding this comment.
Remove cross-clock gating from send-phase reset
This guard compares sendStartedAt (set from the browser clock in beginSendPhase) against activeLatestTurn.completedAt (server-projected completion time), so on any client/server clock skew where the client is ahead, turnCompletedAtMs < sendStartedAtMs remains true even after the new turn settles and resetSendPhase() never runs. In that environment the composer can stay stuck in a busy state after completion—the same failure this effect is meant to clear—so the reset condition should be tied to turn identity/transition rather than absolute timestamps from different clocks.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
# Conflicts: # apps/web/src/components/ChatView.tsx
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.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b71f7b5b90
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| }, []); | ||
| const beginSendPhase = useCallback( | ||
| (nextPhase: Exclude<SendPhase, "idle">) => { | ||
| sendBaselineTurnIdRef.current = activeLatestTurn?.turnId ?? null; |
There was a problem hiding this comment.
Tie busy reset to the send's own turn
beginSendPhase snapshots activeLatestTurn into sendBaselineTurnIdRef at render time, and the new reset effect later clears sendPhase whenever any settled turn id differs from that baseline. During long pre-send work, a delayed snapshot/reconnect catch-up can advance latestTurn to an already-settled newer turn that was not created by this send, so resetSendPhase() fires before thread.turn.start completes and controls guarded only by isSendBusy become available too early. Fresh evidence in this revision is the new baseline-id comparison combined with this render-time snapshot, which still leaves that race window open.
Useful? React with 👍 / 👎.

Summary
This is a small, focused bug fix for a chat flow issue where the send button can remain in a loading state after the assistant has already finished responding.
The change is limited to
apps/web/src/components/ChatView.tsx.Problem
In some chat sessions, a thread can finish rendering the assistant response, but the composer can still behave as if a send is in progress. In that state:
This was first observed on Windows, but it is not Windows-exclusive and can also occur in the web build.
That suggests the local
ChatViewsend state can remain stuck even after the turn has already settled.What changed
Added a small
useEffectinChatViewthat resetssendPhaseback to"idle"whenever:sendPhaseis still activelatestTurnSettledbecomes trueThis keeps the local send state aligned with the actual turn lifecycle and avoids requiring a thread remount to recover.
Why this should exist
ChatViewalready clearssendPhasefor some intermediate states such as:runningBut it did not explicitly clear the local send state once the turn had fully settled. If that earlier transition is missed, the local spinner can stay stuck until the component unmounts/remounts.
This change closes that gap with a minimal, targeted fix.
Before / After
Before:
After:
Manual test
Expected result after this change:
Scope
Note
Low Risk
Low risk UI-state fix limited to
ChatViewsend lifecycle; main risk is prematurely clearing the send spinner if turn IDs are mis-tracked.Overview
Prevents the chat composer send button from getting stuck in a loading state by resetting
sendPhaseonce a send results in a new, settled turn.ChatViewnow records a baselineturnIdwhen sending begins and adds an effect to callresetSendPhase()whenisLatestTurnSettledis true and the latest settled turn’sturnIddiffers from the baseline.session-logicaddshasSettledLatestTurnAdvanced()to encapsulate that check.Written by Cursor Bugbot for commit b71f7b5. This will update automatically on new commits. Configure here.
Note
Reset send phase in
ChatViewwhen the latest turn settles and advancessendBaselineTurnIdRefin ChatView.tsx to record the active turn ID at the moment a send begins.useEffectcallsresetSendPhaseautomatically whenhasSettledLatestTurnAdvancedreturns true — i.e., the latest turn is settled and its ID has moved past the baseline.hasSettledLatestTurnAdvancedin session-logic.ts as a small utility that combinesisLatestTurnSettledwith a turn ID advancement check.Macroscope summarized b71f7b5.