From 0880021665be55ccd66c2ea6c9346e25f4e3666c Mon Sep 17 00:00:00 2001 From: gatsby74 <166927047+gatsby74@users.noreply.github.com> Date: Tue, 23 Jun 2026 23:26:01 +0200 Subject: [PATCH] feat(shortcuts): add assignable shortcut to toggle sleeping workspaces Adds an unbound sidebar.sleepingWorkspaces.toggle keybinding action, wires it to the global shortcut dispatcher, and shows assigned shortcut labels in the workspace-board filter menu. Also resolves the PR against current main and removes an unnecessary exhaustive-switch fallback that blocked current lint. --- src/renderer/src/App.tsx | 15 ++++++++ .../src/components/sidebar/SidebarFilter.tsx | 33 +++++++++++------ .../task-page-github-work-item-status.ts | 3 -- src/shared/keybindings.test.ts | 36 +++++++++++++++++++ src/shared/keybindings.ts | 21 +++++++++++ 5 files changed, 94 insertions(+), 14 deletions(-) diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index 3bcbf8887..1071ff9ae 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -1604,6 +1604,21 @@ function App(): React.JSX.Element { return } + // Toggle the "show sleeping workspaces" sidebar filter without opening the + // filters menu (issue #5209). When revealing them, open the left sidebar + // so the now-visible sleeping worktrees are actually reachable. + if (matchShortcut('sidebar.sleepingWorkspaces.toggle')) { + input.preventDefault() + notifyTerminalCapture('sidebar.sleepingWorkspaces.toggle') + const store = useAppStore.getState() + const nextShowSleeping = !store.showSleepingWorkspaces + store.setShowSleepingWorkspaces(nextShowSleeping) + if (nextShowSleeping) { + store.setSidebarOpen(true) + } + return + } + // Why: rename the active terminal tab. Cmd+R is free in the app/terminal // focus zone because the browser pane owns its own Cmd+R reload and that // focus never reaches this renderer-window handler. Only terminal tabs diff --git a/src/renderer/src/components/sidebar/SidebarFilter.tsx b/src/renderer/src/components/sidebar/SidebarFilter.tsx index 7d21c357a..9927e4e9d 100644 --- a/src/renderer/src/components/sidebar/SidebarFilter.tsx +++ b/src/renderer/src/components/sidebar/SidebarFilter.tsx @@ -13,10 +13,12 @@ import { DropdownMenu, DropdownMenuContent, DropdownMenuSeparator, + DropdownMenuShortcut, DropdownMenuTrigger } from '@/components/ui/dropdown-menu' import { Tooltip, TooltipTrigger, TooltipContent } from '@/components/ui/tooltip' import RepoBadgeLabel from '@/components/repo/RepoBadgeLabel' +import { useShortcutLabel } from '@/hooks/useShortcutLabel' import { searchRepos } from '@/lib/repo-search' import { cn } from '@/lib/utils' import { DEFAULT_SHOW_SLEEPING_WORKSPACES } from '../../../../shared/constants' @@ -37,6 +39,9 @@ const SidebarFilter = React.memo(function SidebarFilter({ }: SidebarFilterProps) { const showSleepingWorkspaces = useAppStore((s) => s.showSleepingWorkspaces) const setShowSleepingWorkspaces = useAppStore((s) => s.setShowSleepingWorkspaces) + // Surface the user-assigned shortcut here so the filter menu doubles as its + // discovery point ('Unassigned' until they bind one in Settings → Shortcuts). + const sleepingShortcut = useShortcutLabel('sidebar.sleepingWorkspaces.toggle') const hideDefaultBranchWorkspace = useAppStore((s) => s.hideDefaultBranchWorkspace) const setHideDefaultBranchWorkspace = useAppStore((s) => s.setHideDefaultBranchWorkspace) const hideAutomationGeneratedWorkspaces = useAppStore((s) => s.hideAutomationGeneratedWorkspaces) @@ -183,6 +188,7 @@ const SidebarFilter = React.memo(function SidebarFilter({ label={translate('auto.components.sidebar.SidebarFilter.638a2d221d', 'Hide sleeping')} checked={!showSleepingWorkspaces} onChange={(hideSleeping) => setShowSleepingWorkspaces(!hideSleeping)} + shortcutLabel={sleepingShortcut === 'Unassigned' ? undefined : sleepingShortcut} /> } @@ -333,12 +339,14 @@ function FilterToggleRow({ icon, label, checked, - onChange + onChange, + shortcutLabel }: { icon: React.ReactNode label: string checked: boolean onChange: (next: boolean) => void + shortcutLabel?: string }) { return ( ) diff --git a/src/renderer/src/components/task-page-github-work-item-status.ts b/src/renderer/src/components/task-page-github-work-item-status.ts index a8028e6a4..ab9d03610 100644 --- a/src/renderer/src/components/task-page-github-work-item-status.ts +++ b/src/renderer/src/components/task-page-github-work-item-status.ts @@ -65,8 +65,5 @@ export function getTaskPageGitHubPRIconTone(item: GitHubWorkItemStatusItem): str return 'text-purple-600 dark:text-purple-300' case 'closed': return 'text-rose-600 dark:text-rose-300' - default: - // Fallback for any unexpected state - return 'text-muted-foreground' } } diff --git a/src/shared/keybindings.test.ts b/src/shared/keybindings.test.ts index 83db7e37e..e69917db0 100644 --- a/src/shared/keybindings.test.ts +++ b/src/shared/keybindings.test.ts @@ -456,6 +456,42 @@ describe('keybindings', () => { ) }) + it('keeps the sleeping-workspaces toggle unassigned until users customize it', () => { + const binding = { + key: 's', + code: 'KeyS', + control: true, + meta: false, + alt: true, + shift: false + } + + // Ships unbound on every platform (issue #5209): assign-it-yourself. + expect(getEffectiveKeybindingsForAction('sidebar.sleepingWorkspaces.toggle', 'darwin')).toEqual( + [] + ) + expect(getEffectiveKeybindingsForAction('sidebar.sleepingWorkspaces.toggle', 'linux')).toEqual( + [] + ) + expect(getEffectiveKeybindingsForAction('sidebar.sleepingWorkspaces.toggle', 'win32')).toEqual( + [] + ) + expect(keybindingMatchesAction('sidebar.sleepingWorkspaces.toggle', binding, 'linux')).toBe( + false + ) + expect( + keybindingMatchesAction('sidebar.sleepingWorkspaces.toggle', binding, 'linux', { + 'sidebar.sleepingWorkspaces.toggle': ['Mod+Alt+S'] + }) + ).toBe(true) + + const definition = getKeybindingDefinition('sidebar.sleepingWorkspaces.toggle') + expect(definition?.title).toBe('Toggle Sleeping Workspaces') + expect(definition?.searchKeywords).toEqual( + expect.arrayContaining(['sleeping', 'workspaces', 'filter']) + ) + }) + it('defines floating workspace panel action metadata', () => { const actionIds = [ 'floatingWorkspace.maximize' as KeybindingActionId, diff --git a/src/shared/keybindings.ts b/src/shared/keybindings.ts index 57a8b7752..6d4c3e64f 100644 --- a/src/shared/keybindings.ts +++ b/src/shared/keybindings.ts @@ -48,6 +48,7 @@ export type KeybindingActionId = | 'sidebar.sourceControl.toggle' | 'sidebar.checks.toggle' | 'sidebar.ports.toggle' + | 'sidebar.sleepingWorkspaces.toggle' | 'sidebar.focusWorktreeList' | 'floatingTerminal.toggle' | 'floatingWorkspace.maximize' @@ -393,6 +394,26 @@ export const KEYBINDING_DEFINITIONS: readonly KeybindingDefinition[] = [ win32: [] } }, + { + id: 'sidebar.sleepingWorkspaces.toggle', + title: 'Toggle Sleeping Workspaces', + group: 'Global', + scope: 'global', + searchKeywords: [ + 'shortcut', + 'sidebar', + 'sleeping', + 'asleep', + 'workspaces', + 'worktree', + 'filter', + 'show', + 'hide' + ], + // Why: ship unbound — issue #5209 asks to "assign a shortcut", so we avoid + // claiming a cross-platform chord and let users bind it in Settings. + defaultBindings: platformBindings([]) + }, { id: 'sidebar.focusWorktreeList', title: 'Focus worktree list',