From 65c0adfd769b9ef11b897c96a3634c61120055b8 Mon Sep 17 00:00:00 2001
From: nsfisis <nsfisis@gmail.com>
Date: Mon, 8 Dec 2025 00:18:03 +0900
Subject: feat(client): redesign frontend with TailwindCSS v4
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Replace inline styles with TailwindCSS, implementing a cohesive Japanese-inspired
design system with custom colors (cream, teal primary), typography (Fraunces,
DM Sans), and animations. Update all pages and components with consistent styling,
improve accessibility by adding aria-hidden to decorative SVGs, and configure
Biome for Tailwind CSS syntax support.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---
 src/client/pages/DeckDetailPage.test.tsx | 68 ++++++++++++++++++--------------
 1 file changed, 38 insertions(+), 30 deletions(-)

(limited to 'src/client/pages/DeckDetailPage.test.tsx')

diff --git a/src/client/pages/DeckDetailPage.test.tsx b/src/client/pages/DeckDetailPage.test.tsx
index 0589073..e4ecade 100644
--- a/src/client/pages/DeckDetailPage.test.tsx
+++ b/src/client/pages/DeckDetailPage.test.tsx
@@ -151,7 +151,8 @@ describe("DeckDetailPage", () => {
 
 		renderWithProviders();
 
-		expect(screen.getByText("Loading...")).toBeDefined();
+		// Loading state shows spinner (svg with animate-spin class)
+		expect(document.querySelector(".animate-spin")).toBeDefined();
 	});
 
 	it("displays empty state when no cards exist", async () => {
@@ -168,9 +169,9 @@ describe("DeckDetailPage", () => {
 		renderWithProviders();
 
 		await waitFor(() => {
-			expect(screen.getByText("This deck has no cards yet.")).toBeDefined();
+			expect(screen.getByText("No cards yet")).toBeDefined();
 		});
-		expect(screen.getByText("Add cards to start studying!")).toBeDefined();
+		expect(screen.getByText("Add cards to start studying")).toBeDefined();
 	});
 
 	it("displays list of cards", async () => {
@@ -208,7 +209,7 @@ describe("DeckDetailPage", () => {
 		renderWithProviders();
 
 		await waitFor(() => {
-			expect(screen.getByRole("heading", { name: "Cards (2)" })).toBeDefined();
+			expect(screen.getByText("(2)")).toBeDefined();
 		});
 	});
 
@@ -226,9 +227,9 @@ describe("DeckDetailPage", () => {
 		renderWithProviders();
 
 		await waitFor(() => {
-			expect(screen.getByText("State: New")).toBeDefined();
+			expect(screen.getByText("New")).toBeDefined();
 		});
-		expect(screen.getByText("State: Review")).toBeDefined();
+		expect(screen.getByText("Review")).toBeDefined();
 	});
 
 	it("displays card stats (reps and lapses)", async () => {
@@ -245,11 +246,10 @@ describe("DeckDetailPage", () => {
 		renderWithProviders();
 
 		await waitFor(() => {
-			expect(screen.getByText("Reviews: 0")).toBeDefined();
+			expect(screen.getByText("0 reviews")).toBeDefined();
 		});
-		expect(screen.getByText("Reviews: 5")).toBeDefined();
-		expect(screen.getByText("Lapses: 0")).toBeDefined();
-		expect(screen.getByText("Lapses: 1")).toBeDefined();
+		expect(screen.getByText("5 reviews")).toBeDefined();
+		expect(screen.getByText("1 lapses")).toBeDefined();
 	});
 
 	it("displays error on API failure for deck", async () => {
@@ -391,7 +391,9 @@ describe("DeckDetailPage", () => {
 				expect(screen.getByText("Hello")).toBeDefined();
 			});
 
-			const deleteButtons = screen.getAllByRole("button", { name: "Delete" });
+			const deleteButtons = screen.getAllByRole("button", {
+				name: "Delete card",
+			});
 			expect(deleteButtons.length).toBe(2);
 		});
 
@@ -414,7 +416,9 @@ describe("DeckDetailPage", () => {
 				expect(screen.getByText("Hello")).toBeDefined();
 			});
 
-			const deleteButtons = screen.getAllByRole("button", { name: "Delete" });
+			const deleteButtons = screen.getAllByRole("button", {
+				name: "Delete card",
+			});
 			const firstDeleteButton = deleteButtons[0];
 			if (firstDeleteButton) {
 				await user.click(firstDeleteButton);
@@ -445,7 +449,9 @@ describe("DeckDetailPage", () => {
 				expect(screen.getByText("Hello")).toBeDefined();
 			});
 
-			const deleteButtons = screen.getAllByRole("button", { name: "Delete" });
+			const deleteButtons = screen.getAllByRole("button", {
+				name: "Delete card",
+			});
 			const firstDeleteButton = deleteButtons[0];
 			if (firstDeleteButton) {
 				await user.click(firstDeleteButton);
@@ -488,18 +494,20 @@ describe("DeckDetailPage", () => {
 				expect(screen.getByText("Hello")).toBeDefined();
 			});
 
-			const deleteButtons = screen.getAllByRole("button", { name: "Delete" });
+			const deleteButtons = screen.getAllByRole("button", {
+				name: "Delete card",
+			});
 			const firstDeleteButton = deleteButtons[0];
 			if (firstDeleteButton) {
 				await user.click(firstDeleteButton);
 			}
 
-			// Find the Delete button in the modal (not the card list)
-			const modalDeleteButtons = screen.getAllByRole("button", {
-				name: "Delete",
-			});
-			const confirmDeleteButton = modalDeleteButtons.find((btn) =>
-				btn.closest('[role="dialog"]'),
+			// Find the Delete button in the modal (using the button's text content)
+			const dialog = screen.getByRole("dialog");
+			const modalButtons = dialog.querySelectorAll("button");
+			// Find the button with "Delete" text (not "Cancel")
+			const confirmDeleteButton = Array.from(modalButtons).find((btn) =>
+				btn.textContent?.includes("Delete"),
 			);
 			if (confirmDeleteButton) {
 				await user.click(confirmDeleteButton);
@@ -518,9 +526,7 @@ describe("DeckDetailPage", () => {
 
 			// Verify card count updated
 			await waitFor(() => {
-				expect(
-					screen.getByRole("heading", { name: "Cards (1)" }),
-				).toBeDefined();
+				expect(screen.getByText("(1)")).toBeDefined();
 			});
 		});
 
@@ -550,18 +556,20 @@ describe("DeckDetailPage", () => {
 				expect(screen.getByText("Hello")).toBeDefined();
 			});
 
-			const deleteButtons = screen.getAllByRole("button", { name: "Delete" });
+			const deleteButtons = screen.getAllByRole("button", {
+				name: "Delete card",
+			});
 			const firstDeleteButton = deleteButtons[0];
 			if (firstDeleteButton) {
 				await user.click(firstDeleteButton);
 			}
 
-			// Find the Delete button in the modal
-			const modalDeleteButtons = screen.getAllByRole("button", {
-				name: "Delete",
-			});
-			const confirmDeleteButton = modalDeleteButtons.find((btn) =>
-				btn.closest('[role="dialog"]'),
+			// Find the Delete button in the modal (using the button's text content)
+			const dialog = screen.getByRole("dialog");
+			const modalButtons = dialog.querySelectorAll("button");
+			// Find the button with "Delete" text (not "Cancel")
+			const confirmDeleteButton = Array.from(modalButtons).find((btn) =>
+				btn.textContent?.includes("Delete"),
 			);
 			if (confirmDeleteButton) {
 				await user.click(confirmDeleteButton);
-- 
cgit v1.2.3-70-g09d2

