Skip to content

Code style

Python

backend/ruff.toml exists and is a real, checked-in configuration:

indent-width = 4

[format]
indent-style = "space"
line-ending = "lf"
quote-style = "double"

[lint]
select = ["E", "F", "I"]
ignore = ["E501"]

That selects pycodestyle errors (E), pyflakes (F), and import sorting (I), and explicitly ignores line-length (E501), so this project has decided not to enforce a line-length limit, not that it forgot to.

ruff is the only Python style tool here, and it is a real dev dependency, so uv sync installs it and these commands work on a fresh clone:

cd backend
uv run ruff check .          # lint: E, F, I as ruff.toml selects them
uv run ruff check --fix .    # and fix what is safely fixable
uv run ruff format .         # format the tree
uv run ruff format --check . # or just report, which is what CI runs

There is no black and no isort. ruff format is a reimplementation of black and produces the same output, and ruff's I rules do isort's job, so all three would be doing one job with two of them redundant, and black and ruff format can disagree at the edges. Older docs and habits name uv run black . && uv run isort .; neither is installed and neither should be added.

CI runs both, and both must pass. .github/workflows/tests.yml runs uv run ruff check . and uv run ruff format --check . as hard steps, not warnings. uv run rather than uvx on purpose: ruff is pinned in uv.lock, so your machine and CI check against one version and a ruff release cannot turn the build red overnight.

The tree conforms. It was formatted in full on 2026-08-07 (205 files) with imports sorted in 112 more, so ruff format . on a clean checkout changes nothing and your diff stays your diff. That is the point of formatting everything at once: before it, running the formatter across the tree turned any focused PR into an unreviewable one, which is why the old advice here was to scope uvx to the files you touched. That advice no longer applies. Run the commands above on the whole tree and commit only what you meant to change.

One thing worth knowing before you run --fix over code you did not write: a # noqa binds to the line ruff reports the diagnostic on, so a suppression that has drifted onto the next line, or that names the wrong rule code, silently suppresses nothing, and the autofix will then delete what it was protecting. common/models.py's SecurityAuditLog import is the live example: it exists only so Django discovers the model for migrations, and its # noqa: F401,E402 has to sit on the same line as the imported name.

Frontend

frontend/package.json defines the real commands:

pnpm run lint     # prettier --check . && eslint .
pnpm run format   # prettier --write .
pnpm run check    # svelte-kit sync && svelte-check --tsconfig ./jsconfig.json

These tools are installed (prettier, eslint, svelte-check, and their plugins are all in frontend/package.json's devDependencies) and CI runs all three, but not symmetrically: .github/workflows/tests.yml's frontend-checks job runs the Prettier check with a || echo "::warning::..." fallback, so a formatting difference shows up as a CI warning, not a failed build. The ESLint step (pnpm eslint .) has no such fallback. A lint error fails the job. pnpm check (svelte-check) and pnpm build run unconditionally after that and also fail the job on error. So in practice: fix ESLint errors and type errors before opening a PR, but don't be surprised if Prettier differences are flagged as a warning rather than a hard failure. Run pnpm run format yourself rather than relying on CI to catch it.

Flutter

cd mobile
dart format .
flutter analyze --no-fatal-infos

mobile/analysis_options.yaml includes package:flutter_lints/flutter.yaml with no project-specific rules added or removed. Neither of this repository's two GitHub Actions workflows (tests.yml, codeql-analysis.yml) mentions Flutter, Dart, or mobile/ at all, so nothing in CI enforces Dart formatting or lint cleanliness. Unlike the backend, which now gates on ruff, running these commands yourself before opening a PR is the only check that happens. See Build and configure for the full command set including flutter test.