3 Commits

Author SHA1 Message Date
8ca7ebdd5b feat(web): drop Delete card from accounts overview
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 `<a>` 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) <noreply@anthropic.com>
2026-05-10 09:30:11 +08:00
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
17f9ee179f feat(db,web): pg_trgm + indexes + Postgres-backed cache and rate-limit
- Add cacheEntries and rateLimitBuckets tables to schema
- Generate migration 0002_left_jimmy_woo.sql with pg_trgm extension and all indexes
- Implement cache.ts (get/set/delete/getOrSet/sweep) backed by Postgres
- Implement rate-limit.ts (sliding-window UPSERT) backed by Postgres
- Implement search.ts (trigramMatch / trigramRank helpers)
- Add vitest 2.1.9 + vitest.config.ts; 7 unit tests pass (4 cache + 3 rate-limit)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 23:03:10 +08:00