Review: T1 — westside-landing SvelteKit /jersey-public route

review-946-2026-04-10 Doc

review approved

Verdict: APPROVED

Board item: #946 — T1 — westside-landing: SvelteKit /jersey-public route
Forgejo issue: forgejo_admin/westside-landing#243 (open)
Reviewer: Dottie
Date: 2026-04-10

Template Completeness

  • [x] ### Type — Feature
  • [x] ### Lineage — standalone, System B production, revised 2026-04-10
  • [x] ### Repo — forgejo_admin/westside-landing
  • [x] ### User Story — As Marcus / I want / So that format
  • [x] ### Context — full auth-chain reuse documented
  • [x] ### File Targets — create/verify/do-not-touch sections explicit
  • [x] ### Prefill from JWT — explicit 4-step flow
  • [x] ### Acceptance Criteria — 8 measurable criteria
  • [x] ### Test Expectations — 4 unit/component tests + run command
  • [x] ### Constraints — auth reuse, (app)/ placement, Svelte 5 runes, no Tailwind
  • [x] ### Checklist — PR, preview, auth doc, grep verify, tests
  • [x] ### Related — story, arch, playground prototype, feedback refs

Traceability

  • [x] story:WS-S31 label — verified on project-westside-basketball user-stories section (Admin stories list, position 18610): "As an admin, I want a long-standing public jersey order link I can share with any player (known or new)..."
  • [x] arch:jersey-intake label — verified: arch-jersey-intake note exists (note id 1406, active, tag arch:jersey-intake). Specifies System B frontend target as westside-landing/src/routes/jersey-public — exact match.
  • [x] Forgejo issue #243 — open, body complete, revised 2026-04-10
  • [x] type:feature, scope:production, wave:1 labels present

File Targets

  • [x] westside-landing repo — verified via Forgejo API (forgejo_admin/westside-landing, default branch main, not empty)
  • [x] src/routes/(app)/ route group — verified via contents API: contains admin, checkout, coach, coaches, forgot-password, my-players, players, register, reset-password, signin, teams, +layout.svelte. jersey-public does NOT exist — clean slate for new route.
  • [x] src/lib/keycloak.js — verified: exports initKeycloak, login, logout, getToken, ready; references keycloak.tokenParsed; realm westside-basketball, client westside-spa, PKCE S256
  • [x] getUserName() — verified present in keycloak.js
  • [x] getEmail() — verified ABSENT in keycloak.js. Ticket correctly instructs the agent to add this 3-line helper reading keycloak.tokenParsed?.email. Accurate scope.
  • [x] src/routes/(app)/+layout.svelte — verified: calls initKeycloak onMount, has $effect guard with goto('/signin') for !authenticated && !isPublic, PUBLIC_APP_ROUTES allowlist matches ticket verbatim (/register, /signin, /jersey, /jersey/success, /jersey/cancel, /checkout, /checkout/success, /checkout/cancel, /forgot-password, /reset-password). /jersey-public is absent — the "do NOT add" instruction lands correctly.
  • [x] Playground prototype — /home/ldraney/westside-playground/jersey-public.html exists, 438 lines (matches ticket claim)

Repo Placement

OK. Frontend-only work in westside-landing. No cross-repo fan-out. Backend POST /api/jersey-public-orders lives in basketball-api but is a separate ticket (#948, wave:2).

Dependencies

  • Soft dependency on #947 (T2: migration 014 jersey_public_orders, wave:1) — same wave, independent (migration is DB-only). Can develop in parallel.
  • Soft dependency on #948 (T3: POST /api/jersey-public-orders, wave:2) — the frontend POSTs to this endpoint. For local dev, agent can stub/mock the endpoint. AC #5 ("request goes to POST /api/jersey-public-orders with Bearer JWT, verify in DevTools") can be validated against any endpoint returning 2xx/4xx; end-to-end insert verification belongs to wave:2+ validation. Not a review blocker — the route code is independently testable.
  • Downstream: #951 (T6 admin page, wave:3) and #952 (T7 Gear link, wave:3) consume this work. Neither blocks #946.
  • No items related to this ticket currently in in_progress that would create a merge conflict (#942 playground prototype is the upstream source, already approved).

Acceptance Criteria

All 8 criteria are measurable and cover the full Keycloak flow plus prefill-from-JWT behavior:
  • Unauth redirect to /signin — verifiable (navigate while logged out)
  • Post-signin return with prefilled name+email — verifiable (inspect form fields)
  • K/Q toggle swaps both images — verifiable (click toggle, check DOM src attrs)
  • Required-field validation blocks submit — verifiable (empty submit)
  • POST with Bearer JWT header — verifiable in DevTools Network tab
  • Success/error UI matches playground — verifiable visually against playground screenshot
  • Mobile layout — verifiable in responsive mode
  • Grep-verifiable absence from PUBLIC_APP_ROUTES — mechanical check
Test Expectations cover unit (validation, toggle, prefill) and component (layout guard redirect). Run command is generic (npm run test) which is acceptable.

Blast Radius

  • No scope leak into System A. Ticket explicitly lists src/routes/(app)/jersey/* as "do NOT touch" and does not modify the WS-S18 roster flow.
  • No scope leak into System C. Ticket explicitly lists src/routes/(app)/checkout/* as "do NOT touch" and does not modify Stripe checkout or the orders table.
  • No admin route touches. (app)/admin/jersey-orders is out of scope (T6/#951, wave:3).
  • No PUBLIC_APP_ROUTES modification. Explicit "NOT" instruction + grep-verifiable AC. Auth chain stays minimal and reuses existing layout guard.
  • Auth layer reuse verified. Ticket imports from $lib/keycloak.js only — no new auth primitives, no oauth2-proxy, no ingress/annotation changes. Matches feedback_keycloak_first.md.
  • Architectural deviation from arch-jersey-intake (noted, not blocking). The arch doc (written earlier 2026-04-10) describes System B as "Self-declared (name + email)" and the POST endpoint as "public, no auth." Ticket now gates the frontend with Keycloak per feedback_funnel_requires_auth.md. The arch doc's "Future: Keycloak Gating" section anticipates this. Recommend a follow-up docs ticket to update arch-jersey-intake to reflect the new System B identity model (JWT claim → email key) — but that is a separate scope item, not a blocker for #946.

Decomposition

5-minute rule assessment:
  • File targets: 1 new file (+page.svelte), 1 3-line helper addition to keycloak.js, 0 modifications to layout/routes allowlist. Well under the 3-file threshold.
  • Repos touched: 1 (westside-landing). Under 2-repo threshold.
  • Acceptance criteria: 8 — slightly above the 5 threshold, but all are lightweight verifications of one cohesive feature (single route). Not worth fracturing.
  • Estimated agent work: copy-paste playground HTML → runes conversion + auth wire-up + tests. Well under 5 min for a focused agent.
No decomposition needed. Fits in a single agent pass.

Recommendation

APPROVED — ready to move backlog → todo → next_up.
No required changes. Optional follow-ups (do NOT block this ticket):
  • [SCOPE] Create a new board item to update arch-jersey-intake System B section to reflect the Keycloak-gated identity model (JWT claims replace self-declared name+email). Add a docs label, arch:jersey-intake, story:WS-S31. This is discovered scope from the 2026-04-10 revision and should be tracked per feedback_discovered_scope_always_tracked.md.
All verified: template complete, traceability triangle intact (story + arch + Forgejo issue all confirmed in pal-e-docs and on Forgejo), file targets resolve correctly against the live westside-landing repo, acceptance criteria measurable, auth chain reuses existing infrastructure cleanly, no System A/C scope leak.