diff --git a/AGENTS.md b/AGENTS.md index 424a4b23..a157a417 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -194,6 +194,7 @@ return a `Command`, so `.then(publish)` is usually all you need. - Use `$state.snapshot` if calling component might unmount - Navigate with `navigate` from `app/modal.ts` rather than `goto` — an open modal owns the current history entry, so a navigation that drops it replaces that entry instead of stacking on it +- Pass `keepModal` to `navigate` to change the page under a modal and leave it open ## Development Workflow diff --git a/e2e/specs/routing.spec.ts b/e2e/specs/routing.spec.ts index d68feaf3..a5c9a8e6 100644 --- a/e2e/specs/routing.spec.ts +++ b/e2e/specs/routing.spec.ts @@ -146,3 +146,39 @@ test("takes over the space menu's history entry when you navigate out of it", as await expect(page).toHaveURL(new RegExp(`${roomPath(space.url, "lounge")}$`)) await expect(drawer).toHaveCount(0) }) + +test("switches spaces inside the space menu without closing it", async ({seed, as}) => { + const scenario = await seed(({relay, user}) => { + const space = relay("space") + const other = relay("other") + + space.room("lounge", {name: "Space Lounge"}) + other.room("garden", {name: "Other Garden"}) + space.join(user.alice, "lounge") + other.join(user.alice, "garden") + }) + + const space = scenario.space("space") + const other = scenario.space("other") + const page = await as(users.alice, roomPath(space.url, "lounge"), { + context: {viewport: {width: 390, height: 844}, hasTouch: true}, + }) + + const drawer = page.locator(".drawer") + + await page.getByRole("button", {name: "Open space menu"}).click() + + await expect(drawer.getByRole("link", {name: "Space Lounge"})).toBeVisible() + + // The rail is in the menu on a phone, so a space is picked with the menu still open and the room + // is picked after it. Navigating out from under the menu would close it on the first tap. + await drawer.locator('.primary-nav [data-tip^="other"]').click() + + await expect(page).toHaveURL(new RegExp(spacePath(other.url))) + await expect(drawer.getByRole("link", {name: "Other Garden"})).toBeVisible() + + await drawer.getByRole("link", {name: "Other Garden"}).click() + + await expect(page).toHaveURL(new RegExp(`${roomPath(other.url, "garden")}$`)) + await expect(drawer).toHaveCount(0) +}) diff --git a/src/app/components/PrimaryNavItemSpace.svelte b/src/app/components/PrimaryNavItemSpace.svelte index 3b52b46e..20c38a21 100644 --- a/src/app/components/PrimaryNavItemSpace.svelte +++ b/src/app/components/PrimaryNavItemSpace.svelte @@ -17,7 +17,7 @@ const onClick = () => { const entryPath = makeSpaceEntryPath(url) - navigate(entryPath, {replaceState: entryPath === $page.url.pathname}) + navigate(entryPath, {keepModal: true, replaceState: entryPath === $page.url.pathname}) } const path = makeSpacePath(url) diff --git a/src/app/components/SpaceMenuDrawer.svelte b/src/app/components/SpaceMenuDrawer.svelte index 7e0c3fb7..2fd1ef14 100644 --- a/src/app/components/SpaceMenuDrawer.svelte +++ b/src/app/components/SpaceMenuDrawer.svelte @@ -3,6 +3,7 @@ import SecondaryNav from "@lib/components/SecondaryNav.svelte" import SpaceMenu from "@app/components/SpaceMenu.svelte" import PrimaryNavSpaces from "@app/components/PrimaryNavSpaces.svelte" + import {lastSpaceUrl} from "@app/routes" type Props = { url: string @@ -10,6 +11,8 @@ const {url}: Props = $props() + const activeUrl = $derived($lastSpaceUrl ?? url) + const duration = 200 const parallax = 10 @@ -32,7 +35,9 @@