Phase 7: SvelteKit + Capacitor Frontend
Goal: Production SvelteKit + Capacitor app. Playground HTML/CSS promoted directly. All screens functional with real data. Deployed to cluster (web) + Capacitor-ready for iOS.
Owner: Dev agent (mechanical promotion) + Lucas (visual verification)
Repo:
forgejo_admin/mcd-tracker-appDepends on: Phase 6 (playground design locked), Phase 5a/5c/5d (backend gaps filled)
Scope
Mechanical promotion from playground per
sop-frontend-experiment. The HTML/CSS is the source of truth — app.css used directly, HTML pasted into Svelte templates, hardcoded data replaced with {data.field}.SvelteKit Routes (from playground alignment)
| Playground | Route | Data (client-side fetch) | Interactivity |
|---|---|---|---|
| index.html | / | None (public landing) | Sign In → keycloak.login(). Create Account → keycloak.register(). In production, Keycloak handles auth UI — these are themed Keycloak pages. |
| signin.html | /signin | None — redirect to Keycloak | keycloak.login() redirect. Page only exists as Keycloak theme reference. On iOS: WebView opens Keycloak directly. |
| register.html | /register | None — redirect to Keycloak | keycloak.register() redirect. Same as signin — Keycloak theme reference. |
| home.html | /home | GET /dashboard + GET /stats (XP/level) | XP banner (level, codes redeemed). Location cards sorted by proximity with 5-day expiry warnings. Near Me GPS. Search + sort. Tap location → /locations/{id}. Floating scan FAB. |
| location-detail.html | /locations/{id} | GET /locations/{id}/slots + GET /locations/{id}/codes | Slot progress bar. Active codes with 5-day countdown + pulsing redeem button. Redeemed codes. Slot timeline (all 5 slots). Cashier overlay with receipt photo. Scan Receipt pre-selects this location. |
| scan.html | /scan | GET /locations/nearby | {#if currentStep} 4-step wizard. Permission card. Camera (Capacitor). Survey code confirm. Copy + open survey (Capacitor browser/clipboard). BOGO code entry + location picker. Save → /scan/success. |
| save-success.html | /scan/success | None (data passed from scan) | Code saved celebration. 5-day expiry warning. Slot status update. +1 XP animation. 'Use Now' → cashier overlay (direct, no navigation). 'Done' → /home. |
| redeem-success.html | /redeem/success | None (data passed from redeem action) | Free Food celebration. Lifetime count (#13). 'What did you get?' chip picker (Big Mac, McChicken, etc. → redeemed_item field). +2 XP animation. Slot reopen date. 'Scan Another' or 'Done'. |
| history.html | /history | GET /receipts + GET /codes → merge + GET /stats | Lifetime stats bar (scanned/earned/redeemed/expired). Date-grouped timeline. Color-coded events (scan=blue, earn=green, redeem=amber, expire=red). Tap event → /history/{id}. |
| event-detail.html | /history/{id} | GET /codes/{id} or GET /receipts/{id} with related data | Full event context: type, timestamp, location (tappable), BOGO code details, survey code, receipt photo, slot impact, XP earned. Cashier overlay (state-aware: active → 'Mark as Redeemed', redeemed → 'Already Redeemed' with item + date). |
Steps
- Create
mcd-tracker-apprepo on Forgejo. SvelteKit scaffold (adapter-static,ssr: falsefor Capacitor SPA mode). - Copy
app.cssfrom playground →src/app.css(direct, no changes). - Promote 7 content pages: copy HTML → Svelte template, replace hardcoded data with
{data.field}. Data loading via client-sidefetch()inonMount(NOT+page.server.ts). Pages:/(landing),/home,/locations/[id],/scan,/scan/success,/redeem/success,/history,/history/[id]. - Create 2 redirect stubs:
/signin→keycloak.login(),/register→keycloak.register(). These are not full page promotions — they call the Keycloak JS method and redirect. No HTML copy needed. - Auth: keycloak-js client-side OIDC (NOT Auth.js). Public client + PKCE. Initialize in
+layout.svelte(onMount). Auth guard blocks rendering until resolved./is public, all other routes require auth. Seeproject-capacitor-mobileauth-decision for full config (redirect URIs, token management, platform detection). - Backend prereqs: COMPLETED (PR #17, merged 2026-03-16).
GET /statsendpoint (XP, levels, lifetime counts) +redeemed_itemfield on redeem endpoint. 144 tests. No backend work needed. - Capacitor init:
npx cap init,npx cap add ios, configurecapacitor.config.ts. - Capacitor plugins:
@capacitor/camera,@capacitor/geolocation,@capacitor/clipboard,@capacitor/browser,@capacitor/app. - Service onboarding: add
mcd-tracker-appto pal-e-services + pal-e-deployments (namespace, Harbor project, ArgoCD app, kustomize overlay, Tailscale funnel). - Deploy to cluster, verify on phone — full flow: landing → auth → home → location → scan → save → use now → redeem → history → event detail.
Deliverables
- PR #2 merged (2026-03-16): SvelteKit + Capacitor scaffold
- 10 routes promoted from playground HTML (verbatim CSS, Svelte templates with data bindings)
- keycloak-js PKCE auth with auth guard in +layout.svelte
src/lib/keycloak.js(OIDC wrapper) +src/lib/api.js(Bearer token fetch) +src/lib/overlay.svelte.js(shared overlay state)- 41 tests (Vitest + @testing-library/svelte): build, routes, modules, components, overlay
- Dockerfile (nginx serving SPA) + .woodpecker.yaml (test + build-and-push)
- Capacitor config initialized (capacitor.config.ts, iOS added)
- PR #5 merged (2026-03-17): Docker Compose local dev stack. 4-service stack (postgres, keycloak, api, app).
dev/realm-export.jsonwith PKCE client + 3 test users.dev/seed-data.pytest data seeder..env.example.api.jsandkeycloak.jsnow use VITE_* env vars with production fallbacks. QA nit fixed:/healthzendpoint. - Remaining:
docker compose uplocal validation, service onboarding (pal-e-services + pal-e-deployments), deploy to cluster, verify on phone
Related
plan-mcd-tracker— parent planphase-mcd-tracker-6-playground— design source of truthsop-frontend-experiment— promotion procedureproject-capacitor-mobile— Capacitor patterns + plugin catalogconvention-frontend-css— CSS rules