diff --git a/apps/web/src/components/AppSidebarLayout.tsx b/apps/web/src/components/AppSidebarLayout.tsx index 8e4baf20451c8..7fc5a2b6be00b 100644 --- a/apps/web/src/components/AppSidebarLayout.tsx +++ b/apps/web/src/components/AppSidebarLayout.tsx @@ -20,6 +20,7 @@ import { import { getLocalStorageItem, removeLocalStorageItem } from "../hooks/useLocalStorage"; import { isRichTextBoldShortcut, + isSidebarToggleShortcut, resolveShortcutCommand, shortcutLabelForCommand, } from "../keybindings"; @@ -347,15 +348,17 @@ function SidebarControl({ showSidebar }: { readonly showSidebar: boolean }) { // the toggle stays available everywhere else. return; } - const command = resolveShortcutCommand(event, keybindings, { - context: { - terminalFocus: isTerminalFocused(), - previewFocus: isPreviewFocused(), - ...readRoutePanelsOpen(router), - usagePageOpen, - }, - }); - if (command !== "sidebar.toggle") return; + if ( + !isSidebarToggleShortcut(event, keybindings, { + context: { + terminalFocus: isTerminalFocused(), + previewFocus: isPreviewFocused(), + ...readRoutePanelsOpen(router), + usagePageOpen, + }, + }) + ) + return; event.preventDefault(); event.stopPropagation(); diff --git a/apps/web/src/keybindings.test.ts b/apps/web/src/keybindings.test.ts index 3b596f838fa0d..fb6f719bb2346 100644 --- a/apps/web/src/keybindings.test.ts +++ b/apps/web/src/keybindings.test.ts @@ -15,6 +15,7 @@ import { effectiveShortcutsForCommand, formatShortcutLabel, isRichTextBoldShortcut, + isSidebarToggleShortcut, isTerminalNewShortcut, isTerminalSplitShortcut, resolveChatShortcutCommand, @@ -813,6 +814,33 @@ describe("isRichTextBoldShortcut", () => { }); }); +describe("isSidebarToggleShortcut", () => { + it("leaves Ctrl+B to a focused terminal, where it is the tmux prefix", () => { + const ctrlB = event({ key: "b", ctrlKey: true }); + assert.isFalse( + isSidebarToggleShortcut(ctrlB, DEFAULT_BINDINGS, { + platform: "Linux", + context: { terminalFocus: true }, + }), + ); + assert.isTrue( + isSidebarToggleShortcut(ctrlB, DEFAULT_BINDINGS, { + platform: "Linux", + context: { terminalFocus: false }, + }), + ); + }); + + it("keeps Cmd+B toggling the sidebar from a focused terminal on macOS", () => { + assert.isTrue( + isSidebarToggleShortcut(event({ key: "b", metaKey: true }), DEFAULT_BINDINGS, { + platform: "MacIntel", + context: { terminalFocus: true }, + }), + ); + }); +}); + describe("composer and pull request shortcuts", () => { it("uses the dictation chord outside the terminal and preserves terminal split", () => { const chord = event({ key: "d", ctrlKey: true, shiftKey: true }); diff --git a/apps/web/src/keybindings.ts b/apps/web/src/keybindings.ts index 75f617d3cca86..88a30f76b675d 100644 --- a/apps/web/src/keybindings.ts +++ b/apps/web/src/keybindings.ts @@ -379,6 +379,16 @@ export function isDiffToggleShortcut( return matchesCommandShortcut(event, keybindings, "diff.toggle", options); } +export function isSidebarToggleShortcut( + event: ShortcutEventLike, + keybindings: ResolvedKeybindingsConfig, + options?: ShortcutMatchOptions, +): boolean { + // A focused terminal owns Ctrl chords, such as tmux's Ctrl+B prefix; Cmd never reaches it. + if (event.ctrlKey && options?.context?.terminalFocus) return false; + return matchesCommandShortcut(event, keybindings, "sidebar.toggle", options); +} + export function isOpenFavoriteEditorShortcut( event: ShortcutEventLike, keybindings: ResolvedKeybindingsConfig, diff --git a/docs/user/keybindings.md b/docs/user/keybindings.md index 3e4d47e7c9433..ff1e8d06cefdb 100644 --- a/docs/user/keybindings.md +++ b/docs/user/keybindings.md @@ -206,6 +206,8 @@ if the browser takes it first. Many defaults include `!terminalFocus` so they do not intercept terminal input. Keep that condition when remapping them if you want the same behavior. +`sidebar.toggle` never takes a Ctrl chord from a focused terminal, so tmux's `Ctrl+B` +prefix reaches it. On macOS, `Cmd+B` still toggles the sidebar there. ## Desktop quit shortcut