Local development
The dev stack
Section titled “The dev stack”docker compose -f docker-compose.dev.yml up --build# frontend → http://localhost:3000# backend → http://localhost:8000 (interactive API docs at /docs)Source is mounted and both sides hot-reload. The default
docker compose up (no -f) is the production stack — use the dev file
for iteration.
Running each side directly
Section titled “Running each side directly”# Backend — host Python is often externally-managed, so use a venvcd backendpython3 -m venv .venv.venv/bin/pip install -r requirements-dev.txt.venv/bin/alembic upgrade head # against a reachable Postgres.venv/bin/uvicorn main:app --reload
# Frontend — requires Node 20+cd frontendnpm installnpm run devThe test suite (and the repo’s own tooling) can run the backend against
SQLite with no infrastructure at all; point DATABASE_URL at Postgres when
you want parity with production.
Before you open a PR
Section titled “Before you open a PR”Run what CI runs — all four must pass:
cd backend && .venv/bin/pytest # backend tests (SQLite, no infra)cd frontend && npm run test # vitestcd frontend && npx tsc --noEmit # type-checkcd frontend && npx eslint . # lintIf the change is user-visible, run it in a browser too — tests alone don’t prove UI behaviour.
The rules the codebase enforces
Section titled “The rules the codebase enforces”These are architectural rules, not preferences — some are enforced by ESLint:
- Every mutation emits an event using the catalogued vocabulary — add the event type to the catalogue before emitting it.
- Every tenant-scoped query is scoped by
competition_idat the data-access layer. - Permission checks go through
require_permission— never an inline role check. Missing permission? Add it to the catalogue first. - One hook module per frontend domain — components never import the API client directly (ESLint-enforced).
- Colours and spacing come from design tokens — no raw hex in components (ESLint-enforced, brand mark excepted).
- New backend features register through the module loader — see Developing modules.
- Every new frontend page/surface is “born extracted” — its
user-facing strings go into
frontend/messages/en.jsonviat()from the first commit (that file is Crowdin’s translation source), or they’re invisible to translators. See Interface languages.
Bugs, features, and questions
Section titled “Bugs, features, and questions”- Bug? Open a bug report (search existing issues first).
- Small, well-defined feature idea? A feature request.
- Big or open-ended idea, or a question? Start a Discussion so scope gets shaped before it becomes a tracked issue.
- Security vulnerability? Never a public issue — private disclosure.
New issues start as needs-triage; a maintainer confirms, labels, and — if
it’s slated for a release — milestones it onto the public roadmap.
Reading list
Section titled “Reading list”docs/ARCHITECTURE.md— the binding technical design. If code and this document disagree, one of them is wrong, and that’s a bug.docs/adr/— why decisions went the way they did (index here).CONTRIBUTING.md— the PR flow. Security issues go through private disclosure, never a public issue.