Captua — Git workflow
This page describes how Captua’s repositories use Git: branches, pull requests, submodule pointers and releases, across the superproject and its submodules. To set up the development stack, see Developer setup.
Repository structure
- Superproject (
UCSB-AMPLab/digitization-toolkit-software): holds the orchestration (docker-compose.yml,docker-compose.dev.yml,docker-compose.pi.yml,nginx.conf), the environment example (.env.example), the setup, update, rollback and provisioning scripts and systemd units inscripts/, the documentation,CHANGELOG.md,VERSION, and the submodule pointers. - Submodules:
frontend/: the SvelteKit application (UCSB-AMPLab/digitization-toolkit-frontend)backend/: the FastAPI application (UCSB-AMPLab/digitization-toolkit-backend)wiki/: the project wiki (UCSB-AMPLab/digitization-toolkit-software.wiki)
Most code changes are made in the submodule repositories. The superproject records which commit of each submodule belongs together.
Team branching model
What the three repositories actually do (checked against their branches, merged pull requests and protection settings on 2026-11-08):
- Branches. Work branches from
devand is named after the repository area:frontend/<topic>andbackend/<topic>in the submodules;chore/bump-<topic>,docs/<topic>orscripts/<topic>in the superproject. - Into dev. Every change reaches
devthrough a pull request;devandmainrefuse direct pushes, force-pushes and deletions. No approving review is required. Before merging: CI passes where the repository has it (backend:pytest -m "not camera"and the migration drift check; frontend:npm run checkand Vitest; the superproject has none), andcodex review --base origin/devreports nothing open. Pull requests are merged with a merge commit (gh pr merge --merge), never squashed or rebased. - Submodule pointers. After a submodule pull request merges, a superproject branch
chore/bump-<topic>moves the pointer to the merged commit, and its own pull request goes intodev. - Releases. A release is a
dev→mainpull request in each submodule and then in the superproject, merged by an administrator with a merge commit. The protection rule asks for one approving review onmain; administrators are exempt (enforce_adminsis off), and release merges have used that exemption. Only the superproject is tagged, without avprefix (0.2.0), with a CHANGELOG pull request, arelease: Captua <version>commit and a GitHub Release. Docker images are built on each unit byscripts/update.sh, not by CI. - Urgent fixes. A few fixes have gone straight to
mainasfix/<topic>pull requests.
frontend/<topic>, backend/<topic> ──PR──► dev ──release PR──► main
chore/bump-<topic> (superproject) ──PR──► dev
Continuous integration
No status check is required by the branch protection on any of the three repositories, so a pull request can be merged with CI failing. Check the result before merging.
| Repository | Workflow | When it runs | What it runs |
|---|---|---|---|
| backend | .github/workflows/ci.yml |
pull requests into dev, pushes to dev |
pixi run -e dev pytest tests/ -m "not camera", then pixi run -e dev db-check-drift |
| frontend | .github/workflows/ci.yml |
every pull request, pushes to dev and main |
npm ci, npm run check, npx vitest run (in Chromium, through Playwright) |
The superproject has no workflow.
Workflow for submodules
1. Prepare the checkout
git fetch --all
git switch dev
git pull origin dev
git submodule update --init --recursive # takes the exact pins the superproject records (no --remote)
2. Create a feature branch in a submodule
cd frontend
git checkout dev
git pull origin dev
git checkout -b frontend/capture-panel
Commit as you work, then push:
git push -u origin frontend/capture-panel
3. Open a pull request in the submodule repository
Open the pull request against dev. Merge it with a merge commit once CI passes (see Team branching model).
4. Update the superproject pointer
A code change lands in two steps:
- Land the change in the submodule first. Branch inside the submodule (
frontend/<topic>orbackend/<topic>), open a PR against that submodule’s repo, merge it. - Bump the superproject pointer. On a superproject branch
chore/bump-<topic>fromdev, advance the submodule to the merged commit, commit the new gitlink, and open a PR intodev.
After the submodule pull request merges:
cd ..
git switch dev
git pull origin dev
git switch -c chore/bump-<topic>
git -C frontend fetch origin # or backend
git -C frontend checkout <merged-sha> # the exact commit merged in the submodule PR
# (read it with: git -C frontend rev-parse --short HEAD)
git add frontend
git commit -m "chore(submodule): bump frontend to <short-sha>"
git push -u origin chore/bump-<topic>
Open a pull request from chore/bump-<topic> into dev in the superproject. Do not push the commit to dev directly: dev refuses direct pushes.
Commit guidelines
Commits follow Conventional Commits, with a scope. Keep each commit to one change:
feat(api): add /upload endpointfix(ui): handle 404 from /healthchore(submodule): bump frontend to <short-sha>
Pull request checklist
- The stack builds and runs in Docker (
./scripts/start-dev.sh) - Backend CI and frontend CI pass on the pull request
-
codex review --base origin/devreports nothing open - Commit messages are meaningful
- No unresolved merge conflicts
- Documentation updated if needed
Working with Docker
On a development machine, the stack runs in Docker, and you do not need Node.js or Python installed locally. On a Raspberry Pi the backend runs natively under pixi, and the backend’s CI runs its tests under pixi as well (see Developer setup).
-
Start the stack:
./scripts/start-dev.shThis runs
docker compose -f docker-compose.yml -f docker-compose.dev.yml --profile with-backend up --build. A plaindocker compose up --buildbuilds the production frontend on port 3000 and does not start the backend, which is in thewith-backendprofile. -
Frontend:
http://localhost:5173 -
Backend:
http://localhost:8000/health
Switching branches
-
If only source files changed,
git checkout <branch>is enough. -
If the frontend’s dependencies changed (
package.jsonorpackage-lock.json), restart the stack. The development frontend runsnpm installeach time it starts (commandindocker-compose.dev.yml), which brings thefrontend_node_modulesvolume in line withpackage-lock.json. If the packages still look wrong after a restart, stop the stack, remove that volume, and start again:docker compose -f docker-compose.yml -f docker-compose.dev.yml --profile with-backend down docker volume rm dtk_frontend_node_modules ./scripts/start-dev.shThe volume name has the Compose project name
dtk(name:indocker-compose.yml) as its prefix. -
If the backend’s dependencies changed (
requirements.txt, which moves together withpixi.tomlandpixi.lock),./scripts/start-dev.shrebuilds the backend image with--build.
Do not use docker compose down -v to refresh dependencies. -v removes the postgres_data volume, which deletes the development database. Without the development overlay, the same command also leaves frontend_node_modules in place, because only docker-compose.dev.yml declares that volume.