Skip to content

fix(web): reset send phase when latest turn settles#1393

Open
aucre-coder wants to merge 7 commits intopingdotgg:mainfrom
aucre-coder:settle-issue
Open

fix(web): reset send phase when latest turn settles#1393
aucre-coder wants to merge 7 commits intopingdotgg:mainfrom
aucre-coder:settle-issue

Conversation

@aucre-coder
Copy link

@aucre-coder aucre-coder commented Mar 25, 2026

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:

  • the send button keeps spinning
  • the user cannot send another message in the same thread
  • navigating away from the thread and back clears the issue

This was first observed on Windows, but it is not Windows-exclusive and can also occur in the web build.

That suggests the local ChatView send state can remain stuck even after the turn has already settled.

What changed

Added a small useEffect in ChatView that resets sendPhase back to "idle" whenever:

  • sendPhase is still active
  • latestTurnSettled becomes true

This keeps the local send state aligned with the actual turn lifecycle and avoids requiring a thread remount to recover.

Why this should exist

ChatView already clears sendPhase for some intermediate states such as:

  • thread entering running
  • pending approval
  • pending user input
  • thread error

But 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:

Before

After:

After

Manual test

  1. Open a thread.
  2. Send a message.
  3. Wait for the assistant to fully finish.
  4. Without leaving the thread, send another message immediately.

Expected result after this change:

  • the send button stops spinning once the turn is settled
  • the next message can be sent in the same thread without navigating away and back

Scope

  • single-file change
  • no backend or protocol changes
  • no unrelated cleanup

Note

Low Risk
Low risk UI-state fix limited to ChatView send 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 sendPhase once a send results in a new, settled turn.

ChatView now records a baseline turnId when sending begins and adds an effect to call resetSendPhase() when isLatestTurnSettled is true and the latest settled turn’s turnId differs from the baseline. session-logic adds hasSettledLatestTurnAdvanced() 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 ChatView when the latest turn settles and advances

  • Adds a sendBaselineTurnIdRef in ChatView.tsx to record the active turn ID at the moment a send begins.
  • A new useEffect calls resetSendPhase automatically when hasSettledLatestTurnAdvanced returns true — i.e., the latest turn is settled and its ID has moved past the baseline.
  • Adds hasSettledLatestTurnAdvanced in session-logic.ts as a small utility that combines isLatestTurnSettled with a turn ID advancement check.

Macroscope summarized b71f7b5.

@coderabbitai
Copy link

coderabbitai bot commented Mar 25, 2026

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: b63a6dd0-9d31-4f7e-87a8-b5f1e0529a6a

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment on lines +2099 to +2102
if (!latestTurnSettled) {
return;
}
resetSendPhase();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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.
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment on lines +2111 to +2112
if (turnCompletedAtMs < sendStartedAtMs) {
return;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Copy link
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

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.
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant