2 Commits

Author SHA1 Message Date
c8199f0bbf fix(web): switch dialog cards to transparent <button> overlay; add test guards
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>
2026-05-10 09:22:30 +08:00
6cb387bf59 feat: per-section reminder edit, activity tab, more tests
Per-section reminder editing
- Replace the wizard-redirect edit shell with four focused single-form
  pages: /reminders/[id]/edit/{account,message,when,groups}.
- Each click on a section card on the detail page goes straight to the
  matching focused editor — no stepper, no other sections, no
  wizard chrome. Save returns to the detail page.
- New form components live under components/reminder-edit/:
  EditMessageForm, EditWhenForm (full recurrence builder reused),
  EditGroupsForm, EditAccountForm. All submit via updateReminderAction
  with the existing values for untouched fields. Switching account
  clears group targets (groups are scoped per account; the form warns
  and the user re-picks groups afterwards).

Activity tab
- New "Activity" item in the bottom nav + sidebar (between Reminders
  and Settings).
- /activity page: full run history (last 200), filter tabs (All /
  Success / Partial / Failed / Skipped), clickable rows that open the
  underlying reminder, and a Clear history dialog. Mirrors the
  dashboard's Recent Activity widget but with deeper data and its own
  empty-state messaging.

Tests (+20 — 80 passing total)
- qr-dedupe.test.ts: 14 tests covering the makeQrDedupe factory (per-
  account, fresh QRs always emit, reset/scope) and countdownRender
  (the QR-expired timer logic — danger threshold, expired flag,
  clamping). The dedupe + countdown logic is now used by pair-handler
  and pair-live.
- reminder-edit/edit-message-form.test.tsx: 6 tests verifying the form
  pre-fills, hides/shows the caption based on attachment, renders the
  Save (not "Schedule reminder") action, and the action receives the
  expected payload shape for both text-only and media-attached paths.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 01:35:17 +08:00