# 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.
