fix: replace home navigation with work dashboard

This commit is contained in:
2026-10-01 14:25:21 +09:00
parent 696e6a51e7
commit 8e0dad98e8
12 changed files with 969 additions and 341 deletions
@@ -1,11 +1,15 @@
/// <reference lib="dom" />
import { assert, assertEquals } from "@std/assert";
import { dirname, fromFileUrl, join } from "@std/path";
import { chromium } from "playwright";
import { chromium, type Page } from "playwright";
const root = join(dirname(fromFileUrl(import.meta.url)), "../../..");
Deno.test("production Home preserves scoped navigation, permission boundaries, responsive layout, and keyboard access", async () => {
const ready = async (page: Page) => {
for (const id of ["home-attention", "home-active", "home-recent"]) {
await page.locator(`[data-feed-ready="${id}"]`).waitFor();
}
};
Deno.test("production Home dashboard isolates failures, shows authoritative records, and supports responsive keyboard navigation", async () => {
const build = await new Deno.Command(Deno.execPath(), {
args: ["task", "build"],
cwd: join(root, "web/workspace"),
@@ -30,19 +34,19 @@ Deno.test("production Home preserves scoped navigation, permission boundaries, r
stderr: "null",
}).spawn();
try {
let ready = false;
let started = false;
for (let n = 0; n < 100; n++) {
try {
const response = await fetch(`${url}/health`);
await response.body?.cancel();
if (response.ok) {
ready = true;
started = true;
break;
}
} catch { /* owned fixture is starting */ }
} catch { /* fixture is starting */ }
await new Promise((resolve) => setTimeout(resolve, 50));
}
assert(ready, "fixture did not start");
assert(started);
const browser = await chromium.launch({ headless: true });
try {
for (const colorScheme of ["light", "dark"] as const) {
@@ -64,66 +68,69 @@ Deno.test("production Home preserves scoped navigation, permission boundaries, r
page.on("request", (request) => requests.push(request.url()));
for (const workspace of ["home-owner", "home-member", "home-long"]) {
await page.goto(`${url}/w/${workspace}`);
const work = page.getByRole("navigation", { name: "Work", exact: true });
await work.waitFor();
assertEquals(
(await work.getByRole("link").allTextContents()).map((text) => text.trim()),
["Tickets", "Objectives", "Merge Requests", "Memory", "Workers"],
);
const settings = page.locator("main").getByRole("navigation", {
name: "Settings",
exact: true,
});
assertEquals(
await settings.getByRole("link").count(),
workspace === "home-member" ? 2 : 6,
);
assertEquals(
await settings.getByRole("link", { name: "Runtimes", exact: true }).count(),
workspace === "home-member" ? 0 : 1,
);
assertEquals(await page.locator("main h1").allTextContents(), ["Work"]);
await ready(page);
assertEquals(await page.locator("main h1").allTextContents(), ["Activity"]);
assertEquals(await page.locator("main nav").count(), 0);
assertEquals(await page.locator("main [role=alert]").count(), 0);
assertEquals(await page.locator("[data-feed-ready=home-attention] a").count(), 3);
assertEquals(await page.locator("[data-feed-ready=home-active] a").count(), 2);
assertEquals(await page.locator("[data-feed-ready=home-recent] a").count(), 4);
await page.getByText("Blocked by T-99", { exact: true }).waitFor();
await page.getByText("W-7", { exact: true }).waitFor();
const geometry = await page.locator(".workspace-home").evaluate((home) => {
const main = home.closest("main")!;
const work = home.querySelector("section")!.getBoundingClientRect();
const settings = home.querySelector(".home-settings")!.getBoundingClientRect();
const sections = [...home.querySelectorAll("section")].map((section) =>
section.getBoundingClientRect()
);
return {
pageOverflow: document.documentElement.scrollWidth > innerWidth,
mainOverflow: main.scrollWidth > main.clientWidth,
firstLinkBottom: home.querySelector("a")!.getBoundingClientRect().bottom,
sideBySide: settings.left > work.right,
stacked: settings.top >= work.bottom,
smallTargets: [...home.querySelectorAll("a")].filter((link) =>
link.getBoundingClientRect().height < 44
).length,
firstLinkTop: home.querySelector("a")!.getBoundingClientRect().top,
sideBySide: sections[1].left > sections[0].right &&
sections[2].left > sections[1].right,
stacked: sections[1].top >= sections[0].bottom &&
sections[2].top >= sections[1].bottom,
};
});
assertEquals(geometry.pageOverflow, false);
assertEquals(geometry.mainOverflow, false);
assert(geometry.firstLinkBottom < 900);
assertEquals(geometry.smallTargets, 0);
assert(geometry.firstLinkTop < 900);
assert(width === 1440 ? geometry.sideBySide : geometry.stacked);
const first = work.getByRole("link", { name: "Tickets", exact: true });
await first.focus();
const outline = await first.evaluate((link) => getComputedStyle(link).outlineStyle);
assertEquals(outline, "solid");
const links = page.locator("main a");
await links.first().focus();
assertEquals(
await links.first().evaluate((link) => getComputedStyle(link).outlineStyle),
"solid",
);
await page.keyboard.press("Tab");
assertEquals(
await page.evaluate(() => document.activeElement?.textContent?.trim()),
"Objectives",
await links.nth(1).evaluate((link) => document.activeElement === link),
true,
);
for (const link of await page.locator("main a").all()) {
assert((await link.getAttribute("href"))!.startsWith(`/w/${workspace}/`));
for (const link of await links.all()) {
assert(
new RegExp(`/w/${workspace}/(tickets|objectives|merge-requests)/[^/]+$`).test(
(await link.getAttribute("href"))!,
),
);
}
}
const memory = page.getByRole("navigation", { name: "Work", exact: true }).getByRole(
"link",
{ name: "Memory", exact: true },
const beforeRefresh = requests.filter((request) =>
request.includes("/merge-requests?")
).length;
await page.getByRole("button", { name: "Refresh", exact: true }).click();
await page.getByRole("button", { name: "Refresh", exact: true }).waitFor();
await ready(page);
assertEquals(
requests.filter((request) => request.includes("/merge-requests?")).length,
beforeRefresh + 1,
);
await memory.focus();
const objective = page.locator("main a[href*='/objectives/O-12-']");
await objective.focus();
await page.keyboard.press("Enter");
await page.waitForURL(`**/w/home-long/memory`);
await page.getByRole("heading", { name: "Home navigation destination" }).waitFor();
await page.waitForURL("**/objectives/O-12-*");
await page.getByText("Home dashboard navigation destination.", { exact: true })
.waitFor();
assertEquals(
requests.some((request) => new URL(request).pathname.endsWith("/hosts")),
false,
@@ -134,6 +141,37 @@ Deno.test("production Home preserves scoped navigation, permission boundaries, r
}
}
}
const page = await browser.newPage({ viewport: { width: 390, height: 844 } });
try {
await page.goto(`${url}/w/home-empty`);
await ready(page);
assertEquals(await page.locator("main a").count(), 0);
await page.getByText("No open Merge Requests.", { exact: true }).waitFor();
await page.goto(`${url}/w/home-error`);
await ready(page);
assertEquals(
await page.locator("[data-feed-ready=home-attention] [role=alert]").count(),
1,
);
assertEquals(await page.getByText("No open Merge Requests.", { exact: true }).count(), 0);
assertEquals(await page.locator("[data-feed-ready=home-active] a").count(), 2);
assertEquals(await page.locator("[data-feed-ready=home-recent] a").count(), 4);
let release!: () => void;
const held = new Promise<void>((resolve) => {
release = resolve;
});
await page.route("**/merge-requests?*", async (route) => {
await held;
await route.continue();
});
await page.goto(`${url}/w/home-owner`);
await page.locator("[data-feed-ready=home-active]").waitFor();
assertEquals(await page.getByText("Loading needs attention…", { exact: true }).count(), 1);
release();
await ready(page);
} finally {
await page.close();
}
} finally {
await browser.close();
}