Built the full Phase-2 daily loop against the live free Supabase project zhcxygxjvcatvdodovtd, continuing straight from Phase 1.
0003_phase2_daily_loop.sql (applied live, 0 security-advisor lints): check_ins (one/day, upsert; mood+energy 1–5, sleep, symptoms/benefits as jsonb), reminders (time + tz_offset + weekday mask + snooze + last_fired_on), push_subscriptions. Owner-only RLS on all three.pages/CheckIn.tsx: mood + energy 1–5 scales, sleep hours, add symptom/benefit entries with severity, notes. Upserts per day.lib/ring.ts + Dashboard: one headline 0–100 number from today's check-in (Oura pattern), conic-gradient ring, "Daily check-in / Update check-in" CTA.lib/cycle.ts (pure on/off-week math) + pages/Cycles.tsx: shows each compound's phase today (ON/OFF/pending), days left, day-of-cycle; edit on/off weeks + start date.pages/Reminders.tsx: per-reminder label/time/weekdays/compound, mute, snooze-1h, delete; "Enable notifications on this device" (with honest iOS "add to Home Screen first" note). Push plumbing: public/sw.js (service worker: push + notificationclick), lib/push.ts (register SW, VAPID subscribe, save subscription).supabase/functions/send-reminders/ (deployed, verify_jwt=false, custom x-cron-secret auth). Finds reminders due now in each user's local time (5-min catch-up window), sends web push via npm:web-push, prunes dead subscriptions (404/410), stamps last_fired_on so nothing double-fires or silently misses. Has {now, dryRun} test hooks.pg_cron + pg_net enabled; job send-reminders runs every minute, reading the cron secret from Supabase Vault (not stored plaintext in the job). Doubles as the free-project keep-alive.App.tsx (checkin/cycles/reminders now real; share/support remain honest Phase 3/4 stubs).CRON_SECRET, VAPID_PUBLIC_KEY, VAPID_PRIVATE_KEY, VAPID_SUBJECT. Client VITE_VAPID_PUBLIC_KEY in .env.local (gitignored).npx tsc -b clean · npm run build clean.now/dryRun hooks + one real run): no-secret→401, wrong-secret→401, correct→200; due at 09:02 inside the 5-min window, NOT at 08:59, NOT at 09:07; weekday mask (Mon-only fires Mon, not Tue); timezone offset (UTC-5 user fires at 14:02 UTC = 09:02 local, not at 09:02 UTC); snooze suppresses; real run attempted delivery to 1 device + stamped last_fired_on; same-day re-run → NOT due (no double-fire); next day → due again.cron.job_run_details shows the job already ran on its own, status = succeeded (proves the pg_cron→pg_net→function chain, not just manual calls).sent=0 in the test because the endpoint was a fake (not a real push service). Real end-to-end ("the notification appears") needs Sondra on her installed PWA — which is exactly how the PRD frames the verify. On iPhone: Add to Home Screen first, then Enable notifications, set a reminder a couple minutes out, confirm it buzzes.Stripe money spine + premium gates (free = 1 compound), half-life curves, named stacks/protocols, descriptive insight cards, clinician share links, admin console with multi-select bulk delete on the user list (§5.4 #3). Then Phase 4 (landing, legal, support form, empty-state/microcopy polish, Lighthouse gate, /app-safety-check, domain cutover).
types.ts hand-written; regenerate from live DB at launch.Generated for the CEO Dashboard · source: PROJECTS/Peptide Pal/handoffs/v2-phase2-daily-loop-2026-07-07.md 🤍