# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Commands ```bash pnpm dev # Start dev server pnpm build # Production build pnpm lint # ESLint pnpm typecheck # tsc --noEmit pnpm test # Vitest unit tests (run once) pnpm test:e2e # Playwright E2E (requires dev server running) pnpm db:generate # Regenerate Prisma client after schema changes pnpm db:push # Sync schema to DB (dev, no migration files) pnpm db:migrate # Apply migrations (production) pnpm db:seed # Seed with sample data pnpm db:studio # Open Prisma Studio ``` Package manager: **pnpm**. ## Coding conventions ### Server-first by default This codebase maximises server-side rendering and minimises client-side JavaScript: - **All pages are server components** by default. Never add `"use client"` to a page file. - **Data fetching always happens server-side** in `src/lib/server-data.ts` — never `fetch()` from the client, never call Prisma from a client component. - **All mutations use server actions** (`src/actions/`) — never create new API routes for mutations. The only remaining API routes are the export endpoints and auth (see below). - **`"use client"` is only added** to components that genuinely need browser APIs or React state (forms, interactive widgets). Keep the surface area small. - **Auth checks happen in every server action** via `const session = await auth()` before touching the DB. ### Data flow pattern - **Server page** calls `src/lib/server-data.ts` functions (which call `auth()` + Prisma internally) - Page passes serialized data as props to **client components** in `src/components/` - Client components call **server actions** (`src/actions/`) for mutations - Server actions call `revalidatePath()` to trigger cache invalidation ### Auth `src/auth.ts` — NextAuth v5 with Credentials provider (email + bcrypt password). JWT strategy with `id` and `role` added to the token. Two roles: `evaluator` (default) and `admin`. `src/middleware.ts` — Protects all routes. Admin routes redirect non-admins. Auth routes redirect logged-in users to `/dashboard`. ### Access control `src/lib/evaluation-access.ts` — `canAccessEvaluation()` is the single source of truth. An evaluation is accessible if: user is admin, user is the evaluator, evaluation is shared with the user (`EvaluationShare`), or `isPublic` is true (read-only). ### Database SQLite in dev (`DATABASE_URL=file:./dev.db`), swap to Postgres for production. Schema lives in `prisma/schema.prisma`. Key relations: - `Evaluation` → `Template` → `TemplateDimension[]` - `Evaluation` → `DimensionScore[]` (one per dimension, `@@unique([evaluationId, dimensionId])`) - `Evaluation` → `EvaluationShare[]` (many users) - `Evaluation` → `AuditLog[]` `TemplateDimension.suggestedQuestions` is stored as a JSON string (array). `TemplateDimension.rubric` is stored as a `"1:X;2:Y;..."` string. Both are parsed client-side. ### API routes (remaining) Only exports and auth use API routes: - `GET /api/export/csv?id=` and `GET /api/export/pdf?id=` — use `src/lib/export-utils.ts` - `POST /api/ai/suggest-followups` — **stub**, returns deterministic suggestions; replace with real LLM call if needed - `POST /api/auth/signup` — user registration ### Key types `src/types/next-auth.d.ts` extends `Session.user` with `id` and `role`. Always use `session.user.id` (never `session.user.email`) as the user identifier in server actions. ### JWT staleness `session.user.name` comes from the JWT token frozen at login time. If a page needs the user's current `name` (or any other mutable profile field), query Prisma directly using `session.user.id` — do not rely on the session object for those values.