feat: SvelteKit SPA rebuild — adapter-static + keycloak-js #36

Closed
opened 2026-03-17 04:53:33 +00:00 by forgejo_admin · 1 comment

Lineage

plan-wkq → Phase 15 (Production Port — SPA Rebuild)

Repo

forgejo_admin/westside-app

User Story

As Marcus (admin), coaches, and parents
I want a phone-first SPA that works on mobile and is Capacitor-ready
So that the app can serve web + future iOS from one codebase with real-time API data

Context

The current westside-app uses SSR (adapter-node + Auth.js + confidential Keycloak client). This architecture can't wrap in Capacitor for iOS. The playground (13 static HTML pages, Gate 1 phone-approved) is the locked design source of truth.

This is a full architecture migration — nuke src/, fresh scaffold. Key decisions made:

  • adapter-static (SPA) replaces adapter-node (SSR)
  • keycloak-js (client-side PKCE) replaces @auth/sveltekit (server-side sessions)
  • Connect to prod API directly — no Docker Compose for iteration 1. Bearer token → basketball-api.tail5b443a.ts.net
  • NO scoped Svelte <style> blocks — all CSS in global app.css, copied literally from playground
  • Same repo, clean break — delete old SSR code, fresh scaffold on this branch

The mcd-tracker project validated this exact pattern: SvelteKit adapter-static + keycloak-js + Capacitor.

File Targets

Files to delete (old SSR architecture):

  • src/auth.js — Auth.js config (replaced by keycloak-js)
  • src/hooks.server.js — server hooks (no server in SPA)
  • All src/routes/**/+page.server.js — server-side data loading (replaced by +page.ts / onMount)

Files to create:

  • src/lib/keycloak.js — keycloak-js init, login, logout, token refresh, role detection
  • src/routes/+layout.svelte — keycloak-js init, auth guard, role-based nav
  • src/routes/+page.svelte — landing page (from index.html)
  • src/routes/tryouts/+page.svelte — from tryouts.html
  • src/routes/register/+page.svelte — from register.html
  • src/routes/signin/+page.svelte — keycloak.login() redirect
  • src/routes/my-players/+page.svelte — from parent.html
  • src/routes/players/[id]/+page.svelte — from player-profile.html
  • src/routes/players/[id]/billing/+page.svelte — from billing.html
  • src/routes/teams/[id]/+page.svelte — from team.html
  • src/routes/coach/+page.svelte — from coach.html
  • src/routes/coaches/[id]/+page.svelte — from coach-profile.html
  • src/routes/admin/+page.svelte — from admin.html
  • src/routes/admin/players/+page.svelte — from admin-players.html
  • src/routes/admin/teams/+page.svelte — from admin-teams.html
  • src/app.css — literal copy from westside-playground/shared/app.css

Files to modify:

  • package.json — swap @sveltejs/adapter-node@sveltejs/adapter-static, remove @auth/sveltekit, add keycloak-js
  • svelte.config.js — adapter-static with fallback for SPA routing

