Make sidebar shortcuts leave focus mode (#2717)

* fix(app): exit focus mode for sidebar shortcuts

Focus mode kept sidebar actions hidden after their handlers ran. Exit only after a sidebar action is handled so the requested action still takes effect.

* fix(app): order focus exit before sidebar action

Leave focus mode before dispatching the sidebar command so its handler runs against the visible panel state.

* fix(app): preserve focus mode outside workspaces

Only exit active workspace focus mode after a sidebar shortcut is handled. Keep persisted focus state unchanged when the shortcut has no foreground workspace target.
This commit is contained in:
Mohamed Boudra
2026-08-01 11:16:48 +02:00
committed by GitHub
parent 70ed70d36e
commit 6177d85e99
4 changed files with 78 additions and 7 deletions
+56 -6
View File
@@ -1,23 +1,73 @@
import { expect, test } from "./fixtures"; import { expect, test, type Page } from "./fixtures";
const modifier = process.platform === "darwin" ? "Meta" : "Control"; const modifier = process.platform === "darwin" ? "Meta" : "Control";
async function pressFocusModeShortcut(page: import("@playwright/test").Page) { async function pressFocusModeShortcut(page: Page) {
await page.keyboard.press(`${modifier}+Shift+F`); await page.keyboard.press(`${modifier}+Shift+F`);
} }
async function pressSettingsShortcut(page: import("@playwright/test").Page) { async function pressSettingsShortcut(page: Page) {
await page.keyboard.press(`${modifier}+Comma`); await page.keyboard.press(`${modifier}+Comma`);
} }
async function blurActiveElement(page: import("@playwright/test").Page) { async function pressRightSidebarShortcut(page: Page) {
await page.keyboard.press(`${modifier}+E`);
}
async function returnToWorkspace(page: Page, workspaceUrl: string) {
await page.goto(workspaceUrl);
}
async function blurActiveElement(page: Page) {
await page.evaluate(() => (document.activeElement as HTMLElement | null)?.blur()); await page.evaluate(() => (document.activeElement as HTMLElement | null)?.blur());
} }
function exitFocusModeButton(page: Page) {
return page.getByRole("button", { name: "Exit focus mode" });
}
async function enterFocusMode(page: Page) {
await blurActiveElement(page);
await pressFocusModeShortcut(page);
await expect(exitFocusModeButton(page)).toBeVisible();
}
test("sidebar shortcuts exit focus mode and execute their action", async ({
page,
withWorkspace,
}) => {
const workspace = await withWorkspace({ prefix: "focus-mode-sidebar-shortcut-" });
await workspace.navigateTo();
await enterFocusMode(page);
await pressRightSidebarShortcut(page);
await expect(exitFocusModeButton(page)).toHaveCount(0);
await expect(page.getByText("Changes", { exact: true })).toBeVisible();
});
test("sidebar shortcuts outside a workspace preserve its focus mode", async ({
page,
withWorkspace,
}) => {
const workspace = await withWorkspace({ prefix: "focus-mode-sidebar-boundary-" });
await workspace.navigateTo();
await enterFocusMode(page);
const workspaceUrl = page.url();
await pressSettingsShortcut(page);
await expect(page.getByRole("navigation", { name: "Settings" })).toBeVisible();
await pressRightSidebarShortcut(page);
await returnToWorkspace(page, workspaceUrl);
await expect(exitFocusModeButton(page)).toBeVisible();
});
test("focus mode only applies to the active workspace screen", async ({ page, withWorkspace }) => { test("focus mode only applies to the active workspace screen", async ({ page, withWorkspace }) => {
const workspace = await withWorkspace({ prefix: "focus-mode-boundary-" }); const workspace = await withWorkspace({ prefix: "focus-mode-boundary-" });
await workspace.navigateTo(); await workspace.navigateTo();
const exitFocusMode = page.getByRole("button", { name: "Exit focus mode" }); const exitFocusMode = exitFocusModeButton(page);
const settingsButton = page.getByRole("button", { name: "Settings", exact: true }); const settingsButton = page.getByRole("button", { name: "Settings", exact: true });
const settingsSidebar = page.getByRole("navigation", { name: "Settings" }); const settingsSidebar = page.getByRole("navigation", { name: "Settings" });
@@ -41,7 +91,7 @@ test("focus mode only applies to the active workspace screen", async ({ page, wi
await expect(exitFocusMode).toHaveCount(0); await expect(exitFocusMode).toHaveCount(0);
await pressFocusModeShortcut(page); await pressFocusModeShortcut(page);
await page.goto(workspaceUrl); await returnToWorkspace(page, workspaceUrl);
await expect(exitFocusMode).toBeVisible(); await expect(exitFocusMode).toBeVisible();
await exitFocusMode.click(); await exitFocusMode.click();
+3
View File
@@ -428,6 +428,7 @@ function AppContainer({ children, chromeEnabled: chromeEnabledOverride }: AppCon
const openDesktopAgentList = usePanelStore((state) => state.openDesktopAgentList); const openDesktopAgentList = usePanelStore((state) => state.openDesktopAgentList);
const closeDesktopAgentList = usePanelStore((state) => state.closeDesktopAgentList); const closeDesktopAgentList = usePanelStore((state) => state.closeDesktopAgentList);
const closeDesktopFileExplorer = usePanelStore((state) => state.closeDesktopFileExplorer); const closeDesktopFileExplorer = usePanelStore((state) => state.closeDesktopFileExplorer);
const exitFocusMode = usePanelStore((state) => state.exitFocusMode);
const isFocusModeEnabled = usePanelStore((state) => state.desktop.focusModeEnabled); const isFocusModeEnabled = usePanelStore((state) => state.desktop.focusModeEnabled);
const isDesktopAgentListOpen = usePanelStore((state) => state.desktop.agentListOpen); const isDesktopAgentListOpen = usePanelStore((state) => state.desktop.agentListOpen);
const isDesktopFileExplorerOpen = usePanelStore((state) => state.desktop.fileExplorerOpen); const isDesktopFileExplorerOpen = usePanelStore((state) => state.desktop.fileExplorerOpen);
@@ -472,8 +473,10 @@ function AppContainer({ children, chromeEnabled: chromeEnabledOverride }: AppCon
useKeyboardShortcuts({ useKeyboardShortcuts({
enabled: keyboardShortcutsEnabled, enabled: keyboardShortcutsEnabled,
isMobile: isCompactLayout, isMobile: isCompactLayout,
isWorkspaceFocusModeEnabled,
toggleAgentList, toggleAgentList,
toggleBothSidebars: toggleDesktopSidebars, toggleBothSidebars: toggleDesktopSidebars,
exitFocusMode,
cycleTheme, cycleTheme,
}); });
@@ -40,14 +40,18 @@ import {
export function useKeyboardShortcuts({ export function useKeyboardShortcuts({
enabled, enabled,
isMobile, isMobile,
isWorkspaceFocusModeEnabled,
toggleAgentList, toggleAgentList,
toggleBothSidebars, toggleBothSidebars,
exitFocusMode,
cycleTheme, cycleTheme,
}: { }: {
enabled: boolean; enabled: boolean;
isMobile: boolean; isMobile: boolean;
isWorkspaceFocusModeEnabled: boolean;
toggleAgentList: () => void; toggleAgentList: () => void;
toggleBothSidebars?: () => void; toggleBothSidebars?: () => void;
exitFocusMode: () => void;
cycleTheme?: () => void; cycleTheme?: () => void;
}) { }) {
const pathname = usePathname(); const pathname = usePathname();
@@ -207,11 +211,15 @@ export function useKeyboardShortcuts({
shortcutsDialogOpen: store.shortcutsDialogOpen, shortcutsDialogOpen: store.shortcutsDialogOpen,
}, },
); );
return performShortcutAction( const handled = performShortcutAction(
shortcutAction, shortcutAction,
input.domEvent, input.domEvent,
input.browserFocusRestoreElement, input.browserFocusRestoreElement,
); );
if (handled && isWorkspaceFocusModeEnabled && input.action.startsWith("sidebar.")) {
exitFocusMode();
}
return handled;
}; };
const resolveAndPerformShortcut = (input: { const resolveAndPerformShortcut = (input: {
@@ -371,10 +379,12 @@ export function useKeyboardShortcuts({
bindings, bindings,
cycleTheme, cycleTheme,
enabled, enabled,
exitFocusMode,
activeWorkspaceSelection, activeWorkspaceSelection,
isDesktopApp, isDesktopApp,
isMac, isMac,
isMobile, isMobile,
isWorkspaceFocusModeEnabled,
openProjectPickerAction, openProjectPickerAction,
pathname, pathname,
publishBrowserShortcutPolicy, publishBrowserShortcutPolicy,
@@ -90,6 +90,7 @@ export interface PanelState {
// Actions // Actions
toggleFocusMode: () => void; toggleFocusMode: () => void;
exitFocusMode: () => void;
showMobileAgent: () => void; showMobileAgent: () => void;
showMobileAgentList: () => void; showMobileAgentList: () => void;
toggleMobileAgentList: () => void; toggleMobileAgentList: () => void;
@@ -157,6 +158,13 @@ export const usePanelStore = create<PanelState>()(
desktop: { ...state.desktop, focusModeEnabled: !state.desktop.focusModeEnabled }, desktop: { ...state.desktop, focusModeEnabled: !state.desktop.focusModeEnabled },
})), })),
exitFocusMode: () =>
set((state) =>
state.desktop.focusModeEnabled
? { desktop: { ...state.desktop, focusModeEnabled: false } }
: state,
),
showMobileAgent: () => set((state) => setMobilePanelTargetPatch(state, "agent")), showMobileAgent: () => set((state) => setMobilePanelTargetPatch(state, "agent")),
showMobileAgentList: () => set((state) => setMobilePanelTargetPatch(state, "agent-list")), showMobileAgentList: () => set((state) => setMobilePanelTargetPatch(state, "agent-list")),