diff --git a/packages/app/e2e/regression/mobile-sidebar-session-selection.spec.ts b/packages/app/e2e/regression/mobile-sidebar-session-selection.spec.ts new file mode 100644 index 000000000000..9d67bf04fd78 --- /dev/null +++ b/packages/app/e2e/regression/mobile-sidebar-session-selection.spec.ts @@ -0,0 +1,46 @@ +import { base64Encode } from "@opencode-ai/core/util/encode" +import { expect, test } from "@playwright/test" +import { fixture, pageMessages } from "../smoke/session-timeline.fixture" +import { mockOpenCodeServer } from "../utils/mock-server" +import { expectSessionTitle } from "../utils/waits" + +const MOBILE_VIEWPORT_WIDTH = 390 +const SIDEBAR_AUTO_DISMISS_THRESHOLD = 250 + +test.use({ viewport: { width: MOBILE_VIEWPORT_WIDTH, height: 800 } }) + +test("dismisses full-width mobile sidebar after selecting a session", async ({ page }) => { + await mockOpenCodeServer(page, { + sessions: fixture.sessions, + provider: fixture.provider, + directory: fixture.directory, + project: fixture.project, + pageMessages, + }) + + await page.addInitScript((directory) => { + localStorage.setItem( + "opencode.global.dat:server", + JSON.stringify({ + projects: { local: [{ worktree: directory, expanded: true }] }, + lastProject: { local: directory }, + }), + ) + }, fixture.directory) + + await page.goto(`/${base64Encode(fixture.directory)}/session/${fixture.sourceID}`) + await expectSessionTitle(page, fixture.expected.sourceTitle) + + const toggle = page.getByRole("button", { name: "Toggle menu" }) + await toggle.click() + await expect(toggle).toHaveAttribute("aria-expanded", "true") + + const sidebar = page.locator('[data-component="sidebar-nav-mobile"]') + await expect + .poll(() => sidebar.evaluate((element) => element.getBoundingClientRect().width)) + .toBeGreaterThanOrEqual(MOBILE_VIEWPORT_WIDTH - SIDEBAR_AUTO_DISMISS_THRESHOLD) + + await sidebar.getByRole("link", { name: fixture.expected.targetTitle }).click() + await expectSessionTitle(page, fixture.expected.targetTitle) + await expect(toggle).toHaveAttribute("aria-expanded", "false") +}) diff --git a/packages/app/src/pages/layout/sidebar-items.tsx b/packages/app/src/pages/layout/sidebar-items.tsx index 776b81ae5a83..9cc08b2919c2 100644 --- a/packages/app/src/pages/layout/sidebar-items.tsx +++ b/packages/app/src/pages/layout/sidebar-items.tsx @@ -104,6 +104,7 @@ const SessionRow = (props: { warmPress: () => void warmFocus: () => void }): JSX.Element => { + const layout = useLayout() const title = () => sessionTitle(props.session.title) return ( @@ -112,7 +113,12 @@ const SessionRow = (props: { class={`flex items-center gap-2 min-w-0 w-full text-left focus:outline-none ${props.dense ? "py-0.5" : "py-1"}`} onPointerDown={props.warmPress} onFocus={props.warmFocus} - onClick={() => { + onClick={(event) => { + const sidebar = event.currentTarget.closest("[data-component='sidebar-nav-mobile']") + const sidebarAutoDismissThreshold = 250 + if (sidebar && sidebar.getBoundingClientRect().width >= window.innerWidth - sidebarAutoDismissThreshold) { + layout.mobileSidebar.hide() + } if (props.sidebarOpened()) return props.clearHoverProjectSoon() }}