Archilyzer · Source

archilyzer

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

commit cee2a55f7b3d1c7cee0cf506ab983e69f4c7b3cb
parent 4e7d84fe910e3e4707c274b47f164a1f0d20ec15
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Thu, 17 Sep 2026 15:06:44 -0400

storage: two accessible names where one contains the other is a trap

Playwright's getByLabel is a SUBSTRING match, and the first e2e run walked
straight into it: "location id" is inside "location identity", "location
root" inside "location root path", so filling the add form targeted a
read-only <dd> instead. The read-outs are renamed ("location path", "volume
identity"); the form fields keep the names the plan's ARIA contract fixes.
It is not only a test problem — a screen reader user hears two controls whose
names shadow each other for the same reason.

And the re-point spec was waiting for the wrong thing. The uuid is on the
page before anybody clicks: the server render probes too. So the settled
condition is now the SETTINGS FILE — the one thing only the action can do is
write the identity, which is what a refresh is for. As written, the test
passed without ever pressing the button.

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

Diffstat:
Meditor/app/storage/components/StorageLocationsTable.tsx | 11+++++++++--
Meditor/e2e/storage-locations.spec.ts | 37+++++++++++++++++++++----------------
2 files changed, 30 insertions(+), 18 deletions(-)

diff --git a/editor/app/storage/components/StorageLocationsTable.tsx b/editor/app/storage/components/StorageLocationsTable.tsx @@ -166,12 +166,19 @@ function LocationCard({ </div> <dl className="grid grid-cols-[max-content_1fr] gap-x-4 gap-y-1 text-sm"> + {/* NOT "location root" and NOT "location identity". Playwright's + getByLabel is a SUBSTRING match, so those names would each swallow a + form field on this very page — "location id" is inside "location + identity", "location root" inside "location root path" — and a spec + filling the form would silently target a read-only <dd>. Two + accessible names where one contains the other are a trap for screen + reader users for the same reason. */} <dt className="text-muted-foreground">Root</dt> - <dd className="font-mono text-xs break-all" aria-label="location root path"> + <dd className="font-mono text-xs break-all" aria-label="location path"> {row.root} </dd> <dt className="text-muted-foreground">Volume</dt> - <dd className="text-xs" aria-label="location identity"> + <dd className="text-xs" aria-label="volume identity"> {row.identity ?? "unknown — nothing here can ask (no findmnt, or a container)"} </dd> <dt className="text-muted-foreground">Channels</dt> diff --git a/editor/e2e/storage-locations.spec.ts b/editor/e2e/storage-locations.spec.ts @@ -64,6 +64,13 @@ async function relocateOnDisk(root: string): Promise<string> { return target; } +async function storedVolumeUuid(id: string): Promise<string | undefined> { + const s = await readJson<{ + storage: { locations: Array<{ id: string; volume?: { uuid?: string } }> }; + }>("test-settings.json"); + return s.storage.locations.find((l) => l.id === id)?.volume?.uuid; +} + function row(page: Page, id: string) { return page.getByLabel(`storage location: ${id}`); } @@ -137,11 +144,11 @@ test("the page lists a location with its status, counts and refusals", async ({ await mkdir(second, { recursive: true }); await clickUntil( page.getByRole("button", { name: "New location" }), - () => page.getByLabel("location id").isVisible(), + () => page.getByLabel("location id", { exact: true }).isVisible(), ); - await page.getByLabel("location id").fill("warm"); - await page.getByLabel("location label").fill("Warm"); - await page.getByLabel("location root").fill(second); + await page.getByLabel("location id", { exact: true }).fill("warm"); + await page.getByLabel("location label", { exact: true }).fill("Warm"); + await page.getByLabel("location root", { exact: true }).fill(second); await page.getByLabel("auto re-point").check(); await page.getByLabel("add storage location").click(); @@ -191,19 +198,17 @@ test("a volume that came up somewhere else is re-pointed in one click", async ({ await page.goto("/storage"); const cold = row(page, "cold"); await expect(cold.getByLabel("location status")).toHaveText("Available"); - await clickUntil(cold.getByLabel("refresh cold"), async () => - (await cold.getByLabel("location identity").textContent())?.includes(UUID) ?? - false, + // THE SETTLED CONDITION IS THE FILE, NOT THE SCREEN. The server render probes + // too, so the uuid is already on the page before anybody clicks anything — + // waiting for it would pass without the button ever being pressed. What only + // the action can do is WRITE the identity, and that is what a refresh is for: + // it writes identity and nothing else (availability is never stored, or every + // page load would bump the pulse revision). + await clickUntil( + cold.getByLabel("refresh cold"), + async () => (await storedVolumeUuid("cold")) === UUID, ); - // A refresh writes IDENTITY and only identity — availability is never stored. - await expect - .poll(async () => { - const s = await readJson<{ - storage: { locations: Array<{ id: string; volume?: { uuid?: string } }> }; - }>("test-settings.json"); - return s.storage.locations.find((l) => l.id === "cold")?.volume?.uuid; - }, { timeout: 15_000 }) - .toBe(UUID); + await expect(cold.getByLabel("volume identity")).toContainText(UUID); await expect(cold.getByLabel("location warning")).toContainText("automount"); // --- the disk comes back somewhere else ---------------------------------