Sections below remain for history; this is the system as it runs today.
┌────────────────────────────────────────────┐
users ───────────► │ CLOUDFLARE (edge) │
│ DNS · SSL · www→apex 301 · cache rules │
│ ┌──────────────┐ ┌──────────────────┐ │
│ │ Pages: │ │ Pages: │ │
│ │ nutrisync- │ │ nutrisync-m │ │
│ │ collective │ │ (mobile PWA) │ │
│ │ web+app+hub │ │ m.nutrisync…com │ │
│ └──────┬───────┘ └────────┬─────────┘ │
│ │ Zero Trust Access │ │
│ │ guards /hub/* │ │
└─────────┼─────────────────────┼────────────┘
iOS app ──────┐ │ │
Android app ──┤ (EAS OTA) ▼ ▼
└──────────► ┌────────────────────────────┐
│ SUPABASE (EU) │
│ Postgres + RLS · Auth │
│ (email verification ON) │
│ RPCs: ns_daily_recs, │
│ waitlist_count, admin_*, │
│ admin_feedback │
└────────────────────────────┘
Four surfaces, one backend. Web app + marketing + gated hub (nutrisynccollective.com), installable mobile PWA (m. — the RN app compiled via react-native-web), and the native iOS/Android apps (Expo SDK 54, updated OTA via EAS, branch production). All speak to the same Supabase project with the public anon key; RLS is the security boundary (each user reads only her rows; service_role never ships to clients).
The PWA is also the development & testing interface. m.nutrisynccollective.com plays a dual role. As a product surface it is the installable mobile web app. As the engineering/testing channel it is how founders test every app release today, without the stores: same RN source compiled via react-native-web, so parity is by construction; deploys land in ~2 minutes with no store review; installable to the home screen on any phone (iPhone included); and it fully substitutes TestFlight while the Apple Developer enrollment is pending — only native-module features (push, HealthKit/Health Connect) wait for real device builds. Release discipline: after every app OTA, re-run Build NutriSync PWA so the testing surface matches the shipped app version.
Data layer (main tables): users (profile, diet/diet_other, conditions, nutrigoal, nutri_avatar) · cycles · daily_logs (mood/energy/flow/symptoms/sleep/libido/sex_logged/notes) · daily_scores (CAS + 5 components, phase, cycle_day) · checklist tables · content DB content_food (153) / content_movement (48) / content_tip (100) with segment/diet/condition axes · waitlist · feedback (in-app feedback, RLS insert-own) · admins (allowlist for aggregate-only admin RPCs — no PII ever returned).
Recommendation engine (SQL, server-side): ns_segment_for_day → ns_daily_recs(date) returns one JSONB with the day's nutrition/movement tips, insights and candidate lists, filtered by segment→diet→condition and ranked (+goal/+condition boosts); deterministic daily rotation.
Auth flow: signup accepts any email but the account only activates via the verification link (Supabase Confirm-email ON; app handles resend + unconfirmed logins). Forgot-password → reset.html on the custom domain.
Deploy pipeline (3 Spotlight commands, from the working folder): web zip → repos/webdeploy → git push → Pages build; app zip → repos/appdev → eas update (OTA, minutes to phones); PWA = re-export of the app source → wrangler pages deploy. Every deployed artifact auto-archives to releases/ (rollback points); Cloudflare/EAS retain every deployment server-side too. Full ops detail: doc 18; corporate ownership plan: doc 19.
Reality (July 2026): iOS, Android, and a live web application, all sharing one Supabase (EU) backend, all shipping continuously from GitHub. This document describes what is live today and separates it clearly from the AWS/Cloudflare scale-up target, which is planned, not built.
NutriSync runs three surfaces against a single Supabase backend:
Analogy: think of Supabase as a single kitchen with three service windows. The mobile apps and the web app are different windows, but they order from the same kitchen and get the same food — a user's cycle, logs, and CAS are identical wherever she signs in.
Expo removes most native build pain via EAS (Build / Submit / Update) — ideal for a lean team. Crucially, the web app is not Expo Web: it is a separately built Claude Design bundle that talks to Supabase directly. The two surfaces share a backend and a scoring engine, not a codebase.
Supabase (managed Postgres + Auth + Row-Level Security + Edge Functions), hosted in the EU region. Project ref nebkqncvapelrarruyqb (https://nebkqncvapelrarruyqb.supabase.co).
auth.uid(). Because of this, the anon/publishable key is public and safe to embed in both clients; the service_role key must never ship client-side.Ops note: for signup to complete, "Confirm email" must be turned OFF in Auth. In-app data deletion (JSON export + delete) is live; deleting the underlying auth record needs a service-role Edge Function, which is pending.
The Cycle Alignment Score is a 0–100 number with five weighted components:
| Component | Weight |
|---|---|
| C1 Phase Confidence | 15 |
| C2 Biomarkers | 25 |
| C3 Nutrition | 30 |
| C4 Fitness | 20 |
| C5 Logging | 10 |
It is implemented in src/lib/cas.ts (ported from packages/core/cas.mjs, backed by 34 tests) and recomputes in real time as the user logs, writing to daily_scores. The web app runs the same five-component CAS and writes the same table, so the score is consistent across surfaces.
Analogy: the CAS is a fuel gauge that redraws itself the instant you add fuel — check off a phase-aligned meal and the needle moves before you leave the screen.
cycleDay = today − last_period_start + 1, wrapped to cycle length. Five internal phases (menstrual, follicular, ovulatory, early_luteal, late_luteal) collapse to four display phases (Menstrual, Follicular, Ovulatory, Luteal). Phase colours: menstrual #E8472A, follicular #6B9E6B, ovulatory #D4A017, luteal #7B5EA7. Poppins throughout; brand orange #FF5509, orb gradient #FF9269 → #FF5509.
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐
│ iOS app │ │ Android app │ │ Mobile PWA │ │ Web app │
│ Expo SDK 54 │ │ Expo SDK 54 │ │ RN via │ │ Claude Design │
│ (RN 0.81) │ │ (RN 0.81) │ │ react- │ │ bundle │
│ │ │ │ │ native-web │ │ │
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘ └────────┬─────────┘
│ │ │ │
│ HTTPS (JWT, anon key) │
└─────────────────┴────────┬────────┴───────────────────┘
│
┌─────────────┴───────────────┐
│ Supabase (EU) │
│ Auth · Postgres + RLS │
│ Edge Functions │
│ 8 core + 3 content tables │
│ CAS → daily_scores │
└──────────────────────────────┘
Web app + marketing site: Cloudflare Pages (project nutrisync-collective)
Mobile PWA: Cloudflare Pages (project nutrisync-m → m.nutrisynccollective.com)
— same RN source as iOS/Android, and today's dev/testing channel (see §0)
Eight core tables plus a small content library, all under RLS (every row scoped to the signed-in user):
Core tables
- users — profile, NutriGoal, allergies, health_conditions, preferences.
- cycles — cycle data (last period start, lengths) used for phase derivation.
- daily_logs — Edit Period symptom/flow/mood logging.
- nutrition_checklist — NutriLog checklist state (feeds CAS C3).
- movement_checklist — Movement Log checklist state (feeds CAS C4).
- meal_logs — free-text meal entries.
- daily_scores — the computed CAS per day (written by the real-time recompute).
- user_phase_averages — per-phase personal baselines (populated as the user accrues cycles).
Content tables
- phase_food — phase-indexed food content.
- movement_activity — phase-indexed movement content.
- phase_tip — daily tips / body insights (indexed by cycle day, modulated by NutriGoal).
Derivation rule (the heart of the app): cycle_day = today − last_period_start + 1, wrapped to cycle length and mapped to a phase. All Home, NutriLog, Movement, and Notification content keys off this. Phase boundaries personalize as the user logs real data.
The web app is the Claude Design compiled bundle, genuinely stitched to Supabase:
esm.sh/@supabase/supabase-js@2, with the anon key baked in (public and safe).daily_scores. In-app JSON export and delete are available.#__bundler_err{display:none}; the durable fix is to strip it in the Design export.Live URLs (Cloudflare Pages, project nutrisync-collective):
- Marketing (root): https://nutrisync-collective.pages.dev
- Web app: https://nutrisync-collective.pages.dev/app.html
- Gated hub (investor/builder docs): footer Team access, code 123456.
The Cloudflare Access email whitelist was removed, so the site is public; it can be re-added via Zero Trust → Access. Two stale artifacts sit unused in the account — an old nutrisync-web.pages.dev project and a broken nutrisync Worker — both deletable, neither in use.
Both deploy pipelines are automated on push.
Analogy: each GitHub Action is a conveyor belt — you push code and the surface rebuilds and ships itself, no manual deploy step.
Mobile — repo githubjuanjose/nutrisync-app:
- Action .github/workflows/eas-update.yml runs eas update --branch production (OTA) on every push. Secret: EXPO_TOKEN.
- EAS project id 3b124e7e-e7e8-43ed-a54c-b660a07109dc; app.json updates.url = https://u.expo.dev/3b124e7e-e7e8-43ed-a54c-b660a07109dc; runtimeVersion policy appVersion.
- Installed builds receive OTA updates. Expo Go does not.
Web — repo githubjuanjose/nutrisync-web:
- Action .github/workflows/deploy-web.yml runs wrangler pages deploy publish --project-name=nutrisync-collective on every push to main.
- Secrets: CLOUDFLARE_API_TOKEN (a Custom token: Account · Cloudflare Pages · Edit — the old "Edit Cloudflare Pages" template no longer exists) and CLOUDFLARE_ACCOUNT_ID.
- This deliberately uses the Action → wrangler direct-upload path, not Cloudflare's built-in "Workers Builds" Git integration, which hung.
eas build --profile preview --platform android (internal distribution + buildType: apk). No Apple or Google paid account needed. Produces a shareable download link/QR; once installed, GitHub pushes reach testers OTA.eas build --platform ios --profile production → eas submit --platform ios --latest → TestFlight (External = one-time Beta App Review). Bundle id com.nutrisync.app; export compliance pre-answered (ios.config.usesNonExemptEncryption = false).npx expo start, or --tunnel for remote (ngrok — flaky). Expo Go does not receive OTA updates.There is no monorepo. NutriSync is two repositories, each with its own auto-deploy pipeline:
githubjuanjose/nutrisync-app — the mobile app (Expo SDK 54) plus the OTA pipeline.githubjuanjose/nutrisync-web — the marketing site, the wired web app (a Claude Design bundle, not Expo Web), the gated hub, and the Cloudflare deploy pipeline.Phase 1 runs on Supabase (EU) by design. The migration is trigger-based, not date-based (sustained p95 degradation · Supabase plan ceilings at ~70% · cost crossover · enterprise/partner requirement), and the intermediate step — Supabase dedicated compute — is evaluated first. Full migration runbook: doc 19, Appendix B2.
┌──────────────────────────────────────────────────┐
users ─────────► │ CLOUDFLARE (edge — unchanged) │
(4 surfaces: │ DNS · CDN · WAF · DDoS · Zero Trust Access │
web, PWA, └───────────────────────┬──────────────────────────┘
iOS, Android) │ HTTPS only
┌───────────────────────▼──────────────────────────┐
│ AWS eu-central-2 (Zurich) │
│ ┌────────────────────────────────────────────┐ │
│ │ PUBLIC SUBNET │ │
│ │ ALB (TLS termination, health checks) │ │
│ └───────────────────┬────────────────────────┘ │
│ ┌───────────────────▼────────────────────────┐ │
│ │ PRIVATE SUBNETS (VPC) │ │
│ │ ECS Fargate services: │ │
│ │ · API layer (PostgREST / thin API) │ │
│ │ · Auth (GoTrue / Cognito adapter) │ │
│ │ · Workers (retention jobs, exports) │ │
│ │ │ │ │
│ │ Aurora PostgreSQL (Multi-AZ, KMS at rest, │ │
│ │ RLS + RPCs ported 1:1, no public endpoint)│ │
│ └────────────────────────────────────────────┘ │
│ S3 (assets, JSON exports, backups·lifecycle) │
│ Secrets Manager (rotated creds — no keys in │
│ code) · CloudWatch + OpenTelemetry (p95, │
│ error rate, alerts) · AWS Backup (cross-AZ) │
└──────────────────────────────────────────────────┘
Does NOT change: the four surfaces and their code · Cloudflare edge (DNS/CDN/Access) · the three deploy commands · the RLS security model and the RPC contracts (plain SQL, ported 1:1). Clients keep calling the same API shapes; config.ts is the single switch point.
Changes underneath: database moves into a private VPC (Aurora Multi-AZ, KMS-encrypted, reachable only from the API services — no public endpoint) · auth runs as our own service (GoTrue export → Cognito or self-hosted) · background jobs get a real worker tier · secrets rotate in Secrets Manager · observability becomes first-class (CloudWatch/OTel, alerting) · region eu-central-2 (Zurich) for Swiss/EU health-data residency.
Cutover plan: logical replication → freeze window → switch connection strings → verify → Supabase retained as rollback for 30 days. The full launch-readiness checklist (doc 19 B1) re-runs against the new stack before cutover.
| Area | Status |
|---|---|
CAS engine (cas.ts, 34 tests) + real-time recompute → daily_scores |
Done |
| Phase model + design tokens (colours, Poppins, orb) | Done |
| 8 core + 3 content tables with RLS | Done |
| All mobile screens (onboarding → tabs → Edit Period/Health → Notification Center → Settings) | Done |
| Web app stitched to Supabase (auth, routing, reads/writes, CAS, export/delete) | Done |
| Marketing site + gated hub on Cloudflare Pages | Done |
| Mobile OTA CI/CD (EAS Update) + Web CI/CD (wrangler Pages) | Done |
| Full auth-record deletion (service-role Edge Function) | Pending |
| Consent / cookie layer | Pending |
| Admin MIS/KPI console wired to live data (currently static/mock) | Pending |
| Observability / security / DR | Pending |
| Wearable connectors (native dev build) | Roadmap 2028 |
AWS/Cloudflare scale-up (Zurich eu-central-2) |
Roadmap (target only) |