ΰΈΰΈ£ΰΉΰΈΰΈ’ ΰΈΰΈ²ΰΈ‘ ΰΈ₯ΰΈ³ΰΈΰΈ±ΰΈ β Delicious in Order
Live Long to Eat Well Β· ΰΈΰΈ’ΰΈΉΰΉΰΉΰΈΰΈ·ΰΉΰΈΰΈΰΈ΄ΰΈΰΈΰΈ³ΰΈΰΈ²ΰΈ
Shinny Guide is an AI-powered food sequencing app that helps you eat Thai food (and any cuisine) in the right order to reduce blood sugar spikes by up to 70%* . Based on real science β eat Veggies β Protein β Carbs β Sweets.
*Based on a Weill Cornell Medical Center study (Shukla et al., 2015) in type-2 diabetes patients. Individual results vary. Citation surfaced inline on the homepage so first-time visitors don't have to wonder where the number came from β see UX-audit Round 7 iter 9 (PR #53).
Meet Shinny (ΰΈΰΈ΄ΰΈΰΈΰΈ΅ΰΉ), your AI food coach who guides you through every meal!
| Feature | Free | Premium (ΰΈΏ199/mo) | Family (ΰΈΏ299/mo) |
|---|---|---|---|
| AI Scan Modes | Meal only | Meal, Menu, Drink/Snack | Meal, Menu, Drink/Snack |
| Food Scanning | 10 scans/mo (up to 3 photos) | β (up to 10 photos) | β (up to 10 photos) |
| Health Score | 3 dimensions | 8 dimensions | 8 dimensions |
| AI Coach Shinny | 3 Q/day | β | β |
| Custom AI Avatar | β | β (Dynamic Postures) | β |
| Thai Recipes | 100 | 1,000+ | 1,000+ |
| Meal Planning | β | β | β |
| Family Members | β | β | 5 |
| Data Export | β | β | β |
Every page shares the same <SiteHeader/> (brand mark + Scan/Recipes/Pricing nav + LanguageSwitcher + auth-aware Login/Dashboard CTA + mobile hamburger drawer). Added in UX-audit Round 7 iter 3 (PR #48) to replace the inconsistent per-page "Back to home" stubs that fresh users hit before β see docs/KNOWLEDGE_BASE.md β Fresh-user audit loop.
- Home β Landing page with concept explanation, features, "free / no signup needed" reassurance, and the canonical primary CTA (Start your scan).
- Scan β AI food analysis with 3 dynamic modes (Meal, Menu, Drink/Snack), sequence visualization & 8-dimension scoring. Surfaces a small privacy note under the upload zone ("photos analyzed in real time, never stored on a server unless you save them"). Features an advanced Debug UI via
?debug=1. - Demo β Interactive walkthrough of food sequencing with blood sugar curves.
- Login β Email + password authentication, voucher field in the register form, and a register-as-default tab for fresh visitors. Google + LINE social-login buttons are rendered as disabled with a visible "Soon" badge (Round 7 iter 6, PR #50) β the buttons signal a future feature without lying about current state. OAuth backend not yet wired (tracked in
docs/KNOWN_ISSUES.mditem 1a). - Dashboard β Member stats, streak tracking, daily challenges, gamification.
- Pricing β Tier comparison with monthly/annual toggle, FAQ, and an expandable disclosure listing all 8 health-score dimensions with one-line explanations and badges marking the 3 the Free tier unlocks (Round 7 iter 5, PR #49). Lets fresh visitors evaluate "is the 8-dim score worth upgrading?" without signing up and scanning a meal first.
- Recipes β Placeholder while the real recipe catalogue ships. The page used to be a bare "coming soon" stub; Round 7 iter 4 (PR #48) replaced that with a Shinny-voiced empty state and a Scan CTA so users don't dead-end.
- Chat (
/[locale]/chat, login-required) β AI Coach Shinny. Conversational nutrition coaching backed by a free-tier provider cascade: Groq Llama 3.3 70B (primary, sub-300ms) β Gemini 1.5 Flash β Cloudflare Workers AI safety net. Tier-quota gated (aiQuestionsPerDay: free=3, premium=β). Locale-aware persona that bakes the brand's three rules: never forbid food, warm older-sister tone, stay in food/nutrition scope. See Tech Stack for the provider chain. - Admin (
/[locale]/admin, restricted) β operator console: quick-stat overview, user management (toggle admin, flip subscription tier), promo-code CRUD, and a health-check view. Gated server-side by theisAdmincolumn onusers; non-admins are silently redirected to/login. Seedocs/ADMIN_BOOTSTRAP.mdfor how to create the first admin viawrangler(the schema ships with zero admins β no accidental admin via public sign-up).
First-impression UX + audit posture: Rounds 7 β 11 (MayβJune 2026) iterated on the product through four lenses β fresh-user reading, live Playwright against the deploy, authed-surface walkthrough with a session cookie, and a Web-Vitals + accessibility inventory. The biggest deltas:
- Round 7 canonicalized CTAs, added a citation footnote to the homepage "70%" claim, shipped a persistent
<SiteHeader/>on every page.- Round 8 caught
/chatbeing unreachable on hard-load for logged-in users (auth-store probe race) + a console-401 on every anonymous page load.- Rounds 9β10 swept 245 lines of unwired frontend code + 25 orphan i18n keys (Γ 4 locales) + two dead SQLAlchemy models, and fixed an iPhone-SE 375px overflow on
/pricing.- Round 11 dropped homepage FCP from 2272ms to 620ms (β73%) by removing two never-applied Google Font families, added
<main>landmarks across all public pages (WCAG 2.1 AA), and stood up the first CI workflow on the repo.- Round 12 added the automated full user-journey spec (
tests/e2e/user-journey.spec.ts) β landing + locale switch, photo upload β AI analyze β terminal result, registration round-trip, recipes/chat/dashboard browse β all driven through the rendered UI against production. Building it surfaced an intermittent client-side error-boundary crash after the register response (now pinned by a sentinel assertion) and the silent sub-500-byte file rejection in the upload hook. Full play-by-play inCHANGELOG.mdanddocs/KNOWLEDGE_BASE.md β Fresh-user audit loop / Live-deploy + authed-surface audit / Backend role clarified / Full user-journey e2e.
Two scopes of code, managed from /admin/promo:
| Scope | Used at | Purpose |
|---|---|---|
| Registration vouchers | sign-up form | Pilot-launch invite codes β required to create an account when VOUCHER_REQUIRED_FOR_REGISTRATION=true is set on Cloudflare. Flag is off by default; flip via wrangler when ready. |
| Upgrade promos | logged-in user, "Have a promo?" field | Tier-upgrade codes (SHINNY2024, EATWELL, etc.). Existing flow, unchanged. |
Each code carries:
- Kind: Personal (single-use) or Organization (N-seat cohort).
- Time limit: optional expiry date; un-set means perpetual.
- Usage limit:
usageLimitcap;usageCountshown live as a fill bar in the admin table. - Active toggle: instant deactivation without affecting already-granted redemptions.
- Notes: free-text admin label (e.g. "Summer pilot β 50 seats for Chula nutrition dept").
Built-in seeded promos (legacy upgrade scope):
SHINNY2024β 30 days Premium (Shinny Fanclub)EATWELLβ 7-day Premium trialLAUNCH50β 50% off first monthFAMILY2024β 14-day Family trial
See docs/ADMIN_BOOTSTRAP.md for how to enable
voucher-only registration during pilot launch.
Nutri-Vision AI uses a strict Identify-First methodology powered by a highly resilient Dual-Provider Fallback Strategy.
- Primary: Google Gemini Flash cascade β
gemini-2.5-flashβgemini-2.0-flashβgemini-1.5-flash(multimodal, free tier ~1500 req/day per model per project).attemptGoogleInferencewalksGEMINI_VISION_MODELSinlib/ai-providers.ts, returning the first model that responds 200. Skips on 404 (model retired) or 429 (per-model quota exhausted) and falls through to the next id. Same Google key covers scan + chat; chat usesGEMINI_VISION_MODELS[0]for single-source-of-truth alignment.- We pin to explicit model ids (no
-latestaliases) β Google retiredgemini-1.5-flash-latestfromv1betain May 2026 without notice. We also cascade rather than hardcode a single id β Google later silently dropped this project'sgemini-2.0-flashfree quota tolimit: 0whilegemini-2.5-flashon the same key still had quota.
- Safety-net fallback: Cloudflare
@cf/meta/llama-3.2-11b-vision-instruct(multimodal, free with the Pages plan).- Runs only when the Gemini cascade exhausts (all three models 429/404'd) or returns garbage. Bug-hunt May 2026 surfaced that CF Llama 3.2 11B vision is much weaker than Gemini on this task β it misidentifies common dishes ("Pineapple" for Shrimp Fried Rice with 100% confidence) and its JSON output is non-deterministic. Keeping it as the last-resort fallback gives users some result when Gemini is fully exhausted rather than a 503; for the routine happy path, Gemini's accuracy wins.
- The route auto-accepts the Meta Llama Community License on first 5016 (via
prompt: 'agree') so the fallback path doesn't require manual operator action in the CF dashboard.
The analysis pipeline is built for Edge Reliability:
- Client-side canvas compression (reduces 10MB photos to ~150KB), now with skipping double-compression for single photos.
- 10-Phase Fault-Tolerant Pipeline: Each stage (DB, Session, AI) is individually isolated in
try/catchblocks. The AI step features an Auto-Correction Loop using asafeParseJsonstrategy and preferred Google provider diversity on retry. - Server-side AI timeout (45s total) using
Promise.race, now with4096output tokens for complex multi-dish extraction. - Request Tracing & Telemetry: Every scan is unique-indexed. Use the
?debug=1query parameter on the scan page for real-time phase timings. - Strict JSON schema validation and graceful "Non-Food" detection rendering.
- Decoupled Architecture: Logic is fully isolated via custom hooks (
useScanUpload,useScanAnalysis,useScanDebug), ensuring a clean Orchestrator Page. - Multi-Photo Collage Engine: Client-side canvas stitching up to 10 photos, with Dynamic Canvas Scaling to prevent memory crashes on older phones (scales based on
navigator.deviceMemory). - Deployment Monitoring: Verify AI bindings, database status, and deployment freshness (commit SHA + branch from CF Pages build env) securely via the
/api/healthendpoint. Thedeployment.shaShortfield lets you eyeball "is this the commit I think it is?" in one curl:curl -s https://shinnyguide.autobahn.bot/api/health | jq .deployment - Per-IP Rate Limiting (
lib/rate-limit.ts): sliding-window throttle on every public POST surface β/api/auth/login(10/15min),/api/auth/register(3/15min),/api/voucher/check(30/min),/api/chat(20/min),/api/analyze. Primary store is a module-scopedMap(per-worker-instance, V8-heap-resident), chosen overcaches.defaultafter bug-hunt May 2026 found the Workers Cache API doesn't give same-millisecond read-after-write consistency in the OpenNext-on-Pages runtime β 40 parallel voucher probes against a 30/min limit had all returned 200. Tested with 5 enforcement cases that prove the limit actually engages, not just that the helper doesn't throw. - Uniform
Cache-Control: no-storeon every/api/*response vialib/api-response.ts β jsonResponse(). UX-audit round 2 (May 2026) caught that 12 of 13 API routes were shipping responses with noCache-Controlheader at all. Each route migrated; an invariant test walkssrc/app/api/**/route.tsat CI time and forbidsNextResponse.json(...)in live code so the class of bug can't reappear. - Security headers on every HTML page via
next.config.js β headers():X-Frame-Options: DENY(no iframe-embed use case),Referrer-Policy: strict-origin-when-cross-origin(stops leaking debug URLs to third-party CDNs),Permissions-Policylocking everything exceptcamera=(self)for the scan flow. HSTS +X-Content-Type-Options: nosniffalready set at the Cloudflare edge. - PWA install support via
src/app/manifest.tsβ served at/manifest.webmanifest. Brand-coloured (theme#ec7064, background#fff5f5), standalone-portrait display for the mobile-first scan flow. - Multi-locale sitemap at
/sitemap.xmlviasrc/app/sitemap.tsβ covers/,/scan,/demo,/pricing,/recipes,/loginacross all 4 locales withhreflangalternates so search engines understand/th/scanand/en/scanare translations of the same page, not duplicate content. Auth-gated routes (/dashboard,/chat,/admin/*) deliberately excluded β indexing them would point search users at a redirect-to-login. - Share-preview metadata + locale-aware 404 page:
og:image+twitter:imageensure social shares render a preview card (Shinny avatar viametadataBase-resolved absolute URLs).src/app/[locale]/not-found.tsxlocalizes the 404 experience across all 4 locales with anot_foundmessage namespace, plus two CTAs (Home + Scan) so users don't dead-end. - Playwright e2e suite at
frontend/tests/e2e/β 97 cases across 6 spec files (smoke,ui-ux,deep-probes,a11y,responsive-perf,user-journey) running against the live production URL. Pins everything Vitest unit tests can't reach:<link rel="alternate" hreflang>,og:image/twitter:image, locale-aware 404 body, sitemap XML,Cache-Controlon every API response, deployment SHA exposure, schema-failure shapes, icon-only-button accessible names,htmlFor/idlabel associations, color-scheme declaration, viewport breakpoints (incl. iPhone-SE 375px overflow), payload caps,<main>landmark on every public page (Round 11), no-wasted-preload guards, anonymous/api/auth/me200-probe contract, third-party-script whitelist. Round 12 addeduser-journey.spec.tsβ a 4-phase full real-user journey (landing + locale switch β photo upload β AI analyze β terminal result, registration round-trip, recipes/chat/dashboard browse) driven through the rendered UI, not API probes. Opt-in vianpm run test:e2e(needs@playwright/test+ Chromium browser). - Five-lens testing posture (Rounds 7 β 12): the project treats correctness as having five orthogonal lenses, each catching a class of bug the others miss.
- Unit (
vitest, code invariants) β 171 cases. - e2e (
playwright, rendered DOM behaviour against the live deploy) β 97 cases. - Fresh-user audit (human/AI walking through with zero context, catching editorial / honesty / IA bugs) β 9 fixes in Round 7, 9 in Round 8, 5 in Round 10β11.
- Web Vitals + accessibility inventory (Round 11) β driving real browsers + axe-style DOM checks to catch perf regressions (FCP/LCP/CLS) and WCAG 2.1 AA gaps (
<main>, h1 hierarchy, label associations) that don't show up as functional failures. - Full user-journey (Round 12) β one automated spec that walks the whole product end-to-end the way a real user would (upload a real file through the file input, click the rendered Analyze CTA, submit the real register form), asserting every flow reaches a terminal UI state rather than a spinner or crash. Catches integration-seam bugs (client timeout vs server budget, error-boundary engagement, silent file-rejection) that per-surface e2e probes miss.
Recipes for all five live in
docs/GUIDELINE.mdβ The fresh-user audit lens, The Web-Vitals + a11y inventory lens, and The full user-journey lens.
- Unit (
- GitHub Actions CI at
.github/workflows/ci.yml(added Round 11) β frontendcheck:all+ backendpyteston every PR. Before this there was no CI at all; the standalone backend went 6 weeks unverified between Round 8 and Round 9, long enough that Round 9 found 3 latent bcrypt failures + a phantompython-cors==1.0.0dep that brokepip installoutright. CI now catches that rot at PR time. The Playwright e2e suite stays opt-in (manual post-deploy step) so it doesn't false-positive against in-flight deploys.
- Frontend: Next.js 14, TypeScript, Tailwind CSS
- i18n: next-intl (π¬π§ EN, πΉπ TH, π©πͺ DE, π©π° DA)
- State: Zustand with persist middleware
- Database: Drizzle ORM + Cloudflare D1 (SQLite)
- Deploy: Cloudflare Pages + Workers
- AI vision (food scan): Google AI Gemini Flash cascade (
gemini-2.5-flashβgemini-2.0-flashβgemini-1.5-flash, fall-through on 404/429) β Cloudflare Workers AI (Llama 3.2 11B Vision) β accuracy-first cascade with multimodal fallback, locale-aware prompting. - AI chat (Coach Shinny): Groq (Llama 3.3 70B, free 30 req/min) β Google AI Gemini (
GEMINI_VISION_MODELS[0], ~1500 req/day) β Cloudflare Workers AI β three-stage cascade, free-tier-first. - Performance: Client-side image compression (HTML5 Canvas)
cd frontend
npm install
npm run dev # β http://localhost:3000npm run build
npm run deploy # β Cloudflare PagesBefore every commit / PR, run the combined check suite. It catches the classes
of regression we've historically had, and CI re-runs the same on every PR
(.github/workflows/ci.yml):
cd frontend
npm run check:all # = type-check + check:i18n + test (171/171)Individually:
| Script | Purpose |
|---|---|
npm run type-check |
Strict TypeScript (tsc --noEmit) across the whole frontend |
npm run check:i18n |
Ensures every t('β¦') key used in code exists in all 4 locale JSONs (th/en/de/da) β 216 keys each |
npm test |
Vitest unit tests β crypto (PBKDF2 + constant-time compare), ai-prompt validators, zod schemas, auth-store authChecked lifecycle |
npm run test:e2e |
Playwright e2e against the live deploy β 97 cases across smoke, ui-ux, deep-probes, a11y, responsive-perf, user-journey (opt-in, needs Chromium + network) |
npm run build |
Full Next.js production build, same thing Cloudflare Pages runs |
Backend (reference impl, runs via CI; see docs/KNOWLEDGE_BASE.md β Backend (FastAPI) β strategic role clarified):
cd backend
pip install -r requirements.txt
PYTHONPATH=. pytest -q # 129 tests: security, scorer, gemini, configSee docs/ITERATION_PROCESS.md for the
continuous-iteration / zero-error navigation process each change
follows before landing on main.
Schema changes live in frontend/src/db/schema.ts (Drizzle). Migrations
under frontend/drizzle/ are hand-reviewable SQL. Apply to Cloudflare D1:
cd frontend
npx wrangler d1 migrations apply eatinorder-db --remote # prod
npx wrangler d1 migrations apply eatinorder-db --local # local devPrerequisite β Cloudflare auth in frontend/.env.local (gitignored):
# Token must include scopes: D1:Edit, Workers Scripts:Edit, Pages:Edit,
# User Details:Read, AND User Memberships:Read (the last is non-obvious β
# `wrangler whoami` works without it but `migrations apply` 401s on
# /memberships). Pick the "Edit Cloudflare Workers" template for the
# full set.
CLOUDFLARE_API_TOKEN=...
# Pin which account this repo deploys to. Required when your token has
# access to multiple accounts (e.g. personal + org).
CLOUDFLARE_ACCOUNT_ID=...Wrangler 4.x auto-reads .env.local β no shell exports, no dotenv-cli,
no account_id in wrangler.toml (it's ignored for Pages configs).
Full debugging story: docs/KNOWN_ISSUES.md β
Resolved β Cloudflare auth (/memberships 10000). Setup guide:
docs/GUIDELINE.md β Cloudflare / wrangler env.
frontend/src/
84: βββ app/[locale]/ # Pages (home, scan, demo, login, dashboard, pricing, recipes)
85: βββ lib/
86: β βββ auth-store.ts # Zustand auth state (login, register, promo codes)
87: β βββ promo-codes.ts # Promotion code system (TRIAL, DISCOUNT, FANCLUB, REFERRAL)
88: β βββ tier-config.ts # Feature gating (Free, Premium, Family)
89: β βββ logger.ts # Client/Edge unified logger
90: βββ db/schema.ts # Drizzle ORM schema (users, promo_codes, sessions)
91: βββ messages/ # i18n translations (en, th, de, da)
- Register with email/password β free tier
- Redeem promo code β upgrade to premium/trial
- Edge-native security: Secure HttpOnly cookies powered by Next.js Edge APIs and Cloudflare D1.
- Free: Basic scanning (10/month), 3 health dimensions, 100 recipes
- Premium (ΰΈΏ199/mo): Unlimited scans, 8 dimensions, meal planning, data export
- Family (ΰΈΏ299/mo): All Premium + 5 family members, kids nutrition mode
- B2C Subscriptions β Freemium β Premium/Family
- B2B Corporate Wellness β Enterprise packages for organizations
- Partnership Revenue β Delivery integration, content licensing, affiliate programs
- Promotion System β Fanclub codes (Shinny), launch codes, referral codes
- Build user base with freemium model
- Engage Shinny fanclub with exclusive promo codes
- Iterate on AI food scanning accuracy
- Scale with minimal operational staff
MIT Β© Werapol Bejranonda