authorgravatar for hussein@raoufs.meEl-Hussein Abdelraouf <hussein@raoufs.me> 2026-08-15 15:27:49+03:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-08-15 12:27:49+00:00
loge17f244e0d411422dacaa0a43669fb4e8436347d
treeeaa756c4246805c9beabef0a0d2c21b5e341af9c
parent62bb97428a1bd007f4a83cdb1fcb8bbde2cdeef3
signature Signed by PGP key B5690EEEBB952194

feat(web): make right panel maximize bindable (#5091)


8 files changed, 42 insertions(+), 7 deletions(-)

apps/server/src/keybindings.test.ts+1
...@@ -203,6 +203,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => {...@@ -203,6 +203,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => {
203 assert.equal(defaultsByCommand.get("projectSearch.toggle"), "mod+shift+f");203 assert.equal(defaultsByCommand.get("projectSearch.toggle"), "mod+shift+f");
204 assert.equal(defaultsByCommand.get("sidebar.toggle"), "mod+b");204 assert.equal(defaultsByCommand.get("sidebar.toggle"), "mod+b");
205 assert.equal(defaultsByCommand.get("rightPanel.toggle"), "mod+alt+b");205 assert.equal(defaultsByCommand.get("rightPanel.toggle"), "mod+alt+b");
206 assert.isFalse(defaultsByCommand.has("rightPanel.toggleMaximized"));
206 assert.equal(defaultsByCommand.get("terminal.splitVertical"), "mod+shift+d");207 assert.equal(defaultsByCommand.get("terminal.splitVertical"), "mod+shift+d");
207 assert.equal(defaultsByCommand.get("modelPicker.jump.1"), "mod+1");208 assert.equal(defaultsByCommand.get("modelPicker.jump.1"), "mod+1");
208 assert.equal(defaultsByCommand.get("modelPicker.jump.9"), "mod+9");209 assert.equal(defaultsByCommand.get("modelPicker.jump.9"), "mod+9");
apps/web/src/components/ChatView.tsx+8
...@@ -4739,6 +4739,13 @@ function ChatViewContent(props: ChatViewProps) {...@@ -4739,6 +4739,13 @@ function ChatViewContent(props: ChatViewProps) {
4739 return;4739 return;
4740 }4740 }
47414741
4742 if (command === "rightPanel.toggleMaximized") {
4743 event.preventDefault();
4744 event.stopPropagation();
4745 toggleRightPanelMaximized();
4746 return;
4747 }
4748
4742 if (command === "terminal.split") {4749 if (command === "terminal.split") {
4743 event.preventDefault();4750 event.preventDefault();
4744 event.stopPropagation();4751 event.stopPropagation();
...@@ -4834,6 +4841,7 @@ function ChatViewContent(props: ChatViewProps) {...@@ -4834,6 +4841,7 @@ function ChatViewContent(props: ChatViewProps) {
4834 keybindings,4841 keybindings,
4835 onToggleDiff,4842 onToggleDiff,
4836 toggleRightPanel,4843 toggleRightPanel,
4844 toggleRightPanelMaximized,
4837 toggleTerminalVisibility,4845 toggleTerminalVisibility,
4838 composerRef,4846 composerRef,
4839 ]);4847 ]);
apps/web/src/components/settings/KeybindingsSettings.logic.test.ts+4-2
...@@ -135,7 +135,7 @@ describe("KeybindingsSettings.logic", () => {...@@ -135,7 +135,7 @@ describe("KeybindingsSettings.logic", () => {
135 expect(options).not.toContain("customModeActive");135 expect(options).not.toContain("customModeActive");
136 });136 });
137137
138 it("builds command options from defaults and resolved project bindings", () => {138 it("builds command options from built-in commands and resolved project bindings", () => {
139 const options = buildKeybindingCommandOptions([139 const options = buildKeybindingCommandOptions([
140 {140 {
141 command: "script.setup-db.run",141 command: "script.setup-db.run",
...@@ -150,7 +150,9 @@ describe("KeybindingsSettings.logic", () => {...@@ -150,7 +150,9 @@ describe("KeybindingsSettings.logic", () => {
150 },150 },
151 ] satisfies ResolvedKeybindingsConfig);151 ] satisfies ResolvedKeybindingsConfig);
152152
153 expect(options).toEqual(expect.arrayContaining(["chat.new", "script.setup-db.run"]));153 expect(options).toEqual(
154 expect.arrayContaining(["chat.new", "rightPanel.toggleMaximized", "script.setup-db.run"]),
155 );
154 });156 });
155157
156 it("reports unknown when variables without rejecting parseable expressions", () => {158 it("reports unknown when variables without rejecting parseable expressions", () => {
apps/web/src/components/settings/KeybindingsSettings.logic.ts+2-4
...@@ -1,4 +1,5 @@...@@ -1,4 +1,5 @@
1import {1import {
2 STATIC_KEYBINDING_COMMANDS,
2 type KeybindingCommand,3 type KeybindingCommand,
3 type KeybindingShortcut,4 type KeybindingShortcut,
4 type KeybindingWhenNode,5 type KeybindingWhenNode,
...@@ -255,10 +256,7 @@ export function buildWhenVariableOptions(): ReadonlyArray<WhenVariableOption> {...@@ -255,10 +256,7 @@ export function buildWhenVariableOptions(): ReadonlyArray<WhenVariableOption> {
255export function buildKeybindingCommandOptions(256export function buildKeybindingCommandOptions(
256 keybindings: ResolvedKeybindingsConfig,257 keybindings: ResolvedKeybindingsConfig,
257): ReadonlyArray<KeybindingCommandOption> {258): ReadonlyArray<KeybindingCommandOption> {
258 const commands = new Set<KeybindingCommand>();259 const commands = new Set<KeybindingCommand>(STATIC_KEYBINDING_COMMANDS);
259 for (const binding of DEFAULT_RESOLVED_KEYBINDINGS) {
260 commands.add(binding.command);
261 }
262 for (const binding of keybindings) {260 for (const binding of keybindings) {
263 commands.add(binding.command);261 commands.add(binding.command);
264 }262 }
apps/web/src/keybindings.test.ts+16
...@@ -679,6 +679,22 @@ describe("resolveShortcutCommand", () => {...@@ -679,6 +679,22 @@ describe("resolveShortcutCommand", () => {
679 );679 );
680 });680 });
681681
682 it("resolves a custom right panel maximize binding", () => {
683 const keybindings = compile([
684 {
685 shortcut: modShortcut("m", { shiftKey: true }),
686 command: "rightPanel.toggleMaximized",
687 },
688 ]);
689
690 assert.strictEqual(
691 resolveShortcutCommand(event({ key: "m", metaKey: true, shiftKey: true }), keybindings, {
692 platform: "MacIntel",
693 }),
694 "rightPanel.toggleMaximized",
695 );
696 });
697
682 it("matches bracket shortcuts using the physical key code", () => {698 it("matches bracket shortcuts using the physical key code", () => {
683 assert.strictEqual(699 assert.strictEqual(
684 resolveShortcutCommand(700 resolveShortcutCommand(
docs/user/keybindings.md+3
...@@ -47,6 +47,9 @@ Use **Inspect** to pick an element in the app and reveal its color token. Inspec...@@ -47,6 +47,9 @@ Use **Inspect** to pick an element in the app and reveal its color token. Inspec
47successful pick; its hover glow and badge preview the element and token that click will select.47successful pick; its hover glow and badge preview the element and token that click will select.
48**Cancel** or `Escape` exits Inspect and clears its selection and spotlight.48**Cancel** or `Escape` exits Inspect and clears its selection and spotlight.
4949
50`rightPanel.toggleMaximized` maximizes or restores the open right panel. It has no default shortcut,
51so add one in **Settings** → **Keybindings** if you want to use it.
52
50The command palette searches active thread titles, projects, branches, user messages, and final53The command palette searches active thread titles, projects, branches, user messages, and final
51agent responses across connected environments. Message matches show one labeled excerpt while54agent responses across connected environments. Message matches show one labeled excerpt while
52keeping the thread's project, branch, and machine context visible. Message search begins after two55keeping the thread's project, branch, and machine context visible. Message search begins after two
packages/contracts/src/keybindings.test.ts+6
...@@ -42,6 +42,12 @@ it.effect("parses keybinding rules", () =>...@@ -42,6 +42,12 @@ it.effect("parses keybinding rules", () =>
42 });42 });
43 assert.strictEqual(parsedRightPanelToggle.command, "rightPanel.toggle");43 assert.strictEqual(parsedRightPanelToggle.command, "rightPanel.toggle");
4444
45 const parsedRightPanelToggleMaximized = yield* decode(KeybindingRule, {
46 key: "mod+shift+m",
47 command: "rightPanel.toggleMaximized",
48 });
49 assert.strictEqual(parsedRightPanelToggleMaximized.command, "rightPanel.toggleMaximized");
50
45 const parsedClose = yield* decode(KeybindingRule, {51 const parsedClose = yield* decode(KeybindingRule, {
46 key: "mod+w",52 key: "mod+w",
47 command: "terminal.close",53 command: "terminal.close",
packages/contracts/src/keybindings.ts+2-1
...@@ -47,7 +47,7 @@ export const MODEL_PICKER_KEYBINDING_COMMANDS = [...@@ -47,7 +47,7 @@ export const MODEL_PICKER_KEYBINDING_COMMANDS = [
47] as const;47] as const;
48export type ModelPickerKeybindingCommand = (typeof MODEL_PICKER_KEYBINDING_COMMANDS)[number];48export type ModelPickerKeybindingCommand = (typeof MODEL_PICKER_KEYBINDING_COMMANDS)[number];
4949
50const STATIC_KEYBINDING_COMMANDS = [50export const STATIC_KEYBINDING_COMMANDS = [
51 "sidebar.toggle",51 "sidebar.toggle",
52 "terminal.toggle",52 "terminal.toggle",
53 "terminal.split",53 "terminal.split",
...@@ -55,6 +55,7 @@ const STATIC_KEYBINDING_COMMANDS = [...@@ -55,6 +55,7 @@ const STATIC_KEYBINDING_COMMANDS = [
55 "terminal.new",55 "terminal.new",
56 "terminal.close",56 "terminal.close",
57 "rightPanel.toggle",57 "rightPanel.toggle",
58 "rightPanel.toggleMaximized",
58 "diff.toggle",59 "diff.toggle",
59 "preview.toggle",60 "preview.toggle",
60 "preview.refresh",61 "preview.refresh",