Coding Standards¶
These standards are enforced by linters (ruff, eslint) and reviewed in every PR.
Python (Backend)¶
Style¶
- Format with ruff format (configured in
pyproject.toml) - Lint with ruff check — all rules enforced
- Type check with mypy in strict mode
Type hints¶
Type hints are required on every function signature. No Any without a code comment explaining why.
# Good
async def get_project(project_id: UUID, db: AsyncSession) -> Project | None:
...
# Bad
async def get_project(project_id, db):
...
Async¶
All I/O-bound code is async. Use async def for routes, services, and database access.
Naming¶
snake_casefor functions, variables, modulesPascalCasefor classesUPPER_SNAKE_CASEfor module-level constants- Database tables:
snake_case, plural (e.g.projects,site_diary_entries)
Comments¶
Comments explain why, not what. The code shows what.
# Bad: Cache the user for 5 minutes
user = cache.get_or_set(user_id, lambda: db.get_user(user_id), ttl=300)
# Good: Cache user lookups for 5 minutes — see ADR-0012 for the trade-off
# between freshness and DB load. Invalidated on user.updated events.
user = cache.get_or_set(user_id, lambda: db.get_user(user_id), ttl=300)
TypeScript (Frontend)¶
Style¶
- Format with Prettier (config in
.prettierrc) - Lint with ESLint — all our rules enforced
Type safety¶
- No
any— useunknownif the type is genuinely unknown, then narrow it - Prefer interfaces over type aliases for object shapes
- Use the auto-generated
api.tstypes — never hand-type API responses
// Good
import type { components } from "@/types/api"
type Project = components["schemas"]["Project"]
// Bad
type Project = {
id: string
name: string
// ... drift inevitable
}
React¶
- Functional components only (no class components)
- Hooks for state and side effects
- One component per file
- Component file name matches the component name (
ProjectList.tsxexportsProjectList)
Imports¶
Order:
- External libraries
- Internal aliases (
@/...) - Relative imports
Both languages¶
File organisation¶
modules/
└── [feature]/
├── models.py # SQLAlchemy
├── schemas.py # Pydantic
├── service.py # Business logic
├── router.py # FastAPI routes
└── tests/ # Pytest tests
Don't repeat yourself, but don't abstract prematurely¶
If you see the same pattern in 2 places, fine. By the 3rd, extract.
Make impossible states impossible¶
Use union types and discriminated unions to make invalid states uncompilable.