From 693ae8b4e0283934ddc1250af639dfd853ea88e5 Mon Sep 17 00:00:00 2001 From: cpacker Date: Mon, 15 Dec 2025 18:10:23 -0800 Subject: [PATCH] fix: prevent escape character from being inserted into input MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Strip escape characters in PasteAwareTextInput handleChange to prevent them from being inserted by ink-text-input. This was causing the 'Press Esc again to clear' hint to flicker and disappear immediately because the value change triggered the useEffect that resets escapePressed. Also extracted the double-escape window duration to a constant (2.5s). 🤖 Generated with [Letta Code](https://letta.com) Co-Authored-By: Letta --- src/cli/components/InputRich.tsx | 6 ++++-- src/cli/components/PasteAwareTextInput.tsx | 16 ++++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/cli/components/InputRich.tsx b/src/cli/components/InputRich.tsx index 5686b0b..063a16b 100644 --- a/src/cli/components/InputRich.tsx +++ b/src/cli/components/InputRich.tsx @@ -20,6 +20,8 @@ const appVersion = getVersion(); // Only show token count when it exceeds this threshold const COUNTER_VISIBLE_THRESHOLD = 1000; +// Window for double-escape to clear input +const ESC_CLEAR_WINDOW_MS = 2500; export function Input({ visible = true, @@ -164,12 +166,12 @@ export function Input({ setEscapePressed(false); if (escapeTimerRef.current) clearTimeout(escapeTimerRef.current); } else { - // First escape - start 1-second timer + // First escape - start timer to allow double-escape to clear setEscapePressed(true); if (escapeTimerRef.current) clearTimeout(escapeTimerRef.current); escapeTimerRef.current = setTimeout(() => { setEscapePressed(false); - }, 1000); + }, ESC_CLEAR_WINDOW_MS); } } } diff --git a/src/cli/components/PasteAwareTextInput.tsx b/src/cli/components/PasteAwareTextInput.tsx index ae8d826..ed31728 100644 --- a/src/cli/components/PasteAwareTextInput.tsx +++ b/src/cli/components/PasteAwareTextInput.tsx @@ -333,6 +333,22 @@ export function PasteAwareTextInput({ }, [internal_eventEmitter]); const handleChange = (newValue: string) => { + // Drop lone escape characters that Ink's text input would otherwise insert; + // they are used as control keys for double-escape handling and should not + // mutate the input value. + const sanitizedValue = newValue.replaceAll("\u001b", ""); + if (sanitizedValue !== newValue) { + // Keep caret in bounds after stripping control chars + const nextCaret = Math.min(caretOffsetRef.current, sanitizedValue.length); + setNudgeCursorOffset(nextCaret); + caretOffsetRef.current = nextCaret; + newValue = sanitizedValue; + // If nothing actually changed after stripping, bail out early + if (sanitizedValue === displayValue) { + return; + } + } + // Heuristic: detect large additions that look like pastes const addedLen = newValue.length - displayValue.length; const lineDelta = countLines(newValue) - countLines(displayValue);