hoangsonww/SymptomSync-Health-App

Reliability Epic: Reminders v2 - Web Push + Service Worker + Offline Sync

Offen

#27 geöffnet am 24.08.2025

 (0 Kommentare) (1 Reaktion) (2 zugewiesene Personen)TypeScript (8 Forks)auto 404
bugdocumentationduplicateenhancementgood first issuehelp wantedquestion

Repository-Metriken

Stars
 (30 Sterne)
PR-Merge-Metriken
 (PR-Metriken ausstehend)

Beschreibung

🔎 Summary

Current reminders may be missed when the tab is unfocused or connectivity is flaky. This epic delivers reliable, OS-level notifications using Web Push + Service Worker, adds offline-first data entry with background sync, and introduces snooze / DND / timezone-smart scheduling—so medication & appointment alerts fire on time, even if the browser is closed.


💡 Goals

  • Deliver push notifications that fire when the app isn’t open (desktop + mobile browsers).
  • Support offline create/edit of meds/appointments/logs and sync later.
  • Add Snooze (e.g., +10m, +30m, +2h), Do-Not-Disturb windows, and travel/timezone awareness.
  • Maintain a single source of truth in Postgres; clients are resilient with optimistic updates + retry queues.

✅ Acceptance Criteria

  • Registration & Permissions

    • Users can opt-in to notifications; permission state is reflected in Settings → Notifications.
    • Each device registers a Web Push subscription (VAPID) and is listed under “My Devices”.
  • Push Delivery

    • Medication/appointment alerts fire via OS notification even if all SymptomSync tabs are closed.
    • Action buttons: Mark Taken / Snooze / Dismiss update the backend within ≤ 5s (online) or queue offline.
  • Offline-First

    • Creating/editing meds, appointments, and logs works offline; changes sync automatically when back online.
    • Background Sync retries until success; local UI shows “Pending sync” status.
  • Scheduling

    • Reminders respect user timezone and DST moves; when timezone changes, next events re-computed.
    • DND window (e.g., 22:00–07:00) defers alerts until the window ends, with a single consolidated reminder.
  • Settings

    • Per-type toggles: Meds / Appointments / Logs.
    • Global volume: All / Critical-only / None.
    • Snooze presets configurable.
  • Observability

    • Dashboard (admin-only): send success %, delivery latency p50/p95, opt-in rate, offline queue depth.

🧱 Architecture / Approach

1) Service Worker (frontend/web)

  • sw.js:

    • Handles pushshowNotification() with actions (taken, snooze, dismiss).
    • Handles notificationclick → focus/open client; postMessage action → page; fallback to fetch to API.
    • Registers Background Sync: sync-reminder-queue.

2) Web Push (Supabase Edge Functions or Next.js API route)

  • Use VAPID keys (VAPID_PUBLIC_KEY, VAPID_PRIVATE_KEY) stored in Supabase secrets.

  • Table user_push_subscriptions stores { user_id, endpoint, p256dh, auth, ua, last_seen_at }.

  • Reminders scanner job (Supabase cron, every minute):

    • Query due reminders (meds/appointments) honoring timezone & DND.
    • Send push to each active device; record send status in notification_events.

3) Offline Queue (IndexedDB)

  • idb store for pending mutations (create/update meds/appts/logs, reminder actions).
  • On regain connectivity or Background Sync, flush queue → Supabase (REST/RPC).

4) Timezone & DND

  • Persist user_settings: timezone, dnd_start, dnd_end, snooze_presets.
  • On client TZ change, call RPC to recompute next_fire_at for upcoming schedules.

🗄 Database (Supabase / Postgres)

  • user_push_subscriptions

    • id uuid pk, user_id uuid fk, endpoint text unique, p256dh text, auth text, ua text, created_at, last_seen_at
    • Indexes: (user_id), (last_seen_at desc)
  • notification_events

    • id uuid pk, user_id, entity_type enum('med','appt'), entity_id uuid, scheduled_at timestamptz, sent_at timestamptz, status enum('queued','sent','failed'), error text?
    • Indexes: (user_id, scheduled_at), (status)
  • user_settings (extend if exists)

    • timezone text, dnd_start time, dnd_end time, snooze_presets int[] default {10,30,120}, notify_meds bool, notify_appts bool, notify_logs bool

