diff --git a/packages/app/e2e/commands/panels.spec.ts b/packages/app/e2e/commands/panels.spec.ts index 20ea347a5..8ba762dc3 100644 --- a/packages/app/e2e/commands/panels.spec.ts +++ b/packages/app/e2e/commands/panels.spec.ts @@ -1,12 +1,13 @@ import { test, expect } from "../fixtures" -import { titlebarRightSelector } from "../selectors" +import { openSidebar } from "../actions" +import { pawworkSessionNewSelector, titlebarRightSelector } from "../selectors" import { modKey } from "../utils" test("desktop right-panel tabs switch between review and files within a unified utility shell", async ({ page, gotoSession }) => { await gotoSession() const rightToggle = page.locator(`${titlebarRightSelector} button`).first() - const rightPanel = page.locator("#right-panel") + const rightPanel = page.locator('[data-component="right-panel"]') await expect(rightToggle).toBeVisible() await expect(rightPanel).toHaveAttribute("aria-hidden", "true") @@ -36,6 +37,22 @@ test("desktop right-panel tabs switch between review and files within a unified await expect(reviewTab).toHaveAttribute("aria-selected", "true") }) +test("desktop remains clickable after right-panel resize ends with mouseup", async ({ page, gotoSession, slug }) => { + await gotoSession() + await openSidebar(page) + + const rightPanel = page.locator("#right-panel") + await page.keyboard.press(`${modKey}+Shift+R`) + await expect(rightPanel).toHaveAttribute("aria-hidden", "false") + + await page.locator('[data-component="right-panel-resize-wrapper"]').dispatchEvent("pointerdown") + await page.mouse.up() + + await page.locator(pawworkSessionNewSelector).click() + await expect(page).toHaveURL(new RegExp(`/${slug}/session(?:\\?|#|$)`)) + await expect(page.locator('[data-component="session-new-home"]')).toBeVisible() +}) + test("desktop session keeps a single right-panel toggle and icon-first utility tabs", async ({ page, gotoSession }) => { await gotoSession() diff --git a/packages/app/e2e/sidebar/sidebar-session-links.spec.ts b/packages/app/e2e/sidebar/sidebar-session-links.spec.ts index d9120cdac..09d331ce6 100644 --- a/packages/app/e2e/sidebar/sidebar-session-links.spec.ts +++ b/packages/app/e2e/sidebar/sidebar-session-links.spec.ts @@ -1,7 +1,21 @@ +import type { Page } from "@playwright/test" import { test, expect } from "../fixtures" import { cleanupSession, cleanupTestProject, createTestProject, openSidebar, waitSession } from "../actions" import { promptSelector } from "../selectors" +async function expectUrlToStayMatched(page: Page, pattern: RegExp, stableFor = 300) { + let stableSince = Date.now() + await expect + .poll(() => { + if (!pattern.test(page.url())) { + stableSince = Date.now() + return false + } + return Date.now() - stableSince >= stableFor + }) + .toBe(true) +} + test("sidebar session links navigate to the selected session", async ({ page, slug, sdk, gotoSession }) => { const stamp = Date.now() @@ -20,9 +34,15 @@ test("sidebar session links navigate to the selected session", async ({ page, sl await expect(target).toBeVisible() await target.click() - await expect(page).toHaveURL(new RegExp(`/${slug}/session/${two.id}(?:\\?|#|$)`)) + const selectedSessionUrl = new RegExp(`/${slug}/session/${two.id}(?:\\?|#|$)`) + await expect(page).toHaveURL(selectedSessionUrl) + await expectUrlToStayMatched(page, selectedSessionUrl) await expect(page.locator(promptSelector)).toBeVisible() await expect(page.locator(`[data-session-id="${two.id}"] a`).first()).toHaveClass(/\bactive\b/) + + await page.locator('[data-action="pawwork-session-new"]').click() + await expect(page).toHaveURL(new RegExp(`/${slug}/session(?:\\?|#|$)`)) + await expect(page.locator('[data-component="session-new-home"]')).toBeVisible() } finally { await cleanupSession({ sdk, sessionID: one.id }) await cleanupSession({ sdk, sessionID: two.id }) diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx index c5116723f..8d5cbf198 100644 --- a/packages/app/src/pages/layout.tsx +++ b/packages/app/src/pages/layout.tsx @@ -94,6 +94,7 @@ import { PawworkTitlebar } from "./layout/pawwork-titlebar" import { SettingsPage, type SettingsPageTab } from "@/components/settings-page" import { DialogDeleteSession } from "@/components/dialog-delete-session" import { sessionTitle } from "@/utils/session-title" +import { sizingStopEvents } from "@/pages/session/helpers" export default function Layout(props: ParentProps) { const [store, setStore, , ready] = persisted( @@ -192,9 +193,7 @@ export default function Layout(props: ParentProps) { onMount(() => { const stop = () => setState("sizing", false) - makeEventListener(window, "pointerup", stop) - makeEventListener(window, "pointercancel", stop) - makeEventListener(window, "blur", stop) + for (const event of sizingStopEvents) makeEventListener(window, event, stop) }) createEffect(() => { diff --git a/packages/app/src/pages/session/helpers.test.ts b/packages/app/src/pages/session/helpers.test.ts index 6c05d49e5..ad7b8f6eb 100644 --- a/packages/app/src/pages/session/helpers.test.ts +++ b/packages/app/src/pages/session/helpers.test.ts @@ -7,6 +7,7 @@ import { createSessionTabs, focusTerminalById, getTabReorderIndex, + sizingStopEvents, shouldFocusTerminalOnKeyDown, } from "./helpers" @@ -117,6 +118,12 @@ describe("getTabReorderIndex", () => { }) }) +describe("createSizing", () => { + test("listens for mouse and touch endings as resize fallbacks", () => { + expect(sizingStopEvents).toEqual(["pointerup", "pointercancel", "mouseup", "touchend", "touchcancel", "blur"]) + }) +}) + describe("createSessionTabs", () => { test("normalizes the effective file tab", () => { createRoot((dispose) => { diff --git a/packages/app/src/pages/session/helpers.ts b/packages/app/src/pages/session/helpers.ts index 5637f5f3b..9710adca8 100644 --- a/packages/app/src/pages/session/helpers.ts +++ b/packages/app/src/pages/session/helpers.ts @@ -153,6 +153,8 @@ export const getTabReorderIndex = (tabs: readonly string[], from: string, to: st return toIndex } +export const sizingStopEvents = ["pointerup", "pointercancel", "mouseup", "touchend", "touchcancel", "blur"] as const + export const createSizing = () => { const [state, setState] = createStore({ active: false }) let t: number | undefined @@ -174,9 +176,7 @@ export const createSizing = () => { } onMount(() => { - makeEventListener(window, "pointerup", stop) - makeEventListener(window, "pointercancel", stop) - makeEventListener(window, "blur", stop) + for (const event of sizingStopEvents) makeEventListener(window, event, stop) }) onCleanup(() => { diff --git a/packages/app/src/pages/session/session-side-panel.tsx b/packages/app/src/pages/session/session-side-panel.tsx index f928eb92d..86d43d670 100644 --- a/packages/app/src/pages/session/session-side-panel.tsx +++ b/packages/app/src/pages/session/session-side-panel.tsx @@ -263,6 +263,7 @@ export function SessionSidePanel(props: {