From fdd8abc9df756e226e5bf3abcb4d603adcfa33ba Mon Sep 17 00:00:00 2001 From: Brendan Allan Date: Thu, 27 Aug 2026 02:37:03 +0800 Subject: [PATCH 1/2] fix(app): clear active tab while settings is open --- .../app/e2e/regression/remote-session-settings.spec.ts | 10 ++++++++++ packages/app/src/shell/titlebar/titlebar.tsx | 8 ++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/packages/app/e2e/regression/remote-session-settings.spec.ts b/packages/app/e2e/regression/remote-session-settings.spec.ts index c137f53d78d8..aad565006852 100644 --- a/packages/app/e2e/regression/remote-session-settings.spec.ts +++ b/packages/app/e2e/regression/remote-session-settings.spec.ts @@ -26,10 +26,13 @@ test("session settings use the remote server context", async ({ page }) => { await page.goto(`/server/${base64Encode(serverB)}/session/${sessionB.id}`) const sessionHeading = page.getByRole("heading", { name: sessionB.title, exact: true, includeHidden: true }) await expect(sessionHeading).toBeVisible() + const activeTabs = page.locator('[data-titlebar-tab-slot][data-active="true"]') + await expect(activeTabs).toHaveCount(1) await page.keyboard.press("Control+,") const settings = page.getByTestId("settings-screen") await expect(settings).toBeVisible() + await expect(activeTabs).toHaveCount(0) await expect(page.getByRole("dialog")).toHaveCount(0) await expect(settings.getByRole("tablist")).toHaveCSS("width", "328px") await expect(sessionHeading).toBeAttached() @@ -67,6 +70,13 @@ test("session settings use the remote server context", async ({ page }) => { await settings.getByRole("button", { name: "Back to app" }).click() await expect(settings).toBeHidden() await expect(sessionHeading).toBeVisible() + await expect(activeTabs).toHaveCount(1) + await page.keyboard.press("Control+,") + await expect(settings).toBeVisible() + await page.locator(`[data-titlebar-tab-slot]:has(a[href$="/session/${sessionB.id}"])`).click() + await expect(settings).toBeHidden() + await expect(sessionHeading).toBeVisible() + await expect(activeTabs).toHaveCount(1) }) test("auto-accept responds for an unfocused server session", async ({ page }) => { diff --git a/packages/app/src/shell/titlebar/titlebar.tsx b/packages/app/src/shell/titlebar/titlebar.tsx index d54d417e75f6..749d1da2eec1 100644 --- a/packages/app/src/shell/titlebar/titlebar.tsx +++ b/packages/app/src/shell/titlebar/titlebar.tsx @@ -12,6 +12,7 @@ import { usePlatform } from "@/runtime/platform/platform" import { useCommand } from "@/shell/commands/command" import { useLanguage } from "@/runtime/i18n/language" import { useSettings } from "@/settings/model" +import { useSettingsSurface } from "@/settings/surface" import { WindowsAppMenu } from "./windows-menu" import { applyPath, backPath, forwardPath } from "./history" import { TitlebarTabStrip } from "@/shell/titlebar/tab-strip" @@ -46,6 +47,7 @@ export function Titlebar(props: { const command = useCommand() const language = useLanguage() const settings = useSettings() + const surface = useSettingsSurface() const navigate = useNavigate() const location = useLocation() const mobile = createMediaQuery("(max-width: 767px)") @@ -199,11 +201,11 @@ export function Titlebar(props: { } } - const currentTab = () => matchRoute(layout.route()) + const currentTab = () => (surface.store.open ? undefined : matchRoute(layout.route())) createEffect(() => { const route = layout.route() - if (!tabs.ready()) return + if (!tabs.ready() || surface.store.open) return const tab = currentTab() if (tab) { const current = session() @@ -372,6 +374,7 @@ export function Titlebar(props: { forceTruncate={tabsAreOverflowing()} onOverflowChange={setTabsAreOverflowing} onNavigate={(tab, el) => { + surface.close() tabs.select(tab) el?.scrollIntoView({ behavior: "instant" }) }} @@ -414,6 +417,7 @@ export function Titlebar(props: { forceTruncate={false} onOverflowChange={setTabsAreOverflowing} onNavigate={(tab, el) => { + surface.close() tabs.select(tab) el?.scrollIntoView({ behavior: "instant", block: "nearest" }) }} From 54e043ca39d52d6eab2b06105721ff2cf51952a9 Mon Sep 17 00:00:00 2001 From: Brendan Allan Date: Thu, 27 Aug 2026 02:56:30 +0800 Subject: [PATCH 2/2] fix(app): preserve draft new-tab action in settings --- .../remote-session-settings.spec.ts | 41 ++++++++++++++++++- packages/app/src/shell/titlebar/titlebar.tsx | 2 +- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/packages/app/e2e/regression/remote-session-settings.spec.ts b/packages/app/e2e/regression/remote-session-settings.spec.ts index aad565006852..af64ef42dbd3 100644 --- a/packages/app/e2e/regression/remote-session-settings.spec.ts +++ b/packages/app/e2e/regression/remote-session-settings.spec.ts @@ -79,6 +79,42 @@ test("session settings use the remote server context", async ({ page }) => { await expect(activeTabs).toHaveCount(1) }) +test("new session works while draft settings are open", async ({ page }) => { + await installSseTransport(page, { server: serverA }) + await installSseTransport(page, { server: serverB }) + await mockServers(page, []) + await configureServers(page) + + await page.goto(`/server/${base64Encode(serverB)}/session/${sessionB.id}`) + await expect(page.getByRole("heading", { name: sessionB.title, exact: true })).toBeVisible() + const newSession = page.locator('[data-slot="titlebar-v2"]').getByRole("button", { name: "New session", exact: true }) + await newSession.click() + await expect(page).toHaveURL((url) => url.pathname === "/new-session" && !!url.searchParams.get("draftId")) + const draftURL = page.url() + const editor = page.locator('[data-component="composer-editor"][contenteditable="true"]') + await expect(editor).toBeEditable() + await editor.fill("Keep the original draft") + const tabs = page.locator("[data-titlebar-tab-slot]") + const activeTabs = page.locator('[data-titlebar-tab-slot][data-active="true"]') + await expect(tabs).toHaveCount(2) + await expect(activeTabs).toHaveCount(1) + await page.keyboard.press("Control+,") + const settings = page.getByTestId("settings-screen") + await expect(settings).toBeVisible() + await expect(activeTabs).toHaveCount(0) + + await newSession.click() + await expect(page).toHaveURL( + (url) => url.pathname === "/new-session" && !!url.searchParams.get("draftId") && url.href !== draftURL, + ) + await expect(settings).toBeHidden() + await expect(tabs).toHaveCount(3) + await expect(activeTabs).toHaveCount(1) + await expect(activeTabs.locator("a")).toHaveAttribute("href", `/new-session${new URL(page.url()).search}`) + await expect(editor).toBeEditable() + await expect(editor).toBeEmpty() +}) + test("auto-accept responds for an unfocused server session", async ({ page }) => { const permissionRequests: string[] = [] const permissionResponses: PermissionResponse[] = [] @@ -395,9 +431,10 @@ async function mockServers( return json(route, { data: [], cursor: {} }) if (sessions.some((session) => url.pathname === `/api/session/${session.id}/inbox`)) return json(route, { data: [] }) - if (url.pathname === "/api/location") return json(route, { directory }) + if (url.pathname === "/api/location") + return json(route, { directory, project: { id: remote ? sessionB.projectID : "project-server-a" } }) if (url.pathname === "/api/vcs") - return json(route, { location: { directory }, data: { branch: "main", defaultBranch: "main" } }) + return json(route, { location: { directory }, data: { branch: { current: "main", default: "main" } } }) if (url.pathname === "/api/pty/shells") return json(route, { location: { directory }, data: [] }) return json(route, {}) }) diff --git a/packages/app/src/shell/titlebar/titlebar.tsx b/packages/app/src/shell/titlebar/titlebar.tsx index 749d1da2eec1..28f87dc1bb7f 100644 --- a/packages/app/src/shell/titlebar/titlebar.tsx +++ b/packages/app/src/shell/titlebar/titlebar.tsx @@ -256,7 +256,7 @@ export function Titlebar(props: { return } case "draft": { - const activeTab = currentTab() + const activeTab = matchRoute(route) if (activeTab?.type !== "draft") return const model = tabs.stateValue(activeTab, "prompt")?.model.current()