From 2d55ff8fcaf3d0f3ac80938437e99af7a9f067ab Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Sun, 28 Jun 2026 19:10:06 -0500 Subject: [PATCH] =?UTF-8?q?feat(desktop):=20=E2=8C=98W=20closes=20the=20fo?= =?UTF-8?q?cused=20terminal?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fold terminal close into the existing ⌘/Ctrl+W handler so focus decides the target: a focused terminal takes ⌘W (closes the active tab) and otherwise the keystroke closes the active preview tab as before. Only the ⌘ gesture is intercepted — Ctrl+W stays the shell's werase — and a focused terminal never lets ⌘/Ctrl+W close a preview out from under it. --- apps/desktop/src/app/desktop-controller.tsx | 19 +++++++++++++++++-- .../app/right-sidebar/terminal/terminals.ts | 6 ++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/apps/desktop/src/app/desktop-controller.tsx b/apps/desktop/src/app/desktop-controller.tsx index 973c32400d8..addf71bf761 100644 --- a/apps/desktop/src/app/desktop-controller.tsx +++ b/apps/desktop/src/app/desktop-controller.tsx @@ -129,6 +129,7 @@ import { ReviewPane } from './right-sidebar/review' import { $terminalTakeover } from './right-sidebar/store' import { TerminalPaneChrome } from './right-sidebar/terminal/chrome' import { PersistentTerminal } from './right-sidebar/terminal/persistent' +import { closeActiveTerminal, isTerminalFocused } from './right-sidebar/terminal/terminals' import { CRON_ROUTE, NEW_CHAT_ROUTE, routeSessionId, sessionRoute, SETTINGS_ROUTE } from './routes' import { SessionPickerOverlay } from './session-picker-overlay' import { SessionSwitcher } from './session-switcher' @@ -389,11 +390,25 @@ export function DesktopController() { useEffect(() => { const onKeyDown = (event: KeyboardEvent) => { - if (!$filePreviewTarget.get() && !$previewTarget.get()) { + if (event.altKey || event.shiftKey || event.key.toLowerCase() !== 'w' || (!event.metaKey && !event.ctrlKey)) { return } - if ((event.metaKey || event.ctrlKey) && !event.altKey && !event.shiftKey && event.key.toLowerCase() === 'w') { + // Terminal focused: ⌘W closes the active terminal. Ctrl+W is left untouched + // for the shell's werase, and nothing else may steal ⌘/Ctrl+W from a + // focused terminal (so it never closes a preview tab out from under it). + if (isTerminalFocused()) { + if (event.metaKey && !event.ctrlKey) { + event.preventDefault() + event.stopPropagation() + closeActiveTerminal() + } + + return + } + + // Otherwise ⌘/Ctrl+W closes the active preview tab when one is open. + if ($filePreviewTarget.get() || $previewTarget.get()) { event.preventDefault() event.stopPropagation() closeActiveRightRailTab() diff --git a/apps/desktop/src/app/right-sidebar/terminal/terminals.ts b/apps/desktop/src/app/right-sidebar/terminal/terminals.ts index 38c9c7125ac..840ab32576d 100644 --- a/apps/desktop/src/app/right-sidebar/terminal/terminals.ts +++ b/apps/desktop/src/app/right-sidebar/terminal/terminals.ts @@ -95,6 +95,12 @@ export function closeActiveTerminal(): void { } } +/** True while an in-app terminal's xterm holds focus (its helper textarea is the + * active element) — lets ⌘W close the focused terminal without a ref dance. */ +export function isTerminalFocused(): boolean { + return document.activeElement?.classList.contains('xterm-helper-textarea') ?? false +} + export function closeOtherTerminals(id: string): void { const keep = $terminals.get().find(term => term.id === id)