Contributing¶
The project ships in slices: small end-to-end changes that each leave the app in a green, deployable state. A typical slice is one PR.
Branch flow¶
git checkout main
git pull --ff-only
git checkout -b feature/<descriptive-slug>
# ... commits ...
git push -u origin feature/<descriptive-slug>
gh pr create
Branch names use kebab-case: feature/vehicle-schedule, fix/triage-counts-contrast, chore/local-readme-static-dir.
Commit messages¶
Short imperative title (≤70 chars), body explains the why. We don't enforce conventional commits but typical prefixes are:
Add ...— new featureFix ...— bug fixMove .../Rename ...— reorganisationUpdate ...— enhancement to an existing featureTighten ...— heuristic / validation change
Use a HEREDOC for multi-line messages:
git commit -m "$(cat <<'EOF'
Topic page: per-block 'Confirm all as filed correctly'
Long explanation of why this slice exists, what changed, and any
non-obvious decisions or trade-offs.
Co-Authored-By: ... <noreply@anthropic.com>
EOF
)"
PRs¶
PR title = the commit subject of the most-meaningful commit. PR body is the summary + test plan, in two sections:
## Summary
Two-three bullets on what landed and why.
## Test plan
- [x] `pytest` — all green
- [ ] Manually click through path X
- [ ] Verify Y on prod after deploy
The CI gate runs the full pytest suite + the docs-drift check on every PR. Both must pass before merge.
CHANGELOG.md¶
Every merged PR adds a dated entry to lifefile/CHANGELOG.md. New entries on top, under today's date. Keep it terse-but-readable — focus on the why, not "added a function called X".
If you have multiple commits in one PR they collapse into one CHANGELOG entry.
Static checks¶
We have two CI tests that prevent specific recurring bugs:
apps/common/tests/test_template_comments.py— multi-line{# ... #}Django comments leak as visible HTML; use{% comment %} ... {% endcomment %}for anything spanning more than one line.apps/common/tests/test_template_id_drift.py— duplicatedid="..."across templates must have identical class lists, because OOB swaps will silently overwrite.
Both run as part of pytest. Add similar checks for any future bug-shape that bites us twice.
Memory + design rules¶
- Text colour on the navy shell.
text-muted/text-ink*are dark tokens designed for white panels. On the navy app shell, usetext-on-surface/text-on-surface-mut. Common offenders: page-header captions / breadcrumbs / counts directly under<header>. - White cards = data, accent-tint cards = guidance. The dashboard's "Get documents in" panel is accent-tint; the "Other things you can file here" empty-topics card is accent-tint. White cards are reserved for actual filed documents and registered named items.
- Topics are a hardcoded enum in
apps/documents/models.py:Topic. User-defined topics aren't viable today — the classifier prompt + auto-assignment routes against the fixed set. - Wizards are opt-in entry points, never required. Users who just want to upload should never be nudged into a wizard / completion flow.
When to add a slice vs. defer¶
A change earns a slice if:
- It would change the user's mental model (UI restructure, new data type).
- It introduces a new external dependency.
- It needs a new model / migration.
- It crosses more than two apps.
Smaller stuff (typo fixes, copy edits, bug fixes contained to one file) goes in as a small PR with a one-line CHANGELOG entry.