This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
FK Vaalimasiina is an electronic voting system for the Guild of Physics, built with Next.js 16, React 19, TypeScript, and PostgreSQL. It implements Single Transferable Vote (STV) with Droop quota and Plain Majority voting methods.
| Task | Command |
|---|---|
| Dev server | pnpm dev |
| Build | pnpm build |
| Lint | pnpm lint |
| Lint + autofix | pnpm lint --fix |
| Type check | pnpm type:check |
| Algorithm tests | npx playwright test tests/algorithm/ |
| E2E tests | npx playwright test |
| Single E2E test | npx playwright test tests/voter-voting.spec.ts |
| Format | pnpm format |
| Check formatting | pnpm format:check |
| DB migrations | pnpm db:migrate |
| Generate migration | pnpm db:generate |
| Generate test data | pnpm generate-election |
Package manager is pnpm (enforced via preinstall hook).
All pages live under src/app/[locale]/ with en and fi locales (next-intl). Do not import Link, redirect, useRouter, or usePathname from next/link or next/navigation — use ~/i18n/navigation instead. This is enforced by oxlint no-restricted-imports.
All user-facing text must use translations from src/i18n/en.ts and src/i18n/fi.ts. Avoid hardcoded strings in JSX; use translations.
Mutations use next-safe-action with Zod input schemas. Pattern:
actionClient
.inputSchema(zodSchema)
.use(isAuthorizedMiddleware) // for admin actions
.action(async ({ parsedInput }) => { ... })Admin actions are in src/actions/admin/, voting action in src/actions/vote.ts.
Drizzle ORM with PostgreSQL. Schema in src/db/schema.ts, relations in src/db/relations.ts. Uses snake_case column convention. Path alias: ~/db.
Key tables: elections (with status enum: CREATED → UPDATING → ONGOING → FINISHED → CLOSED), candidates, voters, ballots, votes, has_voted. Voter identity is separated from ballot data for vote secrecy.
OAuth 2.0 flow (Google/GitHub/Microsoft/Custom) → JWT stored in HTTP-only cookie (admin-token). Admin access controlled by ADMIN_EMAILS env var. Auth middleware in src/actions/middleware/isAuthorized.ts and route protection in src/proxy.ts.
Validated with @t3-oss/env-nextjs in src/env.ts. Import as import { env } from '~/env'. Skip validation with SKIP_ENV_VALIDATION=true.
~/* maps to ./src/* (configured in tsconfig.json).
- Commits: Conventional Commits format —
feat:,fix:,refactor:,chore:, etc. Imperative mood, lowercase, no period. One logical change per commit. - Linting: Always run
pnpm lintafter code changes and fix errors before finishing. - Imports: Optionally sorted by oxfmt
experimentalSortImportswhen enabled. - Styling: Tailwind CSS 4 with oxfmt (experimentalTailwindcss) for class sorting. Single quotes, no trailing commas.
- Pre-commit hook: lint-staged runs
tsc --noEmit,oxlint --fix, andoxfmton staged files.
- Algorithm tests (Playwright):
tests/algorithm/*.spec.ts— STV and majority algorithm logic (no browser needed; run withnpx playwright test tests/algorithm/). - E2E tests (Playwright):
tests/*.spec.ts. Runs againsthttp://localhost:3000/en/. Workers: 1, not parallel. Dev server starts automatically if not running. - In test/dev mode,
test@email.comis auto-authorized as admin.