Skip to content

Commit 78cf123

Browse files
authored
Merge pull request #1 from cschubiner/fix/agent-hotkeys
Fix agent switch hotkeys
2 parents c16e61d + cc5d43f commit 78cf123

8 files changed

Lines changed: 197 additions & 46 deletions

File tree

src/main/settings.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export interface RepositorySettings {
1212
pushOnCreate: boolean;
1313
}
1414

15-
export type ShortcutModifier = 'cmd' | 'ctrl' | 'shift' | 'alt' | 'option';
15+
export type ShortcutModifier = 'cmd' | 'ctrl' | 'shift' | 'alt' | 'option' | 'cmd+shift';
1616

1717
export interface ShortcutBinding {
1818
key: string;
@@ -26,10 +26,13 @@ export interface KeyboardSettings {
2626
toggleRightSidebar?: ShortcutBinding;
2727
toggleTheme?: ShortcutBinding;
2828
toggleKanban?: ShortcutBinding;
29+
toggleEditor?: ShortcutBinding;
2930
closeModal?: ShortcutBinding;
3031
nextProject?: ShortcutBinding;
3132
prevProject?: ShortcutBinding;
3233
newTask?: ShortcutBinding;
34+
nextAgent?: ShortcutBinding;
35+
prevAgent?: ShortcutBinding;
3336
}
3437

3538
export interface InterfaceSettings {
@@ -104,9 +107,12 @@ const DEFAULT_SETTINGS: AppSettings = {
104107
toggleRightSidebar: { key: '.', modifier: 'cmd' },
105108
toggleTheme: { key: 't', modifier: 'cmd' },
106109
toggleKanban: { key: 'p', modifier: 'cmd' },
110+
toggleEditor: { key: 'e', modifier: 'cmd' },
107111
nextProject: { key: 'ArrowRight', modifier: 'cmd' },
108112
prevProject: { key: 'ArrowLeft', modifier: 'cmd' },
109113
newTask: { key: 'n', modifier: 'cmd' },
114+
nextAgent: { key: 'k', modifier: 'cmd+shift' },
115+
prevAgent: { key: 'j', modifier: 'cmd+shift' },
110116
},
111117
interface: {
112118
autoRightSidebarBehavior: false,
@@ -276,7 +282,7 @@ function normalizeSettings(input: AppSettings): AppSettings {
276282

277283
// Keyboard
278284
const keyboard = (input as any)?.keyboard || {};
279-
const validModifiers: ShortcutModifier[] = ['cmd', 'ctrl', 'shift', 'alt', 'option'];
285+
const validModifiers: ShortcutModifier[] = ['cmd', 'ctrl', 'shift', 'alt', 'option', 'cmd+shift'];
280286
const normalizeBinding = (binding: any, defaultBinding: ShortcutBinding): ShortcutBinding => {
281287
if (!binding || typeof binding !== 'object') return defaultBinding;
282288
const key =
@@ -304,9 +310,12 @@ function normalizeSettings(input: AppSettings): AppSettings {
304310
),
305311
toggleTheme: normalizeBinding(keyboard.toggleTheme, DEFAULT_SETTINGS.keyboard!.toggleTheme!),
306312
toggleKanban: normalizeBinding(keyboard.toggleKanban, DEFAULT_SETTINGS.keyboard!.toggleKanban!),
313+
toggleEditor: normalizeBinding(keyboard.toggleEditor, DEFAULT_SETTINGS.keyboard!.toggleEditor!),
307314
nextProject: normalizeBinding(keyboard.nextProject, DEFAULT_SETTINGS.keyboard!.nextProject!),
308315
prevProject: normalizeBinding(keyboard.prevProject, DEFAULT_SETTINGS.keyboard!.prevProject!),
309316
newTask: normalizeBinding(keyboard.newTask, DEFAULT_SETTINGS.keyboard!.newTask!),
317+
nextAgent: normalizeBinding(keyboard.nextAgent, DEFAULT_SETTINGS.keyboard!.nextAgent!),
318+
prevAgent: normalizeBinding(keyboard.prevAgent, DEFAULT_SETTINGS.keyboard!.prevAgent!),
310319
};
311320

312321
// Interface

src/renderer/components/CommandPalette.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
Palette,
1919
} from 'lucide-react';
2020
import { APP_SHORTCUTS } from '../hooks/useKeyboardShortcuts';
21+
import type { ShortcutModifier } from '../types/shortcuts';
2122

2223
interface CommandPaletteProps {
2324
isOpen: boolean;
@@ -42,8 +43,6 @@ interface CommandPaletteProps {
4243
onOpenProject?: () => void;
4344
}
4445

45-
type ShortcutModifier = 'cmd' | 'ctrl' | 'shift' | 'alt' | 'option';
46-
4746
type CommandItem = {
4847
id: string;
4948
label: string;

src/renderer/components/KeyboardSettingsCard.tsx

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ const formatModifier = (modifier: ShortcutModifier | undefined): string => {
2525
switch (modifier) {
2626
case 'cmd':
2727
return '⌘';
28+
case 'cmd+shift':
29+
return '⌘⇧';
2830
case 'ctrl':
2931
return 'Ctrl';
3032
case 'alt':
@@ -161,10 +163,17 @@ const KeyboardSettingsCard: React.FC = () => {
161163

162164
// Determine which modifier is pressed
163165
let modifier: ShortcutModifier | null = null;
164-
if (event.metaKey) modifier = 'cmd';
165-
else if (event.ctrlKey) modifier = 'ctrl';
166-
else if (event.altKey) modifier = 'alt';
167-
else if (event.shiftKey) modifier = 'shift';
166+
if ((event.metaKey || event.ctrlKey) && event.shiftKey) {
167+
modifier = 'cmd+shift';
168+
} else if (event.metaKey) {
169+
modifier = 'cmd';
170+
} else if (event.ctrlKey) {
171+
modifier = 'ctrl';
172+
} else if (event.altKey) {
173+
modifier = 'alt';
174+
} else if (event.shiftKey) {
175+
modifier = 'shift';
176+
}
168177

169178
// Ignore if only modifier key pressed (no actual key)
170179
const isModifierOnly = ['Meta', 'Control', 'Alt', 'Shift'].includes(event.key);

src/renderer/components/MultiAgentTask.tsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,29 @@ const MultiAgentTask: React.FC<Props> = ({ task }) => {
364364
}
365365
}, [task.id, activeTabIndex, variants.length, scrollToBottom]);
366366

367+
// Switch active agent tab via global shortcuts (Cmd+Shift+J/K)
368+
useEffect(() => {
369+
const handleAgentSwitch = (event: Event) => {
370+
const customEvent = event as CustomEvent<{ direction: 'next' | 'prev' }>;
371+
if (variants.length <= 1) return;
372+
const direction = customEvent.detail?.direction;
373+
if (!direction) return;
374+
375+
setActiveTabIndex((current) => {
376+
if (variants.length <= 1) return current;
377+
if (direction === 'prev') {
378+
return current <= 0 ? variants.length - 1 : current - 1;
379+
}
380+
return (current + 1) % variants.length;
381+
});
382+
};
383+
384+
window.addEventListener('emdash:switch-agent', handleAgentSwitch);
385+
return () => {
386+
window.removeEventListener('emdash:switch-agent', handleAgentSwitch);
387+
};
388+
}, [variants.length]);
389+
367390
if (!multi?.enabled) {
368391
return (
369392
<div className="flex h-full items-center justify-center text-sm text-muted-foreground">

src/renderer/components/ui/shortcut-hint.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,13 @@ const ModifierIcon: React.FC<{ modifier: ShortcutModifier }> = ({ modifier }) =>
1313
switch (modifier) {
1414
case 'cmd':
1515
return <Command className="h-3 w-3" aria-hidden="true" />;
16+
case 'cmd+shift':
17+
return (
18+
<>
19+
<Command className="h-3 w-3" aria-hidden="true" />
20+
<span></span>
21+
</>
22+
);
1623
case 'ctrl':
1724
return <span>Ctrl</span>;
1825
case 'alt':

src/renderer/hooks/useKeyboardShortcuts.ts

Lines changed: 63 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ export type ShortcutSettingsKey =
1919
| 'closeModal'
2020
| 'nextProject'
2121
| 'prevProject'
22-
| 'newTask';
22+
| 'newTask'
23+
| 'nextAgent'
24+
| 'prevAgent';
2325

2426
export interface AppShortcut {
2527
key: string;
@@ -131,6 +133,24 @@ export const APP_SHORTCUTS: Record<string, AppShortcut> = {
131133
category: 'Navigation',
132134
settingsKey: 'newTask',
133135
},
136+
137+
NEXT_AGENT: {
138+
key: 'k',
139+
modifier: 'cmd+shift',
140+
label: 'Next Agent',
141+
description: 'Switch to the next agent working on this task',
142+
category: 'Navigation',
143+
settingsKey: 'nextAgent',
144+
},
145+
146+
PREV_AGENT: {
147+
key: 'j',
148+
modifier: 'cmd+shift',
149+
label: 'Previous Agent',
150+
description: 'Switch to the previous agent working on this task',
151+
category: 'Navigation',
152+
settingsKey: 'prevAgent',
153+
},
134154
};
135155

136156
/**
@@ -140,17 +160,29 @@ export const APP_SHORTCUTS: Record<string, AppShortcut> = {
140160
*/
141161

142162
export function formatShortcut(shortcut: ShortcutConfig): string {
143-
const modifier = shortcut.modifier
144-
? shortcut.modifier === 'cmd'
145-
? '⌘'
146-
: shortcut.modifier === 'option'
147-
? '⌥'
148-
: shortcut.modifier === 'shift'
149-
? '⇧'
150-
: shortcut.modifier === 'alt'
151-
? 'Alt'
152-
: 'Ctrl'
153-
: '';
163+
let modifier = '';
164+
if (shortcut.modifier) {
165+
switch (shortcut.modifier) {
166+
case 'cmd':
167+
modifier = '⌘';
168+
break;
169+
case 'option':
170+
modifier = '⌥';
171+
break;
172+
case 'shift':
173+
modifier = '⇧';
174+
break;
175+
case 'alt':
176+
modifier = 'Alt';
177+
break;
178+
case 'ctrl':
179+
modifier = 'Ctrl';
180+
break;
181+
case 'cmd+shift':
182+
modifier = '⌘⇧';
183+
break;
184+
}
185+
}
154186

155187
let key = shortcut.key;
156188
if (key === 'Escape') key = 'Esc';
@@ -196,7 +228,8 @@ function matchesModifier(modifier: ShortcutModifier | undefined, event: Keyboard
196228
switch (modifier) {
197229
case 'cmd':
198230
// On macOS require the Command key; on other platforms allow Ctrl as the Command equivalent
199-
return isMacPlatform ? event.metaKey : event.metaKey || event.ctrlKey;
231+
// Also ensure shift is NOT pressed (to distinguish from cmd+shift)
232+
return (isMacPlatform ? event.metaKey : event.metaKey || event.ctrlKey) && !event.shiftKey;
200233
case 'ctrl':
201234
// Require the Control key without treating Command as equivalent
202235
return event.ctrlKey && !event.metaKey;
@@ -205,6 +238,9 @@ function matchesModifier(modifier: ShortcutModifier | undefined, event: Keyboard
205238
return event.altKey;
206239
case 'shift':
207240
return event.shiftKey;
241+
case 'cmd+shift':
242+
// Compound modifier: Command + Shift
243+
return (isMacPlatform ? event.metaKey : event.metaKey || event.ctrlKey) && event.shiftKey;
208244
default:
209245
return false;
210246
}
@@ -260,6 +296,8 @@ export function useKeyboardShortcuts(handlers: GlobalShortcutHandlers) {
260296
nextProject: getEffectiveConfig(APP_SHORTCUTS.NEXT_TASK, custom),
261297
prevProject: getEffectiveConfig(APP_SHORTCUTS.PREV_TASK, custom),
262298
newTask: getEffectiveConfig(APP_SHORTCUTS.NEW_TASK, custom),
299+
nextAgent: getEffectiveConfig(APP_SHORTCUTS.NEXT_AGENT, custom),
300+
prevAgent: getEffectiveConfig(APP_SHORTCUTS.PREV_AGENT, custom),
263301
};
264302
}, [handlers.customKeyboardSettings]);
265303

@@ -331,6 +369,18 @@ export function useKeyboardShortcuts(handlers: GlobalShortcutHandlers) {
331369
priority: 'global',
332370
requiresClosed: true,
333371
},
372+
{
373+
config: effectiveShortcuts.nextAgent,
374+
handler: () => handlers.onNextAgent?.(),
375+
priority: 'global',
376+
requiresClosed: true,
377+
},
378+
{
379+
config: effectiveShortcuts.prevAgent,
380+
handler: () => handlers.onPrevAgent?.(),
381+
priority: 'global',
382+
requiresClosed: true,
383+
},
334384
];
335385

336386
const handleKeyDown = (event: KeyboardEvent) => {

0 commit comments

Comments
 (0)