From dfaf8d1551784b6d4606a4c7414e2d78a209c927 Mon Sep 17 00:00:00 2001 From: kim Date: Fri, 21 Aug 2026 14:10:21 +0900 Subject: [PATCH] fix(ui): keep the input box usable while a response is streaming MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While a turn was in flight (isThinking or streamingText), the ChatInput was unmounted and replaced with a static "Waiting for response…" box — so the user couldn't type ahead during output, the input field just vanished. Now ChatInput stays mounted the whole time: the user can type and edit while output streams in. A one-line status above it ("Waiting… esc to interrupt — type ahead, Enter sends when done.") replaces the old full-width box. Submitting a second turn mid-stream is still prevented — handleSubmit now guards on isThinking/streamingText and returns early, so Enter is a no-op until the in-flight turn finishes (the typed text stays in the box). The original race this swap was protecting against (overlapping turns on the same session) is preserved by that guard. Verified: typecheck clean, build 274.76 KB, 301 tests pass. --- src/ui/ink/App.tsx | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/src/ui/ink/App.tsx b/src/ui/ink/App.tsx index ce6d7c9..3902524 100644 --- a/src/ui/ink/App.tsx +++ b/src/ui/ink/App.tsx @@ -776,6 +776,11 @@ export function App({ } async function handleSubmit(raw: string) { + // A turn is already in flight (streaming output or waiting for the first chunk). Keep the + // input box mounted so the user can type ahead, but ignore Enter so a second turn can't + // overlap onto the same session. The in-flight turn is the source of truth; the typed text + // stays in the box for when the turn finishes. + if (isThinking || streamingText !== null) return; setInputValue(""); const trimmed = raw.trim(); if (!trimmed) return; @@ -1198,25 +1203,18 @@ export function App({ gitInfo={gitInfo} /> )} - {isThinking || streamingText !== null ? ( - // Swapped in for ChatInput while a turn is in flight. Closes a real (if rare) race: - // handleSubmit had no guard against firing while a turn was already running, so - // typing and hitting Enter mid-stream could submit a second overlapping turn onto - // the same session. - - Waiting for response… (esc to interrupt) - - ) : ( - + {(isThinking || streamingText !== null) && ( + Waiting for response… (esc to interrupt) — type ahead, Enter sends when done. )} + )}