diff --git a/apps/web/src/app/accounts/[id]/page.tsx b/apps/web/src/app/accounts/[id]/page.tsx
index 963b293..7f211ef 100644
--- a/apps/web/src/app/accounts/[id]/page.tsx
+++ b/apps/web/src/app/accounts/[id]/page.tsx
@@ -74,33 +74,37 @@ export default async function AccountDetailPage({ params }: AccountDetailPagePro
- {/* Pair / Re-pair — entire card is the submit button */}
+ {/* Pair / Re-pair — keep the form-submit semantics. The whole
+ card surface is still the click target via a transparent
+ overlay submit button positioned over the card; the visible
+ Card stays a
, so we never nest a
inside a
+
(invalid HTML → SSR hydration mismatch). */}
{account.status !== "connected" && (
-
)}
@@ -130,27 +134,27 @@ export default async function AccountDetailPage({ params }: AccountDetailPagePro
{/* Unpair — entire card opens the confirm dialog */}
-
-
-
-
-
-
-
Unpair
-
- Disconnect from WhatsApp; keep the account so you can re-pair later
-
-
+
+
+
-
-
-
-
+
+
Unpair
+
+ Disconnect from WhatsApp; keep the account so you can re-pair later
+
+
+
+
+
+
@@ -178,27 +182,27 @@ export default async function AccountDetailPage({ params }: AccountDetailPagePro
{/* Delete — entire card opens the confirm dialog */}
-
-
-
-
-
-
-
-
-
Delete Account
-
- Remove the account and all its reminders, groups, and history
-
-
+
+
+
+
-
-
-
-
+
+
Delete Account
+
+ Remove the account and all its reminders, groups, and history
+
+
+
+
+
+
diff --git a/apps/web/src/components/accounts-list-view.test.tsx b/apps/web/src/components/accounts-list-view.test.tsx
index c1aac06..a3174f4 100644
--- a/apps/web/src/components/accounts-list-view.test.tsx
+++ b/apps/web/src/components/accounts-list-view.test.tsx
@@ -109,17 +109,20 @@ describe("AccountsListView", () => {
expect(html).toMatch(/MyBiz<\/strong>/);
});
- it("delete card is a button with the destructive aria-label", () => {
+ it("delete card is a focusable trigger element with the destructive aria-label", () => {
const html = renderToStaticMarkup(
,
);
- // Order of attributes in the rendered isn't guaranteed, so
- // assert each one exists and that they're on a button element.
- expect(html).toMatch(/]*\baria-label="Delete Sales"/);
- expect(html).toMatch(/]*\bdata-testid="account-delete-card"/);
+ // The trigger is a Card (rendered as ) acting as a button via
+ // role+tabIndex. Wrapping a
in a real
would be
+ // invalid HTML and trigger a hydration mismatch.
+ expect(html).toMatch(/role="button"/);
+ expect(html).toMatch(/tabIndex="0"|tabindex="0"/);
+ expect(html).toMatch(/aria-label="Delete Sales"/);
+ expect(html).toMatch(/data-testid="account-delete-card"/);
// `Delete account` heading copy lives inside the card
expect(html).toContain("Delete account");
});
diff --git a/apps/web/src/components/accounts-list-view.tsx b/apps/web/src/components/accounts-list-view.tsx
index 9a744ff..952d993 100644
--- a/apps/web/src/components/accounts-list-view.tsx
+++ b/apps/web/src/components/accounts-list-view.tsx
@@ -106,29 +106,34 @@ export function AccountsListView({ accounts, deleteFormAction }: AccountsListVie
- {/* Dedicated Delete card — entire card is the dialog trigger. */}
+ {/* Dedicated Delete card — entire card is the dialog trigger.
+ We avoid wrapping the Card (a ) in a
because
+ div-inside-button is invalid HTML and the browser auto-
+ closes the button, breaking SSR hydration. Radix's
+ DialogTrigger asChild forwards the click handler to the
+ Card element directly; role="button"+tabIndex makes it
+ keyboard-focusable. */}
-
-
-
-
-
-
-
-
Delete account
-
- Remove {account.label} and its reminders & groups
-
-
-
-
-
+
+
+
+
+
+
Delete account
+
+ Remove {account.label} and its reminders & groups
+
+
+
+