From 99cf41b929d275f432db31b1daafa024055685a6 Mon Sep 17 00:00:00 2001 From: Illia Panasenko Date: Fri, 7 Aug 2026 00:36:57 +0200 Subject: [PATCH 1/2] fix(web): show stop while input is pending --- .../chat/ComposerPrimaryActions.test.ts | 51 ++++++++++++++++++- .../chat/ComposerPrimaryActions.tsx | 28 +++++----- 2 files changed, 64 insertions(+), 15 deletions(-) diff --git a/apps/web/src/components/chat/ComposerPrimaryActions.test.ts b/apps/web/src/components/chat/ComposerPrimaryActions.test.ts index b7624db0a8f..0c7885547ba 100644 --- a/apps/web/src/components/chat/ComposerPrimaryActions.test.ts +++ b/apps/web/src/components/chat/ComposerPrimaryActions.test.ts @@ -1,6 +1,43 @@ -import { describe, expect, it } from "vite-plus/test"; +import { createElement } from "react"; +import { renderToStaticMarkup } from "react-dom/server"; +import { describe, expect, it, vi } from "vite-plus/test"; -import { formatPendingPrimaryActionLabel } from "./ComposerPrimaryActions"; +vi.mock("~/hooks/useSettings", () => ({ + useEnvironmentIdentificationMode: () => "none", +})); +vi.mock("../SidebarStageBackdrop", () => ({ + StageBackdropButtonArt: () => null, + useSidebarStageBackdropVariant: () => null, +})); + +import { ComposerPrimaryActions, formatPendingPrimaryActionLabel } from "./ComposerPrimaryActions"; + +function renderPendingActions(isRunning: boolean) { + return renderToStaticMarkup( + createElement(ComposerPrimaryActions, { + compact: true, + pendingAction: { + questionIndex: 0, + isLastQuestion: true, + canAdvance: true, + isResponding: false, + isComplete: true, + }, + isRunning, + showPlanFollowUpPrompt: false, + promptHasText: false, + isSendBusy: false, + sendDisabledReason: null, + isConnecting: false, + isEnvironmentUnavailable: false, + isPreparingWorktree: false, + hasSendableContent: false, + onPreviousPendingQuestion: () => {}, + onInterrupt: () => {}, + onImplementPlanInNewThread: () => {}, + }), + ); +} describe("formatPendingPrimaryActionLabel", () => { it("returns 'Submitting...' while responding", () => { @@ -91,3 +128,13 @@ describe("formatPendingPrimaryActionLabel", () => { ).toBe("Submit answers"); }); }); + +describe("ComposerPrimaryActions", () => { + it("offers Stop generation while a running turn is waiting for user input", () => { + expect(renderPendingActions(true)).toContain('aria-label="Stop generation"'); + }); + + it("does not offer Stop generation for a pending request without a running turn", () => { + expect(renderPendingActions(false)).not.toContain('aria-label="Stop generation"'); + }); +}); diff --git a/apps/web/src/components/chat/ComposerPrimaryActions.tsx b/apps/web/src/components/chat/ComposerPrimaryActions.tsx index 504b7e1cc44..2637cbad174 100644 --- a/apps/web/src/components/chat/ComposerPrimaryActions.tsx +++ b/apps/web/src/components/chat/ComposerPrimaryActions.tsx @@ -80,10 +80,24 @@ export const ComposerPrimaryActions = memo(function ComposerPrimaryActions({ const stageBackdropVariant = useSidebarStageBackdropVariant( environmentIdentificationMode === "artwork", ); + const stopGenerationButton = ( + + ); if (pendingAction) { return (
+ {isRunning ? stopGenerationButton : null} {pendingAction.questionIndex > 0 ? ( compact ? ( - ); + return stopGenerationButton; } if (showPlanFollowUpPrompt) { From 5420ca88c17bb86b9864c04214ca339666ed6716 Mon Sep 17 00:00:00 2001 From: Illia Panasenko Date: Fri, 7 Aug 2026 01:01:40 +0200 Subject: [PATCH 2/2] fix(web): match pending stop action size --- .../chat/ComposerPrimaryActions.test.ts | 27 +++++++++++++++++++ .../chat/ComposerPrimaryActions.tsx | 11 +++++--- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/apps/web/src/components/chat/ComposerPrimaryActions.test.ts b/apps/web/src/components/chat/ComposerPrimaryActions.test.ts index 0c7885547ba..ba416e9fce3 100644 --- a/apps/web/src/components/chat/ComposerPrimaryActions.test.ts +++ b/apps/web/src/components/chat/ComposerPrimaryActions.test.ts @@ -39,6 +39,27 @@ function renderPendingActions(isRunning: boolean) { ); } +function renderStandaloneStop() { + return renderToStaticMarkup( + createElement(ComposerPrimaryActions, { + compact: true, + pendingAction: null, + isRunning: true, + showPlanFollowUpPrompt: false, + promptHasText: false, + isSendBusy: false, + sendDisabledReason: null, + isConnecting: false, + isEnvironmentUnavailable: false, + isPreparingWorktree: false, + hasSendableContent: false, + onPreviousPendingQuestion: () => {}, + onInterrupt: () => {}, + onImplementPlanInNewThread: () => {}, + }), + ); +} + describe("formatPendingPrimaryActionLabel", () => { it("returns 'Submitting...' while responding", () => { expect( @@ -137,4 +158,10 @@ describe("ComposerPrimaryActions", () => { it("does not offer Stop generation for a pending request without a running turn", () => { expect(renderPendingActions(false)).not.toContain('aria-label="Stop generation"'); }); + + it("matches the small pending action size without changing the standalone size", () => { + expect(renderPendingActions(true)).toContain("size-8 sm:size-7"); + expect(renderStandaloneStop()).toContain("size-8 sm:h-8 sm:w-8"); + expect(renderStandaloneStop()).not.toContain("sm:size-7"); + }); }); diff --git a/apps/web/src/components/chat/ComposerPrimaryActions.tsx b/apps/web/src/components/chat/ComposerPrimaryActions.tsx index 2637cbad174..516d9267090 100644 --- a/apps/web/src/components/chat/ComposerPrimaryActions.tsx +++ b/apps/web/src/components/chat/ComposerPrimaryActions.tsx @@ -80,10 +80,13 @@ export const ComposerPrimaryActions = memo(function ComposerPrimaryActions({ const stageBackdropVariant = useSidebarStageBackdropVariant( environmentIdentificationMode === "artwork", ); - const stopGenerationButton = ( + const renderStopGenerationButton = (insidePendingAction: boolean) => (