Archilyzer · Source

archilyzer

Archilyzer
git clone https://archilyzer.pages.dev/source/archilyzer.git
Log | Files | Refs | README | LICENSE

commit 23c9b511ec05e46a574dd42034556d4a85abdd0b
parent 6f3249a2ad39b13b02074c317ccd35b91aec2142
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Wed, 30 Sep 2026 00:49:21 -0400

editor(e2e): site-scope — a site picked in another tab, and no ?site= once a page settles (review M1, L2)

- New case: tab A stores alpha and sits on /settings (passive refresh off, so
  only the broadcast can move it); tab B picks beta; tab A's picker becomes
  beta without a reload, and its next Channels page is beta's; a client-side
  visit to /sites/alpha from tab A records alpha, and tab B follows. With the
  broadcast's refresh removed, tab A stays on alpha.
- `expectNoSiteParam()`: after the page hydrates and settles, a no-retry check
  that the URL has no `site` param, at the three inverted URL assertions and
  after each settle in the first-paint case. `toHaveURL` alone passes on its
  first poll, before a replace from an effect could land.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Diffstat:
Meditor/e2e/site-scope.spec.ts | 58+++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 57 insertions(+), 1 deletion(-)

diff --git a/editor/e2e/site-scope.spec.ts b/editor/e2e/site-scope.spec.ts @@ -1,7 +1,7 @@ import { test, expect, type Page } from "@playwright/test"; import { activeSiteCookieName } from "../app/lib/activeSite"; import { baseUrl } from "./baseUrl"; -import { readJson, resetData, writeSite } from "./helpers"; +import { readJson, resetData, writeSettings, writeSite } from "./helpers"; type SiteFile = { channels: { slug: string }[] }; @@ -38,12 +38,14 @@ test("selecting a site scopes the channels list and persists", async ({ await expect(page).toHaveURL(/\/channels$/); await expect(page.getByRole("link", { name: "slow-a" })).toBeVisible(); await expect(page.getByRole("link", { name: "slow-b" })).toHaveCount(0); + await expectNoSiteParam(page); // The cookie persists the choice: revisiting with no param re-applies it. await page.goto("/channels"); await expect(page.getByLabel("Active site")).toHaveValue("alpha"); await expect(page).toHaveURL(/\/channels$/); await expect(page.getByRole("link", { name: "slow-b" })).toHaveCount(0); + await expectNoSiteParam(page); // Switching to beta flips the scope. await page.getByLabel("Active site").selectOption("beta"); @@ -79,6 +81,7 @@ test("charts is a site's tab, and the picker follows the path", async ({ await page.goto("/channels"); await expect(page.getByLabel("Active site")).toHaveValue("beta"); await expect(page).toHaveURL(/\/channels$/); + await expectNoSiteParam(page); // "All sites" is the family page. await page.goto("/sites/beta/charts"); await page.getByLabel("Active site").selectOption("__all__"); @@ -212,6 +215,15 @@ function watchHydration(page: Page): string[] { return seen; } +// No `?site=` on the URL once the page has hydrated and settled, read with no +// retry. `toHaveURL` alone would pass on its first poll, before a `replace` from +// an effect (how the old picker seeded the param) could land. +async function expectNoSiteParam(page: Page) { + await hydrated(page); + await page.waitForTimeout(SETTLE_MS); + expect(new URL(page.url()).searchParams.has("site"), page.url()).toBe(false); +} + async function storedCookie(page: Page): Promise<string | undefined> { const cookies = await page.context().cookies(); return cookies.find((c) => c.name === COOKIE)?.value; @@ -246,6 +258,7 @@ test("a stored site is the picker's first paint on every page, never All sites", await hydrated(page); await page.waitForTimeout(SETTLE_MS); expect(await pickerValues(page), `${path}: every value`).toEqual(["alpha"]); + expect(new URL(page.url()).searchParams.has("site"), page.url()).toBe(false); } // Client-side navigations: the layout persists, the picker re-renders. @@ -265,6 +278,7 @@ test("a stored site is the picker's first paint on every page, never All sites", expect(await pickerValues(page), "every value across the navigations").toEqual([ "alpha", ]); + expect(new URL(page.url()).searchParams.has("site"), page.url()).toBe(false); // Visiting another site's page records it; Dashboard then paints it at once. expect(await firstPaint(page, "/sites/beta"), "/sites/beta: first paint").toBe( @@ -356,3 +370,45 @@ test("Back to a site's page shows that site, not the choice made there", async ( await expect(page).toHaveURL(/\/sites\/alpha\/charts$/); await expect(picker).toHaveValue("alpha"); }); + +// A site picked in one tab reaches the others. The cookie is shared, but a +// tab's picker reads its root layout, which a client-side navigation does not +// re-render; without the broadcast, tab A kept showing alpha over beta's pages +// and skipped recording a visit to alpha's page. +test("a site picked in another tab reaches this one", async ({ context }) => { + await twoSites(); + // Passive refresh off: a tree refresh whenever the pulse moves would also + // re-read the layout, and only the broadcast may move tab A here. + await writeSettings({ autoRefreshIntervalSeconds: 0 }); + const a = await context.newPage(); + const pickerA = a.getByLabel("Active site"); + await a.goto("/channels"); + await pickerA.selectOption("alpha"); + await expect(a.getByRole("link", { name: "slow-b" })).toHaveCount(0); + await a.goto("/settings"); + await hydrated(a); + + const b = await context.newPage(); + await b.goto("/channels"); + await b.getByLabel("Active site").selectOption("beta"); + await expect(b.getByRole("link", { name: "slow-a" })).toHaveCount(0); + expect(await storedCookie(b)).toBe("beta"); + + // Tab A follows without a reload, and its next page agrees with its picker. + await expect(pickerA).toHaveValue("beta"); + await a.locator("aside nav").getByRole("link", { name: "Channels", exact: true }).click(); + await expect(a).toHaveURL(/\/channels$/); + await expect(pickerA).toHaveValue("beta"); + await expect(a.getByRole("link", { name: "slow-b" })).toBeVisible(); + await expect(a.getByRole("link", { name: "slow-a" })).toHaveCount(0); + + // And a client-side visit to alpha's page is recorded again: tab A no longer + // believes alpha is stored. + await a.locator("aside nav").getByRole("link", { name: "Sites", exact: true }).click(); + await expect(a).toHaveURL(/\/sites$/); + await a.locator('main a[href="/sites/alpha"]').click(); + await expect(a).toHaveURL(/\/sites\/alpha$/); + await expect.poll(() => storedCookie(a), { timeout: 15_000 }).toBe("alpha"); + // Tab B hears of that write too. + await expect(b.getByLabel("Active site")).toHaveValue("alpha"); +});