From 64ed3dfde83e9ef8e2f10abb4c754cb998b1feb9 Mon Sep 17 00:00:00 2001 From: Shakker Date: Thu, 9 Jul 2026 20:50:31 +0100 Subject: [PATCH] feat: add settings keyboard shortcut --- ui/src/app/app-host.test.ts | 52 ++++++++++++++++++++++++++++++++ ui/src/app/app-host.ts | 14 ++++----- ui/src/components/app-sidebar.ts | 3 +- 3 files changed, 61 insertions(+), 8 deletions(-) diff --git a/ui/src/app/app-host.test.ts b/ui/src/app/app-host.test.ts index 153cd1fb8ad5..067e19fd0772 100644 --- a/ui/src/app/app-host.test.ts +++ b/ui/src/app/app-host.test.ts @@ -30,6 +30,13 @@ type ShellInitializationState = { ) => void; }; +type ShellKeyboardState = { + runtime: { + context: ApplicationContext; + }; + handleDocumentKeydown: (event: KeyboardEvent) => void; +}; + type ShellEpochState = { navDrawerOpen: boolean; navDrawerTrigger: HTMLElement | null; @@ -163,3 +170,48 @@ describe("OpenClaw shell source initialization", () => { expect(secondRuntimeConfig.ensureLoaded).toHaveBeenCalledOnce(); }); }); + +describe("OpenClaw shell keyboard shortcuts", () => { + it("opens Settings with Shift-Command-Comma", () => { + const navigate = vi.fn(); + const shell = document.createElement("openclaw-app-shell") as unknown as ShellKeyboardState; + shell.runtime = { + context: { + navigate, + } as unknown as ApplicationContext, + }; + const event = new KeyboardEvent("keydown", { + key: "<", + code: "Comma", + metaKey: true, + shiftKey: true, + cancelable: true, + }); + + shell.handleDocumentKeydown(event); + + expect(event.defaultPrevented).toBe(true); + expect(navigate).toHaveBeenCalledWith("config", undefined); + }); + + it("leaves plain Command-Comma to the browser", () => { + const navigate = vi.fn(); + const shell = document.createElement("openclaw-app-shell") as unknown as ShellKeyboardState; + shell.runtime = { + context: { + navigate, + } as unknown as ApplicationContext, + }; + const event = new KeyboardEvent("keydown", { + key: ",", + code: "Comma", + metaKey: true, + cancelable: true, + }); + + shell.handleDocumentKeydown(event); + + expect(event.defaultPrevented).toBe(false); + expect(navigate).not.toHaveBeenCalled(); + }); +}); diff --git a/ui/src/app/app-host.ts b/ui/src/app/app-host.ts index 63d578fc3a06..34e05db4318c 100644 --- a/ui/src/app/app-host.ts +++ b/ui/src/app/app-host.ts @@ -633,13 +633,13 @@ class OpenClawShell extends OpenClawLightDomElement { this.exitSettings(); return; } - if ( - event.altKey || - event.shiftKey || - !event.metaKey || - event.ctrlKey || - event.key.toLowerCase() !== "b" - ) { + const commandKey = event.metaKey && !event.ctrlKey && !event.altKey; + if (commandKey && event.shiftKey && event.code === "Comma") { + event.preventDefault(); + this.navigate("config"); + return; + } + if (!commandKey || event.shiftKey || event.key.toLowerCase() !== "b") { return; } event.preventDefault(); diff --git a/ui/src/components/app-sidebar.ts b/ui/src/components/app-sidebar.ts index 40287ad1210e..530e3cb2940f 100644 --- a/ui/src/components/app-sidebar.ts +++ b/ui/src/components/app-sidebar.ts @@ -1807,6 +1807,7 @@ class AppSidebar extends OpenClawLightDomContentsElement { }); const settingsActive = this.activeRouteId !== undefined && isSettingsNavigationRoute(this.activeRouteId); + const settingsTooltip = `${titleForRoute("config")} (⇧⌘,)`; return html`