Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/alessandrobertelli_ibm-bob-hackathon.json

1 line
70 KiB
JSON
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"repo": "alessandrobertelli/ibm-bob-hackathon", "files": {"DEPLOYMENT_GUIDE.md": {"884e54cf365b4105f2ae62d8d2138d5469b96c39": {"t": 1785746530.2394595, "n": 13464, "h": "311aeb2c9e0d3539", "c": "# atavola — Deployment guide\n\nEnd-to-end walkthrough for getting atavola live on **a single Vercel project** with Supabase. ~30 minutes once you have the API keys.\n\n## Cost\n\n- Supabase free tier: 500 MB DB, 1 GB Storage, 50k MAU.\n- Vercel Hobby: 100 GB bandwidth/month, 100k function invocations/month, 60 s function `maxDuration`. Plenty.\n- OpenRouter: free models cost nothing per call (each is rate-limited; the rotation list handles that).\n- Pollinations.ai: free, no account.\n- Hugging Face Inference: free with a token; rate-limited.\n- Cloudflare Workers AI: free tier, ~3,300 FLUX-1-schnell images/day.\n- Resend: 3k emails/month free.\n\nTotal: **$0/month** at hackathon scale.\n\n---\n\n## Prerequisites\n\n- A GitHub repo with this code.\n- Free accounts: Supabase, OpenRouter, Resend, Vercel. Optional but recommended: Hugging Face, Cloudflare (image-gen fallbacks).\n\n---\n\n## 1. Supabase\n\n### 1.1 Create the project\n\n1. https://supabase.com/dashboard → \"New project\".\n2. Pick a region close to your users.\n3. Wait ~2 min for provisioning.\n\n### 1.2 Apply the schema\n\n1. Open **SQL Editor** in the dashboard.\n2. Paste and run [supabase/migrations/0001_init.sql](supabase/migrations/0001_init.sql). Single file, end-to-end:\n - tables (`profiles`, `saved_meals`, `sessions`, `session_meals`, `guests`, `votes`, `rate_log`, `service_status`, `events`, `aggregated_stats`, `error_log`),\n - RLS policies on every one of them,\n - RPCs (`cast_vote`, `ensure_guest`, `check_rate`, `record_service_outcome`, `record_event`, `record_error`, `get_weekly_digest_data`, `cleanup_after_digest`, `list_my_sessions`),\n - the `supabase_realtime` publication for live results,\n - the public-read `meal-images` Storage bucket,\n - and the daily pgcron `atavola-cleanup-expired-sessions` job at 03:15 UTC.\n\nThe file is idempotent — running it twice is safe.\n\n### 1.3 Configure Auth\n\n**Authentication → URL Configuration**\n- Site URL: your future Vercel URL (you'll update once Vercel gives you the domain).\n- Redirect URLs: add `${SITE_URL}/auth/verify`.\n\n**Authentication → Email Templates → Magic Link**\n- Default English copy is fine. Keep `{{ .ConfirmationURL }}` as the link placeholder.\n\n**Authentication → SMTP Settings**\n- Enable \"Custom SMTP\" and point it at Resend.\n ```\n Host: smtp.resend.com\n Port: 465\n User: resend\n Password: <Resend API key>\n Sender: <verified domain> (or onboarding@resend.dev for testing)\n Sender name: atavola\n ```\n\n### 1.4 Grab the keys\n\n**Settings → API**, copy:\n\n- Project URL → `SUPABASE_URL` and `VITE_SUPABASE_URL`.\n- `anon` public key → `VITE_SUPABASE_ANON_KEY`.\n- `service_role` secret → `SUPABASE_SERVICE_ROLE_KEY` (server-only, never expose).\n\n---\n\n## 2. OpenRouter\n\n1. https://openrouter.ai → sign up.\n2. **Keys** → \"Create Key\". Copy the `sk-or-v1-...` value into `OPENROUTER_API_KEY`.\n3. Browse https://openrouter.ai/models?max_price=0 and pick 3–5 free models. A solid starting set:\n ```\n OPENROUTER_MODELS=google/gemini-2.0-flash-exp:free,deepseek/deepseek-chat:free,meta-llama/llama-3.3-70b-instruct:free,mistralai/mistral-7b-instruct:free\n ```\n\n---\n\n## 3. Image-gen providers (optional but recommended)\n\n`IMAGE_PROVIDERS` is comma-separated, tried in order. Pollinations alone works; adding the others gives resilience.\n\n### 3.1 Pollinations.ai (no setup)\n\nAlready free, no account. Add `pollinations` to `IMAGE_PROVIDERS`.\n\n### 3.2 Hugging Face Inference\n\n1. https://huggingface.co/join → sign up (or use existing account).\n2. https://huggingface.co/settings/tokens → \"New token\", role \"Read\".\n3. Save as `HUGGINGFACE_API_TOKEN`.\n4. Add `huggingface` to `IMAGE_PROVIDERS`.\n\n### 3.3 Cloudflare Workers AI\n\n1. https://dash.cloudflare.com → sign up.\n2. **Account home** → copy the Account ID from the right rail → `CLOUDFLARE_ACCOUNT_ID`.\n3. **My Profile → API Tokens → Create Token → Custom token**:\n - Permissions: `Account → Workers AI → Read`.\n - Account Resources: include your account.\n4. Save the token as `CLOUDFLARE_API_TOKEN`.\n5. Add `cloudflare` to `IMAGE_PROVIDERS`.\n\nRecommended order: `pollinations,huggingface,cloudflare`. Pollinations is fastest; HF and CF are sturdier when Pollinations slows down.\n\n---\n\n## 4. Vercel\n\n### 4.1 Import the project\n\n1. https://vercel.com → \"Add New\" → \"Project\".\n2. Import the GitHub repo.\n3. Configure:\n - **Framework Preset**: Other (auto-detected from `vercel.json`).\n - **Root Directory**: `.` (repo root — leave default).\n - **Build / Output / Install** commands: leave at defaults; `vercel.json` overrides them.\n4. Don't deploy yet — set env vars first.\n\n### 4.2 Environment variables\n\nVercel → Project → Settings → Environment Variables. Add each, mark for Production + Preview + Development:\n\n```\nSERVICE_MODE=production\n\nSUPABASE_URL=\nSUPABASE_SERVICE_ROLE_KEY=\nVITE_SUPABASE_URL=\nVITE_SUPABASE_ANON_KEY=\n\nOPENROUTER_API_KEY=\nOPENROUTER_MODELS=\n\nIMAGE_PROVIDERS=pollinations,huggingface,cloudflare\nHUGGINGFACE_API_TOKEN=\nCLOUDFLARE_ACCOUNT_ID=\nCLOUDFLARE_API_TOKEN=\n\n# Weekly digest email (Vercel Cron → /api/cron/weekly-stats)\nRESEND_API_KEY=\nEMAIL_FROM=atavola <no-reply@atavola.ch>\nCRON_SECRET=\nSTATS_EMAIL_RECIPIENTS=\n```\n\nNotes:\n\n- `EMAIL_FROM` is the sender for direct Resend HTTP calls (currently just the weekly digest). It's separate from the Supabase Auth → SMTP settings; magic-link mail still uses whatever you wired up there. The domain in `EMAIL_FROM` must be verified in Resend.\n- `CRON_SECRET` is what Vercel signs cron requests with as `Authorization: Bearer …`. Set it both in the env-vars block above **and** in **Settings → Cron Jobs** for the project. Mismatch → the cron 401s.\n- `STATS_EMAIL_RECIPIENTS` is a comma-separated list. Empty = the cron runs but skips sending (handy in staging).\n- The schedule itself lives in [`vercel.json`](vercel.json) (`crons` block). Default is every Monday 09:00 UTC; change it there and redeploy.\n- `VITE_API_URL` is intentionally absent — same-origin defaults to `/api` and that's correct here.\n\n### 4.3 Deploy\n\nClick \"Deploy\". ~1–2 minutes. Note the URL (e.g. `https://atavola.vercel.app`).\n\n### 4.4 Custom Domain & DNS\n\nIf you have a domain like `atavola.ch`:\n1. **Vercel → Settings → Domains**: Add `atavola.ch`.\n2. **DNS Records**: Vercel will provide an **A record** (for `@`) and a **CNAME record** (for `www`). Set these in your registrar's dashboard (e.g., Cloudflare, Namecheap).\n3. **Verify**: Once the SSL certificate is issued by Vercel, the domain status will turn green.\n\n### 4.5 Loop the URL back to Supabase\n\nGo back to **Supabase → Authentication → URL Configuration**: ensure the **Site URL** and **Redirect URLs** use `https://atavola.ch`. If you don't do this, users will be redirected to the default `vercel.app` domain instead of your custom one.\n\n---\n\n## 5. Smoke test\n\n```bash\ncurl https://your-app.vercel.app/api/system/health\n# → {\"status\":\"ok\",\"mode\":\"production\",...}\n```\n\nIn a private window:\n\n1. Open the Vercel URL.\n2. Sign in with magic link (check your inbox — see \"Resend domain\" gotcha below).\n3. Create a session: vibe + headcount + no pre-selected meals.\n4. Wait for the 4 cards to appear with images.\n5. Click \"Share voting link\". Try the **native share** button (mobile only) and the **copy link** button. Tapping the URL pill itself also copies. Try the **QR code**.\n6. In another private window, open the link, swipe through the 4 cards.\n7. In the host window, watch the live results re-rank.\n8. Heart a card → it appears under \"My Food\" in your profile dropdown.\n9. Reload the profile, drag to reorder, swipe a row left to delete.\n\nIf any step fails, check Vercel function logs (Project → Logs → click a function) and the browser console.\n\n---\n\n## 6. Custom domain (optional)\n\n1. Buy at any registrar (Cloudflare Registrar is at-cost).\n2. Vercel → Project → Settings → Domains → Add. Follow Vercel's DNS instructions.\n3. Once active, update **Supabase → Authentication → URL Configuration** to use the new domain.\n4. Update **Resend → Domains** to verify the new sending domain so magic-link emails work for everyone, not just yourself.\n\nNo code changes needed — the app reads URLs from env / runtime, never hardcoded.\n\n---\n\n## 7. Email deliverability (SPF / DKIM / DMARC)\n\nMagic-link delivery is the silent growth lever — if mail lands in spam, sign-ups die without you noticing. Resend's dashboard walks you through the DNS records; here's what to set on the apex domain (`atavola.ch`):\n\n1. **SPF** (`TXT @`) — tells receivers which servers may send mail \"as\" your domain.\n ```\n v=spf1 include:_spf.resend.com ~all\n ```\n `~all` (soft fail) is the right choice while you stabilise; switch to `-all` (hard fail) once you're confident no other systems send mail from atavola.ch.\n\n2. **DKIM** — Resend auto-generates a CNAME pair when you verify the domain. Copy them into DNS verbatim. They look like:\n ```\n resend._domainkey CNAME resend._domainkey.<region>.amazonses.com\n ```\n Wait for Resend to flip the domain status to \"verified\" (usually <10 min).\n\n3. **DMARC** (`TXT _dmarc`) — tells receivers what to do with mail that fails SPF / DKIM and where to report aggregate failures.\n ```\n v=DMARC1; p=none; rua=mailto:no-reply@atavola.ch; pct=100; aspf=r; adkim=r\n ```\n Start with `p=none` so you collect reports without rejecting legitimate mail. After a week of clean reports, tighten to `p=quarantine`, then `p=reject`.\n\n4. **Reverse DNS** is handled by Resend on their sending IPs — nothing for you to configure.\n\n5. **Verify**: send a magic-link to a Gmail address and inspect the headers. You want `dkim=pass`, `spf=pass`, `dmarc=pass`. [https://www.mail-tester.com/](https://www.mail-tester.com/) gives a 0–10 score; aim for 9+.\n\nThe same Resend domain powers the weekly digest (`/api/cron/weekly-stats`). `EMAIL_FROM` must use this verified domain or sends bounce silently.\n\n---\n\n## 8. SEO submission\n\nThe static landing page, `/about`, `/privacy`, and `/terms` ship with full meta and JSON-LD; `robots.txt`, `sitemap.xml`, `llms.txt`, and `humans.txt` live in `frontend/public/`. After first deploy:\n\n1. **Google Search Console** — [https://search.google.com/search-console](https://search.google.com/search-console). Add the property (use DNS verification on atavola.ch so all subdomains are covered). Submit `https://atavola.ch/sitemap.xml`. Indexing usually starts within hours.\n2. **Bing Webmaster Tools** — [https://www.bing.com/webmasters](https://www.bing.com/webmasters). Same flow; Bing also feeds DuckDuckGo results.\n3. **Yandex Webmaster** (optional, only if you care about RU/CIS traffic).\n4. **Sitemap auto-discovery** — `robots.txt` already references the sitemap. Most crawlers find it automatically; the manual submission above just shortens the discovery window.\n5. **Validate**:\n - [Google Rich Results Test](https://search.google.com/test/rich-results) — paste each of `/`, `/about`, `/privacy`, `/terms` and confirm the JSON-LD blocks parse.\n - [Twitter / X Card Validator](https://cards-dev.twitter.com/validator) — paste the home URL, confirm the OG image renders.\n - [LinkedIn Post Inspector](https://www.linkedin.com/post-inspector/) — same.\n - [Facebook Sharing Debugger](https://developers.facebook.com/tools/debug/) — same. Click \"Scrape Again\" if cached.\n - [securityheaders.com](https://securityheaders.com/) and [Mozilla Observatory](https://observatory.mozilla.org/) — both should score A or A+.\n - [PageSpeed Insights](https://pagespeed.web.dev/) — Core Web Vitals must be green for ranking.\n\n---\n\n## Common pitfalls\n\n| Symptom | Cause | Fix |\n|---|---|---|\n| Function logs say \"Refusing to boot: SERVICE_MODE=…\" | Vercel env var unset or misspelled | Set `SERVICE_MODE=production` exactly. The hard fail is intentional — see [SECURITY.md](SECURITY.md) #C1. |\n| Magic-link email never arrives for anyone but you | Resend domain not verified | Verify the domain in Resend, then point Supabase SMTP at it. Until verified, only your own address receives mail. |\n| 401 on every call | RLS denies because policies didn't run | Re-apply both migrations — verify policies under `Authentication → Policies`. |\n| `cast_vote` errors with `session_expired` or `session_not_open` | Session is older than 30 days or status moved off `voting` | Hosts create a new session; expiry is enforced server-side and the daily pgcron purges past it anyway. |\n| `cast_vote` errors with `invalid_guest_token` | Guest hasn't been minted | Frontend should call `POST /api/votes/guest` once before voting (it does — clear localStorage and reload if stale). |\n| 429 on rate-limited endpoints | The Postgres rate-limit RPC threw | Check function logs for `[checkRate] RPC failed` warnings. The limiter fails open to avoid locking real users out, but a sustained warning means you're effectively unprotected — fix the underlying DB error. |\n| Generated images broken | Every image provider failed magic-byte sniff or quota | Inspect the function log — provider names + reasons are logged. Most common: wrong CF/HF key, or HF model warming up (just retry). |\n| Meal generation 503s | All OpenRouter free models hit their daily quota | Add more models to `OPENROUTER_MODELS`; rotation will skip past empty ones. |\n| Vercel function times out | LLM + 4 image fetches > 60 s | Drop a slow provider from `IMAGE_PROVIDERS` (HF when its servers are loading). Long-term: Vercel Pro = 300 s, or split generation into per-meal endpoints (out of scope today). |\n\n<!-- Made with Bob -->\n"}, "8ca7e9c34d34e903d7cd4efc841240463543d671": {"t": 1785765360.8299544, "n": 13464, "h": "311aeb2c9e0d3539", "c": "# atavola — Deployment guide\n\nEnd-to-end walkthrough for getting atavola live on **a single Vercel project** with Supabase. ~30 minutes once you have the API keys.\n\n## Cost\n\n- Supabase free tier: 500 MB DB, 1 GB Storage, 50k MAU.\n- Vercel Hobby: 100 GB bandwidth/month, 100k function invocations/month, 60 s function `maxDuration`. Plenty.\n- OpenRouter: free models cost nothing per call (each is rate-limited; the rotation list handles that).\n- Pollinations.ai: free, no account.\n- Hugging Face Inference: free with a token; rate-limited.\n- Cloudflare Workers AI: free tier, ~3,300 FLUX-1-schnell images/day.\n- Resend: 3k emails/month free.\n\nTotal: **$0/month** at hackathon scale.\n\n---\n\n## Prerequisites\n\n- A GitHub repo with this code.\n- Free accounts: Supabase, OpenRouter, Resend, Vercel. Optional but recommended: Hugging Face, Cloudflare (image-gen fallbacks).\n\n---\n\n## 1. Supabase\n\n### 1.1 Create the project\n\n1. https://supabase.com/dashboard → \"New project\".\n2. Pick a region close to your users.\n3. Wait ~2 min for provisioning.\n\n### 1.2 Apply the schema\n\n1. Open **SQL Editor** in the dashboard.\n2. Paste and run [supabase/migrations/0001_init.sql](supabase/migrations/0001_init.sql). Single file, end-to-end:\n - tables (`profiles`, `saved_meals`, `sessions`, `session_meals`, `guests`, `votes`, `rate_log`, `service_status`, `events`, `aggregated_stats`, `error_log`),\n - RLS policies on every one of them,\n - RPCs (`cast_vote`, `ensure_guest`, `check_rate`, `record_service_outcome`, `record_event`, `record_error`, `get_weekly_digest_data`, `cleanup_after_digest`, `list_my_sessions`),\n - the `supabase_realtime` publication for live results,\n - the public-read `meal-images` Storage bucket,\n - and the daily pgcron `atavola-cleanup-expired-sessions` job at 03:15 UTC.\n\nThe file is idempotent — running it twice is safe.\n\n### 1.3 Configure Auth\n\n**Authentication → URL Configuration**\n- Site URL: your future Vercel URL (you'll update once Vercel gives you the domain).\n- Redirect URLs: add `${SITE_URL}/auth/verify`.\n\n**Authentication → Email Templates → Magic Link**\n- Default English copy is fine. Keep `{{ .ConfirmationURL }}` as the link placeholder.\n\n**Authentication → SMTP Settings**\n- Enable \"Custom SMTP\" and point it at Resend.\n ```\n Host: smtp.resend.com\n Port: 465\n User: resend\n Password: <Resend API key>\n Sender: <verified domain> (or onboarding@resend.dev for testing)\n Sender name: atavola\n ```\n\n### 1.4 Grab the keys\n\n**Settings → API**, copy:\n\n- Project URL → `SUPABASE_URL` and `VITE_SUPABASE_URL`.\n- `anon` public key → `VITE_SUPABASE_ANON_KEY`.\n- `service_role` secret → `SUPABASE_SERVICE_ROLE_KEY` (server-only, never expose).\n\n---\n\n## 2. OpenRouter\n\n1. https://openrouter.ai → sign up.\n2. **Keys** → \"Create Key\". Copy the `sk-or-v1-...` value into `OPENROUTER_API_KEY`.\n3. Browse https://openrouter.ai/models?max_price=0 and pick 3–5 free models. A solid starting set:\n ```\n OPENROUTER_MODELS=google/gemini-2.0-flash-exp:free,deepseek/deepseek-chat:free,meta-llama/llama-3.3-70b-instruct:free,mistralai/mistral-7b-instruct:free\n ```\n\n---\n\n## 3. Image-gen providers (optional but recommended)\n\n`IMAGE_PROVIDERS` is comma-separated, tried in order. Pollinations alone works; adding the others gives resilience.\n\n### 3.1 Pollinations.ai (no setup)\n\nAlready free, no account. Add `pollinations` to `IMAGE_PROVIDERS`.\n\n### 3.2 Hugging Face Inference\n\n1. https://huggingface.co/join → sign up (or use existing account).\n2. https://huggingface.co/settings/tokens → \"New token\", role \"Read\".\n3. Save as `HUGGINGFACE_API_TOKEN`.\n4. Add `huggingface` to `IMAGE_PROVIDERS`.\n\n### 3.3 Cloudflare Workers AI\n\n1. https://dash.cloudflare.com → sign up.\n2. **Account home** → copy the Account ID from the right rail → `CLOUDFLARE_ACCOUNT_ID`.\n3. **My Profile → API Tokens → Create Token → Custom token**:\n - Permissions: `Account → Workers AI → Read`.\n - Account Resources: include your account.\n4. Save the token as `CLOUDFLARE_API_TOKEN`.\n5. Add `cloudflare` to `IMAGE_PROVIDERS`.\n\nRecommended order: `pollinations,huggingface,cloudflare`. Pollinations is fastest; HF and CF are sturdier when Pollinations slows down.\n\n---\n\n## 4. Vercel\n\n### 4.1 Import the project\n\n1. https://vercel.com → \"Add New\" → \"Project\".\n2. Import the GitHub repo.\n3. Configure:\n - **Framework Preset**: Other (auto-detected from `vercel.json`).\n - **Root Directory**: `.` (repo root — leave default).\n - **Build / Output / Install** commands: leave at defaults; `vercel.json` overrides them.\n4. Don't deploy yet — set env vars first.\n\n### 4.2 Environment variables\n\nVercel → Project → Settings → Environment Variables. Add each, mark for Production + Preview + Development:\n\n```\nSERVICE_MODE=production\n\nSUPABASE_URL=\nSUPABASE_SERVICE_ROLE_KEY=\nVITE_SUPABASE_URL=\nVITE_SUPABASE_ANON_KEY=\n\nOPENROUTER_API_KEY=\nOPENROUTER_MODELS=\n\nIMAGE_PROVIDERS=pollinations,huggingface,cloudflare\nHUGGINGFACE_API_TOKEN=\nCLOUDFLARE_ACCOUNT_ID=\nCLOUDFLARE_API_TOKEN=\n\n# Weekly digest email (Vercel Cron → /api/cron/weekly-stats)\nRESEND_API_KEY=\nEMAIL_FROM=atavola <no-reply@atavola.ch>\nCRON_SECRET=\nSTATS_EMAIL_RECIPIENTS=\n```\n\nNotes:\n\n- `EMAIL_FROM` is the sender for direct Resend HTTP calls (currently just the weekly digest). It's separate from the Supabase Auth → SMTP settings; magic-link mail still uses whatever you wired up there. The domain in `EMAIL_FROM` must be verified in Resend.\n- `CRON_SECRET` is what Vercel signs cron requests with as `Authorization: Bearer …`. Set it both in the env-vars block above **and** in **Settings → Cron Jobs** for the project. Mismatch → the cron 401s.\n- `STATS_EMAIL_RECIPIENTS` is a comma-separated list. Empty = the cron runs but skips sending (handy in staging).\n- The schedule itself lives in [`vercel.json`](vercel.json) (`crons` block). Default is every Monday 09:00 UTC; change it there and redeploy.\n- `VITE_API_URL` is intentionally absent — same-origin defaults to `/api` and that's correct here.\n\n### 4.3 Deploy\n\nClick \"Deploy\". ~1–2 minutes. Note the URL (e.g. `https://atavola.vercel.app`).\n\n### 4.4 Custom Domain & DNS\n\nIf you have a domain like `atavola.ch`:\n1. **Vercel → Settings → Domains**: Add `atavola.ch`.\n2. **DNS Records**: Vercel will provide an **A record** (for `@`) and a **CNAME record** (for `www`). Set these in your registrar's dashboard (e.g., Cloudflare, Namecheap).\n3. **Verify**: Once the SSL certificate is issued by Vercel, the domain status will turn green.\n\n### 4.5 Loop the URL back to Supabase\n\nGo back to **Supabase → Authentication → URL Configuration**: ensure the **Site URL** and **Redirect URLs** use `https://atavola.ch`. If you don't do this, users will be redirected to the default `vercel.app` domain instead of your custom one.\n\n---\n\n## 5. Smoke test\n\n```bash\ncurl https://your-app.vercel.app/api/system/health\n# → {\"status\":\"ok\",\"mode\":\"production\",...}\n```\n\nIn a private window:\n\n1. Open the Vercel URL.\n2. Sign in with magic link (check your inbox — see \"Resend domain\" gotcha below).\n3. Create a session: vibe + headcount + no pre-selected meals.\n4. Wait for the 4 cards to appear with images.\n5. Click \"Share voting link\". Try the **native share** button (mobile only) and the **copy link** button. Tapping the URL pill itself also copies. Try the **QR code**.\n6. In another private window, open the link, swipe through the 4 cards.\n7. In the host window, watch the live results re-rank.\n8. Heart a card → it appears under \"My Food\" in your profile dropdown.\n9. Reload the profile, drag to reorder, swipe a row left to delete.\n\nIf any step fails, check Vercel function logs (Project → Logs → click a function) and the browser console.\n\n---\n\n## 6. Custom domain (optional)\n\n1. Buy at any registrar (Cloudflare Registrar is at-cost).\n2. Vercel → Project → Settings → Domains → Add. Follow Vercel's DNS instructions.\n3. Once active, update **Supabase → Authentication → URL Configuration** to use the new domain.\n4. Update **Resend → Domains** to verify the new sending domain so magic-link emails work for everyone, not just yourself.\n\nNo code changes needed — the app reads URLs from env / runtime, never hardcoded.\n\n---\n\n## 7. Email deliverability (SPF / DKIM / DMARC)\n\nMagic-link delivery is the silent growth lever — if mail lands in spam, sign-ups die without you noticing. Resend's dashboard walks you through the DNS records; here's what to set on the apex domain (`atavola.ch`):\n\n1. **SPF** (`TXT @`) — tells receivers which servers may send mail \"as\" your domain.\n ```\n v=spf1 include:_spf.resend.com ~all\n ```\n `~all` (soft fail) is the right choice while you stabilise; switch to `-all` (hard fail) once you're confident no other systems send mail from atavola.ch.\n\n2. **DKIM** — Resend auto-generates a CNAME pair when you verify the domain. Copy them into DNS verbatim. They look like:\n ```\n resend._domainkey CNAME resend._domainkey.<region>.amazonses.com\n ```\n Wait for Resend to flip the domain status to \"verified\" (usually <10 min).\n\n3. **DMARC** (`TXT _dmarc`) — tells receivers what to do with mail that fails SPF / DKIM and where to report aggregate failures.\n ```\n v=DMARC1; p=none; rua=mailto:no-reply@atavola.ch; pct=100; aspf=r; adkim=r\n ```\n Start with `p=none` so you collect reports without rejecting legitimate mail. After a week of clean reports, tighten to `p=quarantine`, then `p=reject`.\n\n4. **Reverse DNS** is handled by Resend on their sending IPs — nothing for you to configure.\n\n5. **Verify**: send a magic-link to a Gmail address and inspect the headers. You want `dkim=pass`, `spf=pass`, `dmarc=pass`. [https://www.mail-tester.com/](https://www.mail-tester.com/) gives a 0–10 score; aim for 9+.\n\nThe same Resend domain powers the weekly digest (`/api/cron/weekly-stats`). `EMAIL_FROM` must use this verified domain or sends bounce silently.\n\n---\n\n## 8. SEO submission\n\nThe static landing page, `/about`, `/privacy`, and `/terms` ship with full meta and JSON-LD; `robots.txt`, `sitemap.xml`, `llms.txt`, and `humans.txt` live in `frontend/public/`. After first deploy:\n\n1. **Google Search Console** — [https://search.google.com/search-console](https://search.google.com/search-console). Add the property (use DNS verification on atavola.ch so all subdomains are covered). Submit `https://atavola.ch/sitemap.xml`. Indexing usually starts within hours.\n2. **Bing Webmaster Tools** — [https://www.bing.com/webmasters](https://www.bing.com/webmasters). Same flow; Bing also feeds DuckDuckGo results.\n3. **Yandex Webmaster** (optional, only if you care about RU/CIS traffic).\n4. **Sitemap auto-discovery** — `robots.txt` already references the sitemap. Most crawlers find it automatically; the manual submission above just shortens the discovery window.\n5. **Validate**:\n - [Google Rich Results Test](https://search.google.com/test/rich-results) — paste each of `/`, `/about`, `/privacy`, `/terms` and confirm the JSON-LD blocks parse.\n - [Twitter / X Card Validator](https://cards-dev.twitter.com/validator) — paste the home URL, confirm the OG image renders.\n - [LinkedIn Post Inspector](https://www.linkedin.com/post-inspector/) — same.\n - [Facebook Sharing Debugger](https://developers.facebook.com/tools/debug/) — same. Click \"Scrape Again\" if cached.\n - [securityheaders.com](https://securityheaders.com/) and [Mozilla Observatory](https://observatory.mozilla.org/) — both should score A or A+.\n - [PageSpeed Insights](https://pagespeed.web.dev/) — Core Web Vitals must be green for ranking.\n\n---\n\n## Common pitfalls\n\n| Symptom | Cause | Fix |\n|---|---|---|\n| Function logs say \"Refusing to boot: SERVICE_MODE=…\" | Vercel env var unset or misspelled | Set `SERVICE_MODE=production` exactly. The hard fail is intentional — see [SECURITY.md](SECURITY.md) #C1. |\n| Magic-link email never arrives for anyone but you | Resend domain not verified | Verify the domain in Resend, then point Supabase SMTP at it. Until verified, only your own address receives mail. |\n| 401 on every call | RLS denies because policies didn't run | Re-apply both migrations — verify policies under `Authentication → Policies`. |\n| `cast_vote` errors with `session_expired` or `session_not_open` | Session is older than 30 days or status moved off `voting` | Hosts create a new session; expiry is enforced server-side and the daily pgcron purges past it anyway. |\n| `cast_vote` errors with `invalid_guest_token` | Guest hasn't been minted | Frontend should call `POST /api/votes/guest` once before voting (it does — clear localStorage and reload if stale). |\n| 429 on rate-limited endpoints | The Postgres rate-limit RPC threw | Check function logs for `[checkRate] RPC failed` warnings. The limiter fails open to avoid locking real users out, but a sustained warning means you're effectively unprotected — fix the underlying DB error. |\n| Generated images broken | Every image provider failed magic-byte sniff or quota | Inspect the function log — provider names + reasons are logged. Most common: wrong CF/HF key, or HF model warming up (just retry). |\n| Meal generation 503s | All OpenRouter free models hit their daily quota | Add more models to `OPENROUTER_MODELS`; rotation will skip past empty ones. |\n| Vercel function times out | LLM + 4 image fetches > 60 s | Drop a slow provider from `IMAGE_PROVIDERS` (HF when its servers are loading). Long-term: Vercel Pro = 300 s, or split generation into per-meal endpoints (out of scope today). |\n\n<!-- Made with Bob -->\n"}, "11a7a4736772593ff85676c37351199bc754cec2": {"t": 1785765363.1181133, "n": 13468, "h": "9cbe42111db0c5b3", "c": "# atavola — Deployment guide\n\nEnd-to-end walkthrough for getting atavola live on **a single Vercel project** with Supabase. ~30 minutes once you have the API keys.\n\n## Cost\n\n- Supabase free tier: 500 MB DB, 1 GB Storage, 50k MAU.\n- Vercel Hobby: 100 GB bandwidth/month, 100k function invocations/month, 60 s function `maxDuration`. Plenty.\n- OpenRouter: free models cost nothing per call (each is rate-limited; the rotation list handles that).\n- Pollinations.ai: free, no account.\n- Hugging Face Inference: free with a token; rate-limited.\n- Cloudflare Workers AI: free tier, ~3,300 FLUX-1-schnell images/day.\n- Resend: 3k emails/month free.\n\nTotal: **$0/month** at hackathon scale.\n\n---\n\n## Prerequisites\n\n- A GitHub repo with this code.\n- Free accounts: Supabase, OpenRouter, Resend, Vercel. Optional but recommended: Hugging Face, Cloudflare (image-gen fallbacks).\n\n---\n\n## 1. Supabase\n\n### 1.1 Create the project\n\n1. https://supabase.com/dashboard → \"New project\".\n2. Pick a region close to your users.\n3. Wait ~2 min for provisioning.\n\n### 1.2 Apply the schema\n\n1. Open **SQL Editor** in the dashboard.\n2. Paste and run [supabase/migrations/0001_init.sql](supabase/migrations/0001_init.sql). Single file, end-to-end:\n - tables (`profiles`, `saved_meals`, `sessions`, `session_meals`, `guests`, `votes`, `rate_log`, `service_status`, `events`, `aggregated_stats`, `error_log`),\n - RLS policies on every one of them,\n - RPCs (`cast_vote`, `ensure_guest`, `check_rate`, `record_service_outcome`, `record_event`, `record_error`, `get_weekly_digest_data`, `cleanup_after_digest`, `list_my_sessions`),\n - the `supabase_realtime` publication for live results,\n - the public-read `meal-images` Storage bucket,\n - and the daily pg_cron `atavola-cleanup-expired-sessions` job at 03:15 UTC.\n\nThe file is idempotent — running it twice is safe.\n\n### 1.3 Configure Auth\n\n**Authentication → URL Configuration**\n- Site URL: your future Vercel URL (you'll update once Vercel gives you the domain).\n- Redirect URLs: add `${SITE_URL}/auth/verify`.\n\n**Authentication → Email Templates → Magic Link**\n- Default English copy is fine. Keep `{{ .ConfirmationURL }}` as the link placeholder.\n\n**Authentication → SMTP Settings**\n- Enable \"Custom SMTP\" and point it at Resend.\n ```\n Host: smtp.resend.com\n Port: 465\n User: resend\n Password: <Resend API key>\n Sender: <verified domain> (or onboarding@resend.dev for testing)\n Sender name: atavola\n ```\n\n### 1.4 Grab the keys\n\n**Settings → API**, copy:\n\n- Project URL → `SUPABASE_URL` and `VITE_SUPABASE_URL`.\n- `anon` public key → `VITE_SUPABASE_ANON_KEY`.\n- `service_role` secret → `SUPABASE_SERVICE_ROLE_KEY` (server-only, never expose).\n\n---\n\n## 2. OpenRouter\n\n1. https://openrouter.ai → sign up.\n2. **Keys** → \"Create Key\". Copy the `sk-or-v1-...` value into `OPENROUTER_API_KEY`.\n3. Browse https://openrouter.ai/models?max_price=0 and pick 3–5 free models. A solid starting set:\n ```\n OPENROUTER_MODELS=google/gemini-2.0-flash-exp:free,deepseek/deepseek-chat:free,meta-llama/llama-3.3-70b-instruct:free,mistralai/mistral-7b-instruct:free\n ```\n\n---\n\n## 3. Image-gen providers (optional but recommended)\n\n`IMAGE_PROVIDERS` is comma-separated, tried in order. Pollinations alone works; adding the others gives resilience.\n\n### 3.1 Pollinations.ai (no setup)\n\nAlready free, no account. Add `pollinations` to `IMAGE_PROVIDERS`.\n\n### 3.2 Hugging Face Inference\n\n1. https://huggingface.co/join → sign up (or use existing account).\n2. https://huggingface.co/settings/tokens → \"New token\", role \"Read\".\n3. Save as `HUGGINGFACE_API_TOKEN`.\n4. Add `huggingface` to `IMAGE_PROVIDERS`.\n\n### 3.3 Cloudflare Workers AI\n\n1. https://dash.cloudflare.com → sign up.\n2. **Account home** → copy the Account ID from the right rail → `CLOUDFLARE_ACCOUNT_ID`.\n3. **My Profile → API Tokens → Create Token → Custom token**:\n - Permissions: `Account → Workers AI → Read`.\n - Account Resources: include your account.\n4. Save the token as `CLOUDFLARE_API_TOKEN`.\n5. Add `cloudflare` to `IMAGE_PROVIDERS`.\n\nRecommended order: `pollinations,huggingface,cloudflare`. Pollinations is fastest; HF and CF are sturdier when Pollinations slows down.\n\n---\n\n## 4. Vercel\n\n### 4.1 Import the project\n\n1. https://vercel.com → \"Add New\" → \"Project\".\n2. Import the GitHub repo.\n3. Configure:\n - **Framework Preset**: Other (auto-detected from `vercel.json`).\n - **Root Directory**: `.` (repo root — leave default).\n - **Build / Output / Install** commands: leave at defaults; `vercel.json` overrides them.\n4. Don't deploy yet — set env vars first.\n\n### 4.2 Environment variables\n\nVercel → Project → Settings → Environment Variables. Add each, mark for Production + Preview + Development:\n\n```\nSERVICE_MODE=production\n\nSUPABASE_URL=\nSUPABASE_SERVICE_ROLE_KEY=\nVITE_SUPABASE_URL=\nVITE_SUPABASE_ANON_KEY=\n\nOPENROUTER_API_KEY=\nOPENROUTER_MODELS=\n\nIMAGE_PROVIDERS=pollinations,huggingface,cloudflare\nHUGGINGFACE_API_TOKEN=\nCLOUDFLARE_ACCOUNT_ID=\nCLOUDFLARE_API_TOKEN=\n\n# Weekly digest email (Vercel Cron → /api/_cron/weekly-stats)\nRESEND_API_KEY=\nEMAIL_FROM=atavola <no-reply@atavola.ch>\nCRON_SECRET=\nSTATS_EMAIL_RECIPIENTS=\n```\n\nNotes:\n\n- `EMAIL_FROM` is the sender for direct Resend HTTP calls (currently just the weekly digest). It's separate from the Supabase Auth → SMTP settings; magic-link mail still uses whatever you wired up there. The domain in `EMAIL_FROM` must be verified in Resend.\n- `CRON_SECRET` is what Vercel signs cron requests with as `Authorization: Bearer …`. Set it both in the env-vars block above **and** in **Settings → Cron Jobs** for the project. Mismatch → the cron 401s.\n- `STATS_EMAIL_RECIPIENTS` is a comma-separated list. Empty = the cron runs but skips sending (handy in staging).\n- The schedule itself lives in [`vercel.json`](vercel.json) (`crons` block). Default is every Monday 09:00 UTC; change it there and redeploy.\n- `VITE_API_URL` is intentionally absent — same-origin defaults to `/api` and that's correct here.\n\n### 4.3 Deploy\n\nClick \"Deploy\". ~1–2 minutes. Note the URL (e.g. `https://atavola.vercel.app`).\n\n### 4.4 Custom Domain & DNS\n\nIf you have a domain like `atavola.ch`:\n1. **Vercel → Settings → Domains**: Add `atavola.ch`.\n2. **DNS Records**: Vercel will provide an **A record** (for `@`) and a **CNAME record** (for `www`). Set these in your registrar's dashboard (e.g., Cloudflare, Namecheap).\n3. **Verify**: Once the SSL certificate is issued by Vercel, the domain status will turn green.\n\n### 4.5 Loop the URL back to Supabase\n\nGo back to **Supabase → Authentication → URL Configuration**: ensure the **Site URL** and **Redirect URLs** use `https://atavola.ch`. If you don't do this, users will be redirected to the default `vercel.app` domain instead of your custom one.\n\n---\n\n## 5. Smoke test\n\n```bash\ncurl https://your-app.vercel.app/api/system/health\n# → {\"status\":\"ok\",\"mode\":\"production\",...}\n```\n\nIn a private window:\n\n1. Open the Vercel URL.\n2. Sign in with magic link (check your inbox — see \"Resend domain\" gotcha below).\n3. Create a session: vibe + headcount + no pre-selected meals.\n4. Wait for the 4 cards to appear with images.\n5. Click \"Share voting link\". Try the **native share** button (mobile only) and the **copy link** button. Tapping the URL pill itself also copies. Try the **QR code**.\n6. In another private window, open the link, swipe through the 4 cards.\n7. In the host window, watch the live results re-rank.\n8. Heart a card → it appears under \"My Food\" in your profile dropdown.\n9. Reload the profile, drag to reorder, swipe a row left to delete.\n\nIf any step fails, check Vercel function logs (Project → Logs → click a function) and the browser console.\n\n---\n\n## 6. Custom domain (optional)\n\n1. Buy at any registrar (Cloudflare Registrar is at-cost).\n2. Vercel → Project → Settings → Domains → Add. Follow Vercel's DNS instructions.\n3. Once active, update **Supabase → Authentication → URL Configuration** to use the new domain.\n4. Update **Resend → Domains** to verify the new sending domain so magic-link emails work for everyone, not just yourself.\n\nNo code changes needed — the app reads URLs from env / runtime, never hardcoded.\n\n---\n\n## 7. Email deliverability (SPF / DKIM / DMARC)\n\nMagic-link delivery is the silent growth lever — if mail lands in spam, sign-ups die without you noticing. Resend's dashboard walks you through the DNS records; here's what to set on the apex domain (`atavola.ch`):\n\n1. **SPF** (`TXT @`) — tells receivers which servers may send mail \"as\" your domain.\n ```\n v=spf1 include:_spf.resend.com ~all\n ```\n `~all` (soft fail) is the right choice while you stabilise; switch to `-all` (hard fail) once you're confident no other systems send mail from atavola.ch.\n\n2. **DKIM** — Resend auto-generates a CNAME pair when you verify the domain. Copy them into DNS verbatim. They look like:\n ```\n resend._domainkey CNAME resend._domainkey.<region>.amazonses.com\n ```\n Wait for Resend to flip the domain status to \"verified\" (usually <10 min).\n\n3. **DMARC** (`TXT _dmarc`) — tells receivers what to do with mail that fails SPF / DKIM and where to report aggregate failures.\n ```\n v=DMARC1; p=none; rua=mailto:no-reply@atavola.ch; pct=100; aspf=r; adkim=r\n ```\n Start with `p=none` so you collect reports without rejecting legitimate mail. After a week of clean reports, tighten to `p=quarantine`, then `p=reject`.\n\n4. **Reverse DNS** is handled by Resend on their sending IPs — nothing for you to configure.\n\n5. **Verify**: send a magic-link to a Gmail address and inspect the headers. You want `dkim=pass`, `spf=pass`, `dmarc=pass`. [https://www.mail-tester.com/](https://www.mail-tester.com/) gives a 0–10 score; aim for 9+.\n\nThe same Resend domain powers the weekly digest (`/api/_cron/weekly-stats`). `EMAIL_FROM` must use this verified domain or sends bounce silently.\n\n---\n\n## 8. SEO submission\n\nThe static landing page, `/about`, `/privacy`, and `/terms` ship with full meta and JSON-LD; `robots.txt`, `sitemap.xml`, `llms.txt`, and `humans.txt` live in `frontend/public/`. After first deploy:\n\n1. **Google Search Console** — [https://search.google.com/search-console](https://search.google.com/search-console). Add the property (use DNS verification on atavola.ch so all subdomains are covered). Submit `https://atavola.ch/sitemap.xml`. Indexing usually starts within hours.\n2. **Bing Webmaster Tools** — [https://www.bing.com/webmasters](https://www.bing.com/webmasters). Same flow; Bing also feeds DuckDuckGo results.\n3. **Yandex Webmaster** (optional, only if you care about RU/CIS traffic).\n4. **Sitemap auto-discovery** — `robots.txt` already references the sitemap. Most crawlers find it automatically; the manual submission above just shortens the discovery window.\n5. **Validate**:\n - [Google Rich Results Test](https://search.google.com/test/rich-results) — paste each of `/`, `/about`, `/privacy`, `/terms` and confirm the JSON-LD blocks parse.\n - [Twitter / X Card Validator](https://cards-dev.twitter.com/validator) — paste the home URL, confirm the OG image renders.\n - [LinkedIn Post Inspector](https://www.linkedin.com/post-inspector/) — same.\n - [Facebook Sharing Debugger](https://developers.facebook.com/tools/debug/) — same. Click \"Scrape Again\" if cached.\n - [securityheaders.com](https://securityheaders.com/) and [Mozilla Observatory](https://observatory.mozilla.org/) — both should score A or A+.\n - [PageSpeed Insights](https://pagespeed.web.dev/) — Core Web Vitals must be green for ranking.\n\n---\n\n## Common pitfalls\n\n| Symptom | Cause | Fix |\n|---|---|---|\n| Function logs say \"Refusing to boot: SERVICE_MODE=…\" | Vercel env var unset or misspelled | Set `SERVICE_MODE=production` exactly. The hard fail is intentional — see [SECURITY.md](SECURITY.md) #C1. |\n| Magic-link email never arrives for anyone but you | Resend domain not verified | Verify the domain in Resend, then point Supabase SMTP at it. Until verified, only your own address receives mail. |\n| 401 on every call | RLS denies because policies didn't run | Re-apply both migrations — verify policies under `Authentication → Policies`. |\n| `cast_vote` errors with `session_expired` or `session_not_open` | Session is older than 30 days or status moved off `voting` | Hosts create a new session; expiry is enforced server-side and the daily pg_cron purges past it anyway. |\n| `cast_vote` errors with `invalid_guest_token` | Guest hasn't been minted | Frontend should call `POST /api/votes/guest` once before voting (it does — clear localStorage and reload if stale). |\n| 429 on rate-limited endpoints | The Postgres rate-limit RPC threw | Check function logs for `[checkRate] RPC failed` warnings. The limiter fails open to avoid locking real users out, but a sustained warning means you're effectively unprotected — fix the underlying DB error. |\n| Generated images broken | Every image provider failed magic-byte sniff or quota | Inspect the function log — provider names + reasons are logged. Most common: wrong CF/HF key, or HF model warming up (just retry). |\n| Meal generation 503s | All OpenRouter free models hit their daily quota | Add more models to `OPENROUTER_MODELS`; rotation will skip past empty ones. |\n| Vercel function times out | LLM + 4 image fetches > 60 s | Drop a slow provider from `IMAGE_PROVIDERS` (HF when its servers are loading). Long-term: Vercel Pro = 300 s, or split generation into per-meal endpoints (out of scope today). |\n\n<!-- Made with Bob -->\n"}, "d99c0d4e01462a8d640c7417a4b3ce038adecdda": {"t": 1785765364.3181355, "n": 13008, "h": "2e17ab36100c2769", "c": "# atavola — Deployment guide\n\nEnd-to-end walkthrough for getting atavola live on **a single Vercel project** with Supabase. ~30 minutes once you have the API keys.\n\n## Cost\n\n- Supabase free tier: 500 MB DB, 1 GB Storage, 50k MAU.\n- Vercel Hobby: 100 GB bandwidth/month, 100k function invocations/month, 60 s function `maxDuration`. Plenty.\n- OpenRouter: free models cost nothing per call (each is rate-limited; the rotation list handles that).\n- Pollinations.ai: free, no account.\n- Hugging Face Inference: free with a token; rate-limited.\n- Cloudflare Workers AI: free tier, ~3,300 FLUX-1-schnell images/day.\n- Resend: 3k emails/month free.\n\nTotal: **$0/month** at hackathon scale.\n\n---\n\n## Prerequisites\n\n- A GitHub repo with this code.\n- Free accounts: Supabase, OpenRouter, Resend, Vercel. Optional but recommended: Hugging Face, Cloudflare (image-gen fallbacks).\n\n---\n\n## 1. Supabase\n\n### 1.1 Create the project\n\n1. https://supabase.com/dashboard → \"New project\".\n2. Pick a region close to your users.\n3. Wait ~2 min for provisioning.\n\n### 1.2 Apply the schema\n\n1. Open **SQL Editor** in the dashboard.\n2. Paste and run [supabase/migrations/0001_init.sql](supabase/migrations/0001_init.sql). Single file, end-to-end:\n - tables (`profiles`, `saved_meals`, `sessions`, `session_meals`, `guests`, `votes`, `rate_log`, `service_status`, `events`, `aggregated_stats`, `error_log`),\n - RLS policies on every one of them,\n - RPCs (`cast_vote`, `ensure_guest`, `check_rate`, `record_service_outcome`, `record_event`, `record_error`, `get_weekly_digest_data`, `cleanup_after_digest`, `list_my_sessions`),\n - the `supabase_realtime` publication for live results,\n - the public-read `meal-images` Storage bucket,\n - and the daily pg_cron `atavola-cleanup-expired-sessions` job at 03:15 UTC.\n\nThe file is idempotent — running it twice is safe.\n\n### 1.3 Configure Auth\n\n**Authentication → URL Configuration**\n- Site URL: your future Vercel URL (you'll update once Vercel gives you the domain).\n- Redirect URLs: add `${SITE_URL}/auth/verify`.\n\n**Authentication → Email Templates → Magic Link**\n- Default English copy is fine. Keep `{{ .ConfirmationURL }}` as the link placeholder.\n\n**Authentication → SMTP Settings**\n- Enable \"Custom SMTP\" and point it at Resend.\n ```\n Host: smtp.resend.com\n Port: 465\n User: resend\n Password: <Resend API key>\n Sender: <verified domain> (or onboarding@resend.dev for testing)\n Sender name: atavola\n ```\n\n### 1.4 Grab the keys\n\n**Settings → API**, copy:\n\n- Project URL → `SUPABASE_URL` and `VITE_SUPABASE_URL`.\n- `anon` public key → `VITE_SUPABASE_ANON_KEY`.\n- `service_role` secret → `SUPABASE_SERVICE_ROLE_KEY` (server-only, never expose).\n\n---\n\n## 2. OpenRouter\n\n1. https://openrouter.ai → sign up.\n2. **Keys** → \"Create Key\". Copy the `sk-or-v1-...` value into `OPENROUTER_API_KEY`.\n3. Browse https://openrouter.ai/models?max_price=0 and pick 3–5 free models. A solid starting set:\n ```\n OPENROUTER_MODELS=google/gemini-2.0-flash-exp:free,deepseek/deepseek-chat:free,meta-llama/llama-3.3-70b-instruct:free,mistralai/mistral-7b-instruct:free\n ```\n\n---\n\n## 3. Image-gen providers (optional but recommended)\n\n`IMAGE_PROVIDERS` is comma-separated, tried in order. Pollinations alone works; adding the others gives resilience.\n\n### 3.1 Pollinations.ai (no setup)\n\nAlready free, no account. Add `pollinations` to `IMAGE_PROVIDERS`.\n\n### 3.2 Hugging Face Inference\n\n1. https://huggingface.co/join → sign up (or use existing account).\n2. https://huggingface.co/settings/tokens → \"New token\", role \"Read\".\n3. Save as `HUGGINGFACE_API_TOKEN`.\n4. Add `huggingface` to `IMAGE_PROVIDERS`.\n\n### 3.3 Cloudflare Workers AI\n\n1. https://dash.cloudflare.com → sign up.\n2. **Account home** → copy the Account ID from the right rail → `CLOUDFLARE_ACCOUNT_ID`.\n3. **My Profile → API Tokens → Create Token → Custom token**:\n - Permissions: `Account → Workers AI → Read`.\n - Account Resources: include your account.\n4. Save the token as `CLOUDFLARE_API_TOKEN`.\n5. Add `cloudflare` to `IMAGE_PROVIDERS`.\n\nRecommended order: `pollinations,huggingface,cloudflare`. Pollinations is fastest; HF and CF are sturdier when Pollinations slows down.\n\n---\n\n## 4. Vercel\n\n### 4.1 Import the project\n\n1. https://vercel.com → \"Add New\" → \"Project\".\n2. Import the GitHub repo.\n3. Configure:\n - **Framework Preset**: Other (auto-detected from `vercel.json`).\n - **Root Directory**: `.` (repo root — leave default).\n - **Build / Output / Install** commands: leave at defaults; `vercel.json` overrides them.\n4. Don't deploy yet — set env vars first.\n\n### 4.2 Environment variables\n\nVercel → Project → Settings → Environment Variables. Add each, mark for Production + Preview + Development:\n\n```\nSERVICE_MODE=production\n\nSUPABASE_URL=\nSUPABASE_SERVICE_ROLE_KEY=\nVITE_SUPABASE_URL=\nVITE_SUPABASE_ANON_KEY=\n\nOPENROUTER_API_KEY=\nOPENROUTER_MODELS=\n\nIMAGE_PROVIDERS=pollinations,huggingface,cloudflare\nHUGGINGFACE_API_TOKEN=\nCLOUDFLARE_ACCOUNT_ID=\nCLOUDFLARE_API_TOKEN=\n\n# Weekly digest email (Vercel Cron → /api/_cron/weekly-stats)\nRESEND_API_KEY=\nEMAIL_FROM=atavola <no-reply@atavola.ch>\nCRON_SECRET=\nSTATS_EMAIL_RECIPIENTS=\n```\n\nNotes:\n\n- `EMAIL_FROM` is the sender for direct Resend HTTP calls (currently just the weekly digest). It's separate from the Supabase Auth → SMTP settings; magic-link mail still uses whatever you wired up there. The domain in `EMAIL_FROM` must be verified in Resend.\n- `CRON_SECRET` is what Vercel signs cron requests with as `Authorization: Bearer …`. Set it both in the env-vars block above **and** in **Settings → Cron Jobs** for the project. Mismatch → the cron 401s.\n- `STATS_EMAIL_RECIPIENTS` is a comma-separated list. Empty = the cron runs but skips sending (handy in staging).\n- The schedule itself lives in [`vercel.json`](vercel.json) (`crons` block). Default is every Monday 09:00 UTC; change it there and redeploy.\n- `VITE_API_URL` is intentionally absent — same-origin defaults to `/api` and that's correct here.\n\n### 4.3 Deploy\n\nClick \"Deploy\". ~1–2 minutes. Note the URL (e.g. `https://atavola.vercel.app`).\n\n### 4.4 Loop the URL back to Supabase\n\nGo back to **Supabase → Authentication → URL Configuration**: set Site URL and Redirect URL to the Vercel URL.\n\nIf you later add a custom domain, repeat this step with the new domain.\n\n---\n\n## 5. Smoke test\n\n```bash\ncurl https://your-app.vercel.app/api/health\n# → {\"status\":\"ok\",\"mode\":\"production\",...}\n```\n\nIn a private window:\n\n1. Open the Vercel URL.\n2. Sign in with magic link (check your inbox — see \"Resend domain\" gotcha below).\n3. Create a session: vibe + headcount + no pre-selected meals.\n4. Wait for the 4 cards to appear with images.\n5. Click \"Share voting link\". Try the **native share** button (mobile only) and the **copy link** button. Tapping the URL pill itself also copies. Try the **QR code**.\n6. In another private window, open the link, swipe through the 4 cards.\n7. In the host window, watch the live results re-rank.\n8. Heart a card → it appears under \"My Food\" in your profile dropdown.\n9. Reload the profile, drag to reorder, swipe a row left to delete.\n\nIf any step fails, check Vercel function logs (Project → Logs → click a function) and the browser console.\n\n---\n\n## 6. Custom domain (optional)\n\n1. Buy at any registrar (Cloudflare Registrar is at-cost).\n2. Vercel → Project → Settings → Domains → Add. Follow Vercel's DNS instructions.\n3. Once active, update **Supabase → Authentication → URL Configuration** to use the new domain.\n4. Update **Resend → Domains** to verify the new sending domain so magic-link emails work for everyone, not just yourself.\n\nNo code changes needed — the app reads URLs from env / runtime, never hardcoded.\n\n---\n\n## 7. Email deliverability (SPF / DKIM / DMARC)\n\nMagic-link delivery is the silent growth lever — if mail lands in spam, sign-ups die without you noticing. Resend's dashboard walks you through the DNS records; here's what to set on the apex domain (`atavola.ch`):\n\n1. **SPF** (`TXT @`) — tells receivers which servers may send mail \"as\" your domain.\n ```\n v=spf1 include:_spf.resend.com ~all\n ```\n `~all` (soft fail) is the right choice while you stabilise; switch to `-all` (hard fail) once you're confident no other systems send mail from atavola.ch.\n\n2. **DKIM** — Resend auto-generates a CNAME pair when you verify the domain. Copy them into DNS verbatim. They look like:\n ```\n resend._domainkey CNAME resend._domainkey.<region>.amazonses.com\n ```\n Wait for Resend to flip the domain status to \"verified\" (usually <10 min).\n\n3. **DMARC** (`TXT _dmarc`) — tells receivers what to do with mail that fails SPF / DKIM and where to report aggregate failures.\n ```\n v=DMARC1; p=none; rua=mailto:no-reply@atavola.ch; pct=100; aspf=r; adkim=r\n ```\n Start with `p=none` so you collect reports without rejecting legitimate mail. After a week of clean reports, tighten to `p=quarantine`, then `p=reject`.\n\n4. **Reverse DNS** is handled by Resend on their sending IPs — nothing for you to configure.\n\n5. **Verify**: send a magic-link to a Gmail address and inspect the headers. You want `dkim=pass`, `spf=pass`, `dmarc=pass`. [https://www.mail-tester.com/](https://www.mail-tester.com/) gives a 0–10 score; aim for 9+.\n\nThe same Resend domain powers the weekly digest (`/api/_cron/weekly-stats`). `EMAIL_FROM` must use this verified domain or sends bounce silently.\n\n---\n\n## 8. SEO submission\n\nThe static landing page, `/about`, `/privacy`, and `/terms` ship with full meta and JSON-LD; `robots.txt`, `sitemap.xml`, `llms.txt`, and `humans.txt` live in `frontend/public/`. After first deploy:\n\n1. **Google Search Console** — [https://search.google.com/search-console](https://search.google.com/search-console). Add the property (use DNS verification on atavola.ch so all subdomains are covered). Submit `https://atavola.ch/sitemap.xml`. Indexing usually starts within hours.\n2. **Bing Webmaster Tools** — [https://www.bing.com/webmasters](https://www.bing.com/webmasters). Same flow; Bing also feeds DuckDuckGo results.\n3. **Yandex Webmaster** (optional, only if you care about RU/CIS traffic).\n4. **Sitemap auto-discovery** — `robots.txt` already references the sitemap. Most crawlers find it automatically; the manual submission above just shortens the discovery window.\n5. **Validate**:\n - [Google Rich Results Test](https://search.google.com/test/rich-results) — paste each of `/`, `/about`, `/privacy`, `/terms` and confirm the JSON-LD blocks parse.\n - [Twitter / X Card Validator](https://cards-dev.twitter.com/validator) — paste the home URL, confirm the OG image renders.\n - [LinkedIn Post Inspector](https://www.linkedin.com/post-inspector/) — same.\n - [Facebook Sharing Debugger](https://developers.facebook.com/tools/debug/) — same. Click \"Scrape Again\" if cached.\n - [securityheaders.com](https://securityheaders.com/) and [Mozilla Observatory](https://observatory.mozilla.org/) — both should score A or A+.\n - [PageSpeed Insights](https://pagespeed.web.dev/) — Core Web Vitals must be green for ranking.\n\n---\n\n## Common pitfalls\n\n| Symptom | Cause | Fix |\n|---|---|---|\n| Function logs say \"Refusing to boot: SERVICE_MODE=…\" | Vercel env var unset or misspelled | Set `SERVICE_MODE=production` exactly. The hard fail is intentional — see [SECURITY.md](SECURITY.md) #C1. |\n| Magic-link email never arrives for anyone but you | Resend domain not verified | Verify the domain in Resend, then point Supabase SMTP at it. Until verified, only your own address receives mail. |\n| 401 on every call | RLS denies because policies didn't run | Re-apply both migrations — verify policies under `Authentication → Policies`. |\n| `cast_vote` errors with `session_expired` or `session_not_open` | Session is older than 30 days or status moved off `voting` | Hosts create a new session; expiry is enforced server-side and the daily pg_cron purges past it anyway. |\n| `cast_vote` errors with `invalid_guest_token` | Guest hasn't been minted | Frontend should call `POST /api/votes/guest` once before voting (it does — clear localStorage and reload if stale). |\n| 429 on rate-limited endpoints | The Postgres rate-limit RPC threw | Check function logs for `[checkRate] RPC failed` warnings. The limiter fails open to avoid locking real users out, but a sustained warning means you're effectively unprotected — fix the underlying DB error. |\n| Generated images broken | Every image provider failed magic-byte sniff or quota | Inspect the function log — provider names + reasons are logged. Most common: wrong CF/HF key, or HF model warming up (just retry). |\n| Meal generation 503s | All OpenRouter free models hit their daily quota | Add more models to `OPENROUTER_MODELS`; rotation will skip past empty ones. |\n| Vercel function times out | LLM + 4 image fetches > 60 s | Drop a slow provider from `IMAGE_PROVIDERS` (HF when its servers are loading). Long-term: Vercel Pro = 300 s, or split generation into per-meal endpoints (out of scope today). |\n\n<!-- Made with Bob -->\n"}, "48641413b9fa4ad8d3aa40554a368ab1656e6828": {"t": 1785765365.6674185, "n": 14416, "h": "3ca9e88e056919f9", "c": "# 🚀 Group Food Tinder - Complete Deployment Guide\n\n## 📋 Overview\n\nThis guide will help you deploy your Group Food Tinder app online using **free services**. Yes, you can use Render's free tier!\n\n### What You'll Deploy\n- **Frontend**: React app (Vercel Free)\n- **Backend**: Node.js API (Render Free)\n- **Database**: Firebase Realtime Database (Free tier)\n- **AI**: OpenAI API (Pay-as-you-go, ~$0.002 per request)\n- **Email**: Gmail SMTP (Free)\n\n### Total Cost\n- **Development/Testing**: $0 (using mock mode)\n- **Production**: ~$5-10/month (mostly OpenAI usage)\n\n---\n\n## 🎯 Deployment Strategy\n\n### Option 1: Quick Demo (Recommended for Hackathon)\n**Time: 30 minutes**\n- Deploy with mock mode (no API keys needed)\n- Perfect for demos and testing\n- Zero cost\n\n### Option 2: Full Production\n**Time: 2 hours**\n- All services configured\n- Real AI generation\n- Email notifications\n- Best for actual use\n\n---\n\n## 📝 Step-by-Step Deployment Plan\n\n### Phase 1: Get Your API Keys (1 hour)\n\n#### 1.1 Firebase Setup (15 min)\n**What it does**: Database for sessions, votes, and user data\n\n**Steps:**\n1. Go to [Firebase Console](https://console.firebase.google.com)\n2. Click \"Add project\"\n3. Name it: `group-food-tinder`\n4. Disable Google Analytics (optional)\n5. Click \"Create project\"\n\n**Enable Realtime Database:**\n1. In left sidebar → Build → Realtime Database\n2. Click \"Create Database\"\n3. Choose location: `us-central1` (or closest to you)\n4. Start in **test mode** (we'll secure it later)\n5. Click \"Enable\"\n\n**Get Service Account Credentials:**\n1. Click gear icon → Project settings\n2. Go to \"Service accounts\" tab\n3. Click \"Generate new private key\"\n4. Download the JSON file\n5. Open it and copy these values:\n - `project_id` → FIREBASE_PROJECT_ID\n - `private_key` → FIREBASE_PRIVATE_KEY\n - `client_email` → FIREBASE_CLIENT_EMAIL\n\n**Security Rules (Important!):**\n```json\n{\n \"rules\": {\n \"sessions\": {\n \"$sessionId\": {\n \".read\": true,\n \".write\": \"auth != null || data.exists()\"\n }\n },\n \"votes\": {\n \"$sessionId\": {\n \".read\": true,\n \".write\": true\n }\n }\n }\n}\n```\n\n**Cost**: Free up to 1GB storage, 10GB/month downloads\n\n---\n\n#### 1.2 OpenAI API Setup (10 min)\n**What it does**: Generates creative meal suggestions\n\n**Steps:**\n1. Go to [OpenAI Platform](https://platform.openai.com)\n2. Sign up or log in\n3. Click your profile → \"View API keys\"\n4. Click \"Create new secret key\"\n5. Name it: `group-food-tinder`\n6. Copy the key (starts with `sk-`)\n7. **IMPORTANT**: Save it immediately (you can't see it again!)\n\n**Add Credits:**\n1. Go to Settings → Billing\n2. Add payment method\n3. Set usage limit: $5/month (recommended for testing)\n\n**Cost**: \n- GPT-3.5-turbo: ~$0.002 per meal generation\n- 100 sessions = ~$0.20\n- Recommended budget: $5-10/month\n\n---\n\n#### 1.3 Gmail SMTP Setup (10 min)\n**What it does**: Sends magic link emails for authentication\n\n**Steps:**\n1. Go to [Google Account](https://myaccount.google.com)\n2. Enable 2-Factor Authentication:\n - Security → 2-Step Verification → Get Started\n3. Generate App Password:\n - Security → 2-Step Verification → App passwords\n - Select app: \"Mail\"\n - Select device: \"Other\" → Name it \"Group Food Tinder\"\n - Click \"Generate\"\n - Copy the 16-character password\n\n**Alternative Email Services:**\n- **SendGrid**: 100 emails/day free\n- **Mailgun**: 5,000 emails/month free\n- **AWS SES**: 62,000 emails/month free (if on AWS)\n\n**Cost**: Free (Gmail has daily limits ~500 emails/day)\n\n---\n\n#### 1.4 JWT Secrets (5 min)\n**What it does**: Secure token generation\n\n**Generate Random Secrets:**\n```bash\n# Run these commands in your terminal\nnode -e \"console.log(require('crypto').randomBytes(64).toString('hex'))\"\n# Copy output for JWT_SECRET\n\nnode -e \"console.log(require('crypto').randomBytes(64).toString('hex'))\"\n# Copy output for MAGIC_LINK_SECRET\n```\n\n---\n\n### Phase 2: Deploy Backend to Render (30 min)\n\n#### 2.1 Prepare Backend for Deployment (10 min)\n\n**1. Create `backend/.env.production` file:**\n```env\n# Server Configuration\nPORT=3000\nNODE_ENV=production\nFRONTEND_URL=https://your-app.vercel.app\n\n# Service Mode\nSERVICE_MODE=production\n\n# Firebase Admin SDK\nFIREBASE_PROJECT_ID=your_project_id\nFIREBASE_PRIVATE_KEY=\"-----BEGIN PRIVATE KEY-----\\nYour private key here\\n-----END PRIVATE KEY-----\\n\"\nFIREBASE_CLIENT_EMAIL=firebase-adminsdk-xxxxx@your_project_id.iam.gserviceaccount.com\n\n# OpenAI API\nOPENAI_API_KEY=sk-your_openai_api_key_here\n\n# Email Configuration\nSMTP_HOST=smtp.gmail.com\nSMTP_PORT=587\nSMTP_SECURE=false\nSMTP_USER=your_email@gmail.com\nSMTP_PASS=your_16_char_app_password\nEMAIL_FROM=noreply@groupfoodtinder.com\n\n# JWT Configuration\nJWT_SECRET=your_generated_jwt_secret_here\nJWT_EXPIRES_IN=7d\n\n# Magic Link Configuration\nMAGIC_LINK_SECRET=your_generated_magic_link_secret_here\nMAGIC_LINK_EXPIRES_IN=15m\n\n# Logging\nLOG_LEVEL=info\n```\n\n**2. Verify `backend/package.json` has these scripts:**\n```json\n{\n \"scripts\": {\n \"start\": \"node dist/index.js\",\n \"build\": \"tsc\",\n \"dev\": \"nodemon src/index.ts\"\n }\n}\n```\n\n---\n\n#### 2.2 Deploy to Render (20 min)\n\n**Why Render?**\n- ✅ Free tier available\n- ✅ Automatic deployments from GitHub\n- ✅ Easy environment variable management\n- ✅ Built-in SSL certificates\n\n**Steps:**\n\n1. **Push to GitHub** (if not already done):\n ```bash\n git add .\n git commit -m \"Prepare for deployment\"\n git push origin neon\n ```\n\n2. **Sign up for Render**:\n - Go to [Render.com](https://render.com)\n - Sign up with GitHub\n - Authorize Render to access your repositories\n\n3. **Create New Web Service**:\n - Click \"New +\" → \"Web Service\"\n - Connect your GitHub repository\n - Select `ibm-bob-hackathon` repo\n - Click \"Connect\"\n\n4. **Configure Service**:\n ```\n Name: group-food-tinder-api\n Region: Oregon (US West) or closest to you\n Branch: neon\n Root Directory: backend\n Runtime: Node\n Build Command: npm install && npm run build\n Start Command: npm start\n Instance Type: Free\n ```\n\n5. **Add Environment Variables**:\n - Click \"Environment\" tab\n - Click \"Add Environment Variable\"\n - Add ALL variables from your `.env.production` file\n - **IMPORTANT**: For `FIREBASE_PRIVATE_KEY`, paste the entire key including `\\n` characters\n\n6. **Deploy**:\n - Click \"Create Web Service\"\n - Wait 5-10 minutes for deployment\n - Your API will be at: `https://group-food-tinder-api.onrender.com`\n\n**Render Free Tier Limitations:**\n- ⚠️ Spins down after 15 minutes of inactivity\n- ⚠️ First request after spin-down takes 30-60 seconds\n- ⚠️ 750 hours/month free (enough for one service)\n- ✅ Perfect for demos and low-traffic apps\n\n**Pro Tip**: Keep your service alive with a cron job:\n```bash\n# Use cron-job.org to ping your API every 14 minutes\ncurl https://group-food-tinder-api.onrender.com/api/health\n```\n\n---\n\n### Phase 3: Deploy Frontend to Vercel (20 min)\n\n#### 3.1 Prepare Frontend (5 min)\n\n**1. Create `frontend/.env.production`:**\n```env\nVITE_API_URL=https://group-food-tinder-api.onrender.com/api\n```\n\n**2. Update `frontend/vite.config.ts` (if needed):**\n```typescript\nimport { defineConfig } from 'vite'\nimport react from '@vitejs/plugin-react'\n\nexport default defineConfig({\n plugins: [react()],\n server: {\n port: 5173\n },\n build: {\n outDir: 'dist',\n sourcemap: false\n }\n})\n```\n\n---\n\n#### 3.2 Deploy to Vercel (15 min)\n\n**Why Vercel?**\n- ✅ Free tier for personal projects\n- ✅ Automatic HTTPS\n- ✅ Global CDN\n- ✅ Instant deployments\n- ✅ Perfect for React apps\n\n**Steps:**\n\n1. **Sign up for Vercel**:\n - Go to [Vercel.com](https://vercel.com)\n - Sign up with GitHub\n - Authorize Vercel\n\n2. **Import Project**:\n - Click \"Add New...\" → \"Project\"\n - Import `ibm-bob-hackathon` repository\n - Click \"Import\"\n\n3. **Configure Project**:\n ```\n Framework Preset: Vite\n Root Directory: frontend\n Build Command: npm run build\n Output Directory: dist\n Install Command: npm install\n ```\n\n4. **Add Environment Variables**:\n - Click \"Environment Variables\"\n - Add: `VITE_API_URL` = `https://group-food-tinder-api.onrender.com/api`\n - Click \"Add\"\n\n5. **Deploy**:\n - Click \"Deploy\"\n - Wait 2-3 minutes\n - Your app will be at: `https://your-app.vercel.app`\n\n6. **Update Backend CORS**:\n - Go back to Render dashboard\n - Update `FRONTEND_URL` environment variable\n - Set to: `https://your-app.vercel.app`\n - Redeploy backend\n\n---\n\n### Phase 4: Testing & Verification (20 min)\n\n#### 4.1 Test Checklist\n\n**Backend Health Check:**\n```bash\ncurl https://group-food-tinder-api.onrender.com/api/health\n# Should return: {\"status\":\"ok\",\"mode\":\"production\"}\n```\n\n**Frontend Access:**\n1. Open `https://your-app.vercel.app`\n2. Should see landing page\n3. Check browser console for errors\n\n**Full Flow Test:**\n1. ✅ Request magic link (check email)\n2. ✅ Click magic link (should authenticate)\n3. ✅ Create session with vibe and headcount\n4. ✅ AI generates meals (wait 30-60 seconds)\n5. ✅ Share link works\n6. ✅ Voting interface loads\n7. ✅ Swipe cards work\n8. ✅ Winner displays correctly\n\n---\n\n#### 4.2 Common Issues & Fixes\n\n**Issue: Backend takes 30+ seconds to respond**\n- **Cause**: Render free tier cold start\n- **Fix**: Normal behavior, or upgrade to paid tier ($7/month)\n\n**Issue: CORS errors in browser**\n- **Cause**: FRONTEND_URL mismatch\n- **Fix**: Update FRONTEND_URL in Render to match Vercel URL exactly\n\n**Issue: Firebase permission denied**\n- **Cause**: Security rules too strict\n- **Fix**: Update Firebase rules (see Phase 1.1)\n\n**Issue: OpenAI rate limit errors**\n- **Cause**: Too many requests\n- **Fix**: Add rate limiting or upgrade OpenAI plan\n\n**Issue: Email not sending**\n- **Cause**: Gmail app password incorrect\n- **Fix**: Regenerate app password, update SMTP_PASS\n\n---\n\n### Phase 5: Custom Domain (Optional, 10 min)\n\n#### 5.1 Add Custom Domain to Vercel\n\n**Steps:**\n1. Buy domain from Namecheap, GoDaddy, etc. (~$10/year)\n2. In Vercel dashboard → Settings → Domains\n3. Add your domain: `groupfoodtinder.com`\n4. Follow DNS configuration instructions\n5. Wait 24-48 hours for DNS propagation\n\n**Free Domain Options:**\n- Use Vercel's free subdomain: `your-app.vercel.app`\n- Use Render's free subdomain: `your-api.onrender.com`\n\n---\n\n## 🎭 Deployment Modes Comparison\n\n### Mock Mode (Free, Instant)\n```bash\n# Deploy with mock mode for demos\nSERVICE_MODE=mock\n```\n**Pros:**\n- ✅ Zero cost\n- ✅ No API keys needed\n- ✅ Instant deployment\n- ✅ Perfect for demos\n\n**Cons:**\n- ❌ No real AI generation\n- ❌ No email sending\n- ❌ Data resets on restart\n\n---\n\n### Production Mode (Paid, Full Features)\n```bash\nSERVICE_MODE=production\n```\n**Pros:**\n- ✅ Real AI meal generation\n- ✅ Email notifications\n- ✅ Persistent data\n- ✅ Production-ready\n\n**Cons:**\n- ❌ Requires API keys\n- ❌ ~$5-10/month cost\n- ❌ More setup time\n\n---\n\n## 💰 Cost Breakdown\n\n### Free Tier (Mock Mode)\n| Service | Cost | Limits |\n|---------|------|--------|\n| Render | $0 | 750 hours/month, cold starts |\n| Vercel | $0 | 100GB bandwidth/month |\n| **Total** | **$0/month** | Perfect for demos |\n\n### Production Tier\n| Service | Cost | Usage |\n|---------|------|-------|\n| Render | $0 | Free tier |\n| Vercel | $0 | Free tier |\n| Firebase | $0 | Free tier (1GB storage) |\n| OpenAI | ~$5-10 | ~2,500 meal generations |\n| Gmail | $0 | Free (500 emails/day limit) |\n| **Total** | **~$5-10/month** | Light usage |\n\n### Scaling Costs\n| Users/Month | OpenAI Cost | Render Cost | Total |\n|-------------|-------------|-------------|-------|\n| 100 | ~$2 | $0 | ~$2 |\n| 1,000 | ~$20 | $7 | ~$27 |\n| 10,000 | ~$200 | $25 | ~$225 |\n\n---\n\n## 🚀 Quick Deploy Commands\n\n### Deploy Everything (Mock Mode)\n```bash\n# 1. Push to GitHub\ngit add .\ngit commit -m \"Deploy to production\"\ngit push origin neon\n\n# 2. Deploy backend to Render\n# (Use Render dashboard - see Phase 2)\n\n# 3. Deploy frontend to Vercel\n# (Use Vercel dashboard - see Phase 3)\n```\n\n### Update Deployment\n```bash\n# Just push to GitHub - auto-deploys!\ngit add .\ngit commit -m \"Update feature\"\ngit push origin neon\n```\n\n---\n\n## 📊 Monitoring & Maintenance\n\n### Check Backend Logs\n1. Go to Render dashboard\n2. Click your service\n3. Click \"Logs\" tab\n4. Monitor for errors\n\n### Check Frontend Logs\n1. Go to Vercel dashboard\n2. Click your project\n3. Click \"Deployments\"\n4. Click latest deployment → \"View Function Logs\"\n\n### Set Up Alerts\n**Render:**\n- Settings → Notifications\n- Add email for deployment failures\n\n**Vercel:**\n- Settings → Notifications\n- Enable deployment notifications\n\n---\n\n## 🔒 Security Checklist\n\n- [ ] All API keys in environment variables (not in code)\n- [ ] Firebase security rules configured\n- [ ] CORS properly configured\n- [ ] JWT secrets are random and secure\n- [ ] HTTPS enabled (automatic on Vercel/Render)\n- [ ] Rate limiting enabled\n- [ ] Input validation on all endpoints\n- [ ] No sensitive data in logs\n\n---\n\n## 🎯 Recommended Deployment Path\n\n### For Hackathon Demo (30 min)\n1. ✅ Deploy backend in **mock mode** to Render\n2. ✅ Deploy frontend to Vercel\n3. ✅ Test basic flow\n4. ✅ Present demo\n\n### For Production (2 hours)\n1. ✅ Get all API keys (Phase 1)\n2. ✅ Deploy backend in **production mode** to Render\n3. ✅ Deploy frontend to Vercel\n4. ✅ Test full flow with real services\n5. ✅ Monitor and optimize\n\n---\n\n## 📞 Support & Resources\n\n### Documentation\n- [Render Docs](https://render.com/docs)\n- [Vercel Docs](https://vercel.com/docs)\n- [Firebase Docs](https://firebase.google.com/docs)\n- [OpenAI Docs](https://platform.openai.com/docs)\n\n### Troubleshooting\n- Check backend logs in Render\n- Check frontend logs in Vercel\n- Check browser console for errors\n- Test API endpoints with Postman/curl\n\n### Community\n- GitHub Issues\n- Stack Overflow\n- Discord/Slack channels\n\n---\n\n## ✅ Final Checklist\n\n### Before Demo\n- [ ] Backend deployed and responding\n- [ ] Frontend deployed and accessible\n- [ ] Test magic link authentication\n- [ ] Test session creation\n- [ ] Test voting flow\n- [ ] Test winner determination\n- [ ] Prepare backup screenshots\n- [ ] Practice demo script\n\n### After Demo\n- [ ] Monitor usage and costs\n- [ ] Set up error tracking (Sentry)\n- [ ] Add analytics (Google Analytics)\n- [ ] Collect user feedback\n- [ ] Plan improvements\n\n---\n\n## 🎉 You're Ready to Deploy!\n\n**Next Steps:**\n1. Choose your deployment mode (mock or production)\n2. Follow the step-by-step guide above\n3. Test thoroughly\n4. Share your app with the world!\n\n**Questions?** Check the troubleshooting section or open an issue on GitHub.\n\n**Good luck with your deployment! 🚀**"}}}}