From 79a3167ef5f92eaa7c8acde539f15ae419644ead Mon Sep 17 00:00:00 2001 From: kcybe Date: Wed, 9 Sep 2026 11:53:16 +0300 Subject: [PATCH] feat(web): add configurable composer focus shortcut --- apps/web/src/components/ChatView.tsx | 8 +++++++ .../KeybindingsSettings.logic.test.ts | 1 + apps/web/src/keybindings.test.ts | 23 +++++++++++++++++++ docs/user/keybindings.md | 4 ++++ packages/contracts/src/keybindings.test.ts | 6 +++++ packages/contracts/src/keybindings.ts | 1 + 6 files changed, 43 insertions(+) diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx index 0fbef88c81e1..da2c70ec0a3d 100644 --- a/apps/web/src/components/ChatView.tsx +++ b/apps/web/src/components/ChatView.tsx @@ -6091,6 +6091,13 @@ export default function ChatView(props: ChatViewProps) { }); if (!command) return; + if (command === "composer.focus") { + event.preventDefault(); + event.stopPropagation(); + focusComposer(); + return; + } + if (command === "thread.copyReference") { event.preventDefault(); event.stopPropagation(); @@ -6292,6 +6299,7 @@ export default function ChatView(props: ChatViewProps) { supportsSettlement, confirmAndUnpinThread, copyActiveThreadReference, + focusComposer, previewPanelOpen, toggleRightPanel, toggleRightPanelMaximized, diff --git a/apps/web/src/components/settings/KeybindingsSettings.logic.test.ts b/apps/web/src/components/settings/KeybindingsSettings.logic.test.ts index 41e3cc2ec304..52dcc15cc2a8 100644 --- a/apps/web/src/components/settings/KeybindingsSettings.logic.test.ts +++ b/apps/web/src/components/settings/KeybindingsSettings.logic.test.ts @@ -204,6 +204,7 @@ describe("KeybindingsSettings.logic", () => { expect(options).toEqual( expect.arrayContaining([ "chat.new", + "composer.focus", "rightPanel.toggleMaximized", "thread.stop", "script.setup-db.run", diff --git a/apps/web/src/keybindings.test.ts b/apps/web/src/keybindings.test.ts index 598e08e59770..89959825fa44 100644 --- a/apps/web/src/keybindings.test.ts +++ b/apps/web/src/keybindings.test.ts @@ -738,6 +738,29 @@ describe("cross-command precedence", () => { }); describe("resolveShortcutCommand", () => { + it.each(["MacIntel", "Win32", "Linux"])( + "resolves a custom composer-focus shortcut inside and outside terminals on %s", + (platform) => { + const keybindings = compile([ + { shortcut: modShortcut("i", { shiftKey: true }), command: "composer.focus" }, + ]); + const input = event({ + key: "i", + shiftKey: true, + metaKey: platform === "MacIntel", + ctrlKey: platform !== "MacIntel", + }); + + for (const terminalFocus of [false, true]) { + assert.strictEqual( + resolveShortcutCommand(input, keybindings, { platform, context: { terminalFocus } }), + "composer.focus", + ); + assert.isNull(resolveShortcutCommand(input, [], { platform })); + } + }, + ); + it("resolves a custom stop-thread shortcut", () => { const keybindings = compile([{ shortcut: modShortcut("escape"), command: "thread.stop" }]); diff --git a/docs/user/keybindings.md b/docs/user/keybindings.md index 4e46e97a3a56..c48ef29ce6f7 100644 --- a/docs/user/keybindings.md +++ b/docs/user/keybindings.md @@ -53,6 +53,10 @@ a shortcut. ## Commands with special behavior +`composer.focus` moves focus to the end of the current thread's composer, including +from a terminal. It has no default shortcut; assign one with **Add keybinding** in +**Settings → Keybindings**. + `thread.stop` interrupts the running turn in the focused thread. It has no default shortcut; assign one in **Settings → Keybindings**. diff --git a/packages/contracts/src/keybindings.test.ts b/packages/contracts/src/keybindings.test.ts index 27e096cbb2eb..a8888cd2ab64 100644 --- a/packages/contracts/src/keybindings.test.ts +++ b/packages/contracts/src/keybindings.test.ts @@ -84,6 +84,12 @@ it.effect("parses keybinding rules", () => }); assert.strictEqual(parsedThemeEditor.command, "themeEditor.toggle"); + const parsedComposerFocus = yield* decode(KeybindingRule, { + key: "mod+shift+i", + command: "composer.focus", + }); + assert.strictEqual(parsedComposerFocus.command, "composer.focus"); + const parsedLocal = yield* decode(KeybindingRule, { key: "mod+shift+n", command: "chat.newLocal", diff --git a/packages/contracts/src/keybindings.ts b/packages/contracts/src/keybindings.ts index 2be8aa91f7aa..52bfa8e7a44a 100644 --- a/packages/contracts/src/keybindings.ts +++ b/packages/contracts/src/keybindings.ts @@ -72,6 +72,7 @@ export const STATIC_KEYBINDING_COMMANDS = [ "filePicker.toggle", "projectSearch.toggle", "themeEditor.toggle", + "composer.focus", "composer.stash", "chat.new", "chat.newLocal",