A personalized health management tool embedded in a Shopify storefront. Users input their health metrics (body measurements, blood tests) and receive real-time personalized suggestions to discuss with their healthcare provider. Health data is stored as immutable time-series records, allowing users to track their metrics over time.
- Two-panel interface: Input form on the left, live results on the right
- Real-time calculations: Results update as users type
- Unit system support: Automatic locale detection (SI for NZ/AU/UK/EU, conventional for US) with manual toggle, synced to database for logged-in users
- Immutable measurement history: Apple Health-style data model (no edits, only add/delete)
- SI canonical storage: All values stored in SI units (kg, cm, mmol/L, mmol/mol, mmHg) to eliminate unit ambiguity
- Guest mode: Works without signup (data saved to localStorage)
- Shopify login sync: Logged-in customers automatically save data to cloud (HMAC-verified)
- Background sync: App embed block syncs guest localStorage data to Supabase on any storefront page after login
- Personalized suggestions: Based on clinical guidelines for BMI, HbA1c, LDL, blood pressure, etc.
- HIPAA audit logging: All write operations logged for compliance (no PHI in metadata)
- Account data deletion: Users can delete all their data with a single click (measurements, profile, auth user)
- Email reminder notifications: Daily cron sends HIPAA-aware reminders when screenings, blood tests, or medication reviews are due. Per-category opt-out with group-level cooldowns (90d screening, 180d blood test, 365d medication). Token-based unsubscribe preferences page.
- Node.js 20+
- A Shopify Partner account
- A Supabase project
- A Fly.io account (for backend hosting)
git clone <repo-url>
cd roadmap
npm install- Go to Shopify Partners and create a new app
- Note the Client ID and Client Secret
cp shopify.app.toml.example shopify.app.tomlEdit shopify.app.toml:
- Set
client_idto your app's Client ID - Set
application_urlto your Fly.io app URL (e.g.https://your-app.fly.dev) - Update
redirect_urlsand[app_proxy] urlto match
cp fly.toml.example fly.tomlEdit fly.toml:
- Set
appto your Fly.io app name
cp .env.example .envEdit .env with your Supabase credentials (found in Supabase Dashboard > Settings > API).
Run the SQL migration in your Supabase SQL Editor:
# Copy the contents of supabase/rls-policies.sql into the Supabase SQL Editor and run itThis creates:
- profiles table — Maps Shopify customer IDs to Supabase Auth user IDs (
shopify_customer_idis nullable for future mobile-only users) - health_measurements table — Immutable time-series health records with
metric_type,value(SI canonical units), andrecorded_at - audit_logs table — HIPAA audit trail for all write operations (anonymized on account deletion)
- Auth trigger — Auto-creates a profile row when a Supabase Auth user is created
- get_latest_measurements() RPC — Efficiently returns the latest value per metric type (scoped by
auth.uid()) - CHECK constraints — Per-metric-type value range validation at the database level
- RLS policies — Enforced access control using
auth.uid()(SELECT, INSERT, DELETE; no UPDATE)
# Deploy Shopify extensions (widget + sync embed)
npm run build:widget
npx shopify app deploy --force
# Deploy backend to Fly.io
fly deploy
# Set secrets on Fly.io
fly secrets set SUPABASE_URL=https://your-project.supabase.co
fly secrets set SUPABASE_SERVICE_KEY=your-service-key
fly secrets set SUPABASE_ANON_KEY=your-anon-key
fly secrets set SUPABASE_JWT_SECRET=your-jwt-secret- Install the app on your Shopify store and accept the required permissions (
write_app_proxy,read_customers) - Enable the "Health Data Sync" app embed — In the Theme Editor, go to App Embeds and toggle on "Health Data Sync". This runs silently on every storefront page and syncs guest localStorage data to Supabase when the user logs in.
- Add the "Health Roadmap Tool" block to the homepage — In the Theme Editor, navigate to the homepage, click Add block, and select "Health Roadmap Tool"
- Create a Health History page — Online Store > Pages > Add page (e.g. title "Health History", URL handle
health-history→/pages/health-history) - Add the "Health History" block to the History page — In the Theme Editor, navigate to the History page, click Add block, and select "Health History"
- Configure customer account extensions — The "Health Roadmap Link" extension auto-deploys to the customer account profile page and order history page after app install. Go to Settings > Customer accounts, customize the customer account pages, and set the
roadmap_urlsetting to your store's homepage URL (e.g.https://yourdomain.com) - Verify — Test guest mode (no login), logged-in mode, guest→logged-in data sync (enter data as guest, log in, confirm it appears), history page, and customer account links
┌─────────────────────────────────────────────────────────────────┐
│ Theme Widget (Storefront) │
│ ├── Guest: localStorage (works without login) │
│ ├── Logged in: Auto-detects Shopify customer │
│ └── Calls backend measurement API for cloud sync │
├─────────────────────────────────────────────────────────────────┤
│ App Embed Sync Block (every storefront page) │
│ └── Background localStorage→Supabase sync for logged-in users │
├─────────────────────────────────────────────────────────────────┤
│ Backend API (Remix App on Fly.io) │
│ ├── GET/POST/DELETE /api/measurements (HMAC auth) │
│ ├── DELETE /api/user-data (account deletion, HMAC auth) │
│ └── Dual Supabase clients (admin + RLS-enforced user client) │
├─────────────────────────────────────────────────────────────────┤
│ Shared Library (packages/health-core) │
│ ├── Unit conversions (SI ↔ conventional) │
│ ├── Health calculations (IBW, BMI, protein target) │
│ ├── Suggestion generation (unit-system-aware) │
│ └── Field↔metric mappings, validation schemas │
├─────────────────────────────────────────────────────────────────┤
│ Supabase Database (RLS enabled) │
│ ├── profiles (shopify_customer_id → user mapping) │
│ ├── health_measurements (immutable time-series records) │
│ ├── reminder_preferences + reminder_log (email reminders) │
│ └── audit_logs (HIPAA audit trail, anonymized on deletion) │
└─────────────────────────────────────────────────────────────────┘
All health data for logged-in customers is protected by Shopify's app proxy HMAC signature verification. The widget never calls the backend directly.
Guest (not logged in):
Widget → localStorage (no server calls)
Logged-in customer (storefront widget):
Widget → /apps/health-tool-1/api/measurements (same-origin request)
→ Shopify app proxy adds logged_in_customer_id + HMAC signature
→ Fly.io backend verifies HMAC via authenticate.public.appProxy()
→ Extracts verified customer ID from signed query params
→ Maps Shopify customer → Supabase Auth user
→ Creates RLS-scoped client (anon key + custom JWT)
→ RLS enforces auth.uid() on every query
- HMAC-verified identity: Shopify signs every proxied request with the app's secret key. The
logged_in_customer_idparameter cannot be forged — any tampering invalidates the signature. - No client-side secrets: No API keys, tokens, or customer IDs are exposed in client code.
- No CORS: Requests go through Shopify's proxy (same origin as the storefront).
- Server-side authorization: The backend never trusts client-supplied identity. Customer ID always comes from the HMAC-verified query parameters.
- Row Level Security: Supabase RLS policies enforce data isolation at the database level. All data queries use an anon key + custom JWT scoped to
auth.uid()— the service key is only used for user creation. - Error boundaries: React error boundaries prevent component crashes from taking down the entire tool.
/roadmap
├── /app # Remix app (Shopify admin + API)
│ ├── /lib
│ │ └── supabase.server.ts # Dual Supabase clients, JWT signing, CRUD
│ └── /routes
│ ├── api.measurements.ts # Measurement CRUD API (HMAC auth)
│ ├── api.reminders.ts # Reminder preferences + unsubscribe page
│ └── api.user-data.ts # Account data deletion (HMAC auth)
├── /packages
│ └── /health-core # Shared library
│ └── /src
│ ├── calculations.ts # IBW, BMI, protein target
│ ├── suggestions.ts # Recommendation generation (unit-aware)
│ ├── units.ts # Unit definitions, conversions, thresholds
│ ├── mappings.ts # Field↔metric mappings, data conversion
│ ├── reminders.ts # Pure reminder logic (computeDueReminders)
│ ├── validation.ts # Zod schemas
│ └── types.ts # TypeScript interfaces
├── /widget-src # React widget source code
│ └── /src
│ ├── /components # HealthTool, InputPanel, ResultsPanel
│ └── /lib
│ ├── storage.ts # localStorage + unit preference
│ └── api.ts # Measurement API client (app proxy)
├── /extensions
│ └── /health-tool-widget # Shopify theme extension (widget + sync embed)
├── /supabase
│ └── rls-policies.sql # DB schema + RLS policies
└── Dockerfile # Docker build for Fly.io
| Metric | Formula | Source |
|---|---|---|
| Ideal Body Weight | Devine Formula: 50kg + 0.91 × (height - 152.4cm) for males | Clinical standard |
| Protein Target | 1.2g × IBW | Evidence-based recommendation |
| BMI | weight / height² | WHO standard |
| Waist-to-Height Ratio | waist / height | Metabolic risk indicator |
npm test # Run all 138 tests once
npm run test:watch # Watch modenpm run build:widget # Build the health widget
npm run dev:widget # Watch widget for changes
npm run deploy # Deploy extensions to Shopify CDN
fly deploy # Deploy backend to Fly.ioIf measurements return 500 errors after deployment, check the Fly.io logs (fly logs --no-tail) for these common issues:
admin=false / email is null: The Shopify offline access token is missing from the PostgreSQL session table. Re-authenticate by visiting the app in Shopify admin, or uninstall and reinstall the app.
"A user with this email address has already been registered": The Supabase Auth user exists but the profiles row is missing or doesn't match the shopify_customer_id. The getOrCreateSupabaseUser() function handles this by looking up the existing auth user by email and re-creating the profile row.
"violates foreign key constraint health_measurements_user_id_fkey": The user_id doesn't exist in the profiles table. This is a symptom of the above issue — the profile wasn't created properly.
Shopify sessions are stored in Supabase PostgreSQL, so deploys don't affect session persistence. The app runs stateless on Fly.io with no persistent volume.
This tool is for educational purposes only and is not a substitute for professional medical advice. Users should always consult with their healthcare provider before making health decisions.