Files NOT to touch:

  • westside-playground/* — playground is read-only source of truth, never modified by this work

Acceptance Criteria

  • npm run dev starts Vite dev server on port 5174
  • Landing page (/) renders without auth — matches playground index.html visually
  • Tryouts page (/tryouts) renders without auth
  • Sign-in redirects to prod Keycloak login
  • After Keycloak login, user is redirected based on role (admin→/admin, coach→/coach, member→/my-players)
  • All 13 routes render and match playground design
  • Authenticated pages fetch from prod basketball-api with Bearer token
  • Missing API endpoints show placeholder UI (not errors)
  • Role-based visibility: admin sections hidden from members, payment hidden from coaches (per @variants in each HTML)
  • npm run build succeeds and produces static output in build/
  • NO scoped <style> blocks in any .svelte file — all CSS in app.css
  • NO +page.server.js files anywhere

Test Expectations

  • npm run build completes without errors (adapter-static validation)
  • npm run check passes (svelte-check type validation)
  • Manual: each route loads without console errors
  • Run command: npm run build && npm run check

Constraints

  • Port order: Low complexity first (index, tryouts, signin, coach-profile, team), then Medium (parent, coach, admin, admin-players), then High (register, player-profile, billing, admin-teams)
  • Read each playground HTML file's @api, @state, @interactivity, @variants comment block — these are the spec for Svelte bindings
  • Clone forgejo_admin/westside-playground to read source HTML files
  • Keycloak config: public client, PKCE, realm pal-e, client ID TBD (check existing clients or create westside-spa)
  • Token storage: in-memory only, never localStorage
  • All colors/spacing via CSS custom properties from app.css — no hardcoded values in Svelte
  • app.js logic from playground translates to Svelte reactive statements and event handlers — do NOT import app.js

Checklist

  • PR opened
  • npm run build passes
  • npm run check passes
  • No unrelated changes
  • All 13 routes present and rendering
  • westside-basketball — project
  • phase-wkq-15-production-port — phase note with full architecture table
  • phase-wkq-10-playground — design source of truth (COMPLETED)
  • sop-capacitor-mobile-lifecycle — Stage 2 rules (mechanical copy, no scoped styles)
### Lineage `plan-wkq` → Phase 15 (Production Port — SPA Rebuild) ### Repo `forgejo_admin/westside-app` ### User Story As Marcus (admin), coaches, and parents I want a phone-first SPA that works on mobile and is Capacitor-ready So that the app can serve web + future iOS from one codebase with real-time API data ### Context The current westside-app uses SSR (adapter-node + Auth.js + confidential Keycloak client). This architecture can't wrap in Capacitor for iOS. The playground (13 static HTML pages, Gate 1 phone-approved) is the locked design source of truth. This is a full architecture migration — nuke `src/`, fresh scaffold. Key decisions made: - **adapter-static** (SPA) replaces adapter-node (SSR) - **keycloak-js** (client-side PKCE) replaces @auth/sveltekit (server-side sessions) - **Connect to prod API directly** — no Docker Compose for iteration 1. Bearer token → `basketball-api.tail5b443a.ts.net` - **NO scoped Svelte `<style>` blocks** — all CSS in global `app.css`, copied literally from playground - **Same repo, clean break** — delete old SSR code, fresh scaffold on this branch The mcd-tracker project validated this exact pattern: SvelteKit adapter-static + keycloak-js + Capacitor. ### File Targets Files to delete (old SSR architecture): - `src/auth.js` — Auth.js config (replaced by keycloak-js) - `src/hooks.server.js` — server hooks (no server in SPA) - All `src/routes/**/+page.server.js` — server-side data loading (replaced by +page.ts / onMount) Files to create: - `src/lib/keycloak.js` — keycloak-js init, login, logout, token refresh, role detection - `src/routes/+layout.svelte` — keycloak-js init, auth guard, role-based nav - `src/routes/+page.svelte` — landing page (from `index.html`) - `src/routes/tryouts/+page.svelte` — from `tryouts.html` - `src/routes/register/+page.svelte` — from `register.html` - `src/routes/signin/+page.svelte` — keycloak.login() redirect - `src/routes/my-players/+page.svelte` — from `parent.html` - `src/routes/players/[id]/+page.svelte` — from `player-profile.html` - `src/routes/players/[id]/billing/+page.svelte` — from `billing.html` - `src/routes/teams/[id]/+page.svelte` — from `team.html` - `src/routes/coach/+page.svelte` — from `coach.html` - `src/routes/coaches/[id]/+page.svelte` — from `coach-profile.html` - `src/routes/admin/+page.svelte` — from `admin.html` - `src/routes/admin/players/+page.svelte` — from `admin-players.html` - `src/routes/admin/teams/+page.svelte` — from `admin-teams.html` - `src/app.css` — literal copy from `westside-playground/shared/app.css` Files to modify: - `package.json` — swap `@sveltejs/adapter-node` → `@sveltejs/adapter-static`, remove `@auth/sveltekit`, add `keycloak-js` - `svelte.config.js` — adapter-static with fallback for SPA routing Files NOT to touch: - `westside-playground/*` — playground is read-only source of truth, never modified by this work ### Acceptance Criteria - [ ] `npm run dev` starts Vite dev server on port 5174 - [ ] Landing page (/) renders without auth — matches playground index.html visually - [ ] Tryouts page (/tryouts) renders without auth - [ ] Sign-in redirects to prod Keycloak login - [ ] After Keycloak login, user is redirected based on role (admin→/admin, coach→/coach, member→/my-players) - [ ] All 13 routes render and match playground design - [ ] Authenticated pages fetch from prod basketball-api with Bearer token - [ ] Missing API endpoints show placeholder UI (not errors) - [ ] Role-based visibility: admin sections hidden from members, payment hidden from coaches (per @variants in each HTML) - [ ] `npm run build` succeeds and produces static output in `build/` - [ ] NO scoped `<style>` blocks in any .svelte file — all CSS in app.css - [ ] NO `+page.server.js` files anywhere ### Test Expectations - [ ] `npm run build` completes without errors (adapter-static validation) - [ ] `npm run check` passes (svelte-check type validation) - [ ] Manual: each route loads without console errors - Run command: `npm run build && npm run check` ### Constraints - Port order: Low complexity first (index, tryouts, signin, coach-profile, team), then Medium (parent, coach, admin, admin-players), then High (register, player-profile, billing, admin-teams) - Read each playground HTML file's `@api`, `@state`, `@interactivity`, `@variants` comment block — these are the spec for Svelte bindings - Clone `forgejo_admin/westside-playground` to read source HTML files - Keycloak config: public client, PKCE, realm `pal-e`, client ID TBD (check existing clients or create `westside-spa`) - Token storage: in-memory only, never localStorage - All colors/spacing via CSS custom properties from app.css — no hardcoded values in Svelte - `app.js` logic from playground translates to Svelte reactive statements and event handlers — do NOT import app.js ### Checklist - [ ] PR opened - [ ] `npm run build` passes - [ ] `npm run check` passes - [ ] No unrelated changes - [ ] All 13 routes present and rendering ### Related - `westside-basketball` — project - `phase-wkq-15-production-port` — phase note with full architecture table - `phase-wkq-10-playground` — design source of truth (COMPLETED) - `sop-capacitor-mobile-lifecycle` — Stage 2 rules (mechanical copy, no scoped styles)
Author
Owner

Scope Update: Dev Overlay Pattern

Decision: Instead of tailscale serve --bg 5174 for dev access, use a kustomize dev overlay in pal-e-deployments.

What this means

The existing production pod already has Keycloak, Tailscale funnel, and networking configured. A dev overlay swaps:

  • Image: nginx → node:22
  • Command: serve static files → npm run dev -- --host
  • Volume: built SPA → hostPath mount to ~/westside-app

Same URL (westsidekingsandqueens.tail5b443a.ts.net), same auth, same API connection. Edit a .svelte file on the host → Vite picks it up → phone refreshes in under a second.

Impact on this issue

  • No CORS changes needed on basketball-api (same origin as prod)
  • No new Keycloak redirect URIs for a dev URL
  • Still need a public Keycloak client (keycloak-js requires it — the existing confidential client is for the old Auth.js SSR)
  • Dev overlay work lives in pal-e-deployments, not this repo

Additional file targets (in pal-e-deployments)

overlays/westside-app/
  dev/kustomization.yaml    — node:22, hostPath, npm run dev
  prod/kustomization.yaml   — existing nginx overlay (may need updating for SPA)
## Scope Update: Dev Overlay Pattern **Decision:** Instead of `tailscale serve --bg 5174` for dev access, use a kustomize dev overlay in `pal-e-deployments`. ### What this means The existing production pod already has Keycloak, Tailscale funnel, and networking configured. A dev overlay swaps: - **Image:** nginx → node:22 - **Command:** serve static files → `npm run dev -- --host` - **Volume:** built SPA → hostPath mount to `~/westside-app` Same URL (`westsidekingsandqueens.tail5b443a.ts.net`), same auth, same API connection. Edit a `.svelte` file on the host → Vite picks it up → phone refreshes in under a second. ### Impact on this issue - **No CORS changes needed** on basketball-api (same origin as prod) - **No new Keycloak redirect URIs** for a dev URL - Still need a **public Keycloak client** (keycloak-js requires it — the existing confidential client is for the old Auth.js SSR) - Dev overlay work lives in `pal-e-deployments`, not this repo ### Additional file targets (in pal-e-deployments) ``` overlays/westside-app/ dev/kustomization.yaml — node:22, hostPath, npm run dev prod/kustomization.yaml — existing nginx overlay (may need updating for SPA) ```
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
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
forgejo_admin/westside-landing#36
No description provided.