From b003bfc14d79918049e0203c7d7827492bc3f26e Mon Sep 17 00:00:00 2001 From: Aidan Daly Date: Mon, 2 Feb 2026 10:14:58 -0500 Subject: [PATCH] fix: change quit keybinding from 'q' to Ctrl+Q Prevents accidental quit when typing prompts containing 'q' in dev/invoke screens. --- src/cli/tui/components/FullScreenLogView.tsx | 2 +- src/cli/tui/components/HelpText.tsx | 2 +- src/cli/tui/components/PromptScreen.tsx | 6 +++--- src/cli/tui/components/Screen.tsx | 2 +- src/cli/tui/components/SelectScreen.tsx | 2 +- src/cli/tui/hooks/useExitHandler.ts | 4 ++-- src/cli/tui/hooks/useListNavigation.ts | 2 +- src/cli/tui/screens/dev/DevScreen.tsx | 6 +++--- src/cli/tui/screens/invoke/InvokeScreen.tsx | 2 +- src/cli/tui/screens/outline/OutlineScreen.tsx | 2 +- 10 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/cli/tui/components/FullScreenLogView.tsx b/src/cli/tui/components/FullScreenLogView.tsx index b8b9af803..170252382 100644 --- a/src/cli/tui/components/FullScreenLogView.tsx +++ b/src/cli/tui/components/FullScreenLogView.tsx @@ -38,7 +38,7 @@ export function FullScreenLogView({ logs, logFilePath, onExit }: FullScreenLogVi }, [scrollUpOffset, maxScrollPos]); useInput((input, key) => { - if (key.escape || input === 'q' || input === 'l') { + if (key.escape || (key.ctrl && input === 'q') || input === 'l') { onExit(); return; } diff --git a/src/cli/tui/components/HelpText.tsx b/src/cli/tui/components/HelpText.tsx index 8ef85c01c..e1f415c68 100644 --- a/src/cli/tui/components/HelpText.tsx +++ b/src/cli/tui/components/HelpText.tsx @@ -20,5 +20,5 @@ export function HelpText({ text }: HelpTextProps) { * Standard exit help text. */ export function ExitHelpText() { - return ; + return ; } diff --git a/src/cli/tui/components/PromptScreen.tsx b/src/cli/tui/components/PromptScreen.tsx index 2925592c0..c7b6f3515 100644 --- a/src/cli/tui/components/PromptScreen.tsx +++ b/src/cli/tui/components/PromptScreen.tsx @@ -13,7 +13,7 @@ interface PromptScreenProps { onConfirm?: () => void; /** Called when secondary/back action is triggered (Enter or 'b') */ onBack?: () => void; - /** Called when exit is triggered (Escape or 'q' or 'n') */ + /** Called when exit is triggered (Escape or Ctrl+Q or 'n') */ onExit?: () => void; /** Panel border color (default: undefined, no color) */ borderColor?: string; @@ -49,7 +49,7 @@ export function PromptScreen({ if (!inputEnabled) { return; } - if (key.escape || input === 'q' || input === 'n') { + if (key.escape || (key.ctrl && input === 'q') || input === 'n') { onExit?.(); return; } @@ -120,7 +120,7 @@ export function ErrorPrompt({ onExit?: () => void; }) { useInput((input, key) => { - if (key.escape || input === 'q' || input === 'n') { + if (key.escape || (key.ctrl && input === 'q') || input === 'n') { onExit?.(); return; } diff --git a/src/cli/tui/components/Screen.tsx b/src/cli/tui/components/Screen.tsx index 26500ac86..2d5b9f95e 100644 --- a/src/cli/tui/components/Screen.tsx +++ b/src/cli/tui/components/Screen.tsx @@ -20,7 +20,7 @@ interface ScreenProps { * Standard screen wrapper that provides: * - ScreenLayout with responsive padding * - ScreenHeader with title - * - Exit handling (Escape / 'q') + * - Exit handling (Escape / Ctrl+Q) * - Help text at the bottom */ export function Screen({ title, color, onExit, helpText, headerContent, footerContent, children }: ScreenProps) { diff --git a/src/cli/tui/components/SelectScreen.tsx b/src/cli/tui/components/SelectScreen.tsx index 9486447d8..50211db8f 100644 --- a/src/cli/tui/components/SelectScreen.tsx +++ b/src/cli/tui/components/SelectScreen.tsx @@ -18,7 +18,7 @@ interface SelectScreenProps { items: T[]; /** Called when an item is selected */ onSelect: (item: T, index: number) => void; - /** Called when exiting (Escape or 'q') */ + /** Called when exiting (Escape or Ctrl+Q) */ onExit: () => void; /** Whether navigation is active (default: true) */ isActive?: boolean; diff --git a/src/cli/tui/hooks/useExitHandler.ts b/src/cli/tui/hooks/useExitHandler.ts index 5d78457e0..89a1ba08b 100644 --- a/src/cli/tui/hooks/useExitHandler.ts +++ b/src/cli/tui/hooks/useExitHandler.ts @@ -1,7 +1,7 @@ import { useInput } from 'ink'; /** - * Hook that handles the standard exit keys (Escape, 'q'). + * Hook that handles the standard exit keys (Escape, Ctrl+Q). * Should be used at the top level of screens that need exit handling. * * @param onExit - Callback to invoke when exit is requested @@ -10,7 +10,7 @@ import { useInput } from 'ink'; export function useExitHandler(onExit: () => void, enabled = true): void { useInput( (input, key) => { - if (key.escape || input === 'q') { + if (key.escape || (key.ctrl && input === 'q')) { onExit(); } }, diff --git a/src/cli/tui/hooks/useListNavigation.ts b/src/cli/tui/hooks/useListNavigation.ts index f30fd8055..b45eb2318 100644 --- a/src/cli/tui/hooks/useListNavigation.ts +++ b/src/cli/tui/hooks/useListNavigation.ts @@ -85,7 +85,7 @@ export function useListNavigation({ useInput( (input, key) => { // Handle exit - if (key.escape || input === 'q') { + if (key.escape || (key.ctrl && input === 'q')) { onExit?.(); return; } diff --git a/src/cli/tui/screens/dev/DevScreen.tsx b/src/cli/tui/screens/dev/DevScreen.tsx index 9e4025612..504eac53f 100644 --- a/src/cli/tui/screens/dev/DevScreen.tsx +++ b/src/cli/tui/screens/dev/DevScreen.tsx @@ -231,7 +231,7 @@ export function DevScreen(props: DevScreenProps) { (input, key) => { // Agent selection mode if (mode === 'select-agent') { - if (key.escape || input === 'q') { + if (key.escape || (key.ctrl && input === 'q')) { props.onBack(); return; } @@ -253,8 +253,8 @@ export function DevScreen(props: DevScreenProps) { // In chat mode if (mode === 'chat') { - // Esc or q to quit (but skip if we just cancelled from input mode) - if (key.escape || input === 'q' || (key.ctrl && input === 'c')) { + // Esc or Ctrl+Q to quit (but skip if we just cancelled from input mode) + if (key.escape || (key.ctrl && input === 'q') || (key.ctrl && input === 'c')) { if (justCancelledRef.current) { // Skip this escape - it's from the input cancel justCancelledRef.current = false; diff --git a/src/cli/tui/screens/invoke/InvokeScreen.tsx b/src/cli/tui/screens/invoke/InvokeScreen.tsx index e7b66e2c5..df908c5f5 100644 --- a/src/cli/tui/screens/invoke/InvokeScreen.tsx +++ b/src/cli/tui/screens/invoke/InvokeScreen.tsx @@ -59,7 +59,7 @@ export function InvokeScreen({ useInput((input, key) => { if (phase === 'loading' || phase === 'error' || !config) return; - if (key.escape || input === 'q') { + if (key.escape || (key.ctrl && input === 'q')) { if (mode === 'input') { setMode('chat'); } else if (mode === 'chat' && config.agents.length > 1) { diff --git a/src/cli/tui/screens/outline/OutlineScreen.tsx b/src/cli/tui/screens/outline/OutlineScreen.tsx index c0e4e30e9..38983b320 100644 --- a/src/cli/tui/screens/outline/OutlineScreen.tsx +++ b/src/cli/tui/screens/outline/OutlineScreen.tsx @@ -73,7 +73,7 @@ export function OutlineScreen({ isInteractive: _isInteractive, onExit }: Outline } if (key.escape) setMode('select-scope'); } else if (mode === 'view') { - if (key.escape || input === 'q') setMode('select-scope'); + if (key.escape || (key.ctrl && input === 'q')) setMode('select-scope'); } });