Push a history entry when switching spaces instead of replacing the current one (#407)

Co-authored-by: Coracle-Bot <npub1klq6260@nostr.local>
This commit is contained in:
Coracle-Bot 2026-09-02 23:42:29 +00:00 committed by hodlbod
parent 70e3b2bd32
commit 097cde0201
7 changed files with 79 additions and 13 deletions

View file

@ -1,7 +1,7 @@
import {spec} from "@welshman/lib" import {spec} from "@welshman/lib"
import type {TrustedEvent} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import {RelayMessageType} from "@welshman/net" import {RelayMessageType} from "@welshman/net"
import {expect, getTranscript, roomPath, test, users} from "../harness" import {expect, getTranscript, roomPath, spacePath, test, users} from "../harness"
test("keeps two spaces' contents on their own relays", async ({seed, as}) => { test("keeps two spaces' contents on their own relays", async ({seed, as}) => {
const scenario = await seed(({relay, user}) => { const scenario = await seed(({relay, user}) => {
@ -55,3 +55,60 @@ test("keeps two spaces' contents on their own relays", async ({seed, as}) => {
expect(strays).toEqual([]) expect(strays).toEqual([])
}) })
test("goes back to the room you left when you switch spaces", 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("lounge", {name: "Other Lounge"})
space.join(user.alice, "lounge")
other.join(user.alice, "lounge")
})
const space = scenario.space("space")
const other = scenario.space("other")
const page = await as(users.alice, roomPath(space.url, "lounge"))
await expect(page.getByRole("link", {name: "Space Lounge"})).toBeVisible()
await page.locator('.primary-nav [data-tip^="other"]').click()
await expect(page).toHaveURL(new RegExp(spacePath(other.url)))
await page.getByRole("link", {name: "Other Lounge"}).click()
await expect(page).toHaveURL(new RegExp(`${roomPath(other.url, "lounge")}$`))
// Two entries back: the other space's landing page, then the room this started on. The switch
// used to replace that room's entry rather than push one, so the second step overshot it.
await page.goBack()
await page.goBack()
await expect(page).toHaveURL(new RegExp(`${roomPath(space.url, "lounge")}$`))
})
test("does not stack a history entry for the space you are already in", async ({seed, as}) => {
const scenario = await seed(({relay, user}) => {
const space = relay("space")
space.room("lounge", {name: "Space Lounge"})
space.room("garden", {name: "Space Garden"})
space.join(user.alice, "lounge")
space.join(user.alice, "garden")
})
const space = scenario.space("space")
const page = await as(users.alice, roomPath(space.url, "lounge"))
await page.getByRole("link", {name: "Space Garden"}).click()
await expect(page).toHaveURL(new RegExp(`${roomPath(space.url, "garden")}$`))
// The space's entry page is the room you are on, so this navigates nowhere and should replace
// rather than push. One step back is the room this started on, not the one it never left.
await page.locator('.primary-nav [data-tip^="space"]').click()
await expect(page).toHaveURL(new RegExp(`${roomPath(space.url, "garden")}$`))
await page.goBack()
await expect(page).toHaveURL(new RegExp(`${roomPath(space.url, "lounge")}$`))
})

View file

@ -1,8 +1,10 @@
<script lang="ts"> <script lang="ts">
import {goto} from "$app/navigation"
import {page} from "$app/stores"
import PrimaryNavItem from "@lib/components/PrimaryNavItem.svelte" import PrimaryNavItem from "@lib/components/PrimaryNavItem.svelte"
import RelayIcon from "@app/components/RelayIcon.svelte" import RelayIcon from "@app/components/RelayIcon.svelte"
import {relays} from "@app/core" import {relays} from "@app/core"
import {makeSpacePath, goToSpace} from "@app/routes" import {makeSpacePath, makeSpaceEntryPath} from "@app/routes"
import {notifications} from "@app/notifications" import {notifications} from "@app/notifications"
type Props = { type Props = {
@ -12,7 +14,11 @@
const {url, showTooltip = true}: Props = $props() const {url, showTooltip = true}: Props = $props()
const onClick = () => goToSpace(url) const onClick = () => {
const entryPath = makeSpaceEntryPath(url)
goto(entryPath, {replaceState: entryPath === $page.url.pathname})
}
const path = makeSpacePath(url) const path = makeSpacePath(url)

View file

@ -63,7 +63,7 @@
} }
pushToast({message: "Welcome to the space!"}) pushToast({message: "Welcome to the space!"})
await goToSpace(data.url) await goToSpace(data.url, {replaceState: true})
} }
const join = async () => { const join = async () => {

View file

@ -40,7 +40,7 @@
await access.completeJoin(notifications) await access.completeJoin(notifications)
pushToast({message: "Welcome to the space!"}) pushToast({message: "Welcome to the space!"})
await goToSpace(url) await goToSpace(url, {replaceState: true})
} catch (e) { } catch (e) {
console.error("Failed to join space:", e) console.error("Failed to join space:", e)
pushToast({theme: "error", message: "Failed to join space. Please try again."}) pushToast({theme: "error", message: "Failed to join space. Please try again."})

View file

@ -85,30 +85,33 @@ export const makeSpacePath = (url: string, ...extra: (string | undefined)[]) =>
return path return path
} }
export const goToSpace = (url: string, hash = "") => { export const makeSpaceEntryPath = (url: string) => {
const prevPath = lastPageBySpaceUrl.get(encodeRelay(url)) const prevPath = lastPageBySpaceUrl.get(encodeRelay(url))
if (prevPath && prevPath !== makeSpacePath(url)) { if (prevPath && prevPath !== makeSpacePath(url)) {
return goto(prevPath + hash, {replaceState: true}) return prevPath
} }
if (!relays.get().get(url)?.hasNip(29)) { if (!relays.get().get(url)?.hasNip(29)) {
return goto(makeSpaceChatPath(url) + hash, {replaceState: true}) return makeSpaceChatPath(url)
} }
if (window.matchMedia(`(min-width: ${theme.screens.md})`).matches) { if (window.matchMedia(`(min-width: ${theme.screens.md})`).matches) {
return goto(makeSpacePath(url, "about") + hash, {replaceState: true}) return makeSpacePath(url, "about")
} }
return goto(makeSpacePath(url) + hash, {replaceState: true}) return makeSpacePath(url)
} }
export const goToSpace = (url: string, options: {replaceState?: boolean} = {}) =>
goto(makeSpaceEntryPath(url), options)
export const goToMovedSpace = (oldUrl: string, newUrl: string) => export const goToMovedSpace = (oldUrl: string, newUrl: string) =>
goto(get(page).url.pathname.replace(encodeRelay(oldUrl), encodeRelay(newUrl))) goto(get(page).url.pathname.replace(encodeRelay(oldUrl), encodeRelay(newUrl)))
export const goToHome = () => { export const goToHome = () => {
if (PLATFORM_RELAYS.length > 0) { if (PLATFORM_RELAYS.length > 0) {
return goToSpace(PLATFORM_RELAYS[0], get(page).url.hash) return goto(makeSpaceEntryPath(PLATFORM_RELAYS[0]) + get(page).url.hash, {replaceState: true})
} }
return goto("/home" + get(page).url.hash) return goto("/home" + get(page).url.hash)

View file

@ -15,7 +15,7 @@
onMount(async () => { onMount(async () => {
if (PLATFORM_RELAYS.length > 0) { if (PLATFORM_RELAYS.length > 0) {
goToSpace(PLATFORM_RELAYS[0]) goToSpace(PLATFORM_RELAYS[0], {replaceState: true})
} }
}) })
</script> </script>

View file

@ -13,7 +13,7 @@
$effect(() => { $effect(() => {
if (width > md) { if (width > md) {
goToSpace(url) goToSpace(url, {replaceState: true})
} }
}) })
</script> </script>