diff --git a/e2e/specs/routing.spec.ts b/e2e/specs/routing.spec.ts
index bce050a8..81de5ec7 100644
--- a/e2e/specs/routing.spec.ts
+++ b/e2e/specs/routing.spec.ts
@@ -1,8 +1,34 @@
-import {spec} from "@welshman/lib"
+import {HOUR, spec} from "@welshman/lib"
import type {TrustedEvent} from "@welshman/util"
import {RelayMessageType} from "@welshman/net"
+import {Article} from "@welshman/domain"
+import type {Page} from "@playwright/test"
import {expect, getTranscript, pathPattern, roomPath, spacePath, test, users} from "../harness"
+// Every page in a space renders one PageContent, so the number added to the document is the number
+// of times the page was built.
+const watchPageBuilds = (page: Page) =>
+ page.evaluate(() => {
+ const selector = "[data-component='PageContent']"
+
+ let builds = 0
+
+ document.documentElement.dataset.pageBuilds = "0"
+
+ new MutationObserver(records => {
+ for (const record of records) {
+ for (const node of record.addedNodes) {
+ if (node instanceof Element && (node.matches(selector) || node.querySelector(selector))) {
+ document.documentElement.dataset.pageBuilds = String(++builds)
+ }
+ }
+ }
+ }).observe(document.body, {childList: true, subtree: true})
+ })
+
+const expectPageBuilds = (page: Page, builds: number) =>
+ expect(page.locator("html")).toHaveAttribute("data-page-builds", String(builds))
+
test("keeps two spaces' contents on their own relays", async ({seed, as}) => {
const scenario = await seed(({relay, user}) => {
const space = relay("space")
@@ -202,3 +228,63 @@ test("enters a space on its details page whatever its relay advertises", async (
await expect(page).toHaveURL(pathPattern(spacePath(space.url) + "/about"))
await expect(page.locator('[data-component="PageBar"]')).toContainText("Space Details")
})
+
+test("builds a page once when it opens and again when its params change", async ({seed, as}) => {
+ const scenario = await seed(({relay, user, at}) => {
+ const space = relay("space")
+
+ space.room("general", {name: "General"})
+ space.join(user.alice, "general")
+ space.join(user.bob, "general")
+ space.profile(user.alice, {name: "Alice Anderson"})
+ space.relayList(user.alice)
+
+ const article = (identifier: string, title: string, hours: number) =>
+ space.event(
+ user.alice,
+ () =>
+ space
+ .kind(Article)
+ .writer()
+ .setIdentifier(identifier)
+ .setTitle(title)
+ .setPublishedAt(at(hours, HOUR))
+ .setContent("Gardens are worth the trouble.")
+ .renderTemplate(),
+ at(hours, HOUR),
+ )
+
+ article("tending-the-garden", "Tending the Garden", 4)
+ article("repotting-in-winter", "Repotting in Winter", 3)
+ })
+
+ const {url} = scenario.space("space")
+ const page = await as(users.bob, `${spacePath(url)}/articles`)
+
+ await watchPageBuilds(page)
+
+ await page
+ .locator('[data-component="ArticleItem"]')
+ .filter({hasText: "Tending the Garden"})
+ .getByRole("link", {name: "Tending the Garden", exact: true})
+ .click({position: {x: 20, y: 20}})
+
+ await expect(
+ page.locator("article header").getByRole("heading", {name: "Tending the Garden"}),
+ ).toBeVisible()
+
+ // The rebuild this guards against landed 20ms after the first build, which is before the article
+ // itself is on screen on a slower run.
+ await page.waitForTimeout(250)
+ await expectPageBuilds(page, 1)
+
+ // Another article is the same route with different params, which SvelteKit answers by keeping the
+ // page it has. The page reads the address it renders once, so this one does have to be rebuilt.
+ await page.getByRole("link", {name: "Repotting in Winter"}).click()
+
+ await expect(
+ page.locator("article header").getByRole("heading", {name: "Repotting in Winter"}),
+ ).toBeVisible()
+
+ await expectPageBuilds(page, 2)
+})
diff --git a/src/routes/spaces/+layout.svelte b/src/routes/spaces/+layout.svelte
index fd035702..79dedbec 100644
--- a/src/routes/spaces/+layout.svelte
+++ b/src/routes/spaces/+layout.svelte
@@ -1,14 +1,14 @@
-{#key $page.params.relay}
+{#key params.relay}
{@render children?.()}
{/key}
diff --git a/src/routes/spaces/[relay]/+layout.svelte b/src/routes/spaces/[relay]/+layout.svelte
index 661808bd..423ae746 100644
--- a/src/routes/spaces/[relay]/+layout.svelte
+++ b/src/routes/spaces/[relay]/+layout.svelte
@@ -28,9 +28,10 @@
type Props = {
children?: Snippet
+ params: Record
}
- const {children}: Props = $props()
+ const {children, params}: Props = $props()
const url = decodeRelay($page.params.relay!)
@@ -113,7 +114,10 @@
- {#key $page.url.pathname}
+
+ {#key JSON.stringify(params)}
{@render children?.()}
{/key}