From 4275678b434a253d56609aa27bce7237252ef9e5 Mon Sep 17 00:00:00 2001 From: axelray-dev <110029405+axelray-dev@users.noreply.github.com> Date: Thu, 4 Jun 2026 06:47:50 +0800 Subject: [PATCH] feat(webui): add new chat keyboard shortcut Add Cmd/Ctrl+Shift+O shortcut to start a new chat, matching the convention used by ChatGPT, Claude.ai, and Gemini. Addresses #4178 Signed-off-by: axelray-dev <110029405+axelray-dev@users.noreply.github.com> --- webui/src/App.tsx | 9 ++++++++- webui/src/tests/app-layout.test.tsx | 20 ++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/webui/src/App.tsx b/webui/src/App.tsx index aa0c59c1b..8c128828f 100644 --- a/webui/src/App.tsx +++ b/webui/src/App.tsx @@ -1007,6 +1007,13 @@ function Shell({ useEffect(() => { const handleKeyDown = (event: globalThis.KeyboardEvent) => { if (event.defaultPrevented) return; + const commandShiftO = + (event.metaKey || event.ctrlKey) && event.shiftKey && !event.altKey; + if (commandShiftO && event.key.toLowerCase() === "o") { + event.preventDefault(); + onNewChat(); + return; + } const plainCommandK = (event.metaKey || event.ctrlKey) && !event.altKey && !event.shiftKey; if (!plainCommandK) return; @@ -1017,7 +1024,7 @@ function Shell({ window.addEventListener("keydown", handleKeyDown); return () => window.removeEventListener("keydown", handleKeyDown); - }, [onOpenSessionSearch]); + }, [onNewChat, onOpenSessionSearch]); const onSelectSearchResult = useCallback( (key: string) => { diff --git a/webui/src/tests/app-layout.test.tsx b/webui/src/tests/app-layout.test.tsx index c25154050..7af04801d 100644 --- a/webui/src/tests/app-layout.test.tsx +++ b/webui/src/tests/app-layout.test.tsx @@ -1354,6 +1354,26 @@ describe("App layout", () => { expect(createChatSpy).not.toHaveBeenCalled(); }); + it("starts a new chat from the keyboard shortcut", async () => { + mockSessions = [ + { + key: "websocket:chat-a", + channel: "websocket", + chatId: "chat-a", + createdAt: "2026-04-16T10:00:00Z", + updatedAt: "2026-04-16T10:00:00Z", + preview: "Existing chat", + }, + ]; + + render(); + + await waitFor(() => expect(connectSpy).toHaveBeenCalled()); + fireEvent.keyDown(window, { key: "O", shiftKey: true, metaKey: true }); + + expect(window.location.hash).toBe("#/new"); + }); + it("keeps large sidebars light while search still covers every chat", async () => { mockSessions = Array.from({ length: 170 }, (_, index) => { const chatId = `chat-${index}`;