This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
- The concom (Finnish: preferred conitea, official järjestelytoimikunta) means the organizing committee, a team of volunteers organizing a convention.
This is a monorepo with two main components:
kompassi/— Django backend (Python 3.14+)kompassi-v2-frontend/— Next.js frontend (TypeScript, React, Apollo Client)
The backend serves both a legacy Django-rendered UI (V1) and a GraphQL API consumed by the V2 frontend. The V2 frontend lives at a separate origin and authenticates via OAuth2/OIDC Bearer tokens.
The easiest way to run everything:
docker compose up --buildBackend available at http://localhost:8000, superuser mahti/mahti.
The frontend can connect to dev.kompassi.eu (default) or a local backend:
cd kompassi-v2-frontend
npm install
export NEXT_PUBLIC_KOMPASSI_BASE_URL=http://localhost:8000
npm run dev # starts Next.js at localhost:3000 with GraphQL codegen watchAll backend commands assume the virtualenv is active (source .venv/bin/activate) or are run via Docker.
# Run backend tests — must use docker-compose.test.yml (no local DB)
# The default command is pytest --reuse-db; append path args to target specific tests
docker compose -f docker-compose.test.yml run --rm test
docker compose -f docker-compose.test.yml run --rm test pytest kompassi/program_v2/tests.py
# Lint and format
ruff check --fix .
ruff format .
# Migrations (must run as root inside Docker to write files)
docker compose exec --user=root backend python manage.py makemigrations
# Apply migrations
python manage.py migrate
# Compile i18n (extract, update .po, compile .mo)
python manage.py kompassi_i18n -aeuccd kompassi-v2-frontend
npm run dev # Next.js dev server + GraphQL codegen watch
npm run build # production build
npm run lint # ESLint
npm run test # lint + prettier check
npm run format # prettier writeGraphQL types are auto-generated into src/__generated__/. After changing any GraphQL query/fragment/mutation in .ts/.tsx files, codegen must run (it runs automatically with npm run dev). The generated files are committed to the repo so the production Docker build (Dockerfile) can run next build without a live backend. Never add codegen to the build script — it requires a live backend and breaks CI.
To run codegen + build against the local backend via docker-compose (e.g. to verify types after schema changes):
docker compose up -d router backend postgres redis minio
docker compose run --rm frontend sh -c "graphql-codegen && next build"To verify the production Docker image builds correctly (no backend required):
docker build -f kompassi-v2-frontend/Dockerfile kompassi-v2-frontend- Python linter/formatter: ruff (line length 120, configured in
pyproject.toml) - Pre-commit hooks run ruff, prettier, and end-of-file/trailing-whitespace fixers
- Python package manager: uv (
uv.lockpresent) - The 8-8-8-8 rule: always spell out
hostname,database,username,passwordin full — never abbreviate
The kompassi/ package contains many Django apps. The most active ones in V2 development:
core—Event,Person,Organizationmodels; the central domain objectsforms— Surveys V2:Survey,Form,Field,Response,ResponseDimensionValueprogram_v2— Program V2:Program,ScheduleItem,ProgramV2EventMetatickets_v2— Tickets V2: orders, products, quotas, Paytrail payment integrationdimensions— shared dimension system:Universe,Dimension,DimensionValue,Scopeinvolvement— cross-app person involvement tracking:Involvement,Invitation,Registrygraphql_api— single GraphQL schema (schema.py) that aggregates mutations/queries from all apps
Legacy V1 apps (labour, badges, mailings, access, etc.) still exist and are active but are not being extended.
All V2 features are exposed through a single Graphene schema at /graphql. The entry point is kompassi/graphql_api/schema.py. Each app contributes its own graphql/ subdirectory with query types and mutations.
The frontend uses Apollo Client with @apollo/client-integration-nextjs. All queries/mutations are written inline in .tsx files using the graphql() tag and types are generated by GraphQL Codegen.
Dimensions are a central cross-cutting concept: a Universe defines a set of Dimensions (each with DimensionValues) that can be attached to Programs, Survey responses, or Involvements. A Scope ties a Universe to an Organization or Event.
cachedDimensions is a JSON field on Program/Response/Involvement that caches applied dimension values for fast filtering without joins. It must be kept in sync via refresh_cached_fields().
RequestWithCache (in core/middleware.py) is a typed HttpRequest subclass that carries a RequestLocalCache with EventCache and UniverseCache. This is used to avoid redundant DB queries within a single request. GraphQL resolvers receive this via info.context.
Involvement is the universal model for "a person's relationship to an event" — covering program hosts, survey respondents, volunteers, and badge recipients. It links Person → Event (via Scope) and can carry dimension values. The Invitation model handles email-based invitations that create Involvements upon acceptance.
The Next.js app uses the App Router with [locale] and [eventSlug] as top-level dynamic segments:
src/app/[locale]/
[eventSlug]/
program/ — public program schedule
program-admin/ — program manager views
surveys/ — survey responder views
orders/ — ticket order flow
...
profile/ — user profile
Admin views under *-admin routes are server components that fetch via Apollo and pass data down. Client interactivity (favorites, filters) uses client components with server actions.
src/auth.ts uses NextAuth with the Kompassi OIDC provider. The Apollo client (src/apolloClient.ts) injects the session's accessToken as a Bearer token on every GraphQL request.
Translations are in src/translations/ as TypeScript objects (not i18n library files). getTranslations(locale) returns the typed translation tree for the current locale (fi, en, sv). The type is derived from en.tsx, so all locales must have the same keys — fi.tsx has full translations, sv.tsx uses UNTRANSLATED(en.X) helpers for untranslated sections. When adding a new translation key, add it to all three files or the TypeScript build will fail.
Never hardcode user-visible strings in V2 frontend components. All user-visible text must go through the translation system. Pass a messages prop (a slice of the Translations type) to any component that renders text, and add new keys to all three translation files (en.tsx, fi.tsx, sv.tsx) — including a real Swedish translation, not UNTRANSLATED(...).
The backend uses CBAC (claims-based access control) via graphql_check_instance / graphql_check_model from kompassi/access/cbac.py. Key patterns:
public_only=True(default) on resolvers — no auth required, but data may be filteredpublic_only=False— must callgraphql_check_modelorgraphql_check_instancebefore returning data- Admin frontend pages should query with
publicOnly: false; public pages omit the argument (defaults totrue)