NUTRISYNCBuilders Hub
🏠 🛠
NUTRISYNC · Docs

NutriSync — Technical Architecture

0. As-built snapshot (19 Jul 2026) — read this first

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_dayns_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/appdeveas 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.


1. The stack today (and why)

Three surfaces, one backend

NutriSync runs three surfaces against a single Supabase backend:

  1. Native mobile apps (iOS + Android) — React Native on Expo SDK 54 (React Native 0.81, React 19.1). Requires Node ≥ 20.19.4.
  2. Web application — the Claude Design compiled UI bundle, genuinely stitched to the same Supabase backend, deployed on Cloudflare Pages.
  3. Marketing site — the same Cloudflare Pages deployment, served at the site root.

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.

Backend — Supabase (Phase 1)

Supabase (managed Postgres + Auth + Row-Level Security + Edge Functions), hosted in the EU region. Project ref nebkqncvapelrarruyqb (https://nebkqncvapelrarruyqb.supabase.co).

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 CAS engine — the core IP

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.

Phase model

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.

2. High-level system diagram (today)

   ┌──────────────┐  ┌──────────────┐  ┌──────────────┐  ┌──────────────────┐
   │  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)

3. Data model

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.

4. Web application — how it is wired

The web app is the Claude Design compiled bundle, genuinely stitched to Supabase:

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.

5. CI/CD — both tracks live

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.

6. Remote testing

7. Repository structure

There is no monorepo. NutriSync is two repositories, each with its own auto-deploy pipeline:

8. Privacy & compliance (build-time requirements)

9. Future architecture — AWS scale-up target (planned, NOT built)

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.

9.1 Target architecture once migrated

                    ┌──────────────────────────────────────────────────┐
  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)    │
                    └──────────────────────────────────────────────────┘

9.2 The migration contract — what changes and what doesn't

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.

10. Build sequence — done vs pending

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)