# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Project Overview **MOPC (Monaco Ocean Protection Challenge)** — a secure jury voting platform for managing multi-round project selection. Jury members evaluate ocean conservation projects through configurable competitions with rounds (intake, filtering, evaluation, submission, mentoring, live finals, deliberation). Domain: `monaco-opc.com`. ## Common Commands ```bash # Development npm run dev # Next.js dev (Turbopack) npm run build # Production build (always test before push) npm run typecheck # tsc --noEmit # Database npx prisma generate # Regenerate client after schema changes npx prisma migrate dev # Create + apply migration npx prisma studio # GUI database browser npm run db:seed # Run seed (tsx prisma/seed.ts) # Testing (vitest, not jest) npx vitest # Run all tests (watch mode) npx vitest run # Run all tests once npx vitest run tests/unit/round-engine.test.ts # Single file npx vitest run -t 'test name' # Single test by name # Code Quality npm run lint # ESLint npm run format # Prettier # Docker (production) docker compose -f docker/docker-compose.yml up -d docker compose -f docker/docker-compose.dev.yml up -d # Dev stack ``` ## Tech Stack Next.js 15 (App Router) | TypeScript 5 (strict) | Tailwind CSS 4 | shadcn/ui | tRPC 11 (superjson) | Prisma 6 | PostgreSQL 16 | NextAuth 5 (Auth.js) | Vitest 4 | OpenAI SDK 6 | MinIO (S3) | Nodemailer + Poste.io ## Architecture ### Data Flow: Prisma → tRPC → React End-to-end type safety: `prisma/schema.prisma` defines models → `src/server/routers/*.ts` expose tRPC procedures with Zod validation → `src/lib/trpc/client.ts` provides typed React hooks → components call `trpc.domain.procedure.useQuery()`. ### tRPC Middleware Hierarchy (`src/server/trpc.ts`) All role-based access is enforced via procedure types: | Procedure | Roles Allowed | |-----------|---------------| | `publicProcedure` | Anyone (no auth) | | `protectedProcedure` | Any authenticated user | | `adminProcedure` | SUPER_ADMIN, PROGRAM_ADMIN | | `superAdminProcedure` | SUPER_ADMIN only | | `juryProcedure` | JURY_MEMBER only | | `mentorProcedure` | SUPER_ADMIN, PROGRAM_ADMIN, MENTOR | | `observerProcedure` | SUPER_ADMIN, PROGRAM_ADMIN, OBSERVER | | `awardMasterProcedure` | SUPER_ADMIN, PROGRAM_ADMIN, AWARD_MASTER | | `audienceProcedure` | Any authenticated user | ### Route Groups (Next.js App Router) - `src/app/(auth)/` — Public auth pages (login, verify, accept-invite, onboarding) - `src/app/(admin)/` — Admin dashboard, competition management, round config - `src/app/(jury)/` — Jury evaluation interface, round assignments, live voting - `src/app/(applicant)/` — Applicant dashboard, competition progress, document uploads - `src/app/(mentor)/` — Mentor workspace ### Competition System The core domain model. A **Competition** represents a complete evaluation cycle: ``` Competition → has many Rounds (ordered) ``` - **Competition** (`src/server/routers/competition.ts`): Top-level competition config with program link, status, and settings - **Round** (`RoundType: INTAKE | FILTERING | EVALUATION | SUBMISSION | MENTORING | LIVE_FINAL | DELIBERATION`): Each competition has ordered rounds with type-specific config in `configJson` Key models: - `JuryGroup` — Named jury panel with chair/member/observer roles - `AdvancementRule` — Auto-advance, score threshold, top-N, admin selection between rounds - `SubmissionWindow` — File submission deadlines per round - `AssignmentPolicy` / `AssignmentIntent` — Governance layer for jury assignment - `ProjectRoundState` — Per-project state within each round - `DeliberationSession` / `DeliberationVote` / `ResultLock` — Structured deliberation and result finalization Key services: - `src/server/services/round-engine.ts` — State machine for round transitions - `src/server/services/round-assignment.ts` — Jury assignment generation with policy enforcement - `src/server/services/submission-manager.ts` — File submission + filtering with duplicate detection - `src/server/services/deliberation.ts` — Deliberation session management and vote tallying - `src/server/services/result-lock.ts` — Result finalization and unlock governance - `src/server/services/live-control.ts` — Live ceremony cursor management - `src/server/services/competition-context.ts` — Cross-cutting competition context resolver Competition types: `src/types/competition.ts`, `src/types/competition-configs.ts` ### AI Services (`src/server/services/ai-*.ts`) All AI calls anonymize data before sending to OpenAI. Services: - `ai-filtering.ts` — AI-powered project screening with rubric - `ai-assignment.ts` — GPT-suggested jury-project matching - `ai-evaluation-summary.ts` — Strengths/weaknesses synthesis from evaluations - `ai-tagging.ts` — Auto-tagging projects - `ai-award-eligibility.ts` — Award eligibility assessment - `ai-shortlist.ts` — AI-powered shortlist recommendations - `anonymization.ts` — Strips PII before AI calls ### Auth System NextAuth v5 with two providers: **Email** (magic links) and **Credentials** (password + invite token). Failed login tracking with 5-attempt lockout (15 min). Session includes `user.role` for RBAC. ### Docker Entrypoint (`docker/docker-entrypoint.sh`) On container start: retry `prisma migrate deploy` → `prisma generate` → auto-seed if User table is empty → `node server.js`. A `docker compose down -v && docker compose up -d` will run all migrations from scratch and seed. ## Testing Infrastructure - **Framework**: Vitest 4 with `fileParallelism: false` and `pool: 'forks'` (tests run sequentially) - **Setup**: `tests/setup.ts` provides `prisma` client (uses `DATABASE_URL_TEST` or `DATABASE_URL`), `createTestContext(user)`, `createCaller(router, user)` - **Factories**: `tests/helpers.ts` has `createTestUser()`, `createTestProgram()`, `createTestCompetition()`, `createTestRound()`, `uid()` helper, and `cleanupTestData()` - **Pattern**: Create data with factories → build caller with `createCaller(routerModule, user)` → call procedures → assert → cleanup in `afterAll` ## User Roles `SUPER_ADMIN` | `PROGRAM_ADMIN` | `JURY_MEMBER` | `OBSERVER` | `MENTOR` | `APPLICANT` | `AWARD_MASTER` | `AUDIENCE` ## Coding Standards - **TypeScript**: Strict mode, `type` over `interface`, prefer `unknown` over `any` - **Files**: kebab-case. **Components**: PascalCase. **DB models**: PascalCase in Prisma - **React**: Server Components by default, `'use client'` only when needed - **Styling**: Tailwind utility classes, mobile-first (`md:`, `lg:` breakpoints), shadcn/ui as base - **tRPC**: Group by domain (`trpc.competition.create()`), Zod input validation, `TRPCError` for errors - **Brand colors**: Primary Red `#de0f1e`, Dark Blue `#053d57`, White `#fefefe`, Teal `#557f8c` - **Typography**: Montserrat (600/700 headings, 300/400 body) ## Key Constraints 1. Jury can only see assigned projects (enforced at query level) 2. Voting windows are strict — submissions blocked outside active window 3. All admin actions are audited via `DecisionAuditLog` 4. Files accessed via MinIO pre-signed URLs only (no public bucket) 5. COI declaration required before evaluation (blocking dialog) 6. Smart assignment skips COI-declared jurors, applies geo-diversity penalty and familiarity bonus 7. Cron endpoints protected by `CRON_SECRET` header 8. Round notifications never throw — all errors caught and logged ## Security - CSRF: tRPC uses `application/json` (triggers CORS preflight). Do NOT add permissive CORS headers. - Rate limiting: 100 req/min tRPC, 10 req/min auth, 5-attempt lockout - AI privacy: All data anonymized before OpenAI calls ## Seed Data `prisma/seed.ts` imports from `docs/Candidatures 2026 *.csv` with special handling for non-breaking spaces (U+00A0) around French guillemets. Includes ALL CSV rows (no filtering/dedup) — duplicate detection happens in `submission-manager.ts` at runtime. ## Environment Variables Required: `DATABASE_URL`, `NEXTAUTH_URL`, `NEXTAUTH_SECRET`, `MINIO_ENDPOINT`, `MINIO_ACCESS_KEY`, `MINIO_SECRET_KEY`, `MINIO_BUCKET`, `SMTP_HOST`, `SMTP_PORT`, `SMTP_USER`, `SMTP_PASS`, `EMAIL_FROM`, `OPENAI_API_KEY`, `CRON_SECRET` External services (pre-existing on VPS): MinIO `:9000`, Poste.io `:587`, Nginx reverse proxy with SSL. ## Git Remote: `code.monaco-opc.com/MOPC/MOPC-Portal`. Branch: `main`. Always `npm run build` before pushing.