yiekheng ab547c7b34 fix(reminder-edit): preserve message stack across all section forms; UI cleanup
Several user-reported bugs and UX nits fixed in one cut:

1. Editing account / when / groups silently dropped messages 2..N
   --------------------------------------------------------------
   Symptom: a reminder with 3 message parts came back with 1 after
   the user edited any section other than the message itself.

   Cause: the three section forms were still on the legacy
   {text, mediaId, caption} prop shape. The parent pages pulled only
   messages[0] from the DB, reduced it to those three fields, and
   the form posted them through to updateReminderAction. The action
   then folded the legacy fields into a single MessagePart and
   replaced the whole reminder_messages row set — wiping parts 2..N
   even though the user only meant to change the schedule.

   Fix: each form (edit-account / edit-when / edit-groups) now takes
   the full `messages: MessagePart[]` and forwards it unchanged. The
   three parent pages load the full stack (sorted by position) and
   pass it through.

   Test: new edit-section-forms.test.tsx asserts a 3-part stack
   reaches updateReminderAction intact for both the account-form and
   groups-form code paths, plus a sanity test that the legacy
   single-message payload shape (without `messages`) is what a
   future regression would look like.

2. Reminders list: removed the Group filter
   --------------------------------------------------------------
   Per request — Account + Search already cover the use cases the
   Group filter was supposed to. Search even matches group names
   directly, so the dropdown was redundant. Page no longer fetches
   the groups table for its filter bar at all.

3. Mobile chrome: bottom nav → top header w/ menu drawer
   --------------------------------------------------------------
   Removed the bottom tab bar. Mobile now has a single-row top
   header:

       ┌──┐                          ┌────┐
       │cm│   <current page title>   │menu│
       └──┘                          └────┘

   - Brand mark on the left links home.
   - Current page title sits in the middle so the user always knows
     where they are.
   - Menu icon on the right opens a right-side Sheet (radix Dialog)
     containing the full nav list. Active item highlighted; the
     drawer auto-closes when a nav item is clicked (effect on the
     pathname change).
   - Theme toggle stays only in the desktop sidebar footer per the
     follow-up ask.

   Main content padding adjusted: pt-16 (mobile) for the h-14
   header, no bottom padding now.

4. Cleaned up the now-unused legacy props
   --------------------------------------------------------------
   `text` / `mediaId` / `caption` removed from the three section
   form prop types. The wizard's URL-state pass-through still
   accepts the legacy fields and folds them into the new
   `messages` shape on entry, so old bookmarked /reminders/new
   URLs still work.

194 passing web tests (was 194; net 0 — the new edit-section-forms
tests replaced coverage we lost when the legacy props went away).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 12:47:38 +08:00
2026-05-09 17:10:48 +08:00

cm WhatsApp Reminder Bot

Self-hosted WhatsApp reminder bot. Pairs multiple WhatsApp accounts via Telegram-delivered QR codes and sends scheduled reminders to groups.

Status

Plan 1 complete. Foundation, DB schema, and Telegram-driven WhatsApp pairing are working end-to-end. Reminder scheduling, the web dashboard, and production deploy are upcoming plans (docs/superpowers/plans/).

What's working today:

  • Single-operator Telegram bot with a whitelist + audit log of every command.
  • BotFather-style menu navigation: /menu opens a single message that edits in place as you navigate.
  • Pair a new WhatsApp account with /menu📡 Pair New → reply with a label. QR is delivered to Telegram and refreshed in place as it expires.
  • Browse paired accounts with 📒 Accounts. Tap an account → see groups, send a test text message, or unpair.
  • Group sync runs at pairing and on every Baileys groups.upsert / groups.update event, plus a manual 🔄 Refresh button. Removed groups are pruned automatically.
  • Auto-reconnect on transient drops; restart-survival via Baileys useMultiFileAuthState (no QR rescan needed across container restarts as long as WhatsApp hasn't logged the device out).

Host requirements

Only Docker. No host Node, pnpm, or any other language toolchain — everything runs in containers via the long-lived tools service.

Architecture in one paragraph

Two app containers and one external dependency. bot (Node.js) holds the live Baileys WhatsApp sessions, the grammy Telegram bot, and (in plan 2) a pg-boss scheduler. web (Next.js, plan 3) is stateless UI + API. tools is a long-running Node 22 + pnpm sidecar used for installs/tests/typechecks/migrations so the host doesn't need a Node toolchain. Postgres lives external at 192.168.0.210 in a wabot database. All cross-service communication goes through Postgres (LISTEN/NOTIFY for events, table writes for state).

Full design spec: docs/superpowers/specs/2026-05-03-whatsapp-bot-design.md

Quick start (dev)

Prerequisites: Docker, the wabot database + waBot role on 192.168.0.210 (with a pg_hba.conf line permitting 192.168.0.0/24), and a Telegram bot token from @BotFather.

# 1. Configure env
cp envs/.env.example .env.development
# edit .env.development: real DATABASE_URL, TELEGRAM_BOT_TOKEN, your TG user ID
scripts/gen_auth_secret.sh --write

# 2. Bring up the tools container, install deps
NO_SUDO=1 scripts/dev.sh up
NO_SUDO=1 scripts/dev.sh pnpm install

# 3. Apply migrations and seed your operator row
NO_SUDO=1 scripts/db.sh migrate
NO_SUDO=1 scripts/db.sh seed

# 4. Watch the bot service
NO_SUDO=1 scripts/dev.sh logs bot

In Telegram, message your dev bot /menu, tap 📡 Pair New, reply with a label, scan the QR.

NO_SUDO=1 is the right setting if your user is in the docker group (the default for this repo). Drop it if you need sudo docker.

Layout

  • apps/bot/ — Node service: Baileys WhatsApp + grammy Telegram + (later) pg-boss scheduler
  • apps/web/ — Next.js dashboard (plan 3)
  • packages/db/ — Drizzle schema and migrations
  • packages/shared/ — cross-app helpers (rrule, media paths, timezones)
  • docs/superpowers/specs/ — design specs and manual test runbooks
  • docs/superpowers/plans/ — implementation plans
  • docker/ — Dockerfiles (tools.Dockerfile, bot.Dockerfile, web.Dockerfile placeholder)
  • scripts/dev.sh, db.sh, gen_auth_secret.sh, plus stubs for plans 2/4

Scripts

All pnpm/tsx/drizzle-kit invocations run inside the tools container, so no host Node is needed.

Script Purpose
scripts/dev.sh up|down|logs|status|build|exec|pnpm|shell|restart-bot Stack lifecycle and tools-container shell
scripts/db.sh migrate|generate|studio|seed|reset Drizzle migration helper
scripts/gen_auth_secret.sh [--write] Generate AUTH_SECRET (host-only, no Node needed)
scripts/publish.sh Push to Gitea registry — implemented in plan 4
scripts/link-account.sh CLI pairing without Telegram — implemented in plan 2

Set NO_SUDO=1 if your user is in the docker group (recommended).

Next plan

docs/superpowers/plans/<next-date>-reminder-scheduling.md — pg-boss, reminder CRUD via Telegram, fire-reminder handler, sender (text/image/video), retry policy, run history.

Description
No description provided
Readme 1.5 MiB
Languages
TypeScript 97.8%
Shell 1.2%
Dockerfile 0.5%
CSS 0.5%