From e17f244e0d411422dacaa0a43669fb4e8436347d Mon Sep 17 00:00:00 2001 From: El-Hussein Abdelraouf Date: Sat, 15 Aug 2026 15:27:49 +0300 Subject: [PATCH] feat(web): make right panel maximize bindable (#5091) --- apps/server/src/keybindings.test.ts | 1 + apps/web/src/components/ChatView.tsx | 8 ++++++++ .../settings/KeybindingsSettings.logic.test.ts | 6 ++++-- .../settings/KeybindingsSettings.logic.ts | 6 ++---- apps/web/src/keybindings.test.ts | 16 ++++++++++++++++ docs/user/keybindings.md | 3 +++ packages/contracts/src/keybindings.test.ts | 6 ++++++ packages/contracts/src/keybindings.ts | 3 ++- 8 files changed, 42 insertions(+), 7 deletions(-) diff --git a/apps/server/src/keybindings.test.ts b/apps/server/src/keybindings.test.ts index b674688f04105ed3217c21f0567a84342aa3b580..24a137d933fade32efe47ee75c67fb7ae4c5387a 100644 --- a/apps/server/src/keybindings.test.ts +++ b/apps/server/src/keybindings.test.ts @@ -203,6 +203,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => { assert.equal(defaultsByCommand.get("projectSearch.toggle"), "mod+shift+f"); assert.equal(defaultsByCommand.get("sidebar.toggle"), "mod+b"); assert.equal(defaultsByCommand.get("rightPanel.toggle"), "mod+alt+b"); + assert.isFalse(defaultsByCommand.has("rightPanel.toggleMaximized")); 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/ChatView.tsx b/apps/web/src/components/ChatView.tsx index d2d3e908c1cb9e13d3127dd9ca18ffe6e73a5b4a..feac2d5978ce238eee57b8364edd4f48c506a2a7 100644 --- a/apps/web/src/components/ChatView.tsx +++ b/apps/web/src/components/ChatView.tsx @@ -4739,6 +4739,13 @@ function ChatViewContent(props: ChatViewProps) { return; } + if (command === "rightPanel.toggleMaximized") { + event.preventDefault(); + event.stopPropagation(); + toggleRightPanelMaximized(); + return; + } + if (command === "terminal.split") { event.preventDefault(); event.stopPropagation(); @@ -4834,6 +4841,7 @@ function ChatViewContent(props: ChatViewProps) { keybindings, onToggleDiff, toggleRightPanel, + toggleRightPanelMaximized, toggleTerminalVisibility, composerRef, ]); diff --git a/apps/web/src/components/settings/KeybindingsSettings.logic.test.ts b/apps/web/src/components/settings/KeybindingsSettings.logic.test.ts index 22a91b7c1504e6882e3a676843ae4d6bb9303888..90eaaef99413fd83689ba5c715fdc2bacb363ffb 100644 --- a/apps/web/src/components/settings/KeybindingsSettings.logic.test.ts +++ b/apps/web/src/components/settings/KeybindingsSettings.logic.test.ts @@ -135,7 +135,7 @@ describe("KeybindingsSettings.logic", () => { expect(options).not.toContain("customModeActive"); }); - it("builds command options from defaults and resolved project bindings", () => { + it("builds command options from built-in commands and resolved project bindings", () => { const options = buildKeybindingCommandOptions([ { command: "script.setup-db.run", @@ -150,7 +150,9 @@ describe("KeybindingsSettings.logic", () => { }, ] satisfies ResolvedKeybindingsConfig); - expect(options).toEqual(expect.arrayContaining(["chat.new", "script.setup-db.run"])); + expect(options).toEqual( + expect.arrayContaining(["chat.new", "rightPanel.toggleMaximized", "script.setup-db.run"]), + ); }); it("reports unknown when variables without rejecting parseable expressions", () => { diff --git a/apps/web/src/components/settings/KeybindingsSettings.logic.ts b/apps/web/src/components/settings/KeybindingsSettings.logic.ts index da54e86e42e1c46950d23e9d7362808f6cd2b8c9..8c15111e1969da45e49ae2ae8e87bfffb4a4fc99 100644 --- a/apps/web/src/components/settings/KeybindingsSettings.logic.ts +++ b/apps/web/src/components/settings/KeybindingsSettings.logic.ts @@ -1,4 +1,5 @@ import { + STATIC_KEYBINDING_COMMANDS, type KeybindingCommand, type KeybindingShortcut, type KeybindingWhenNode, @@ -255,10 +256,7 @@ export function buildWhenVariableOptions(): ReadonlyArray { export function buildKeybindingCommandOptions( keybindings: ResolvedKeybindingsConfig, ): ReadonlyArray { - const commands = new Set(); - for (const binding of DEFAULT_RESOLVED_KEYBINDINGS) { - commands.add(binding.command); - } + const commands = new Set(STATIC_KEYBINDING_COMMANDS); for (const binding of keybindings) { commands.add(binding.command); } diff --git a/apps/web/src/keybindings.test.ts b/apps/web/src/keybindings.test.ts index 11aa97dc8e861313e4e4fcb391139e4011074955..da76e13d56e32430b9418656a840defc6e849460 100644 --- a/apps/web/src/keybindings.test.ts +++ b/apps/web/src/keybindings.test.ts @@ -679,6 +679,22 @@ describe("resolveShortcutCommand", () => { ); }); + it("resolves a custom right panel maximize binding", () => { + const keybindings = compile([ + { + shortcut: modShortcut("m", { shiftKey: true }), + command: "rightPanel.toggleMaximized", + }, + ]); + + assert.strictEqual( + resolveShortcutCommand(event({ key: "m", metaKey: true, shiftKey: true }), keybindings, { + platform: "MacIntel", + }), + "rightPanel.toggleMaximized", + ); + }); + it("matches bracket shortcuts using the physical key code", () => { assert.strictEqual( resolveShortcutCommand( diff --git a/docs/user/keybindings.md b/docs/user/keybindings.md index f7f6facbe5945ab81178cf62a1170d66d92ece11..63264bed10b5170495c4b1911dc3235d07819305 100644 --- a/docs/user/keybindings.md +++ b/docs/user/keybindings.md @@ -47,6 +47,9 @@ Use **Inspect** to pick an element in the app and reveal its color token. Inspec successful pick; its hover glow and badge preview the element and token that click will select. **Cancel** or `Escape` exits Inspect and clears its selection and spotlight. +`rightPanel.toggleMaximized` maximizes or restores the open right panel. It has no default shortcut, +so add one in **Settings** → **Keybindings** if you want to use it. + The command palette searches active thread titles, projects, branches, user messages, and final agent responses across connected environments. Message matches show one labeled excerpt while keeping the thread's project, branch, and machine context visible. Message search begins after two diff --git a/packages/contracts/src/keybindings.test.ts b/packages/contracts/src/keybindings.test.ts index 342e446789383dee465e5d120a31dfebf4d4f664..71d8624a8aeabe569e157cf0a80d86da3466a63e 100644 --- a/packages/contracts/src/keybindings.test.ts +++ b/packages/contracts/src/keybindings.test.ts @@ -42,6 +42,12 @@ it.effect("parses keybinding rules", () => }); assert.strictEqual(parsedRightPanelToggle.command, "rightPanel.toggle"); + const parsedRightPanelToggleMaximized = yield* decode(KeybindingRule, { + key: "mod+shift+m", + command: "rightPanel.toggleMaximized", + }); + assert.strictEqual(parsedRightPanelToggleMaximized.command, "rightPanel.toggleMaximized"); + const parsedClose = yield* decode(KeybindingRule, { key: "mod+w", command: "terminal.close", diff --git a/packages/contracts/src/keybindings.ts b/packages/contracts/src/keybindings.ts index 3fcbf6ef5fdb35d8acdf2a7dd6ba09565cd61e5b..19276c41e7b6951d5b3b2981eae4c2a352dd4303 100644 --- a/packages/contracts/src/keybindings.ts +++ b/packages/contracts/src/keybindings.ts @@ -47,7 +47,7 @@ export const MODEL_PICKER_KEYBINDING_COMMANDS = [ ] as const; export type ModelPickerKeybindingCommand = (typeof MODEL_PICKER_KEYBINDING_COMMANDS)[number]; -const STATIC_KEYBINDING_COMMANDS = [ +export const STATIC_KEYBINDING_COMMANDS = [ "sidebar.toggle", "terminal.toggle", "terminal.split", @@ -55,6 +55,7 @@ const STATIC_KEYBINDING_COMMANDS = [ "terminal.new", "terminal.close", "rightPanel.toggle", + "rightPanel.toggleMaximized", "diff.toggle", "preview.toggle", "preview.refresh", -- 2.54.0