From f734a2eaf0ce9b1c15db7eab31f2b532305ff542 Mon Sep 17 00:00:00 2001 From: Jeccoman Date: Sun, 22 Mar 2026 09:55:14 +0300 Subject: [PATCH] Clarify running-turn stop messaging --- .../web/src/components/ChatView.logic.test.ts | 20 ++++++++- apps/web/src/components/ChatView.logic.ts | 6 +++ apps/web/src/components/ChatView.tsx | 44 ++++++++++++------- 3 files changed, 54 insertions(+), 16 deletions(-) diff --git a/apps/web/src/components/ChatView.logic.test.ts b/apps/web/src/components/ChatView.logic.test.ts index bf72ec0b84..365cd28075 100644 --- a/apps/web/src/components/ChatView.logic.test.ts +++ b/apps/web/src/components/ChatView.logic.test.ts @@ -1,7 +1,11 @@ import { ThreadId } from "@t3tools/contracts"; import { describe, expect, it } from "vitest"; -import { buildExpiredTerminalContextToastCopy, deriveComposerSendState } from "./ChatView.logic"; +import { + buildExpiredTerminalContextToastCopy, + buildRunningTurnBlockedMessage, + deriveComposerSendState, +} from "./ChatView.logic"; describe("deriveComposerSendState", () => { it("treats expired terminal pills as non-sendable content", () => { @@ -67,3 +71,17 @@ describe("buildExpiredTerminalContextToastCopy", () => { }); }); }); + +describe("buildRunningTurnBlockedMessage", () => { + it("explains when a turn is still running", () => { + expect(buildRunningTurnBlockedMessage(false)).toBe( + "A turn is still running. Stop it before sending another prompt.", + ); + }); + + it("explains when a long-running command is keeping the turn active", () => { + expect(buildRunningTurnBlockedMessage(true)).toBe( + "A long-running command is still active. Stop the current turn before sending another prompt.", + ); + }); +}); diff --git a/apps/web/src/components/ChatView.logic.ts b/apps/web/src/components/ChatView.logic.ts index ddc84718e6..899291b674 100644 --- a/apps/web/src/components/ChatView.logic.ts +++ b/apps/web/src/components/ChatView.logic.ts @@ -160,3 +160,9 @@ export function buildExpiredTerminalContextToastCopy( description: "Re-add it if you want that terminal output included.", }; } + +export function buildRunningTurnBlockedMessage(hasRunningSubprocess: boolean): string { + return hasRunningSubprocess + ? "A long-running command is still active. Stop the current turn before sending another prompt." + : "A turn is still running. Stop it before sending another prompt."; +} diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx index e628f6ea6a..86daf76029 100644 --- a/apps/web/src/components/ChatView.tsx +++ b/apps/web/src/components/ChatView.tsx @@ -164,6 +164,7 @@ import { ProviderHealthBanner } from "./chat/ProviderHealthBanner"; import { ThreadErrorBanner } from "./chat/ThreadErrorBanner"; import { buildExpiredTerminalContextToastCopy, + buildRunningTurnBlockedMessage, buildLocalDraftThread, buildTemporaryWorktreeBranchName, cloneComposerImageForRetry, @@ -661,6 +662,8 @@ export default function ChatView({ threadId }: ChatViewProps) { const isSendBusy = sendPhase !== "idle"; const isPreparingWorktree = sendPhase === "preparing-worktree"; const isWorking = phase === "running" || isSendBusy || isConnecting || isRevertingCheckpoint; + const hasRunningTerminalSubprocess = terminalState.runningTerminalIds.length > 0; + const runningTurnBlockedMessage = buildRunningTurnBlockedMessage(hasRunningTerminalSubprocess); const nowIso = new Date(nowTick).toISOString(); const activeWorkStartedAt = deriveActiveWorkStartedAt( activeLatestTurn, @@ -2346,6 +2349,10 @@ export default function ChatView({ threadId }: ChatViewProps) { e?.preventDefault(); const api = readNativeApi(); if (!api || !activeThread || isSendBusy || isConnecting || sendInFlightRef.current) return; + if (phase === "running") { + setThreadError(activeThread.id, runningTurnBlockedMessage); + return; + } if (activePendingProgress) { onAdvanceActivePendingUserInput(); return; @@ -3931,22 +3938,29 @@ export default function ChatView({ threadId }: ChatViewProps) { ) : phase === "running" ? ( - + + Stop + + ) : pendingUserInputs.length === 0 ? ( showPlanFollowUpPrompt ? ( prompt.trim().length > 0 ? (