fix(workspace): suppress pane focus when moving tabs between panes

This commit is contained in:
Mohamed Boudra
2026-03-23 11:30:35 +07:00
parent d6977352aa
commit 4b4e55a246
5 changed files with 35 additions and 27 deletions
@@ -9,6 +9,7 @@ import {
type ReactNode, type ReactNode,
type SetStateAction, type SetStateAction,
} from "react"; } from "react";
import { useStableEvent } from "@/hooks/use-stable-event";
import { import {
DndContext, DndContext,
DragOverlay, DragOverlay,
@@ -714,6 +715,7 @@ function SplitPaneView({
}: SplitPaneViewProps) { }: SplitPaneViewProps) {
const { theme } = useUnistyles(); const { theme } = useUnistyles();
const paneRef = useRef<View | null>(null); const paneRef = useRef<View | null>(null);
const stableOnFocusPane = useStableEvent(onFocusPane);
const paneState = useMemo( const paneState = useMemo(
() => () =>
deriveWorkspacePaneState({ deriveWorkspacePaneState({
@@ -743,7 +745,7 @@ function SplitPaneView({
tab: activeTabDescriptor, tab: activeTabDescriptor,
}) })
: null, : null,
[activeTabDescriptor, buildPaneContentModel, pane.id], [activeTabDescriptor, buildPaneContentModel, isFocused, pane.id],
); );
useEffect(() => { useEffect(() => {
@@ -764,14 +766,14 @@ function SplitPaneView({
if (!shouldFocusPaneFromEventTarget(event.target)) { if (!shouldFocusPaneFromEventTarget(event.target)) {
return; return;
} }
onFocusPane(pane.id); stableOnFocusPane(pane.id);
}; };
const handlePaneFocusIn = (event: FocusEvent) => { const handlePaneFocusIn = (event: FocusEvent) => {
if (!shouldFocusPaneFromEventTarget(event.target)) { if (!shouldFocusPaneFromEventTarget(event.target)) {
return; return;
} }
onFocusPane(pane.id); stableOnFocusPane(pane.id);
}; };
paneElement.addEventListener("pointerdown", handlePanePointerDown, true); paneElement.addEventListener("pointerdown", handlePanePointerDown, true);
@@ -781,7 +783,7 @@ function SplitPaneView({
paneElement.removeEventListener("pointerdown", handlePanePointerDown, true); paneElement.removeEventListener("pointerdown", handlePanePointerDown, true);
paneElement.removeEventListener("focusin", handlePaneFocusIn, true); paneElement.removeEventListener("focusin", handlePaneFocusIn, true);
}; };
}, [onFocusPane, pane.id]); }, [stableOnFocusPane, pane.id]);
return ( return (
<View ref={paneRef} collapsable={false} style={styles.pane}> <View ref={paneRef} collapsable={false} style={styles.pane}>
@@ -24,6 +24,7 @@ interface TerminalPaneProps {
serverId: string; serverId: string;
cwd: string; cwd: string;
terminalId: string; terminalId: string;
isPaneFocused: boolean;
} }
const TERMINAL_REFIT_DELAYS_MS = [0, 48, 144, 320]; const TERMINAL_REFIT_DELAYS_MS = [0, 48, 144, 320];
@@ -82,6 +83,7 @@ export function TerminalPane({
serverId, serverId,
cwd, cwd,
terminalId, terminalId,
isPaneFocused,
}: TerminalPaneProps) { }: TerminalPaneProps) {
const isScreenFocused = useIsFocused(); const isScreenFocused = useIsFocused();
const { theme } = useUnistyles(); const { theme } = useUnistyles();
@@ -127,7 +129,7 @@ export function TerminalPane({
}, []); }, []);
useEffect(() => { useEffect(() => {
if (isMobile || !isScreenFocused || !terminalId) { if (isMobile || !isScreenFocused || !isPaneFocused || !terminalId) {
lastAutoFocusKeyRef.current = null; lastAutoFocusKeyRef.current = null;
return; return;
} }
@@ -139,7 +141,7 @@ export function TerminalPane({
lastAutoFocusKeyRef.current = nextFocusKey; lastAutoFocusKeyRef.current = nextFocusKey;
requestTerminalFocus(); requestTerminalFocus();
}, [isMobile, isScreenFocused, requestTerminalFocus, scopeKey, terminalId]); }, [isMobile, isPaneFocused, isScreenFocused, requestTerminalFocus, scopeKey, terminalId]);
const clearKeyboardRefitTimeouts = useCallback(() => { const clearKeyboardRefitTimeouts = useCallback(() => {
if (keyboardRefitTimeoutsRef.current.length === 0) { if (keyboardRefitTimeoutsRef.current.length === 0) {
+11 -11
View File
@@ -439,7 +439,7 @@ const SHORTCUT_BINDINGS: readonly ShortcutBinding[] = [
id: "workspace-pane-split-right-cmd-backslash", id: "workspace-pane-split-right-cmd-backslash",
action: "workspace.pane.split.right", action: "workspace.pane.split.right",
combo: "Cmd+\\", combo: "Cmd+\\",
when: { mac: true, terminal: false, commandCenter: false }, when: { mac: true, commandCenter: false },
help: { help: {
id: "workspace-pane-split-right", id: "workspace-pane-split-right",
section: "tabs-panes", section: "tabs-panes",
@@ -451,7 +451,7 @@ const SHORTCUT_BINDINGS: readonly ShortcutBinding[] = [
id: "workspace-pane-split-down-cmd-shift-backslash", id: "workspace-pane-split-down-cmd-shift-backslash",
action: "workspace.pane.split.down", action: "workspace.pane.split.down",
combo: "Cmd+Shift+\\", combo: "Cmd+Shift+\\",
when: { mac: true, terminal: false, commandCenter: false }, when: { mac: true, commandCenter: false },
help: { help: {
id: "workspace-pane-split-down", id: "workspace-pane-split-down",
section: "tabs-panes", section: "tabs-panes",
@@ -463,7 +463,7 @@ const SHORTCUT_BINDINGS: readonly ShortcutBinding[] = [
id: "workspace-pane-focus-left-cmd-shift-left", id: "workspace-pane-focus-left-cmd-shift-left",
action: "workspace.pane.focus.left", action: "workspace.pane.focus.left",
combo: "Cmd+Shift+ArrowLeft", combo: "Cmd+Shift+ArrowLeft",
when: { mac: true, terminal: false, commandCenter: false }, when: { mac: true, commandCenter: false },
help: { help: {
id: "workspace-pane-focus-left", id: "workspace-pane-focus-left",
section: "tabs-panes", section: "tabs-panes",
@@ -475,7 +475,7 @@ const SHORTCUT_BINDINGS: readonly ShortcutBinding[] = [
id: "workspace-pane-focus-right-cmd-shift-right", id: "workspace-pane-focus-right-cmd-shift-right",
action: "workspace.pane.focus.right", action: "workspace.pane.focus.right",
combo: "Cmd+Shift+ArrowRight", combo: "Cmd+Shift+ArrowRight",
when: { mac: true, terminal: false, commandCenter: false }, when: { mac: true, commandCenter: false },
help: { help: {
id: "workspace-pane-focus-right", id: "workspace-pane-focus-right",
section: "tabs-panes", section: "tabs-panes",
@@ -487,7 +487,7 @@ const SHORTCUT_BINDINGS: readonly ShortcutBinding[] = [
id: "workspace-pane-focus-up-cmd-shift-up", id: "workspace-pane-focus-up-cmd-shift-up",
action: "workspace.pane.focus.up", action: "workspace.pane.focus.up",
combo: "Cmd+Shift+ArrowUp", combo: "Cmd+Shift+ArrowUp",
when: { mac: true, terminal: false, commandCenter: false }, when: { mac: true, commandCenter: false },
help: { help: {
id: "workspace-pane-focus-up", id: "workspace-pane-focus-up",
section: "tabs-panes", section: "tabs-panes",
@@ -499,7 +499,7 @@ const SHORTCUT_BINDINGS: readonly ShortcutBinding[] = [
id: "workspace-pane-focus-down-cmd-shift-down", id: "workspace-pane-focus-down-cmd-shift-down",
action: "workspace.pane.focus.down", action: "workspace.pane.focus.down",
combo: "Cmd+Shift+ArrowDown", combo: "Cmd+Shift+ArrowDown",
when: { mac: true, terminal: false, commandCenter: false }, when: { mac: true, commandCenter: false },
help: { help: {
id: "workspace-pane-focus-down", id: "workspace-pane-focus-down",
section: "tabs-panes", section: "tabs-panes",
@@ -511,7 +511,7 @@ const SHORTCUT_BINDINGS: readonly ShortcutBinding[] = [
id: "workspace-pane-move-tab-left-cmd-shift-alt-left", id: "workspace-pane-move-tab-left-cmd-shift-alt-left",
action: "workspace.pane.move-tab.left", action: "workspace.pane.move-tab.left",
combo: "Cmd+Alt+Shift+ArrowLeft", combo: "Cmd+Alt+Shift+ArrowLeft",
when: { mac: true, terminal: false, commandCenter: false }, when: { mac: true, commandCenter: false },
help: { help: {
id: "workspace-pane-move-tab-left", id: "workspace-pane-move-tab-left",
section: "tabs-panes", section: "tabs-panes",
@@ -523,7 +523,7 @@ const SHORTCUT_BINDINGS: readonly ShortcutBinding[] = [
id: "workspace-pane-move-tab-right-cmd-shift-alt-right", id: "workspace-pane-move-tab-right-cmd-shift-alt-right",
action: "workspace.pane.move-tab.right", action: "workspace.pane.move-tab.right",
combo: "Cmd+Alt+Shift+ArrowRight", combo: "Cmd+Alt+Shift+ArrowRight",
when: { mac: true, terminal: false, commandCenter: false }, when: { mac: true, commandCenter: false },
help: { help: {
id: "workspace-pane-move-tab-right", id: "workspace-pane-move-tab-right",
section: "tabs-panes", section: "tabs-panes",
@@ -535,7 +535,7 @@ const SHORTCUT_BINDINGS: readonly ShortcutBinding[] = [
id: "workspace-pane-move-tab-up-cmd-shift-alt-up", id: "workspace-pane-move-tab-up-cmd-shift-alt-up",
action: "workspace.pane.move-tab.up", action: "workspace.pane.move-tab.up",
combo: "Cmd+Alt+Shift+ArrowUp", combo: "Cmd+Alt+Shift+ArrowUp",
when: { mac: true, terminal: false, commandCenter: false }, when: { mac: true, commandCenter: false },
help: { help: {
id: "workspace-pane-move-tab-up", id: "workspace-pane-move-tab-up",
section: "tabs-panes", section: "tabs-panes",
@@ -547,7 +547,7 @@ const SHORTCUT_BINDINGS: readonly ShortcutBinding[] = [
id: "workspace-pane-move-tab-down-cmd-shift-alt-down", id: "workspace-pane-move-tab-down-cmd-shift-alt-down",
action: "workspace.pane.move-tab.down", action: "workspace.pane.move-tab.down",
combo: "Cmd+Alt+Shift+ArrowDown", combo: "Cmd+Alt+Shift+ArrowDown",
when: { mac: true, terminal: false, commandCenter: false }, when: { mac: true, commandCenter: false },
help: { help: {
id: "workspace-pane-move-tab-down", id: "workspace-pane-move-tab-down",
section: "tabs-panes", section: "tabs-panes",
@@ -559,7 +559,7 @@ const SHORTCUT_BINDINGS: readonly ShortcutBinding[] = [
id: "workspace-pane-close-cmd-shift-w", id: "workspace-pane-close-cmd-shift-w",
action: "workspace.pane.close", action: "workspace.pane.close",
combo: "Cmd+Shift+W", combo: "Cmd+Shift+W",
when: { mac: true, terminal: false, commandCenter: false }, when: { mac: true, commandCenter: false },
help: { help: {
id: "workspace-pane-close", id: "workspace-pane-close",
section: "tabs-panes", section: "tabs-panes",
+2 -1
View File
@@ -49,7 +49,7 @@ function useTerminalPanelDescriptor(
function TerminalPanel() { function TerminalPanel() {
const isFocused = useIsFocused(); const isFocused = useIsFocused();
const { serverId, workspaceId, target } = usePaneContext(); const { serverId, workspaceId, target, isPaneFocused } = usePaneContext();
invariant(target.kind === "terminal", "TerminalPanel requires terminal target"); invariant(target.kind === "terminal", "TerminalPanel requires terminal target");
if (!isFocused) { if (!isFocused) {
@@ -61,6 +61,7 @@ function TerminalPanel() {
serverId={serverId} serverId={serverId}
cwd={workspaceId} cwd={workspaceId}
terminalId={target.terminalId} terminalId={target.terminalId}
isPaneFocused={isPaneFocused}
/> />
); );
} }
@@ -74,6 +74,7 @@ import type { ListTerminalsResponse } from "@server/shared/messages";
import { upsertTerminalListEntry } from "@/utils/terminal-list"; import { upsertTerminalListEntry } from "@/utils/terminal-list";
import { confirmDialog } from "@/utils/confirm-dialog"; import { confirmDialog } from "@/utils/confirm-dialog";
import { useArchiveAgent } from "@/hooks/use-archive-agent"; import { useArchiveAgent } from "@/hooks/use-archive-agent";
import { useStableEvent } from "@/hooks/use-stable-event";
import { buildProviderCommand } from "@/utils/provider-command-templates"; import { buildProviderCommand } from "@/utils/provider-command-templates";
import { generateDraftId } from "@/stores/draft-keys"; import { generateDraftId } from "@/stores/draft-keys";
import { import {
@@ -740,6 +741,7 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps)
const splitWorkspacePaneEmpty = useWorkspaceLayoutStore((state) => state.splitPaneEmpty); const splitWorkspacePaneEmpty = useWorkspaceLayoutStore((state) => state.splitPaneEmpty);
const moveWorkspaceTabToPane = useWorkspaceLayoutStore((state) => state.moveTabToPane); const moveWorkspaceTabToPane = useWorkspaceLayoutStore((state) => state.moveTabToPane);
const focusWorkspacePane = useWorkspaceLayoutStore((state) => state.focusPane); const focusWorkspacePane = useWorkspaceLayoutStore((state) => state.focusPane);
const paneFocusSuppressedRef = useRef(false);
const resizeWorkspaceSplit = useWorkspaceLayoutStore((state) => state.resizeSplit); const resizeWorkspaceSplit = useWorkspaceLayoutStore((state) => state.resizeSplit);
const reorderWorkspaceTabsInPane = useWorkspaceLayoutStore((state) => state.reorderTabsInPane); const reorderWorkspaceTabsInPane = useWorkspaceLayoutStore((state) => state.reorderTabsInPane);
const pinnedAgentIds = useWorkspaceLayoutStore((state) => const pinnedAgentIds = useWorkspaceLayoutStore((state) =>
@@ -1556,7 +1558,11 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps)
const activePaneTabId = focusedPaneTabState.activeTabId; const activePaneTabId = focusedPaneTabState.activeTabId;
const adjacentPaneId = findAdjacentPane(workspaceLayout.root, focusedPane.id, direction); const adjacentPaneId = findAdjacentPane(workspaceLayout.root, focusedPane.id, direction);
if (activePaneTabId && adjacentPaneId) { if (activePaneTabId && adjacentPaneId) {
paneFocusSuppressedRef.current = true;
moveWorkspaceTabToPane(persistenceKey, activePaneTabId, adjacentPaneId); moveWorkspaceTabToPane(persistenceKey, activePaneTabId, adjacentPaneId);
requestAnimationFrame(() => {
paneFocusSuppressedRef.current = false;
});
} }
} }
return true; return true;
@@ -1790,15 +1796,12 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps)
[buildPaneContentModel], [buildPaneContentModel],
); );
const handleFocusPane = useCallback( const handleFocusPane = useStableEvent(function handleFocusPane(paneId: string) {
function handleFocusPane(paneId: string) { if (!persistenceKey || paneFocusSuppressedRef.current) {
if (!persistenceKey) { return;
return; }
} focusWorkspacePane(persistenceKey, paneId);
focusWorkspacePane(persistenceKey, paneId); });
},
[focusWorkspacePane, persistenceKey],
);
const handleSplitPane = useCallback( const handleSplitPane = useCallback(
function handleSplitPane(input: { function handleSplitPane(input: {