fix(site/src/pages/AgentsPage): treat interrupting chats as busy in the composer - #28209
Conversation
…he composer The composer derived isStreaming from hasStreamState or a running chat status, excluding interrupting. Once an interrupt is requested the stream state is gone, so a chat still finalizing an interruption rendered the idle composer: no Stop button, the normal Send button, and no busy indication. That made the interrupting state visually indistinguishable from waiting. Include interrupting in the composer's streaming check and add Storybook coverage for the I1 state (interrupting with a queued message) alongside a running-state control.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2969ae989f
ℹ️ 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".
…rupting The interrupt POST resolves as soon as the request lands, but the worker may finalize the interruption for several seconds, and the backend rejects a second interrupt with 409. Gate the composer's stop action on the interrupting status in addition to the mutation's pending flag so neither the Stop button nor Escape re-fires an interrupt that the close state machine (I0/I1) would reject. Also trims the interrupting story comment to the non-obvious invariant only.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ad127ef2f7
ℹ️ 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".
…story The RunningShowsBusyComposer header comment restated what the story code and assertions already say; FE4 prohibits that narration.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3308c4f082
ℹ️ 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".
…ueued-message fixture The interrupting story now focuses the composer and sends Escape, asserting the interrupt callback is not invoked while finalization is still pending. The queued message is built from MockChatQueuedMessage instead of an inline literal so fixture drift type-checks. isActiveChatStatus is intentionally not reused for the composer's busy check: it means active for store synchronization, a broader contract than the composer needs, and conflating the two could show a Stop button for a future busy status where interrupting is not legal.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cde235bdc7
ℹ️ 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".
… role
Prefer getByRole("textbox", { name: "Chat message" }) over the test ID
when focusing the editor in the interrupting story, so a regression
that drops the accessible name fails instead of passing on a stale
test ID.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d6fbee2e6b
ℹ️ 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".
…eyboard users While an interrupt is finalizing, the composer's Stop button is disabled and skipped in tab order, leaving keyboard and screen-reader users with no indication that the interruption is still in progress. Wrap the button in a tooltip that reads "Interrupting..." while pending, and add an sr-only live region with role=status announcing the same. The interrupting story now asserts the live region text.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 93eda335d3
ℹ️ 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".
…during finalization While an interrupt finalizes, the interrupted turn's flushed stream state made the transcript render "Thinking" as if the agent were still producing output. deriveLiveStatus now takes chatStatus and the new interrupting phase outranks stream leftovers, so the activity slot reads "Interrupting" with a pause icon (matching the sidebar's interrupting status icon) instead. The interrupting story asserts the transcript label and the absence of Thinking.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e72c31927f
ℹ️ 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".
| // The transcript must not claim the agent is still thinking while | ||
| // the interruption finalizes. |
There was a problem hiding this comment.
Remove the new assertion-narrating comment
Fresh evidence after the earlier comment cleanups: this newly added comment merely restates the two assertions immediately below it, namely that the transcript shows Interrupting and does not show Thinking. Remove the narration and let the assertions describe the expected behavior, as required by FE4.
AGENTS.md reference: site/AGENTS.md:L15-L15
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in fcde0dd. Removed the comment; the assertions speak for themselves.
| ], | ||
| [ | ||
| "interrupting", | ||
| { chatStatus: "interrupting" as const }, |
There was a problem hiding this comment.
Type the case table instead of asserting the literal
The as const assertion is only compensating for widening in the heterogeneous it.each table and is avoidable by typing the case table or using satisfies. Remove the assertion so this test follows FE2's requirement to avoid unnecessary as casts rather than weakening inference at the value site.
AGENTS.md reference: site/AGENTS.md:L11-L12
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in fcde0dd. The it.each table is now a typed cases array (phase label, overrides, expected LiveStatusModel) and the expected-status fixtures use satisfies, so the interrupting row needs no cast at the value site.
….each typing Removes a comment that restated the story assertions, and replaces the as-const cast in the deriveLiveStatus case table with a typed cases array plus satisfies annotations on the expected status fixtures.
|
@codex review |
|
Codex Review: Didn't find any major issues. Hooray! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
Fixes CODAGT-755
The chat composer derived
isStreamingfromhasStreamState || chatStatus === "running", excluding"interrupting". Once an interrupt is requested the stream state is already gone, so a chat still finalizing an interruption rendered the idle composer: no Stop button, the normal Send button, and no busy indication. The interrupting state was visually indistinguishable from waiting.Include
"interrupting"in the composer's streaming check and add Storybook coverage for the I1 state (interrupting with a queued message) alongside a running-state control.Implementation notes (Coder Agents generated)
waitingwith a non-empty queue maps toStateInvalid(coderd/x/chatd/chatstate/state.go), so the observed "queued message with idle composer" symptom can only occur in the interrupting state (I1), where interrupt was requested and the stream has already been torn down.isActiveChatStatushelper inchatStore.tsalready treats"interrupting"as active, but was only used for reconnect-state surfacing, not the composer.playassertions onChatPageInputrendered with a realcreateChatStore(), asserting Stop is shown and Send is hidden while interrupting; the control story asserts the same rendering forrunningwith an identical history and queue.