refactor: migrate UI to Tailwind and unify chat with shadcn

This commit is contained in:
2026-10-07 20:51:05 +09:00
parent f41d0c1964
commit 1e03e8f4ff
56 changed files with 2014 additions and 4399 deletions
+13 -7
View File
@@ -74,18 +74,18 @@ test("creates, switches, renames and deletes persisted deck profiles", async ({
await page.getByRole("button", { name: "Create deck", exact: true }).click();
await page.getByLabel("New deck name").fill("別の調査");
await page.getByRole("button", { name: "Create", exact: true }).click();
await expect(page.locator(".deck-column")).toHaveCount(0);
await expect(page.locator("[data-testid='deck-column']")).toHaveCount(0);
await addColumn(page, "別の観点", "second-deck", "alternate");
await showDeckManagement(page);
await page.getByLabel("Deck profile", { exact: true }).selectOption({ label: "最初の調査" });
await closeMobileSidebar(page);
await expect(page.locator(".deck-column h2")).toHaveText(["最初の観点"]);
await expect(page.locator("[data-testid='deck-column'] h2")).toHaveText(["最初の観点"]);
await page.reload();
await expect(page.getByRole("heading", { level: 1 })).toHaveText("最初の調査");
await showDeckManagement(page);
await page.getByLabel("Deck profile", { exact: true }).selectOption({ label: "別の調査" });
await closeMobileSidebar(page);
await expect(page.locator(".deck-column h2")).toHaveText(["別の観点"]);
await expect(page.locator("[data-testid='deck-column'] h2")).toHaveText(["別の観点"]);
await showDeckManagement(page);
await page.getByRole("button", { name: "Delete deck", exact: true }).click();
await page.getByRole("button", { name: "Delete", exact: true }).click();
@@ -108,11 +108,17 @@ test("reads user and list timelines within columns and reorders, deletes and res
await expect(list.getByText("list page 2")).toBeVisible();
await list.getByRole("button", { name: "Actions for リストの投稿" }).click();
await page.getByRole("menuitem", { name: "Move リストの投稿 left" }).click();
await expect(page.locator(".deck-column h2")).toHaveText(["リストの投稿", "アカウント"]);
await expect(page.locator("[data-testid='deck-column'] h2")).toHaveText([
"リストの投稿",
"アカウント",
]);
await list.getByRole("button", { name: "Actions for リストの投稿" }).click();
await page.getByRole("menuitem", { name: "Delete リストの投稿" }).click();
await page.getByRole("button", { name: "Undo" }).click();
await expect(page.locator(".deck-column h2")).toHaveText(["リストの投稿", "アカウント"]);
await expect(page.locator("[data-testid='deck-column'] h2")).toHaveText([
"リストの投稿",
"アカウント",
]);
await page.reload();
await expect(user.getByText("alternate profile page 1")).toBeVisible();
await expect(list.getByText("list page 1")).toBeVisible();
@@ -149,7 +155,7 @@ test("keeps the deck and editor accessible within narrow viewports", async ({
expect(await page.evaluate(() => document.documentElement.scrollHeight <= innerHeight)).toBe(
true,
);
const board = await page.locator(".deck-board").boundingBox();
const board = await page.locator("[data-testid='deck-board']").boundingBox();
expect(board).not.toBeNull();
expect((board?.y ?? 0) + (board?.height ?? 0)).toBeLessThanOrEqual(901);
}
@@ -184,7 +190,7 @@ test("closes the column editor with Escape and restores focus without applying e
await page.keyboard.press("Escape");
await expect(page.getByRole("dialog", { name: "Add column", exact: true })).toHaveCount(0);
await expect(opener).toBeFocused();
await expect(page.locator(".deck-column")).toHaveCount(0);
await expect(page.locator("[data-testid='deck-column']")).toHaveCount(0);
await opener.click();
await expect(page.getByLabel("Column name", { exact: true })).toHaveValue("");
});
+1 -1
View File
@@ -154,7 +154,7 @@ test("keeps completed actions in their original positions with visible outcomes"
}, testInfo) => {
await page.goto("/");
await page.waitForLoadState("networkidle");
const titles = page.locator(".execution-flow-title");
const titles = page.locator("[data-testid='execution-flow-title']");
const before = await titles.allTextContents();
await page.getByRole("button", { name: "Complete Review the documents", exact: true }).click();
await expect(
+3 -3
View File
@@ -3,7 +3,7 @@ import { expect, test } from "../fixtures";
test("selects articles and keeps the reader focused on their content", async ({ page }) => {
await page.goto("/inbox");
await page.waitForLoadState("networkidle");
const rows = page.locator(".inbox-article-row");
const rows = page.locator("[data-testid='inbox-article-row']");
await expect(rows).toHaveCount(9);
const articleTitle = "Context for an agent can be brief";
await rows.filter({ hasText: articleTitle }).click();
@@ -33,7 +33,7 @@ test("selects articles and keeps the reader focused on their content", async ({
test("recovers from empty search and shares navigation with Research", async ({ page, a11y }) => {
await page.goto("/inbox");
await page.waitForLoadState("networkidle");
const rows = page.locator(".inbox-article-row");
const rows = page.locator("[data-testid='inbox-article-row']");
await page.getByRole("textbox", { name: "Search articles" }).fill("nonexistent-query-xyz");
await expect(rows).toHaveCount(0);
await expect(page.getByText("No articles found", { exact: true })).toBeVisible();
@@ -58,7 +58,7 @@ test("keeps article navigation readable without horizontal overflow", async ({
await page.setViewportSize({ width, height: 850 });
await expect(page.getByRole("textbox", { name: "Search articles" })).toBeVisible();
expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBe(width);
await page.locator(".inbox-article-row").first().click();
await page.locator("[data-testid='inbox-article-row']").first().click();
const reader = page.getByRole("article", {
name: "Thinking in the browser: a new workflow with AI agents",
exact: true,
+9 -6
View File
@@ -100,7 +100,7 @@ test("restores the conversation and growing temporary deck from reconnect snapsh
"background-color",
"rgba(0, 0, 0, 0)",
);
await expect(page.locator(".deck-column h2")).toHaveText(["最初の観点"]);
await expect(page.locator("[data-testid='deck-column'] h2")).toHaveText(["最初の観点"]);
run.deckVersion = 2;
run.status = "complete";
run.messages.push({
@@ -124,7 +124,10 @@ test("restores the conversation and growing temporary deck from reconnect snapsh
await showResearch(page);
await expect(conversation).toContainText("既存の観点を使って調査して");
await expect(conversation).toContainText("比較する観点を追加しました。");
await expect(page.locator(".deck-column h2")).toHaveText(["最初の観点", "比較の観点"]);
await expect(page.locator("[data-testid='deck-column'] h2")).toHaveText([
"最初の観点",
"比較の観点",
]);
await closeMobileSidebar(page);
await expect(page.getByRole("button", { name: "Save as deck", exact: true })).toBeEnabled();
await showResearch(page);
@@ -260,15 +263,15 @@ test("renders Markdown and reveals an unloaded citation in an offscreen deck col
await expect(page.getByText("AIが調査時に取得した引用本文", { exact: true })).toHaveCount(0);
await chat.getByRole("link", { name: "根拠の投稿" }).click();
const card = page.locator(
'.research-post[data-post-url="https://twitter.com/author/status/123456789"]',
'[data-testid="research-post"][data-post-url="https://twitter.com/author/status/123456789"]',
);
await expect(card).toBeVisible();
await expect(card).toHaveClass(/citation-highlight/);
await expect(card).toHaveAttribute("data-citation-highlight", "true");
await expect(card).toBeFocused();
await expect(card).toContainText("Post saved during research");
await expect(card).toContainText("AIが調査時に取得した引用本文");
expect(
await page.locator(".deck-board").evaluate((element) => element.scrollLeft),
await page.locator("[data-testid='deck-board']").evaluate((element) => element.scrollLeft),
).toBeGreaterThan(0);
expect(page.context().pages()).toHaveLength(1);
await expect(page).toHaveURL(/citation=twitter%3A123456789/);
@@ -283,7 +286,7 @@ test("renders Markdown and reveals an unloaded citation in an offscreen deck col
await expect(card).toBeVisible();
await expect(card).toHaveClass(/citation-highlight/);
await expect(page).toHaveURL(/citation=twitter%3A123456789/);
await expect(page.locator(".deck-column")).toHaveCount(3);
await expect(page.locator("[data-testid='deck-column']")).toHaveCount(3);
expect(submitted).toEqual([]);
expect((await a11y().analyze()).violations).toEqual([]);
});
+8 -7
View File
@@ -9,16 +9,16 @@ test("reviews an edited reply and keeps the simulated result in history", async
}) => {
await page.goto("/support");
await page.waitForLoadState("networkidle");
const rows = page.locator(".support-row");
const rows = page.locator("[data-testid='support-row']");
await expect(rows).toHaveCount(3);
await rows.filter({ hasText: reviewSubject }).click();
await page.locator(".conversation-suggestions summary:visible").click();
await page.locator("[data-testid='conversation-suggestions'] summary:visible").click();
await page.getByRole("button", { name: /^Polite/ }).click();
const reply = page.getByRole("textbox", { name: "Reply", exact: true });
await expect(reply).toHaveValue(/Thank you for sharing/);
const edited = "I reviewed the materials. Let’s discuss the first-time experience.";
await reply.fill(edited);
await page.locator(".conversation-scheduling summary:visible").click();
await page.locator("[data-testid='conversation-scheduling'] summary:visible").click();
const slot = "Mon, Oct 5, 14:00–14:30";
await page.getByRole("combobox", { name: "Suggested times" }).selectOption(slot);
await page.getByRole("button", { name: "Add to reply" }).click();
@@ -37,15 +37,16 @@ test("reviews an edited reply and keeps the simulated result in history", async
await expect(rows.filter({ hasText: reviewSubject })).toHaveCount(0);
await page.getByRole("button", { name: "Archive", exact: true }).click();
await rows.filter({ hasText: reviewSubject }).click();
await expect(page.locator(".support-outcome")).toContainText(edited);
await expect(page.locator(".support-outcome")).toContainText(slot);
await expect(page.locator("[data-testid='support-outcome']")).toContainText(edited);
await expect(page.locator("[data-testid='support-outcome']")).toContainText(slot);
});
test("defers a conversation and restores it to the current queue", async ({ page }) => {
await page.goto("/support");
await page.waitForLoadState("networkidle");
const rows = page.locator(".support-row");
const rows = page.locator("[data-testid='support-row']");
await rows.filter({ hasText: coffeeSubject }).click();
await expect(rows.filter({ hasText: coffeeSubject })).toHaveAttribute("aria-current", "true");
await page
.getByRole("textbox", { name: "Reply", exact: true })
.fill("土曜の予定を確認してから返事するね。");
@@ -117,7 +118,7 @@ test("fits communication and journal interactions in narrow screens", async ({
await page.goto("/support");
await page.waitForLoadState("networkidle");
expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBe(width);
await page.locator(".support-row").first().click();
await page.locator("[data-testid='support-row']").first().click();
await expect(page.getByRole("textbox", { name: "Reply", exact: true })).toBeVisible();
expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBe(width);
if (width === 320) expect((await a11y().analyze()).violations).toEqual([]);
+20 -12
View File
@@ -2,19 +2,22 @@ import { expect, test } from "../fixtures";
test("restores article selection with browser history and reload", async ({ page }) => {
await page.goto("/inbox", { waitUntil: "networkidle" });
const rows = page.locator(".inbox-article-row");
const heading = await rows.nth(1).locator(".inbox-row-title").innerText();
const rows = page.locator("[data-testid='inbox-article-row']");
const heading = await rows.nth(1).locator("[data-testid='inbox-row-title']").innerText();
await rows.nth(1).click();
await expect(page.locator(".inbox-article-title")).toHaveText(heading);
await expect(page.locator("[data-testid='inbox-article-title']")).toHaveText(heading);
const selectedUrl = page.url();
expect(new URL(selectedUrl).searchParams.get("article")).toBeTruthy();
await page.reload({ waitUntil: "networkidle" });
await expect(page.locator(".inbox-article-title")).toHaveText(heading ?? "");
await expect(page.locator("[data-testid='inbox-article-title']")).toHaveText(heading ?? "");
await page.goBack();
await expect(page.locator(".inbox-workspace")).toHaveAttribute("data-reading", "false");
await expect(page.locator("[data-testid='inbox-workspace']")).toHaveAttribute(
"data-reading",
"false",
);
await page.goForward();
await expect(page).toHaveURL(selectedUrl);
await expect(page.locator(".inbox-article-title")).toHaveText(heading ?? "");
await expect(page.locator("[data-testid='inbox-article-title']")).toHaveText(heading ?? "");
});
test("restores search and replaces typing history", async ({ page }) => {
@@ -24,10 +27,10 @@ test("restores search and replaces typing history", async ({ page }) => {
await search.pressSequentially("browser");
await expect(search).toHaveValue("browser");
await expect.poll(() => new URL(page.url()).searchParams.get("q")).toBe("browser");
const count = await page.locator(".inbox-article-row").count();
const count = await page.locator("[data-testid='inbox-article-row']").count();
await page.reload({ waitUntil: "networkidle" });
await expect(search).toHaveValue("browser");
await expect(page.locator(".inbox-article-row")).toHaveCount(count);
await expect(page.locator("[data-testid='inbox-article-row']")).toHaveCount(count);
await page.goBack();
await expect(page).toHaveURL("http://127.0.0.1:4173/");
});
@@ -47,16 +50,21 @@ test("restores account dialog without losing underlying article", async ({ page
test("restores a message view and conversation from its URL", async ({ page }) => {
await page.goto("/support", { waitUntil: "networkidle" });
await page.getByRole("button", { name: "All", exact: true }).click();
const row = page.locator(".support-row").nth(1);
const row = page.locator("[data-testid='support-row']").nth(1);
const heading = await row.locator("span").first().innerText();
await row.click();
await expect(page.locator(".support-toolbar > span").first()).toHaveText(heading);
await expect(page.locator("[data-testid='support-toolbar'] > span").first()).toHaveText(heading);
const url = page.url();
expect(new URL(url).searchParams.get("view")).toBe("all");
expect(new URL(url).searchParams.get("conversation")).toBeTruthy();
await page.reload({ waitUntil: "networkidle" });
await expect(page.locator(".support-toolbar > span").first()).toHaveText(heading ?? "");
await expect(page.locator(".support-workspace")).toHaveAttribute("data-reading", "true");
await expect(page.locator("[data-testid='support-toolbar'] > span").first()).toHaveText(
heading ?? "",
);
await expect(page.locator("[data-testid='support-workspace']")).toHaveAttribute(
"data-reading",
"true",
);
});
test("keeps a deep link through login", async ({ page, context }) => {
+8 -5
View File
@@ -4,7 +4,7 @@ test("filters body systems and opens lab history", async ({ page, a11y }) => {
await page.goto("/vitals");
await page.waitForLoadState("networkidle");
await expect(page.getByRole("heading", { name: "Body", exact: true })).toBeVisible();
const systems = page.locator(".vital-system-card");
const systems = page.locator("[data-testid='vital-system-card']");
await expect(systems).toHaveCount(8);
await page
.getByRole("group", { name: "Body systems", exact: true })
@@ -24,11 +24,11 @@ test("filters body systems and opens lab history", async ({ page, a11y }) => {
await expect(dialog).toBeVisible();
await dialog.getByRole("button", { name: "Close", exact: true }).last().click();
await page.getByRole("button", { name: "Lab history", exact: true }).click();
await expect(page.locator(".vital-history")).toContainText("Jul 28");
await expect(page.locator(".vital-history")).toContainText("93");
await expect(page.locator("[data-testid='vital-history']")).toContainText("Jul 28");
await expect(page.locator("[data-testid='vital-history']")).toContainText("93");
await page.reload();
await page.waitForLoadState("networkidle");
await expect(page.locator(".vital-history")).toContainText("Jul 28");
await expect(page.locator("[data-testid='vital-history']")).toContainText("Jul 28");
await page.getByRole("button", { name: "Systems", exact: true }).click();
await page
.getByRole("group", { name: "Body systems", exact: true })
@@ -61,7 +61,10 @@ test("explores environment charts and simulated climate controls", async ({ page
await expect(
page.getByRole("img", { name: "7D CO₂ simulated environmental readings" }),
).toBeVisible();
await page.locator(".vital-metric").filter({ hasText: "Room temperature" }).click();
await page
.locator("[data-testid='vital-metric']")
.filter({ hasText: "Room temperature" })
.click();
const dialog = page.getByRole("dialog", { name: "Room temperature" });
await expect(dialog).toContainText("64.0");
await page.reload();
+1 -1
View File
@@ -234,7 +234,7 @@ test("rejects invalid input without changing the active view", async ({ page })
expect((await executeTool(page, "set_deck", { title: "変更しない", columns })).isError).toBe(
true,
);
await expect(page.locator(".deck-column h2")).toHaveText(["残す観点"]);
await expect(page.locator("[data-testid='deck-column'] h2")).toHaveText(["残す観点"]);
}
expect(savedCount()).toEqual({ count: 0 });
});