5 Commits

Author SHA1 Message Date
234e8aa690 fix(web,bot): drop next-themes, extend QR validity, fix retry CTA
next-themes hydration mismatch
- Removed the next-themes wrapper, ThemeProvider component, and the
  Settings appearance card — there's no theme-toggle UI anywhere in
  the app, so the library was just adding a pre-hydration `<script>`
  that triggered React 19's "script tag while rendering" warning and
  the `<html>` class swap caused the hydration mismatch.
- Sonner Toaster now uses a fixed `theme="light"` instead of useTheme.
- Layout drops `suppressHydrationWarning` on `<html>` since we no
  longer mutate it on mount.

QR refs exhausted before the user could scan
- Pass `qrTimeout: 60_000` to makeWASocket so each QR (first AND
  subsequent) lasts a full minute. Default was 60 s for the first and
  20 s for each subsequent → ~6 refs × default = ~2.5 min before
  Baileys gave up. With 60 s flat, the user has the full ~5 min
  window matching pair-handler's PAIR_TIMEOUT_MS.

Pairing-timed-out screen
- "Try again" used to link to /accounts/new (creates a new account
  instead of re-pairing the existing one). Link now points to the
  existing /accounts/[id] detail page where the operator can hit
  Re-pair.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 08:57:13 +08:00
7b4f0d0b84 fix(web): pairing-window timer, reminder filter tabs
QR / pairing
- Replace the per-QR 30 s countdown with a single pairing-window timer
  matching the bot's PAIR_TIMEOUT (5 minutes). Baileys naturally rotates
  QR images every ~5 s — the previous 30 s bar reset on every rotation,
  which felt like a constantly-cycling timer to the user.
- The new timer starts on the first QR and ticks down once; later QR
  rotations refresh the displayed image but leave the countdown alone.
- Added a hint: "The QR rotates automatically every few seconds — scan
  whichever one is showing." Format switches to MM:SS.
- countdownRender's danger threshold scales: 10 s for short windows
  (≤ 60 s), 30 s for the multi-minute pairing window, so the warning
  flash appears while the user can still react.

Reminder filter tabs
- Tabs are now: All / Active / Ended / Paused. "Failed" is dropped —
  reminder.status doesn't carry "failed" (run statuses do; that view
  belongs in /activity?filter=failed).

Tests (+4 = 84 passing total)
- qr-dedupe.test.ts: extended with a "pairing-window scaling" suite
  covering pct/danger/expired at 5-minute scale and the threshold split
  between short and long windows.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 08:36:26 +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
2b738383e4 feat: recurring reminders, fix QR pairing, account UX polish, tests
Reminders
- Add recurrence to wizard step 3 (None / Daily / Weekly+weekday picker /
  Monthly / Yearly). Build the RRULE client-side and thread it through
  the wizard URL state.
- Action stores rrule + scheduleKind="recurring" on insert.
- Bot reschedules the next occurrence after firing a recurring reminder
  using the existing rrule helpers in @cmbot/shared. One-off behavior
  unchanged.
- Add reminders.last_fired_at column to track last fire.

Pairing
- Move QR PNG out of the pg_notify payload (the 8000-byte limit was
  silently truncating it; QR never reached the web → "QR hang"). PNG
  now lives on whatsapp_accounts.last_qr_png; NOTIFY just signals
  {type: session.qr, accountId, ts}. Web fetches the bytes from a new
  read-only /api/qr/[accountId] route (allowed via middleware).
- handleStartPairing now stops any in-flight session before starting a
  fresh one — fixes Re-pair where session.start was a silent no-op and
  Baileys never re-emitted QR.
- Pair-live: countdown moved out from over the QR (it was overlapping
  the scan area); shown as a discrete progress bar above the QR.
- Add a "Save QR" download button.

Account detail page
- Pair / Unpair / Delete cards are themselves the trigger (form submit
  or DialogTrigger) — no inline buttons, whole card is clickable.
- Sync Groups Now card removed earlier; bot already auto-syncs.

Account list page
- Cards are the link target. A small floating Delete trigger (top-right
  trash icon) opens the destructive confirm dialog without blocking
  navigation on the rest of the card.

Tests
- recurrence.test.ts: 10 tests for buildRrule / kindFromRrule /
  describeRecurrence (incl. weekly day combos and BYDAY ordering).
- reminders.schema.test.ts: regression for the "Invalid datetime" bug —
  proves strict Zod .datetime() rejected luxon's offset ISO and the
  { offset: true } option accepts both forms.

Migration: 0004_next_prowler.sql
- whatsapp_accounts.last_qr_png (text)
- reminders.last_fired_at (timestamptz)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 01:01:31 +08:00
68b46f8d71 feat(web): pair / unpair / sync server actions + live QR page 2026-05-09 23:42:16 +08:00