From 8a9d09adda7bfe6367154bb54a8cc50bb256290b Mon Sep 17 00:00:00 2001 From: Ivan Malison Date: Wed, 22 Jul 2026 02:32:07 -0700 Subject: [PATCH 1/2] Add configurable add-project shortcut --- apps/server/src/keybindings.test.ts | 1 + apps/web/src/components/CommandPalette.tsx | 73 ++++++++++++---------- apps/web/src/keybindings.test.ts | 23 +++++++ packages/contracts/src/keybindings.test.ts | 6 ++ packages/contracts/src/keybindings.ts | 1 + packages/shared/src/keybindings.ts | 1 + 6 files changed, 73 insertions(+), 32 deletions(-) diff --git a/apps/server/src/keybindings.test.ts b/apps/server/src/keybindings.test.ts index 2eef6ac8416..c1d5f1fa157 100644 --- a/apps/server/src/keybindings.test.ts +++ b/apps/server/src/keybindings.test.ts @@ -200,6 +200,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => { assert.equal(defaultsByCommand.get("modelPicker.toggle"), "mod+shift+m"); assert.equal(defaultsByCommand.get("sidebar.toggle"), "mod+b"); assert.equal(defaultsByCommand.get("rightPanel.toggle"), "mod+alt+b"); + assert.equal(defaultsByCommand.get("project.add"), "mod+alt+a"); assert.equal(defaultsByCommand.get("terminal.splitVertical"), "mod+shift+d"); assert.equal(defaultsByCommand.get("modelPicker.jump.1"), "mod+1"); assert.equal(defaultsByCommand.get("modelPicker.jump.9"), "mod+9"); diff --git a/apps/web/src/components/CommandPalette.tsx b/apps/web/src/components/CommandPalette.tsx index aa7547c8ba6..b90f35fab91 100644 --- a/apps/web/src/components/CommandPalette.tsx +++ b/apps/web/src/components/CommandPalette.tsx @@ -408,16 +408,20 @@ export function CommandPalette({ children }: { children: ReactNode }) { terminalOpen, }, }); - if (command !== "commandPalette.toggle") { + if (command !== "commandPalette.toggle" && command !== "project.add") { return; } event.preventDefault(); event.stopPropagation(); - toggleOpen(); + if (command === "project.add") { + openAddProject(); + } else { + toggleOpen(); + } }; window.addEventListener("keydown", onKeyDown); return () => window.removeEventListener("keydown", onKeyDown); - }, [keybindings, terminalOpen, toggleOpen]); + }, [keybindings, openAddProject, terminalOpen, toggleOpen]); useEffect( () => @@ -1146,6 +1150,35 @@ function OpenCommandPaletteDialog(props: { ]); const actionItems: Array = []; + const addProjectAction: CommandPaletteActionItem = { + kind: "action", + value: "action:add-project", + searchTerms: [ + "add project", + "folder", + "directory", + "browse", + "clone", + "remote", + "repository", + "repo", + "git", + "github", + "gitlab", + "bitbucket", + "azure", + "devops", + "url", + "environment", + ], + title: "Add project", + icon: , + shortcutCommand: "project.add", + keepOpen: true, + run: async () => { + openAddProjectFlow(); + }, + }; if (projects.length > 0) { const activeProjectTitle = @@ -1182,38 +1215,14 @@ function OpenCommandPaletteDialog(props: { title: "New thread in...", icon: , addonIcon: , - groups: [{ value: "projects", label: "Projects", items: projectThreadItems }], + groups: [ + { value: "projects", label: "Projects", items: projectThreadItems }, + { value: "actions", label: "Actions", items: [addProjectAction] }, + ], }); } - actionItems.push({ - kind: "action", - value: "action:add-project", - searchTerms: [ - "add project", - "folder", - "directory", - "browse", - "clone", - "remote", - "repository", - "repo", - "git", - "github", - "gitlab", - "bitbucket", - "azure", - "devops", - "url", - "environment", - ], - title: "Add project", - icon: , - keepOpen: true, - run: async () => { - openAddProjectFlow(); - }, - }); + actionItems.push(addProjectAction); if (wslAddProjectEnvironmentOption) { actionItems.push({ diff --git a/apps/web/src/keybindings.test.ts b/apps/web/src/keybindings.test.ts index c0d326edd55..7ae59b6b4e1 100644 --- a/apps/web/src/keybindings.test.ts +++ b/apps/web/src/keybindings.test.ts @@ -125,6 +125,11 @@ const DEFAULT_BINDINGS = compile([ }, { shortcut: modShortcut("o", { shiftKey: true }), command: "chat.new" }, { shortcut: modShortcut("n", { shiftKey: true }), command: "chat.newLocal" }, + { + shortcut: modShortcut("a", { altKey: true }), + command: "project.add", + whenAst: whenNot(whenIdentifier("terminalFocus")), + }, { shortcut: modShortcut("o"), command: "editor.openFavorite" }, { shortcut: modShortcut("[", { shiftKey: true }), command: "thread.previous" }, { shortcut: modShortcut("]", { shiftKey: true }), command: "thread.next" }, @@ -327,6 +332,7 @@ describe("shortcutLabelForCommand", () => { shortcutLabelForCommand(DEFAULT_BINDINGS, "commandPalette.toggle", "MacIntel"), "⌘K", ); + assert.strictEqual(shortcutLabelForCommand(DEFAULT_BINDINGS, "project.add", "MacIntel"), "⌥⌘A"); assert.strictEqual( shortcutLabelForCommand(DEFAULT_BINDINGS, "modelPicker.toggle", "Linux"), "Ctrl+Shift+M", @@ -514,6 +520,23 @@ describe("chat/editor shortcuts", () => { ); }); + it("matches project.add shortcut outside terminal focus", () => { + assert.strictEqual( + resolveShortcutCommand(event({ key: "a", metaKey: true, altKey: true }), DEFAULT_BINDINGS, { + platform: "MacIntel", + context: { terminalFocus: false }, + }), + "project.add", + ); + assert.notStrictEqual( + resolveShortcutCommand(event({ key: "a", metaKey: true, altKey: true }), DEFAULT_BINDINGS, { + platform: "MacIntel", + context: { terminalFocus: true }, + }), + "project.add", + ); + }); + it("matches diff.toggle shortcut outside terminal focus", () => { assert.isTrue( isDiffToggleShortcut(event({ key: "d", metaKey: true }), DEFAULT_BINDINGS, { diff --git a/packages/contracts/src/keybindings.test.ts b/packages/contracts/src/keybindings.test.ts index 33ecd38039f..d9e07b82b6a 100644 --- a/packages/contracts/src/keybindings.test.ts +++ b/packages/contracts/src/keybindings.test.ts @@ -65,6 +65,12 @@ it.effect("parses keybinding rules", () => }); assert.strictEqual(parsedLocal.command, "chat.newLocal"); + const parsedAddProject = yield* decode(KeybindingRule, { + key: "mod+alt+a", + command: "project.add", + }); + assert.strictEqual(parsedAddProject.command, "project.add"); + const parsedModelPickerToggle = yield* decode(KeybindingRule, { key: "mod+shift+m", command: "modelPicker.toggle", diff --git a/packages/contracts/src/keybindings.ts b/packages/contracts/src/keybindings.ts index c7cff9943cd..008317f670c 100644 --- a/packages/contracts/src/keybindings.ts +++ b/packages/contracts/src/keybindings.ts @@ -65,6 +65,7 @@ const STATIC_KEYBINDING_COMMANDS = [ "commandPalette.toggle", "chat.new", "chat.newLocal", + "project.add", "editor.openFavorite", ...MODEL_PICKER_KEYBINDING_COMMANDS, ...THREAD_KEYBINDING_COMMANDS, diff --git a/packages/shared/src/keybindings.ts b/packages/shared/src/keybindings.ts index b6bdd7b4783..53a4d3f3473 100644 --- a/packages/shared/src/keybindings.ts +++ b/packages/shared/src/keybindings.ts @@ -38,6 +38,7 @@ export const DEFAULT_KEYBINDINGS: ReadonlyArray = [ { key: "mod+n", command: "chat.new", when: "!terminalFocus" }, { key: "mod+shift+o", command: "chat.new", when: "!terminalFocus" }, { key: "mod+shift+n", command: "chat.newLocal", when: "!terminalFocus" }, + { key: "mod+alt+a", command: "project.add", when: "!terminalFocus" }, { key: "mod+shift+m", command: "modelPicker.toggle", when: "!terminalFocus" }, { key: "mod+o", command: "editor.openFavorite" }, { key: "mod+shift+[", command: "thread.previous" }, From 38d2257e751a21a22d43a6e6f3e485f925008e75 Mon Sep 17 00:00:00 2001 From: Ivan Malison Date: Wed, 22 Jul 2026 02:57:14 -0700 Subject: [PATCH 2/2] Use Alt+A for add-project shortcut --- apps/server/src/keybindings.test.ts | 2 +- apps/web/src/keybindings.test.ts | 15 +++++++++++---- packages/contracts/src/keybindings.test.ts | 2 +- packages/shared/src/keybindings.ts | 2 +- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/apps/server/src/keybindings.test.ts b/apps/server/src/keybindings.test.ts index c1d5f1fa157..76285d26cca 100644 --- a/apps/server/src/keybindings.test.ts +++ b/apps/server/src/keybindings.test.ts @@ -200,7 +200,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => { assert.equal(defaultsByCommand.get("modelPicker.toggle"), "mod+shift+m"); assert.equal(defaultsByCommand.get("sidebar.toggle"), "mod+b"); assert.equal(defaultsByCommand.get("rightPanel.toggle"), "mod+alt+b"); - assert.equal(defaultsByCommand.get("project.add"), "mod+alt+a"); + assert.equal(defaultsByCommand.get("project.add"), "alt+a"); assert.equal(defaultsByCommand.get("terminal.splitVertical"), "mod+shift+d"); assert.equal(defaultsByCommand.get("modelPicker.jump.1"), "mod+1"); assert.equal(defaultsByCommand.get("modelPicker.jump.9"), "mod+9"); diff --git a/apps/web/src/keybindings.test.ts b/apps/web/src/keybindings.test.ts index 7ae59b6b4e1..90a797506c6 100644 --- a/apps/web/src/keybindings.test.ts +++ b/apps/web/src/keybindings.test.ts @@ -126,7 +126,14 @@ const DEFAULT_BINDINGS = compile([ { shortcut: modShortcut("o", { shiftKey: true }), command: "chat.new" }, { shortcut: modShortcut("n", { shiftKey: true }), command: "chat.newLocal" }, { - shortcut: modShortcut("a", { altKey: true }), + shortcut: { + key: "a", + metaKey: false, + ctrlKey: false, + shiftKey: false, + altKey: true, + modKey: false, + }, command: "project.add", whenAst: whenNot(whenIdentifier("terminalFocus")), }, @@ -332,7 +339,7 @@ describe("shortcutLabelForCommand", () => { shortcutLabelForCommand(DEFAULT_BINDINGS, "commandPalette.toggle", "MacIntel"), "⌘K", ); - assert.strictEqual(shortcutLabelForCommand(DEFAULT_BINDINGS, "project.add", "MacIntel"), "⌥⌘A"); + assert.strictEqual(shortcutLabelForCommand(DEFAULT_BINDINGS, "project.add", "MacIntel"), "⌥A"); assert.strictEqual( shortcutLabelForCommand(DEFAULT_BINDINGS, "modelPicker.toggle", "Linux"), "Ctrl+Shift+M", @@ -522,14 +529,14 @@ describe("chat/editor shortcuts", () => { it("matches project.add shortcut outside terminal focus", () => { assert.strictEqual( - resolveShortcutCommand(event({ key: "a", metaKey: true, altKey: true }), DEFAULT_BINDINGS, { + resolveShortcutCommand(event({ key: "å", code: "KeyA", altKey: true }), DEFAULT_BINDINGS, { platform: "MacIntel", context: { terminalFocus: false }, }), "project.add", ); assert.notStrictEqual( - resolveShortcutCommand(event({ key: "a", metaKey: true, altKey: true }), DEFAULT_BINDINGS, { + resolveShortcutCommand(event({ key: "å", code: "KeyA", altKey: true }), DEFAULT_BINDINGS, { platform: "MacIntel", context: { terminalFocus: true }, }), diff --git a/packages/contracts/src/keybindings.test.ts b/packages/contracts/src/keybindings.test.ts index d9e07b82b6a..15ddc6d4a65 100644 --- a/packages/contracts/src/keybindings.test.ts +++ b/packages/contracts/src/keybindings.test.ts @@ -66,7 +66,7 @@ it.effect("parses keybinding rules", () => assert.strictEqual(parsedLocal.command, "chat.newLocal"); const parsedAddProject = yield* decode(KeybindingRule, { - key: "mod+alt+a", + key: "alt+a", command: "project.add", }); assert.strictEqual(parsedAddProject.command, "project.add"); diff --git a/packages/shared/src/keybindings.ts b/packages/shared/src/keybindings.ts index 53a4d3f3473..7d8c2470800 100644 --- a/packages/shared/src/keybindings.ts +++ b/packages/shared/src/keybindings.ts @@ -38,7 +38,7 @@ export const DEFAULT_KEYBINDINGS: ReadonlyArray = [ { key: "mod+n", command: "chat.new", when: "!terminalFocus" }, { key: "mod+shift+o", command: "chat.new", when: "!terminalFocus" }, { key: "mod+shift+n", command: "chat.newLocal", when: "!terminalFocus" }, - { key: "mod+alt+a", command: "project.add", when: "!terminalFocus" }, + { key: "alt+a", command: "project.add", when: "!terminalFocus" }, { key: "mod+shift+m", command: "modelPicker.toggle", when: "!terminalFocus" }, { key: "mod+o", command: "editor.openFavorite" }, { key: "mod+shift+[", command: "thread.previous" },