From e181b2ab12eab1b35acde7b78f20149667588ce4 Mon Sep 17 00:00:00 2001 From: Alex Blank Date: Mon, 31 Aug 2026 17:41:33 +0200 Subject: [PATCH] feat(session): add Ctrl+N new-session shortcut --- ui/src/App.test.tsx | 23 +++++++++++++++++++++++ ui/src/App.tsx | 24 ++++++++++++++++++++---- 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/ui/src/App.test.tsx b/ui/src/App.test.tsx index e3edcdd..fa155a8 100644 --- a/ui/src/App.test.tsx +++ b/ui/src/App.test.tsx @@ -135,6 +135,29 @@ describe("App command progress and errors", () => { expect(status).toHaveTextContent("Ready for your next prompt"); }); + it("starts a session in selected directory with Ctrl+N", async () => { + mocks.invoke.mockImplementation((command: string) => { + if (command === "create_session_runtime") + return Promise.resolve({ runtime: { runtimeId: "runtime-new" } }); + return Promise.resolve(uiState); + }); + render(); + const event = new KeyboardEvent("keydown", { + bubbles: true, + cancelable: true, + ctrlKey: true, + key: "n", + }); + window.dispatchEvent(event); + + expect(event.defaultPrevented).toBe(true); + await waitFor(() => + expect(mocks.invoke).toHaveBeenCalledWith("create_session_runtime", { + worktreePath: "/repo", + }), + ); + }); + it("loads categorized snapshot selectors and derives providers from choices", async () => { const user = userEvent.setup(); const loaded: UiState = { diff --git a/ui/src/App.tsx b/ui/src/App.tsx index 25d1209..3f917a4 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -320,10 +320,7 @@ export default function App() { followUpProvider: followUpModel?.provider ?? "", followUpModel: followUpModel?.modelId ?? "", defaultThinkingLevel: selectedQuickModel - ? reconcileThinkingLevel( - selectedQuickModel, - current.defaultThinkingLevel, - ) + ? reconcileThinkingLevel(selectedQuickModel, current.defaultThinkingLevel) : current.defaultThinkingLevel, }; if ( @@ -890,6 +887,25 @@ export default function App() { entry.kind === "opening" || entry.kind === "listing"), ); + useEffect(() => { + const onKeyDown = (event: KeyboardEvent) => { + if ( + event.defaultPrevented || + event.repeat || + !event.ctrlKey || + event.metaKey || + event.altKey || + event.key.toLowerCase() !== "n" || + sessionTabsBusy + ) + return; + event.preventDefault(); + void createRuntime(); + }; + window.addEventListener("keydown", onKeyDown); + return () => window.removeEventListener("keydown", onKeyDown); + }, [createRuntime, sessionTabsBusy]); + const closingRuntimeIds = new Set( activeOperations .filter((entry) => entry.kind === "closing" && entry.runtimeId)