diff --git a/packages/app/e2e/regression/tab-navigate-mousedown.spec.ts b/packages/app/e2e/regression/tab-navigate-mousedown.spec.ts index dd471f311e9..e8706ad99f4 100644 --- a/packages/app/e2e/regression/tab-navigate-mousedown.spec.ts +++ b/packages/app/e2e/regression/tab-navigate-mousedown.spec.ts @@ -301,11 +301,14 @@ for (const count of [0, 26]) { } for (const direction of ["ltr", "rtl"]) { - test(`vertical tabs keep Settings pinned while scrolling in ${direction}`, async ({ page }, testInfo) => { + test(`vertical tabs keep Status pinned without Settings in ${direction}`, async ({ page }, testInfo) => { await mockServer(page) await page.addInitScript( ({ server, sessionA, sessionB, directory }) => { - localStorage.setItem("settings.v3", JSON.stringify({ appearance: { tabLayout: "vertical" } })) + localStorage.setItem( + "settings.v3", + JSON.stringify({ appearance: { tabLayout: "vertical" }, general: { showStatus: true } }), + ) localStorage.setItem( "opencode.window.browser.dat:tabs", JSON.stringify([ @@ -322,23 +325,27 @@ for (const direction of ["ltr", "rtl"]) { }, { server, sessionA: sessionA.id, sessionB: sessionB.id, directory: sessionA.directory }, ) - await page.goto("/") + await page.goto(`/server/${base64Encode(server)}/session/${sessionA.id}`) const sidebar = page.locator('[data-slot="vertical-tabs-sidebar"]') const settings = sidebar.getByRole("button", { name: "Settings", exact: true }) + const status = sidebar.getByRole("button", { name: "Status", exact: true }) const scroll = sidebar.locator('[data-slot="vertical-tabs-scroll"]') const hrefB = `/server/${base64Encode(server)}/session/${sessionB.id}` const tabB = sidebar.locator(`[data-titlebar-tab-link][href="${hrefB}"]`) await expect(sidebar.locator("[data-titlebar-tab-slot]")).toHaveCount(26) - await expect(settings).toHaveText("Settings") + await expect(status).toHaveText("Status") + await expect(settings).toHaveCount(0) + await expect(status.locator('[data-slot="status-indicator"]')).toBeVisible() await page.evaluate((direction) => document.documentElement.setAttribute("dir", direction), direction) for (const width of [1280, 800]) { await page.setViewportSize({ width, height: 360 }) - await expect(settings).toBeInViewport({ ratio: 1 }) await expect(sidebar).toHaveCSS("padding-inline-start", "10px") await expect(sidebar).toHaveCSS("padding-bottom", "10px") - await expect(settings).toHaveCSS("margin-top", "8px") + await expect(sidebar.locator('[data-slot="vertical-tabs-footer"]')).toHaveCSS("margin-top", "8px") + await expect(status).toBeInViewport({ ratio: 1 }) + await expect(status).toHaveCSS("height", "28px") await expect .poll(() => sidebar.locator('[data-slot="vertical-tabs-footer"]').evaluate((element) => { @@ -353,12 +360,12 @@ for (const direction of ["ltr", "rtl"]) { await expect(scroll).toHaveCSS("mask-image", /linear-gradient/) await scroll.evaluate((element) => element.scrollTo(0, 0)) await expect(scroll).toHaveJSProperty("scrollTop", 0) - const pinned = await settings.boundingBox() + const pinnedStatus = await status.boundingBox() await scroll.hover() await page.mouse.wheel(0, 200) await expect.poll(() => scroll.evaluate((element) => element.scrollTop)).toBeGreaterThan(0) - await expect.poll(() => settings.boundingBox()).toEqual(pinned) - await testInfo.attach(`vertical-tabs-settings-${width}`, { + await expect.poll(() => status.boundingBox()).toEqual(pinnedStatus) + await testInfo.attach(`vertical-tabs-status-${width}`, { body: await sidebar.screenshot(), contentType: "image/png", }) @@ -372,17 +379,14 @@ for (const direction of ["ltr", "rtl"]) { return !!tab && !!viewport && tab.y + tab.height <= viewport.y + viewport.height - 16 }) .toBe(true) - await expect.poll(() => settings.boundingBox()).toEqual(pinned) + await expect.poll(() => status.boundingBox()).toEqual(pinnedStatus) + await expect(settings).toHaveCount(0) } - await settings.click() - await expect(page.getByTestId("settings-screen")).toBeVisible() - await expect(settings).toHaveAttribute("aria-pressed", "true") - await sidebar.getByRole("button", { name: "Home", exact: true }).click() - await expect(page.getByTestId("settings-screen")).toBeHidden() - await settings.focus() - await settings.press("Enter") - await expect(page.getByTestId("settings-screen")).toBeVisible() + await status.click() + await expect(status).toHaveAttribute("aria-expanded", "true") + await status.press("Escape") + await expect(status).toHaveAttribute("aria-expanded", "false") }) } diff --git a/packages/app/src/shell/status/status-popover.tsx b/packages/app/src/shell/status/status-popover.tsx index 02549541bf8..4e59f2178cf 100644 --- a/packages/app/src/shell/status/status-popover.tsx +++ b/packages/app/src/shell/status/status-popover.tsx @@ -20,6 +20,7 @@ export function StatusPopover() { const sdk = useWorkspaceLocation() const settings = useSettings() const desktop = createMediaQuery("(min-width: 768px)") + const sidebar = () => desktop() && settings.appearance.tabLayout() === "vertical" const [shown, setShown] = createSignal(false) const serverHealth = () => global.servers.health[server.key]?.healthy const mcp = () => data.location.mcp.server.list({ directory: sdk().directory }) @@ -41,8 +42,9 @@ export function StatusPopover() { serverHealth: serverHealth(), attention: attention(), issue: issue(), - placement: desktop() && settings.appearance.tabLayout() === "vertical" ? "top-start" : "bottom-end", - shift: desktop() && settings.appearance.tabLayout() === "vertical" ? 0 : -168, + sidebar: sidebar(), + placement: sidebar() ? "top-start" : "bottom-end", + shift: sidebar() ? 0 : -168, label: language.t("status.popover.trigger"), onOpenChange: setShown, body: () => ( @@ -61,6 +63,7 @@ type StatusPopoverState = { serverHealth: boolean | undefined attention: boolean issue: boolean + sidebar: boolean placement: "top-start" | "bottom-end" shift: number label: string @@ -93,21 +96,37 @@ function StatusPopoverView(props: { state: StatusPopoverState }) { - -
-
+ <> +
+ +
+
+ + {props.state.label} + + } {...popoverProps} > diff --git a/packages/app/src/shell/titlebar/right-slot.tsx b/packages/app/src/shell/titlebar/right-slot.tsx index e58403663e8..fb6570e103f 100644 --- a/packages/app/src/shell/titlebar/right-slot.tsx +++ b/packages/app/src/shell/titlebar/right-slot.tsx @@ -38,9 +38,15 @@ export function createTitlebarRightSlot(): TitlebarRightSlot { } } -export function TitlebarRightMount() { +export function TitlebarRightMount(props: { vertical?: boolean }) { const slot = useTitlebarRightSlot() - return
+ return ( +
+ ) } export function TitlebarRight(props: ParentProps) { diff --git a/packages/app/src/shell/titlebar/titlebar.tsx b/packages/app/src/shell/titlebar/titlebar.tsx index 040e6971fc6..c7f52036d27 100644 --- a/packages/app/src/shell/titlebar/titlebar.tsx +++ b/packages/app/src/shell/titlebar/titlebar.tsx @@ -680,20 +680,8 @@ export function Titlebar(props: { onReorder={(keys) => tabsStoreActions.reorder(keys)} />
- -
- +
+
)}