From 0b2b697aa55be4333b596bb82504943775c516c4 Mon Sep 17 00:00:00 2001 From: RyderAsking Date: Mon, 10 Aug 2026 09:22:56 +0000 Subject: [PATCH] fix(chat): prevent shell prefix from corrupting input --- packages/ui/src/components/chat/ChatInput.tsx | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/components/chat/ChatInput.tsx b/packages/ui/src/components/chat/ChatInput.tsx index 89c7fcf6..f5764fd8 100644 --- a/packages/ui/src/components/chat/ChatInput.tsx +++ b/packages/ui/src/components/chat/ChatInput.tsx @@ -1402,6 +1402,19 @@ const ChatInputComponent: React.FC = ({ onOpenSettings, scrollTo // Uses keyCode === 229 fallback for WebKit where compositionend fires before keydown. if (isIMECompositionEvent(e)) return; + // Enter shell mode before CodeMirror inserts the trigger. Keeping the + // document unchanged also keeps the caret at the start for the first + // command character. + if (inputMode === 'normal' && e.key === '!') { + const selection = composerRef.current?.getSelection(); + if (selection?.start === 0 && selection.end === 0) { + e.preventDefault(); + setInputMode('shell'); + closeAutocomplete(); + return; + } + } + if (inputMode === 'shell' && e.key === 'Escape') { e.preventDefault(); setInputMode('normal'); @@ -1723,13 +1736,20 @@ const ChatInputComponent: React.FC = ({ onOpenSettings, scrollTo const inputSource: FileMentionAutocompleteInputSource = isPasteInput ? 'paste' : 'manual'; // A leading `!` switches the composer into shell mode and is consumed. + // Mobile keyboards and paste may update the document without a usable + // keydown, so consume the trigger in the same editor transaction rather + // than moving the caret in a later frame against stale text. if (inputMode === 'normal' && value.startsWith('!')) { const shellCommand = value.slice(1); const nextCursor = Math.max(0, selection.start - 1); setInputMode('shell'); - setMessage(shellCommand); closeAutocomplete(); - requestAnimationFrame(() => composerRef.current?.setSelection(nextCursor)); + const editor = composerRef.current; + if (editor) { + editor.replaceRange(0, 1, '', nextCursor); + } else { + setMessage(shellCommand); + } return; }