Skip to content

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 feature
  • Fix ... — bug fix
  • Move ... / Rename ... — reorganisation
  • Update ... — enhancement to an existing feature
  • Tighten ... — 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 — duplicated id="..." 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, use text-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.