/reminders list
- New ReminderFilterBar (client component, URL-driven):
* Free-text search across reminder name, first message text,
account label, and target group names. Debounced 250 ms.
* Account dropdown — filters to one paired account.
* Group dropdown — narrows to a single group; auto-scoped to the
chosen account.
* Sort dropdown — Newest first / Oldest first / Recently created /
Name A→Z. Default is `scheduled_desc`.
- Status tabs (All / Active / Ended / Paused) preserve all other
filter params when flipping, so changing tab doesn't lose context.
- Empty-state copy is filter-aware ("No reminders match your filters."
vs "No <status> reminders yet.").
- Pure helpers in `lib/reminder-filter.ts` so the same q+account+
group+status+sort logic can be unit-tested without a DB.
/reminders/[id] detail
- New ActionsBar (Pause / Restart / Delete) replaces the bare delete
button. Each card is a transparent <button> overlay over a Card
(no <button>-wrapping-Card — the static guard keeps it that way).
Confirm dialogs gate every destructive action.
- Pause: visible only when status === "active"; flips to "paused".
- Restart: visible when status is "paused" or "ended". For a
recurring reminder, computes the next occurrence from the RRULE
and re-arms pg-boss; for a one-off reminder it sets the next
fire to "now + 1 minute".
- Delete: always available (run history is preserved on /activity).
Server actions
- `pauseReminderAction(formData)` — sets status="paused" if active.
- `restartReminderAction(formData)` — recomputes next fire and
re-arms via pg_notify(`reminder.schedule`).
- The existing deleteReminderAction is reused.
`lib/queries.ts#listReminders`
- Now also returns accountId, group ids, joined group names, and the
first message text — fields the search/filter logic needs.
- Coerces SQL timestamp strings to Date objects (raw `db.execute(sql)`
hands them back as strings, which broke .getTime() in the sorter).
Tests (+22 new, 130 web tests + 26 bot tests = 156 across the repo)
- lib/reminder-filter.test.ts (16 tests):
* search hits across all four indexed fields, case-insensitive
* account / group / status filters
* every sort key, including handling of null scheduledAt
* combined AND-of-all-filters check
- app/reminders/[id]/actions-bar.test.tsx (6 tests):
* Pause card only shown for `active`
* Restart card only shown for `paused` / `ended`
* Delete card always rendered
* Restart description differs for recurring vs one-off
* every confirm dialog carries the matching `reminderId` hidden input
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>