SvelteKit + Capacitor scaffold: 10 routes, keycloak-js auth, playground promotion #1

Closed
opened 2026-03-16 22:49:10 +00:00 by forgejo_admin · 0 comments
Contributor

Lineage

plan-mcd-tracker → Phase 7 (SvelteKit + Capacitor Frontend)

Repo

forgejo_admin/mcd-tracker-app

User Story

As a user on my phone (web or iOS)
I want to use the MCD Tracker app with real data instead of hardcoded playground prototypes
So that I can actually track my BOGO codes, scan receipts, and redeem at the counter

Context

The playground prototype (12 pages at playground.tail5b443a.ts.net/mcd-tracker/) is the design source of truth. This phase promotes it to a production SvelteKit app with Capacitor for iOS.

Critical architecture decisions already made:

  • SPA mode: adapter-static + ssr: false (required for Capacitor). No +page.server.ts — all data loading is client-side via fetch() in onMount.
  • Auth: keycloak-js client-side OIDC with PKCE (NOT Auth.js — Auth.js requires SSR). Public Keycloak client mcd-tracker-app. Redirect URIs: Web https://mcd-tracker-app.tail5b443a.ts.net/*, iOS capacitor://localhost/*.
  • API base URL: https://mcd-tracker.tail5b443a.ts.net (FastAPI backend, already deployed with 13 endpoints + 144 tests)
  • Backend prereqs merged: GET /stats (XP, levels) + redeemed_item on redeem endpoint (PR #17 on mcd-tracker-api)

Playground CSS is the source of truth. Copy app.css directly. Copy HTML into Svelte templates. Replace hardcoded data with {data.field} bindings. Do not redesign or reinterpret.

Routes (10 total)

Playground Route Data (client-side fetch)
index.html / None (public landing)
signin.html /signin None — keycloak.login() redirect
register.html /register None — keycloak.register() redirect
home.html /home GET /dashboard + GET /stats
location-detail.html /locations/[id] GET /locations/{id}/slots + GET /locations/{id}/codes
scan.html /scan GET /locations/nearby
save-success.html /scan/success Data passed from scan page state
redeem-success.html /redeem/success Data passed from redeem action state
history.html /history GET /receipts + GET /codes + GET /stats
event-detail.html /history/[id] GET /codes/{id} with related receipt/location data

File Targets

Files to create:

  • src/routes/+layout.svelte — keycloak-js init, auth guard, bottom nav
  • src/routes/+page.svelte — landing page (public, no auth required)
  • src/routes/signin/+page.sveltekeycloak.login() redirect (one-liner, not a full page)
  • src/routes/register/+page.sveltekeycloak.register() redirect (one-liner)
  • src/routes/home/+page.svelte — location dashboard + XP banner
  • src/routes/locations/[id]/+page.svelte — location detail + cashier overlay
  • src/routes/scan/+page.svelte — 4-step wizard
  • src/routes/scan/success/+page.svelte — save celebration + cashier overlay
  • src/routes/redeem/success/+page.svelte — redeem celebration + item picker
  • src/routes/history/+page.svelte — date-grouped timeline
  • src/routes/history/[id]/+page.svelte — event detail + state-aware overlay
  • src/app.css — direct copy from ~/mcd-tracker-playground/app.css
  • src/lib/keycloak.js — keycloak-js wrapper (init, login, register, updateToken, getToken)
  • src/lib/api.js — fetch wrapper with Bearer token injection from keycloak
  • svelte.config.js — adapter-static, ssr: false
  • capacitor.config.ts — Capacitor configuration (appId, appName, webDir: 'build')
  • Dockerfile — nginx serving static build/ directory
  • .woodpecker.yaml — test + build + kaniko push pipeline

The playground HTML lives at ~/mcd-tracker-playground/. Read each file and promote it.

Acceptance Criteria

  • npm run build succeeds (no SSR errors with adapter-static)
  • Landing page (/) renders without auth
  • /signin triggers keycloak.login() redirect
  • /register triggers keycloak.register() redirect
  • After auth, /home fetches from GET /dashboard + GET /stats
  • XP banner renders level name and progress bar from stats data
  • Tapping a location navigates to /locations/{id} with real slot + code data
  • Scan wizard has all 4 steps with Capacitor camera/clipboard/browser integration points
  • Save success shows code details, XP gain, cashier overlay via "Use Now"
  • Redeem sends PATCH /codes/{id}/redeem with optional redeemed_item from chip picker
  • History renders date-grouped timeline with color-coded event types
  • Event detail shows full context with state-aware overlay (active vs redeemed)
  • Bottom nav (Home/Scan/History) works across all authenticated routes
  • Dockerfile builds and serves the SPA via nginx
  • .woodpecker.yaml has test + build-and-push stages

Test Expectations

  • npm run build produces static output in build/
  • No TypeScript errors
  • npx cap init succeeds (Capacitor project initialized)
  • Run: npm run build

Constraints

  • Direct copy from playground HTML/CSS — do not redesign or reinterpret
  • app.css copied verbatim from ~/mcd-tracker-playground/app.css, no modifications
  • All data fetching via client-side fetch() in onMount — NO +page.server.ts (SPA mode)
  • keycloak-js for auth — NOT Auth.js (Auth.js requires SSR)
  • Platform detection via Capacitor.isNativePlatform() for redirect URIs
  • API base URL: https://mcd-tracker.tail5b443a.ts.net
  • Service onboarding is a separate follow-up (pal-e-services + pal-e-deployments, different repos)
  • Deploy verification is post-merge — this PR is the scaffold only

Checklist

  • PR opened
  • Build succeeds
  • Capacitor initialized
  • No unrelated changes
  • project-mcd-tracker — parent project
  • Playground source: ~/mcd-tracker-playground/ (12 HTML files + app.css)
  • Backend API: https://mcd-tracker.tail5b443a.ts.net (13 endpoints, 144 tests)
  • Auth reference: search Forgejo for project-capacitor-mobile note in pal-e-docs (auth-decision section)
### Lineage `plan-mcd-tracker` → Phase 7 (SvelteKit + Capacitor Frontend) ### Repo `forgejo_admin/mcd-tracker-app` ### User Story As a user on my phone (web or iOS) I want to use the MCD Tracker app with real data instead of hardcoded playground prototypes So that I can actually track my BOGO codes, scan receipts, and redeem at the counter ### Context The playground prototype (12 pages at `playground.tail5b443a.ts.net/mcd-tracker/`) is the design source of truth. This phase promotes it to a production SvelteKit app with Capacitor for iOS. **Critical architecture decisions already made:** - **SPA mode:** `adapter-static` + `ssr: false` (required for Capacitor). No `+page.server.ts` — all data loading is client-side via `fetch()` in `onMount`. - **Auth:** `keycloak-js` client-side OIDC with PKCE (NOT Auth.js — Auth.js requires SSR). Public Keycloak client `mcd-tracker-app`. Redirect URIs: Web `https://mcd-tracker-app.tail5b443a.ts.net/*`, iOS `capacitor://localhost/*`. - **API base URL:** `https://mcd-tracker.tail5b443a.ts.net` (FastAPI backend, already deployed with 13 endpoints + 144 tests) - **Backend prereqs merged:** `GET /stats` (XP, levels) + `redeemed_item` on redeem endpoint (PR #17 on mcd-tracker-api) **Playground CSS is the source of truth.** Copy `app.css` directly. Copy HTML into Svelte templates. Replace hardcoded data with `{data.field}` bindings. Do not redesign or reinterpret. ### Routes (10 total) | Playground | Route | Data (client-side fetch) | |---|---|---| | index.html | `/` | None (public landing) | | signin.html | `/signin` | None — `keycloak.login()` redirect | | register.html | `/register` | None — `keycloak.register()` redirect | | home.html | `/home` | `GET /dashboard` + `GET /stats` | | location-detail.html | `/locations/[id]` | `GET /locations/{id}/slots` + `GET /locations/{id}/codes` | | scan.html | `/scan` | `GET /locations/nearby` | | save-success.html | `/scan/success` | Data passed from scan page state | | redeem-success.html | `/redeem/success` | Data passed from redeem action state | | history.html | `/history` | `GET /receipts` + `GET /codes` + `GET /stats` | | event-detail.html | `/history/[id]` | `GET /codes/{id}` with related receipt/location data | ### File Targets Files to create: - `src/routes/+layout.svelte` — keycloak-js init, auth guard, bottom nav - `src/routes/+page.svelte` — landing page (public, no auth required) - `src/routes/signin/+page.svelte` — `keycloak.login()` redirect (one-liner, not a full page) - `src/routes/register/+page.svelte` — `keycloak.register()` redirect (one-liner) - `src/routes/home/+page.svelte` — location dashboard + XP banner - `src/routes/locations/[id]/+page.svelte` — location detail + cashier overlay - `src/routes/scan/+page.svelte` — 4-step wizard - `src/routes/scan/success/+page.svelte` — save celebration + cashier overlay - `src/routes/redeem/success/+page.svelte` — redeem celebration + item picker - `src/routes/history/+page.svelte` — date-grouped timeline - `src/routes/history/[id]/+page.svelte` — event detail + state-aware overlay - `src/app.css` — direct copy from `~/mcd-tracker-playground/app.css` - `src/lib/keycloak.js` — keycloak-js wrapper (init, login, register, updateToken, getToken) - `src/lib/api.js` — fetch wrapper with Bearer token injection from keycloak - `svelte.config.js` — adapter-static, ssr: false - `capacitor.config.ts` — Capacitor configuration (appId, appName, webDir: 'build') - `Dockerfile` — nginx serving static `build/` directory - `.woodpecker.yaml` — test + build + kaniko push pipeline The playground HTML lives at `~/mcd-tracker-playground/`. Read each file and promote it. ### Acceptance Criteria - [ ] `npm run build` succeeds (no SSR errors with adapter-static) - [ ] Landing page (`/`) renders without auth - [ ] `/signin` triggers `keycloak.login()` redirect - [ ] `/register` triggers `keycloak.register()` redirect - [ ] After auth, `/home` fetches from `GET /dashboard` + `GET /stats` - [ ] XP banner renders level name and progress bar from stats data - [ ] Tapping a location navigates to `/locations/{id}` with real slot + code data - [ ] Scan wizard has all 4 steps with Capacitor camera/clipboard/browser integration points - [ ] Save success shows code details, XP gain, cashier overlay via "Use Now" - [ ] Redeem sends `PATCH /codes/{id}/redeem` with optional `redeemed_item` from chip picker - [ ] History renders date-grouped timeline with color-coded event types - [ ] Event detail shows full context with state-aware overlay (active vs redeemed) - [ ] Bottom nav (Home/Scan/History) works across all authenticated routes - [ ] Dockerfile builds and serves the SPA via nginx - [ ] `.woodpecker.yaml` has test + build-and-push stages ### Test Expectations - [ ] `npm run build` produces static output in `build/` - [ ] No TypeScript errors - [ ] `npx cap init` succeeds (Capacitor project initialized) - Run: `npm run build` ### Constraints - Direct copy from playground HTML/CSS — do not redesign or reinterpret - `app.css` copied verbatim from `~/mcd-tracker-playground/app.css`, no modifications - All data fetching via client-side `fetch()` in `onMount` — NO `+page.server.ts` (SPA mode) - `keycloak-js` for auth — NOT Auth.js (Auth.js requires SSR) - Platform detection via `Capacitor.isNativePlatform()` for redirect URIs - API base URL: `https://mcd-tracker.tail5b443a.ts.net` - **Service onboarding is a separate follow-up** (pal-e-services + pal-e-deployments, different repos) - **Deploy verification is post-merge** — this PR is the scaffold only ### Checklist - [ ] PR opened - [ ] Build succeeds - [ ] Capacitor initialized - [ ] No unrelated changes ### Related - `project-mcd-tracker` — parent project - Playground source: `~/mcd-tracker-playground/` (12 HTML files + `app.css`) - Backend API: `https://mcd-tracker.tail5b443a.ts.net` (13 endpoints, 144 tests) - Auth reference: search Forgejo for `project-capacitor-mobile` note in pal-e-docs (auth-decision section)
Commenting is not possible because the repository is archived.
No labels
No milestone
No project
No assignees
1 participant
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
ldraney/mcd-tracker-app#1
No description provided.