Skip to content

Commit d464cd5

Browse files
jinye.djyqwencoder
andcommitted
fix(cli): fix early input capture being lost under React StrictMode
Move stopAndGetCapturedInput() from inside KeypressProvider's useEffect to before render() in startInteractiveUI. When DEBUG=1, React StrictMode deliberately runs effect→cleanup→effect, causing the first mount to drain the buffer and schedule a replay that the cleanup immediately cancels. The second mount found an empty buffer, silently discarding startup keystrokes. By draining once before render() and passing the bytes as a stable prop, StrictMode remounts always read the same data and can schedule replay on the second (stable) mount. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
1 parent 5c2c81d commit d464cd5

File tree

2 files changed

+13
-3
lines changed

2 files changed

+13
-3
lines changed

packages/cli/src/gemini.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,11 @@ export async function startInteractiveUI(
151151
const version = await getCliVersion();
152152
setWindowTitle(basename(workspaceRoot), settings);
153153

154+
// Drain the early-captured input exactly once, before any React rendering.
155+
// Must be outside any component/effect so StrictMode's mount/cleanup/remount
156+
// always reads from the same stable prop rather than the (now empty) module buffer.
157+
const initialCapturedInput = stopAndGetCapturedInput();
158+
154159
// Create wrapper component to use hooks inside render
155160
const AppWrapper = () => {
156161
const kittyProtocolStatus = useKittyKeyboardProtocol();
@@ -164,6 +169,7 @@ export async function startInteractiveUI(
164169
pasteWorkaround={
165170
process.platform === 'win32' || nodeMajorVersion < 20
166171
}
172+
initialCapturedInput={initialCapturedInput}
167173
>
168174
<SessionStatsProvider sessionId={config.getSessionId()}>
169175
<VimModeProvider settings={settings}>

packages/cli/src/ui/contexts/KeypressContext.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ import {
3939
import { clipboardHasImage } from '../utils/clipboardUtils.js';
4040

4141
import { FOCUS_IN, FOCUS_OUT } from '../hooks/useFocus.js';
42-
import { stopAndGetCapturedInput } from '../../utils/earlyInputCapture.js';
4342

4443
const ESC = '\u001B';
4544
export const PASTE_MODE_PREFIX = `${ESC}[200~`;
@@ -129,12 +128,14 @@ export function KeypressProvider({
129128
pasteWorkaround = false,
130129
config,
131130
debugKeystrokeLogging,
131+
initialCapturedInput,
132132
}: {
133133
children?: React.ReactNode;
134134
kittyProtocolEnabled: boolean;
135135
pasteWorkaround?: boolean;
136136
config?: Config;
137137
debugKeystrokeLogging?: boolean;
138+
initialCapturedInput?: Buffer;
138139
}) {
139140
const { stdin, setRawMode } = useStdin();
140141
const subscribers = useRef<Set<KeypressHandler>>(new Set()).current;
@@ -159,8 +160,10 @@ export function KeypressProvider({
159160
setRawMode(true);
160161
}
161162

162-
// Startup optimization: stop early input capture and get captured input
163-
const capturedInput = stopAndGetCapturedInput();
163+
// Use pre-drained captured input passed from outside React.
164+
// Draining happens before render() so StrictMode's mount/cleanup/remount
165+
// always reads from the stable prop reference, not the (already empty) module buffer.
166+
const capturedInput = initialCapturedInput ?? Buffer.alloc(0);
164167

165168
const keypressStream = new PassThrough();
166169
let usePassthrough = false;
@@ -1060,6 +1063,7 @@ export function KeypressProvider({
10601063
pasteWorkaround,
10611064
config,
10621065
subscribers,
1066+
initialCapturedInput,
10631067
]);
10641068

10651069
return (

0 commit comments

Comments
 (0)