diff --git a/apps/desktop/src/app/desktop-controller.tsx b/apps/desktop/src/app/desktop-controller.tsx index addf71bf761..4c9a34ae9a2 100644 --- a/apps/desktop/src/app/desktop-controller.tsx +++ b/apps/desktop/src/app/desktop-controller.tsx @@ -10,6 +10,7 @@ import { GatewayConnectingOverlay } from '@/components/gateway-connecting-overla import { Pane, PaneMain } from '@/components/pane-shell' import { RemoteDisplayBanner } from '@/components/remote-display-banner' import { useMediaQuery } from '@/hooks/use-media-query' +import { isFocusWithin } from '@/lib/keybinds/combo' import { cn } from '@/lib/utils' import { useSkinCommand } from '@/themes/use-skin-command' @@ -129,7 +130,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 { closeActiveTerminal } 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' @@ -397,7 +398,7 @@ export function DesktopController() { // 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 (isFocusWithin('[data-terminal]')) { if (event.metaKey && !event.ctrlKey) { event.preventDefault() event.stopPropagation() diff --git a/apps/desktop/src/app/right-sidebar/terminal/instance.tsx b/apps/desktop/src/app/right-sidebar/terminal/instance.tsx index d228199174f..671c368281c 100644 --- a/apps/desktop/src/app/right-sidebar/terminal/instance.tsx +++ b/apps/desktop/src/app/right-sidebar/terminal/instance.tsx @@ -40,6 +40,8 @@ export function TerminalInstance({ id, active, cwd, onAddSelectionToChat }: Term 'absolute inset-0 flex flex-col bg-(--ui-editor-surface-background) px-2 pb-2 pt-0', active ? 'visible' : 'invisible pointer-events-none' )} + // Focus-scope marker so isFocusWithin('[data-terminal]') can route ⌘W here. + data-terminal="" > {status === 'starting' && (
diff --git a/apps/desktop/src/app/right-sidebar/terminal/terminals.ts b/apps/desktop/src/app/right-sidebar/terminal/terminals.ts index 840ab32576d..38c9c7125ac 100644 --- a/apps/desktop/src/app/right-sidebar/terminal/terminals.ts +++ b/apps/desktop/src/app/right-sidebar/terminal/terminals.ts @@ -95,12 +95,6 @@ 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) diff --git a/apps/desktop/src/lib/keybinds/combo.ts b/apps/desktop/src/lib/keybinds/combo.ts index b203ded952d..cfaaa840b32 100644 --- a/apps/desktop/src/lib/keybinds/combo.ts +++ b/apps/desktop/src/lib/keybinds/combo.ts @@ -175,6 +175,13 @@ export function formatCombo(combo: string): string { return IS_MAC ? tokens.join('') : tokens.join('+') } +// True when focus currently sits inside an element matching `selector`. The +// primitive for focus-scoped shortcuts — e.g. routing ⌘W to whichever surface +// (terminal, preview, …) owns focus. +export function isFocusWithin(selector: string): boolean { + return document.activeElement?.closest(selector) != null +} + // True when focus is in a text-entry surface, so bare-key shortcuts don't fire // while the user is typing. export function isEditableTarget(target: EventTarget | null): boolean {