Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .claude/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"version": "0.0.1",
"configurations": [
{
"name": "quiztin-frontend",
"runtimeExecutable": "sh",
"runtimeArgs": ["-c", "cd frontend && npm run dev"],
"port": 5173
}
]
}
2 changes: 1 addition & 1 deletion context/foundation.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ The heart of the file. Numbered so other files cite `foundation.md §7 #N`. Rows
| 22 | **Hosting: containerized (Docker) → Railway**, with **Railway-managed Postgres** *(this session)* | Simplest path for a solo dev on a student budget; Docker keeps it portable; Postgres is Railway-native | Azure (heavier); Fly/Render (Railway chosen); self-hosted SQL Server container (heavy) |
| 23 | **Frontend language: TypeScript (strict)** *(spec 0001)* | The API boundary is where drift bites; types plus Zod catch it before a screen does | Plain JavaScript (no boundary safety) |
| 24 | **Tailwind v4, bound to the design tokens by reference (`@theme inline`), Preflight not loaded** *(spec 0001)* | Makes `bg-primary` compile to `var(--primary)`, so the "semantic tokens only, no raw hex" rule is enforced by the compiler rather than by review; `design-system/tokens/base.css` is already the reset, so a second one would fight it | CSS Modules (rule survives on discipline alone); Tailwind with Preflight (two competing resets) |
| 25 | **React Router v7 data router (`createBrowserRouter`), loaders and actions unused; TanStack Query owns all server state** *(spec 0001)* | Nested routes and error boundaries without framework mode, whose Vite plugin and server rendering are the same machinery rejected in #5; one data layer, not two | Framework mode (adds a Node runtime to a .NET architecture); loaders plus Query (two caches, one truth) |
| 25 | **React Router v8 data router (`createBrowserRouter`), loaders and actions unused; TanStack Query owns all server state** *(spec 0001; the installed major is 8.2.x, confirmed and its memory router used for the spec 0003 prerender)* | Nested routes and error boundaries without framework mode, whose Vite plugin and server rendering are the same machinery rejected in #5; one data layer, not two | Framework mode (adds a Node runtime to a .NET architecture); loaders plus Query (two caches, one truth) |
| 26 | **Session: short-lived access token held in memory, rotating refresh token in an `HttpOnly` cookie; AuthService gains `POST /api/auth/refresh` + `POST /api/auth/logout`, with reuse detection and a grace window** *(spec 0001)* | A token in `localStorage` is readable by any injected script, and an 8h token guarding student records is an unacceptable blast radius (`security.md`); the cookie is unreachable by script and rotation bounds replay | Token in `localStorage` (script-readable); server-held session (a second runtime to operate) |
| 27 | **The frontend is same-origin with the API: Vite dev proxy now, the YARP gateway (#16) later. No CORS in any service** *(spec 0001)* | CORS in five services is five places to get it wrong, then a sixth at the gateway; same-origin means the app never learns the concept, and the gateway move is config, not a rewrite | CORS per service (port chaos, five configs) |
| 28 | **Build only against endpoints that exist; no mock layer** *(spec 0001)* | A mock is a second source of truth that agrees with the backend right up until it doesn't, and the divergence surfaces late; scope follows the real API surface instead | Mocked endpoints to unblock screens (Facade-first, rejected against §0) |
Expand Down
19 changes: 15 additions & 4 deletions context/library-docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,9 @@
- **How used:** `@theme inline { --color-primary: var(--primary); … }` over the semantic aliases only. Import `tailwindcss/theme.css` + `tailwindcss/utilities.css`; **never bare `tailwindcss`**, which would pull in Preflight and fight `design-system/tokens/base.css`.
- **Gotchas:** ⚠️ Preflight is also what sets `border-style: solid` globally. Without it every Tailwind `border` utility renders **invisible**. Restore it with a three-line `@layer base` rule. Declare `@layer theme, base, components, utilities;` first, or the design system reset outranks the utilities.

### React Router v7 — ✅ (new)
- **How used:** `createBrowserRouter` for routing only (nested routes, `errorElement`, lazy routes). **Loaders and actions stay unused** — TanStack Query owns server state (§7 #25).
- **Gotchas:** do not adopt *framework mode*; its Vite plugin and server rendering are what #5 rejected with Next.js.
### React Router v8 — ✅ (new)
- **How used:** `createBrowserRouter` for routing only (nested routes, `errorElement`, lazy routes). **Loaders and actions stay unused** — TanStack Query owns server state (§7 #25). The landing prerender (spec 0003) uses `createMemoryRouter` for the same route table in Node.
- **Gotchas:** the installed major is **8.2.x**; earlier docs said v7, a drift now reconciled (spec 0003) after the prerender was wired and verified against 8.2.x (`createBrowserRouter`, `createMemoryRouter`, `RouterProvider`, `renderToString`). Do not adopt *framework mode*; its Vite plugin and server rendering are what #5 rejected with Next.js.

### TanStack Query v5 — ✅ (new)
- **How used:** all server state. Query keys centralized; never retry a 4xx.
Expand All @@ -77,6 +77,16 @@
### Vitest + Testing Library + vitest-axe — ✅ (new, test)
- **How used:** component tests and an automated accessibility floor on the primitives.

### framer-motion — ✅ (new, spec 0003)
- **Why:** foundation §7 #21 gates new dependencies. The landing page (spec 0003, AC-10) needs real motion with a genuine reduced motion path, and framer-motion ships `useReducedMotion` plus declarative enter and scroll animation. Chosen over hand rolled CSS so the reduced motion branch is one hook, not media queries scattered across the stylesheet.
- **How used:** landing page only, inside `features/landing/`. A small kit (`features/landing/motion/`) wraps it: `useMotionReady` (in `useMotionReady.ts`) combines framer-motion's `useReducedMotion` with a mount gate, so the resting state is always the fully visible one; `Reveal` fades and lifts content the first time it scrolls into view; `AmbientFloat` drifts the decorative bubbles; the hero crossfades its copy and persona on toggle; the `Highlight` fill sweeps in. Every animation checks `useMotionReady` first.
- **Gotchas:** keep it out of the authenticated app bundle. The landing route is lazy loaded (AC-13) so framer-motion lands in its own chunk; never import it from a shared component. During the build time prerender (AC-11) `useMotionReady` is false (no window, not mounted), so the static markup carries no hidden or shifted state and a no JavaScript reader still sees every word.

### Landing prerender (react-dom/server, no new package) — ✅ (spec 0003)
- **Why:** the landing page needs real HTML for search engines and social cards (spec 0003, AC-11), but the app is a client rendered SPA with no SSR (foundation §7 #5/#25). Prerendering only the `/` route at build time gives that HTML without adopting an SSR framework.
- **How used:** `renderToString` from `react-dom/server` (already present through react-dom, so no new package) renders the same app tree the client hydrates, with a memory router pinned to `/` (`src/prerender.tsx`, built by `vite build --ssr`). A postbuild script (`scripts/prerender.mjs`) injects that markup plus the SEO head (`features/landing/seo.ts`) into the built `index.html` and writes a separate `index.prerender.html`. The neutral `index.html` stays the SPA fallback. The gateway serves `index.prerender.html` only at exact `/` and the neutral bootstrap everywhere else (AC-14). `main.tsx` hydrates when `#root` has prerendered markup, else mounts fresh.
- **Gotchas:** the route table lives in `routes.tsx` (no `createBrowserRouter`), so the prerender can import it in Node without touching the History API. Keep every provider above the routes identical between `main.tsx` and `prerender.tsx` or `useId` and hydration drift. `vite-react-ssg` is not used; if this script path is ever abandoned, add it here first.

## Approved dependencies

Do not install anything outside this list without adding it here first (with a why + how-used).
Expand All @@ -95,10 +105,11 @@ Do not install anything outside this list without adding it here first (with a w
| xUnit, Moq, coverlet | Testing | ✅ |
| React 19, Vite 8, TypeScript | Frontend SPA | ✅ (spec 0001) |
| tailwindcss 4 + @tailwindcss/vite | Styling, bound to design tokens | ✅ (spec 0001) |
| react-router 7 | Routing (data router, no loaders) | ✅ (spec 0001) |
| react-router 8 | Routing (data router, no loaders); memory router for the spec 0003 prerender | ✅ (spec 0001, 8.2.x) |
| @tanstack/react-query 5 | Server state | ✅ (spec 0001) |
| react-hook-form + @hookform/resolvers + zod 4 | Forms and boundary validation | ✅ (spec 0001) |
| @phosphor-icons/react 2 | Icons | ✅ (spec 0001) |
| framer-motion 12 | Landing page motion, reduced motion aware (landing only, code split) | ✅ (spec 0003) |
| vitest, @testing-library/react, vitest-axe | Frontend tests + a11y floor | ✅ (spec 0001) |
| eslint + typescript-eslint + eslint-plugin-jsx-a11y + prettier | Lint (a11y rules enforced) | ✅ (spec 0001) |
| UI components | Authored in-repo from `design-system/` | ✅ no library (export ships no React source) |
Expand Down
Loading
Loading