RLS: row ownership by auth.uid(); separate service role for cron/edge function.


🔌 APIs / Edge Functions

  • POST /api/notifications/subscribe → save subscription (upsert by endpoint).

  • POST /api/notifications/unsubscribe → delete by endpoint.

  • POST /api/notifications/action{type:'taken'|'snooze'|'dismiss', entityType, entityId, context}

    • snooze shifts next_fire_at by preset; taken writes adherence event + schedules next dose.
  • POST /api/admin/notifications/test (admin only) → ping selected user/device.

  • CRON function reminders_dispatcher: scans due items; sends via web-push.


🖥 Frontend (Next.js + Shadcn + Tailwind)

  • Settings → Notifications

    • Permission state badge; toggle per type; DND editor; Snooze presets (chips).
    • “My Devices” list with last seen date; revoke device.
  • Medication card & Appointment row

    • “Enable reminders” switch; “Snooze” quick actions.
    • Status chips: Due, Snoozed until 10:30, Done.
  • Service Worker utils

    • registerSW(), subscribePush(), queueMutation(), flushQueue(); toasts for offline/queued/synced.

🔐 Security

  • Validate subscription ownership; rate-limit subscribe/unsubscribe.
  • Sign reminder actions with session/JWT; verify on API/edge function.
  • Sanitize notification payloads (no user-generated HTML).

📊 Observability

  • Log per-send results to notification_events; aggregate for KPIs.
  • Metrics tiles (admin): opt-in %, delivery p95, fail % by UA, avg queue flush latency.

🧪 Testing

  • Unit: SW notification action handlers; timezone rollovers; DND deferral; snooze math.
  • Integration: Subscribe/unsubscribe flow; reminder scan → push → action update lifecycle.
  • E2E (Playwright): Permissions prompt, offline edits, background sync, action buttons in notifications (where supported).
  • Load: 10k reminders/min scan stays < 500ms DB time; push queue throughput sustained.

🚀 Rollout Plan

  1. Phase 1 (behind flag): subscription UI + test push + settings page.
  2. Phase 2: meds only; snooze + taken actions; offline queue for meds.
  3. Phase 3: appointments + DND; background sync for all entities.
  4. Phase 4: admin metrics dashboard; gradual default-on.

⚠️ Risks & Mitigations

  • iOS Web Push limitations: support iOS 16.4+ only; show “Add to Home Screen” CTA if needed; fall back to in-app toasts.
  • User fatigue: DND by default (22:00–07:00), daily digest option, easy per-type toggles.
  • Rate limits: batch sends; exponential backoff; dead-letter log (failed) for replays.

📋 Tasks

  • DB: create user_push_subscriptions, notification_events; extend user_settings; RLS
  • Backend: subscribe/unsubscribe/action routes (or Supabase Edge functions)
  • CRON: reminders_dispatcher job; batching + retries
  • Frontend: Settings UI; devices list; permission gating
  • SW: push handler, actions, background sync, IDB queue
  • Meds/Appts UIs: snooze/taken/dismiss; status chips
  • Analytics: metrics aggregation + admin tiles
  • Docs: README “Notifications & Offline” section; env vars; VAPID keygen steps
  • Tests: unit/integration/E2E + load scripts
  • Rollout: feature flag + progressive enablement

Env vars (new):

  • VAPID_PUBLIC_KEY, VAPID_PRIVATE_KEY
  • REMINDER_SCAN_CRON="* * * * *" (or Supabase schedule)
  • PUSH_BATCH_SIZE=100, PUSH_MAX_RETRIES=3

This epic removes “missed reminder” edge cases, makes SymptomSync truly set-and-forget, and lays a solid PWA foundation for future features (adherence streaks, caregiver sharing, and refill nudges).

Contributor Guide