diff --git a/packages/app/src/components/split-container.tsx b/packages/app/src/components/split-container.tsx index b80663771..f8034f223 100644 --- a/packages/app/src/components/split-container.tsx +++ b/packages/app/src/components/split-container.tsx @@ -9,6 +9,7 @@ import { type ReactNode, type SetStateAction, } from "react"; +import { useStableEvent } from "@/hooks/use-stable-event"; import { DndContext, DragOverlay, @@ -714,6 +715,7 @@ function SplitPaneView({ }: SplitPaneViewProps) { const { theme } = useUnistyles(); const paneRef = useRef(null); + const stableOnFocusPane = useStableEvent(onFocusPane); const paneState = useMemo( () => deriveWorkspacePaneState({ @@ -743,7 +745,7 @@ function SplitPaneView({ tab: activeTabDescriptor, }) : null, - [activeTabDescriptor, buildPaneContentModel, pane.id], + [activeTabDescriptor, buildPaneContentModel, isFocused, pane.id], ); useEffect(() => { @@ -764,14 +766,14 @@ function SplitPaneView({ if (!shouldFocusPaneFromEventTarget(event.target)) { return; } - onFocusPane(pane.id); + stableOnFocusPane(pane.id); }; const handlePaneFocusIn = (event: FocusEvent) => { if (!shouldFocusPaneFromEventTarget(event.target)) { return; } - onFocusPane(pane.id); + stableOnFocusPane(pane.id); }; paneElement.addEventListener("pointerdown", handlePanePointerDown, true); @@ -781,7 +783,7 @@ function SplitPaneView({ paneElement.removeEventListener("pointerdown", handlePanePointerDown, true); paneElement.removeEventListener("focusin", handlePaneFocusIn, true); }; - }, [onFocusPane, pane.id]); + }, [stableOnFocusPane, pane.id]); return ( diff --git a/packages/app/src/components/terminal-pane.tsx b/packages/app/src/components/terminal-pane.tsx index 2f29959cc..14f603b02 100644 --- a/packages/app/src/components/terminal-pane.tsx +++ b/packages/app/src/components/terminal-pane.tsx @@ -24,6 +24,7 @@ interface TerminalPaneProps { serverId: string; cwd: string; terminalId: string; + isPaneFocused: boolean; } const TERMINAL_REFIT_DELAYS_MS = [0, 48, 144, 320]; @@ -82,6 +83,7 @@ export function TerminalPane({ serverId, cwd, terminalId, + isPaneFocused, }: TerminalPaneProps) { const isScreenFocused = useIsFocused(); const { theme } = useUnistyles(); @@ -127,7 +129,7 @@ export function TerminalPane({ }, []); useEffect(() => { - if (isMobile || !isScreenFocused || !terminalId) { + if (isMobile || !isScreenFocused || !isPaneFocused || !terminalId) { lastAutoFocusKeyRef.current = null; return; } @@ -139,7 +141,7 @@ export function TerminalPane({ lastAutoFocusKeyRef.current = nextFocusKey; requestTerminalFocus(); - }, [isMobile, isScreenFocused, requestTerminalFocus, scopeKey, terminalId]); + }, [isMobile, isPaneFocused, isScreenFocused, requestTerminalFocus, scopeKey, terminalId]); const clearKeyboardRefitTimeouts = useCallback(() => { if (keyboardRefitTimeoutsRef.current.length === 0) { diff --git a/packages/app/src/keyboard/keyboard-shortcuts.ts b/packages/app/src/keyboard/keyboard-shortcuts.ts index 413ec2588..0291fb243 100644 --- a/packages/app/src/keyboard/keyboard-shortcuts.ts +++ b/packages/app/src/keyboard/keyboard-shortcuts.ts @@ -439,7 +439,7 @@ const SHORTCUT_BINDINGS: readonly ShortcutBinding[] = [ id: "workspace-pane-split-right-cmd-backslash", action: "workspace.pane.split.right", combo: "Cmd+\\", - when: { mac: true, terminal: false, commandCenter: false }, + when: { mac: true, commandCenter: false }, help: { id: "workspace-pane-split-right", section: "tabs-panes", @@ -451,7 +451,7 @@ const SHORTCUT_BINDINGS: readonly ShortcutBinding[] = [ id: "workspace-pane-split-down-cmd-shift-backslash", action: "workspace.pane.split.down", combo: "Cmd+Shift+\\", - when: { mac: true, terminal: false, commandCenter: false }, + when: { mac: true, commandCenter: false }, help: { id: "workspace-pane-split-down", section: "tabs-panes", @@ -463,7 +463,7 @@ const SHORTCUT_BINDINGS: readonly ShortcutBinding[] = [ id: "workspace-pane-focus-left-cmd-shift-left", action: "workspace.pane.focus.left", combo: "Cmd+Shift+ArrowLeft", - when: { mac: true, terminal: false, commandCenter: false }, + when: { mac: true, commandCenter: false }, help: { id: "workspace-pane-focus-left", section: "tabs-panes", @@ -475,7 +475,7 @@ const SHORTCUT_BINDINGS: readonly ShortcutBinding[] = [ id: "workspace-pane-focus-right-cmd-shift-right", action: "workspace.pane.focus.right", combo: "Cmd+Shift+ArrowRight", - when: { mac: true, terminal: false, commandCenter: false }, + when: { mac: true, commandCenter: false }, help: { id: "workspace-pane-focus-right", section: "tabs-panes", @@ -487,7 +487,7 @@ const SHORTCUT_BINDINGS: readonly ShortcutBinding[] = [ id: "workspace-pane-focus-up-cmd-shift-up", action: "workspace.pane.focus.up", combo: "Cmd+Shift+ArrowUp", - when: { mac: true, terminal: false, commandCenter: false }, + when: { mac: true, commandCenter: false }, help: { id: "workspace-pane-focus-up", section: "tabs-panes", @@ -499,7 +499,7 @@ const SHORTCUT_BINDINGS: readonly ShortcutBinding[] = [ id: "workspace-pane-focus-down-cmd-shift-down", action: "workspace.pane.focus.down", combo: "Cmd+Shift+ArrowDown", - when: { mac: true, terminal: false, commandCenter: false }, + when: { mac: true, commandCenter: false }, help: { id: "workspace-pane-focus-down", section: "tabs-panes", @@ -511,7 +511,7 @@ const SHORTCUT_BINDINGS: readonly ShortcutBinding[] = [ id: "workspace-pane-move-tab-left-cmd-shift-alt-left", action: "workspace.pane.move-tab.left", combo: "Cmd+Alt+Shift+ArrowLeft", - when: { mac: true, terminal: false, commandCenter: false }, + when: { mac: true, commandCenter: false }, help: { id: "workspace-pane-move-tab-left", section: "tabs-panes", @@ -523,7 +523,7 @@ const SHORTCUT_BINDINGS: readonly ShortcutBinding[] = [ id: "workspace-pane-move-tab-right-cmd-shift-alt-right", action: "workspace.pane.move-tab.right", combo: "Cmd+Alt+Shift+ArrowRight", - when: { mac: true, terminal: false, commandCenter: false }, + when: { mac: true, commandCenter: false }, help: { id: "workspace-pane-move-tab-right", section: "tabs-panes", @@ -535,7 +535,7 @@ const SHORTCUT_BINDINGS: readonly ShortcutBinding[] = [ id: "workspace-pane-move-tab-up-cmd-shift-alt-up", action: "workspace.pane.move-tab.up", combo: "Cmd+Alt+Shift+ArrowUp", - when: { mac: true, terminal: false, commandCenter: false }, + when: { mac: true, commandCenter: false }, help: { id: "workspace-pane-move-tab-up", section: "tabs-panes", @@ -547,7 +547,7 @@ const SHORTCUT_BINDINGS: readonly ShortcutBinding[] = [ id: "workspace-pane-move-tab-down-cmd-shift-alt-down", action: "workspace.pane.move-tab.down", combo: "Cmd+Alt+Shift+ArrowDown", - when: { mac: true, terminal: false, commandCenter: false }, + when: { mac: true, commandCenter: false }, help: { id: "workspace-pane-move-tab-down", section: "tabs-panes", @@ -559,7 +559,7 @@ const SHORTCUT_BINDINGS: readonly ShortcutBinding[] = [ id: "workspace-pane-close-cmd-shift-w", action: "workspace.pane.close", combo: "Cmd+Shift+W", - when: { mac: true, terminal: false, commandCenter: false }, + when: { mac: true, commandCenter: false }, help: { id: "workspace-pane-close", section: "tabs-panes", diff --git a/packages/app/src/panels/terminal-panel.tsx b/packages/app/src/panels/terminal-panel.tsx index 6c18d8d36..2f77fec4c 100644 --- a/packages/app/src/panels/terminal-panel.tsx +++ b/packages/app/src/panels/terminal-panel.tsx @@ -49,7 +49,7 @@ function useTerminalPanelDescriptor( function TerminalPanel() { const isFocused = useIsFocused(); - const { serverId, workspaceId, target } = usePaneContext(); + const { serverId, workspaceId, target, isPaneFocused } = usePaneContext(); invariant(target.kind === "terminal", "TerminalPanel requires terminal target"); if (!isFocused) { @@ -61,6 +61,7 @@ function TerminalPanel() { serverId={serverId} cwd={workspaceId} terminalId={target.terminalId} + isPaneFocused={isPaneFocused} /> ); } diff --git a/packages/app/src/screens/workspace/workspace-screen.tsx b/packages/app/src/screens/workspace/workspace-screen.tsx index 818536cf6..afd8ea21a 100644 --- a/packages/app/src/screens/workspace/workspace-screen.tsx +++ b/packages/app/src/screens/workspace/workspace-screen.tsx @@ -74,6 +74,7 @@ import type { ListTerminalsResponse } from "@server/shared/messages"; import { upsertTerminalListEntry } from "@/utils/terminal-list"; import { confirmDialog } from "@/utils/confirm-dialog"; import { useArchiveAgent } from "@/hooks/use-archive-agent"; +import { useStableEvent } from "@/hooks/use-stable-event"; import { buildProviderCommand } from "@/utils/provider-command-templates"; import { generateDraftId } from "@/stores/draft-keys"; import { @@ -740,6 +741,7 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps) const splitWorkspacePaneEmpty = useWorkspaceLayoutStore((state) => state.splitPaneEmpty); const moveWorkspaceTabToPane = useWorkspaceLayoutStore((state) => state.moveTabToPane); const focusWorkspacePane = useWorkspaceLayoutStore((state) => state.focusPane); + const paneFocusSuppressedRef = useRef(false); const resizeWorkspaceSplit = useWorkspaceLayoutStore((state) => state.resizeSplit); const reorderWorkspaceTabsInPane = useWorkspaceLayoutStore((state) => state.reorderTabsInPane); const pinnedAgentIds = useWorkspaceLayoutStore((state) => @@ -1556,7 +1558,11 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps) const activePaneTabId = focusedPaneTabState.activeTabId; const adjacentPaneId = findAdjacentPane(workspaceLayout.root, focusedPane.id, direction); if (activePaneTabId && adjacentPaneId) { + paneFocusSuppressedRef.current = true; moveWorkspaceTabToPane(persistenceKey, activePaneTabId, adjacentPaneId); + requestAnimationFrame(() => { + paneFocusSuppressedRef.current = false; + }); } } return true; @@ -1790,15 +1796,12 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps) [buildPaneContentModel], ); - const handleFocusPane = useCallback( - function handleFocusPane(paneId: string) { - if (!persistenceKey) { - return; - } - focusWorkspacePane(persistenceKey, paneId); - }, - [focusWorkspacePane, persistenceKey], - ); + const handleFocusPane = useStableEvent(function handleFocusPane(paneId: string) { + if (!persistenceKey || paneFocusSuppressedRef.current) { + return; + } + focusWorkspacePane(persistenceKey, paneId); + }); const handleSplitPane = useCallback( function handleSplitPane(input: {