Contributing¶
ALMa is currently a single-author project, but the conventions below apply to anyone landing a change — including future-you.
Branch model¶
main— stable. Never push directly.dev— active work. PRs targetdev;devmerges intomainperiodically.- Feature branches are usually short-lived; rebase onto
devbefore merging.
Commits¶
Conventional commit prefixes:
feat:— new featurefix:— bug fixrefactor:— restructure without behaviour changechore:— tooling, dependencies, builddocs:— documentation onlytest:— tests onlyperf:— performance improvement
Subject lines are short (≤ 72 chars). Body explains the why; the diff explains the what.
Sign your commits as yourself. Don't squash-merge feature branches that contain meaningful intermediate commits — let them carry their history.
Code style¶
Python¶
- Black-formatted.
rufflints and catches the obvious. - Type-hinted on public functions and module boundaries.
- Docstrings on classes and on functions whose intent isn't obvious from the signature. Brief, factual.
- Comments explain why, not what. Identifiers should make the what obvious.
TypeScript¶
- Strict mode, no implicit
any. - Function components only; no class components.
constfor everything;letonly when actually reassigning.- Avoid default exports — named exports stay greppable.
- Imports ordered: external libs → internal aliases → relative.
DRY without overengineering¶
Three similar lines is better than a premature abstraction. But when you've copied a meaningful pattern three times, lift it.
Where the lifted pieces live:
- Backend helpers:
src/alma/api/helpers.py. Don't defineraise_internal,row_to_paper_response,safe_div,table_exists,json_loads,normalize_topic_termlocally. - Frontend utilities:
frontend/src/lib/utils.ts. Don't defineformatTimestamp,formatNumber,truncate, badge class functions locally. - Query invalidation:
frontend/src/lib/queryHelpers.ts. UseinvalidateQueries(qc, ...keys)instead of repeatedqc.invalidateQueries(...)calls.
Forward-looking changes¶
Renames, schema changes, and contract changes are one-shot
migrations, not multi-version compatibility shims. If
/library/likes becomes /library/saved, the old endpoint is
removed in the same change.
The reasoning: ALMa is single-user, single-version. There's no client out there running an old build that needs the old contract. Carrying both adds complexity nobody benefits from.
Testing your change¶
Before opening a PR:
# python
pytest tests/test_<the_thing_you_changed>.py
ruff check .
black --check .
# frontend (if you touched it)
cd frontend
npm run typecheck
npm run build
cd ..
For changes to the recommender or scheduler, exercise a real refresh and inspect Activity → Operations for per-source timing. Compare against the previous run on the same lens — unexplained shifts are the regression signal.
For UI changes, capture screenshots at three viewports (1440×900, 1024×768, 390×844) and look at them. Static review misses real defects.
Pull requests¶
- Title:
<type>(<scope>): <imperative summary>. Example:feat(discovery): add S2 snippet retrieval lane. - Description: what changed, why, what's tested. Include screenshots for UI changes.
- Keep diffs small. If you find unrelated work that needs doing, open a separate PR.
- Self-review the diff before requesting review.
Things to avoid¶
- Adding fallbacks for impossible scenarios. Trust internal code and framework guarantees; only validate at system boundaries (user input, external APIs).
- Hidden auto-compute / auto-enrichment hooks. Heavy work is Activity-backed and explicit, not silently triggered on read paths.
- Half-finished implementations. Either ship it or don't.
window.confirm/window.alert. Use the AlertDialog primitive.- Provider-inferred labels. AI provider names come from the backend contract, not from frontend ternaries.
- Mocking the database in integration tests. Real SQLite, every time.
Releasing¶
Releases are versioned tags (vX.Y.Z). A tag push publishes the
Docker flavors to GHCR (.github/workflows/docker-publish.yml),
and scripts/release.sh X.Y.Z is the one command that cuts the
whole thing: preflight gates → full test suite → version bump
(pyproject.toml + connector manifests) → commit + tag → Firefox
connector signed locally from the tag → push → GitHub release
whose body is the committed docs/releases/vX.Y.Z.md → .xpi
upload. The script is resumable after a partial failure and keeps
the AMO signing key strictly local (no CI/repo secrets). Deploy the
published image with scripts/deploy-prod.sh X.Y.Z.
When a change requires user action on existing installs (a config key rename, a new env var), call it out in the release notes file and document it in the relevant Reference page.