From 8ca7ebdd5b103e2904f3b7f2c3dca7cbbe961cda Mon Sep 17 00:00:00 2001 From: yiekheng Date: Sun, 10 May 2026 09:30:11 +0800 Subject: [PATCH] feat(web): drop Delete card from accounts overview MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Account-level destructive actions (Delete, Unpair, Re-pair) live on the detail page only. The overview is now a calm grid of one card per account, each linking to its detail page. - Removed the dedicated Delete card and its dialog from accounts-list-view.tsx. - The whole account card is once again the link target — no inline trigger surfaces, no Dialog component, no destructive click area. - AccountsListView no longer needs the deleteFormAction prop; the /accounts page passes only `accounts`. Tests updated: - accounts-list-view.test.tsx: 6 tests now (was 8). The two cases that asserted on the delete card are replaced with one positive test that asserts no Delete affordance is rendered on the overview, plus a test that the only `` per cell wraps the card with no inline buttons inside it. - no-render-warnings.test.tsx: drops the obsolete deleteFormAction prop in its renderQuiet calls. Hydration: live curl on /, /accounts, /reminders, /activity, /settings and a detail page returns 200 with no Hydration / script-tag warning in the web logs after this commit. 98 tests passing. Co-Authored-By: Claude Opus 4.7 (1M context) --- apps/web/src/app/accounts/page.tsx | 3 +- .../components/accounts-list-view.test.tsx | 113 +++---------- .../web/src/components/accounts-list-view.tsx | 150 +++++------------- apps/web/src/test/no-render-warnings.test.tsx | 6 +- 4 files changed, 72 insertions(+), 200 deletions(-) diff --git a/apps/web/src/app/accounts/page.tsx b/apps/web/src/app/accounts/page.tsx index d847734..4a8eb0c 100644 --- a/apps/web/src/app/accounts/page.tsx +++ b/apps/web/src/app/accounts/page.tsx @@ -1,11 +1,10 @@ import { AccountsListView } from "@/components/accounts-list-view"; import { getSeededOperator } from "@/lib/operator"; import { listAccounts } from "@/lib/queries"; -import { deleteAccountAction } from "@/actions/accounts"; export default async function AccountsPage() { const op = await getSeededOperator(); const accounts = await listAccounts(op.id); - return ; + return ; } diff --git a/apps/web/src/components/accounts-list-view.test.tsx b/apps/web/src/components/accounts-list-view.test.tsx index 415234b..e20e24a 100644 --- a/apps/web/src/components/accounts-list-view.test.tsx +++ b/apps/web/src/components/accounts-list-view.test.tsx @@ -16,21 +16,6 @@ vi.mock("next/link", () => ({ ), })); -// Radix Dialog uses portals + client refs. For SSR markup tests we want -// the trigger to be rendered inline (with `asChild`) and the content tree -// to render too, so we can assert dialog text deterministically. -vi.mock("./ui/dialog", () => ({ - Dialog: ({ children }: { children: ReactNode }) => <>{children}, - DialogTrigger: ({ children }: { children: ReactNode; asChild?: boolean }) => <>{children}, - DialogContent: ({ children }: { children: ReactNode }) => ( -
{children}
- ), - DialogHeader: ({ children }: { children: ReactNode }) =>
{children}
, - DialogTitle: ({ children }: { children: ReactNode }) =>

{children}

, - DialogDescription: ({ children }: { children: ReactNode }) =>
{children}
, - DialogFooter: ({ children }: { children: ReactNode }) =>
{children}
, -})); - const mkAccount = (over: Partial = {}): AccountsListAccount => ({ id: "a-1", label: "Personal", @@ -60,11 +45,9 @@ function render(html: string) { }; } -const noopAction = () => {}; - describe("AccountsListView", () => { describe("layout — accounts present", () => { - it("renders one cell per account, each with a main card and a delete card", () => { + it("renders exactly one card per account (no inline destructive triggers)", () => { const accounts = [ mkAccount({ id: "a-1", label: "Personal" }), mkAccount({ id: "a-2", label: "Work" }), @@ -72,100 +55,56 @@ describe("AccountsListView", () => { ]; const { count } = render( - renderToStaticMarkup( - , - ), + renderToStaticMarkup(), ); expect(count('data-testid="account-cell"')).toBe(3); expect(count('data-testid="account-card"')).toBe(3); - expect(count('data-testid="account-delete-card"')).toBe(3); }); - it("each main card links to /accounts/[id]", () => { - const { has } = render( - renderToStaticMarkup( - , - ), - ); - expect(has(/href="\/accounts\/abc-123"/)).toBe(true); - }); - - it("renders the account label in both the main card and the delete card", () => { + it("does NOT render any Delete affordance on the overview", () => { + // Account-level destructive actions live on the detail page only. const html = renderToStaticMarkup( - , + , ); - // Main card title - expect(html).toContain(">MyBiz<"); - // Delete card description references the account by name - expect(html).toContain("Remove MyBiz and its reminders"); - // Dialog confirmation also uses the label - expect(html).toMatch(/MyBiz<\/strong>/); + expect(html).not.toContain("Delete account"); + expect(html).not.toContain("Remove Sales"); + expect(html).not.toMatch(/data-testid="account-delete-card"/); + expect(html).not.toMatch(/aria-label="Delete /); }); - it("delete card uses a real
- - - - - + ) : null} + + + ))} ) : ( diff --git a/apps/web/src/test/no-render-warnings.test.tsx b/apps/web/src/test/no-render-warnings.test.tsx index 36c67cf..c1d63f3 100644 --- a/apps/web/src/test/no-render-warnings.test.tsx +++ b/apps/web/src/test/no-render-warnings.test.tsx @@ -99,7 +99,7 @@ const account: AccountsListAccount = { describe("SSR render — no React errors or warnings", () => { it("AccountsListView (populated) renders cleanly", () => { const { errors, warns } = renderQuiet( - {}} />, + , ); expect(errors).toEqual([]); expect(warns).toEqual([]); @@ -107,7 +107,7 @@ describe("SSR render — no React errors or warnings", () => { it("AccountsListView (empty) renders cleanly", () => { const { errors, warns } = renderQuiet( - {}} />, + , ); expect(errors).toEqual([]); expect(warns).toEqual([]); @@ -153,7 +153,7 @@ describe("SSR render — no React errors or warnings", () => { describe("SSR markup — no
inside region for any //