Contributing
The repository accepts pull requests against main for new work,
against release/0.2.x for the active line, and against
release/0.1.x for maintenance fixes to the 0.1.0 export-format
line. All three branches require the test suite to pass before
merge; CI runs on PRs to main, develop, and release/**.
Development setup
git clone https://github.com/aaronstevenwhite/fovea.git
cd fovea
docker compose -f docker-compose.yml -f docker-compose.dev.yml up
The dev compose file binds the source directories into the backend and frontend containers so a save triggers a rebuild. The model service is built once and reused; re-build when its Python dependencies change.
Backend
The backend lives under server/. Run the test suite with:
cd server
npm install
npm test
Schema changes go through Prisma:
npx prisma migrate dev --name <migration_name>
Never edit a landed migration. Add a new migration that performs the desired transformation; see Project > Stability.
Frontend
The frontend lives under annotation-tool/. Run:
cd annotation-tool
npm install
npm run dev # vite dev server
npm test # vitest unit tests
npm run lint # eslint
End-to-end tests live under annotation-tool/e2e/ and run via
Playwright against the e2e compose stack:
docker compose -f docker-compose.yml -f docker-compose.e2e.yml up -d
npx playwright test
Model service
The model service lives under model-service/. Use uv:
cd model-service
uv sync
uv run pytest
Tests live under model-service/test/ (not tests/).
pytest.ini has coverage addopts; locally run with
--override-ini="addopts=" to skip coverage.
Commit messages
The convention is a single sentence with no conventional-commit
prefix. PR titles do use prefixes (fix:, feat:, docs:).
The PR template lives at .github/PULL_REQUEST_TEMPLATE.md; PR
bodies must keep every checkbox section, checked or unchecked.
Type rules
The model-service Python code does not use Any or bare object
type annotations. Use Protocols, generics, or concrete types
instead. Never soften a type to silence a lint.
The backend and frontend TypeScript follow strict mode. Add a TypeBox schema for every route; the request and response shapes are part of the API contract.
RBAC test baseline
Integration tests that exercise multi-user isolation
(test/integration/multi-user-isolation.test.ts,
import-export-cross-user.test.ts,
issue-121-real-fixture.test.ts) call the shared helper
test/integration/_rbac-baseline.ts from their beforeEach. The
helper deletes the test-helper's blanket-grant RolePermission
rows under the system scope and re-seeds an ownership-aware
production-like baseline: every action on every content type
(persona, annotation, summary, claim, world_state) is
seeded as ownOnly: true for the user system role, plus an
unconditional video read. This makes CASL's per-row condition
the gate the test exercises rather than an unconditional grant.
Without _rbac-baseline.ts, the test-helper's blanket grants
hide ownership leaks: the matrix would falsely pass even when
v0.2.0's permission state would have allowed cross-user access in
production. New isolation tests should pull in the helper.
Filing issues
Bugs go to the GitHub issues tracker. Reproduction steps that
hit the actual stack (docker compose up, then a sequence of
curl commands) are easier to act on than UI-only descriptions.
Multi-user isolation regressions should reference
test/integration/multi-user-isolation.test.ts so the matrix
gains a new test for the affected route.