Previously the name field auto-derived from the first text part when
the operator left it blank. That's brittle once reminders carry
multiple parts of varying provenance, and confusing in lists where
"Reminder" or partial sentences crowd in.
Now: every reminder must carry a non-empty name, capped at 60 chars.
- Zod schema on createReminder/updateReminder: name moves from
`z.string().nullable().optional()` to
`z.string().trim().min(1, "Give the reminder a name").max(60)`.
Stale-URL legacy callers that omit it now get a clear server error.
- Wizard compose step: input has `required` + `aria-required`,
placeholder + label simplified ("(optional)" tag and the helper
paragraph removed), Continue blocks on empty.
- Edit-message form: same — required, aria-required, save blocked
on empty, the "leave blank and we'll auto-derive" hint dropped.
- Review-submit client: defensive fail-fast for stale-bookmark URLs
that arrive at step 5 without a name — bounces back with
"Give the reminder a name (back on the Message step)" instead of
letting the server reject.
The resolveReminderName helper stays put — duplicateReminderAction
and any future caller still benefit from the trim+clamp+fallback
chain. Helper unit tests unaffected (they test the resolver in
isolation, the policy-tightening lives at the schema layer above).
298 web tests still passing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Records the design decisions for the next planned work:
- Per-reminder delivery window (default 6am–6pm, operator timezone).
Window-close hard-stops the run; remaining targets become
skipped; status reports as partial with a clear "this account is
at capacity, consider another paired account" message.
- Per-account isolation via pg-boss teamSize ≥ N + an in-process
PerKeyMutex keyed by accountId. Different accounts run in
parallel; the same account serialises (no double-rate sends
that would risk a ban).
- Per-account token-bucket rate limiter (default 40 msg/min,
BOT_MAX_SEND_PER_MINUTE).
- Up-front media-upload cache via prepareWAMessageMedia: 1000
groups × 5 MB upload turns into 5 MB. Biggest single win for
text+picture reminders.
- Bounded group concurrency (default 3 in-flight per account);
parts-within-a-group stay serial for visible message order.
- Pre-fetched DB Maps (groups / messages / media), no inner-loop
round-trips.
- Replaces the rigid 1.5 s inter-part sleep with 200–500 ms
jitter; the per-account rate-limiter is the real gate.
Out of scope for v1 (documented under "v2 candidates"): cross-day
window resume, mid-restart resumability, multi-account auto-split,
adaptive rate-limit back-off, pause/resume mid-run.
Acceptance: 1000-group reminder + one image, established account
finishes in ~30–50 minutes, well inside a 6am–6pm window. Two
reminders on different accounts at the same wall-clock minute
both progress in parallel.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
13 sections covering every flow that unit tests can't:
1. Smoke (mobile header, sidebar, page titles, tab strips)
2. Account pairing (live QR, sync groups, send-test)
3. Re-pair / unpair (status flips, no spurious reconnect)
4. Reminders — single-message one-off (5-step wizard)
5. Reminders — multi-message stack with media swap
6. Reminders — recurrence picker (Daily / Weekly / Monthly /
Yearly + multi-rule, descriptions are sentences not raw cron)
7. Reminders — edit each section preserves the message stack
(regression guard for the parts-2..N drop bug)
8. Reminders list — swipe + lifecycle (no reshuffle on Pause /
Restart, shelf collapses after action)
9. Activity — swipe archive / delete + Archived tab + restore
10. Send-test feedback round trip (Sending… → Sent ✓)
11. Media upload limits (size caps, HEIC/MOV → document fallback)
12. PWA install (standalone display, offline shell)
13. Theme toggle (desktop only)
Captures the policy decisions that aren't immediately obvious from
the code:
- browser-extension hydration warnings are expected, app code is
not at fault — Incognito clears them
- HEIC/MOV uploads are NOT rejected; they fall back to document
delivery
- cron rules render as descriptive sentences in the UI
- status changes don't reorder the reminders list
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After live-testing the Telegram bot we hit limits that don't go away with
more menu polish (Markdown fragility, callback_data limits, no native
date pickers, awkward media UX). Pivot to a Next.js PWA installable on
the operator's phone; remove Telegram entirely.
Spec covers: service topology with bot codebase shrunk, no-auth access
stance with rate limiting + reverse-proxy gating, Server Actions
replacing public REST mutation endpoints, SSE for live updates, the new
web-side pair flow with live QR display, multi-step reminder wizard
backed by URL state, mobile-first shadcn/ui visual layer, PWA service
worker via @serwist/next, and a step-by-step plan to delete the existing
Telegram code first.
Inherits all confirmed values from the 2026-05-03 master spec.
Captures the validated design from the brainstorming session: two-service
topology (Next.js web + Node bot) communicating via Postgres LISTEN/NOTIFY,
Baileys for WhatsApp, grammy for Telegram, pg-boss for scheduling, Drizzle
for the data model, and Docker/Gitea-registry deploy flow.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>