The remaining "Hydration failed" error came from passing a Card (a <div>)
as the asChild target of Radix's DialogTrigger. Radix's Slot then
injects button-specific props (type="button", aria-haspopup, …) onto
the underlying <div>, and React's SSR vs client trees diverge on those
attributes.
Same overlay pattern that already worked for the Pair card now applies
to every Dialog-card-trigger in the app:
- accounts list — Delete card per row
- account detail — Unpair card
- account detail — Delete card
The visible Card stays a <div>. A real <button type="button"> with no
children sits absolutely-positioned over the card surface and is the
DialogTrigger target. Click area is identical, HTML is valid, no Radix
prop-forwarding into the wrong element type.
Also fixed: edit-account-form.tsx had the original
<button>...<Card>...</Card></button>
nesting (the new static guard caught it). Replaced with a Card that's
its own pressable region (onClick + onKeyDown + role=button on the
<div>; no nested button).
Test guards
-----------
+ src/test/no-render-warnings.test.tsx (6 tests)
Renders AccountsListView, ThemeToggle, EditMessageForm via
renderToString and asserts neither console.error nor console.warn
was invoked. Also scans the produced HTML for any <button> region
that contains a <div>/<p>/<h*> — invalid nesting that would cause
a hydration mismatch in the browser.
+ src/test/no-button-wrapping-card.test.ts (2 tests)
Walks every production .tsx file in src/ and fails if any contains
a literal `<button` (lowercase) that wraps `<Card`/`<CardContent`/
`<CardHeader`. Caught a real instance in edit-account-form.tsx that
I missed in the earlier round.
Total tests: 100.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>