feat: add workspace and tab navigation keyboard shortcuts

This commit is contained in:
Mohamed Boudra
2026-03-05 11:28:02 +07:00
parent 6f2b967599
commit 6dc49ccbc7
17 changed files with 944 additions and 110 deletions
@@ -0,0 +1,43 @@
import { beforeEach, describe, expect, it } from "vitest";
import { useKeyboardShortcutsStore } from "./keyboard-shortcuts-store";
beforeEach(() => {
useKeyboardShortcutsStore.setState({
commandCenterOpen: false,
shortcutsDialogOpen: false,
altDown: false,
cmdOrCtrlDown: false,
sidebarShortcutWorkspaceTargets: [],
visibleWorkspaceTargets: [],
messageInputActionRequest: null,
nextMessageInputActionRequestId: 1,
workspaceTabActionRequest: null,
nextWorkspaceTabActionRequestId: 1,
});
});
describe("keyboard-shortcuts-store", () => {
it("queues and clears workspace tab action requests", () => {
useKeyboardShortcutsStore.getState().requestWorkspaceTabAction({
serverId: "srv-1",
workspaceId: "/repo/main",
kind: "navigate-index",
index: 3,
});
const request = useKeyboardShortcutsStore.getState().workspaceTabActionRequest;
expect(request).toEqual({
id: 1,
serverId: "srv-1",
workspaceId: "/repo/main",
kind: "navigate-index",
index: 3,
});
useKeyboardShortcutsStore.getState().clearWorkspaceTabActionRequest(999);
expect(useKeyboardShortcutsStore.getState().workspaceTabActionRequest).not.toBeNull();
useKeyboardShortcutsStore.getState().clearWorkspaceTabActionRequest(1);
expect(useKeyboardShortcutsStore.getState().workspaceTabActionRequest).toBeNull();
});
});
@@ -8,6 +8,28 @@ export type MessageInputActionRequest = {
kind: MessageInputKeyboardActionKind;
};
export type WorkspaceTabActionRequest =
| {
id: number;
serverId: string;
workspaceId: string;
kind: "new" | "close-current";
}
| {
id: number;
serverId: string;
workspaceId: string;
kind: "navigate-index";
index: number;
}
| {
id: number;
serverId: string;
workspaceId: string;
kind: "navigate-relative";
delta: 1 | -1;
};
interface KeyboardShortcutsState {
commandCenterOpen: boolean;
shortcutsDialogOpen: boolean;
@@ -15,14 +37,19 @@ interface KeyboardShortcutsState {
cmdOrCtrlDown: boolean;
/** Sidebar-visible workspace targets (up to 9), in top-to-bottom visual order. */
sidebarShortcutWorkspaceTargets: SidebarShortcutWorkspaceTarget[];
/** All visible workspace targets in top-to-bottom visual order. */
visibleWorkspaceTargets: SidebarShortcutWorkspaceTarget[];
messageInputActionRequest: MessageInputActionRequest | null;
nextMessageInputActionRequestId: number;
workspaceTabActionRequest: WorkspaceTabActionRequest | null;
nextWorkspaceTabActionRequestId: number;
setCommandCenterOpen: (open: boolean) => void;
setShortcutsDialogOpen: (open: boolean) => void;
setAltDown: (down: boolean) => void;
setCmdOrCtrlDown: (down: boolean) => void;
setSidebarShortcutWorkspaceTargets: (targets: SidebarShortcutWorkspaceTarget[]) => void;
setVisibleWorkspaceTargets: (targets: SidebarShortcutWorkspaceTarget[]) => void;
resetModifiers: () => void;
requestMessageInputAction: (input: {
@@ -30,6 +57,26 @@ interface KeyboardShortcutsState {
kind: MessageInputKeyboardActionKind;
}) => void;
clearMessageInputActionRequest: (id: number) => void;
requestWorkspaceTabAction: (input:
| {
serverId: string;
workspaceId: string;
kind: "new" | "close-current";
}
| {
serverId: string;
workspaceId: string;
kind: "navigate-index";
index: number;
}
| {
serverId: string;
workspaceId: string;
kind: "navigate-relative";
delta: 1 | -1;
}) => void;
clearWorkspaceTabActionRequest: (id: number) => void;
}
export const useKeyboardShortcutsStore = create<KeyboardShortcutsState>(
@@ -39,8 +86,11 @@ export const useKeyboardShortcutsStore = create<KeyboardShortcutsState>(
altDown: false,
cmdOrCtrlDown: false,
sidebarShortcutWorkspaceTargets: [],
visibleWorkspaceTargets: [],
messageInputActionRequest: null,
nextMessageInputActionRequestId: 1,
workspaceTabActionRequest: null,
nextWorkspaceTabActionRequestId: 1,
setCommandCenterOpen: (open) => set({ commandCenterOpen: open }),
setShortcutsDialogOpen: (open) => set({ shortcutsDialogOpen: open }),
@@ -48,6 +98,7 @@ export const useKeyboardShortcutsStore = create<KeyboardShortcutsState>(
setCmdOrCtrlDown: (down) => set({ cmdOrCtrlDown: down }),
setSidebarShortcutWorkspaceTargets: (targets) =>
set({ sidebarShortcutWorkspaceTargets: targets }),
setVisibleWorkspaceTargets: (targets) => set({ visibleWorkspaceTargets: targets }),
resetModifiers: () => set({ altDown: false, cmdOrCtrlDown: false }),
requestMessageInputAction: ({ agentKey, kind }) => {
@@ -64,5 +115,23 @@ export const useKeyboardShortcutsStore = create<KeyboardShortcutsState>(
}
set({ messageInputActionRequest: null });
},
requestWorkspaceTabAction: (input) => {
const id = get().nextWorkspaceTabActionRequestId;
set({
workspaceTabActionRequest: {
...input,
id,
} as WorkspaceTabActionRequest,
nextWorkspaceTabActionRequestId: id + 1,
});
},
clearWorkspaceTabActionRequest: (id) => {
const current = get().workspaceTabActionRequest;
if (!current || current.id !== id) {
return;
}
set({ workspaceTabActionRequest: null });
},
})
);