Development workflow
Branch, commit, PR
- Open (or claim) an issue describing the change.
- Branch from
main:git checkout -b feat/short-description. - Keep commits focused. Conventional commit prefixes (
feat:,fix:,chore:,docs:) are encouraged. - Open a PR using the template; reference the issue with
Closes #Nwhen applicable.
Database migrations
Alembic migrations are the single source of truth for the schema. On startup the app brings the database to head automatically (alembic upgrade head), so a fresh database is built from migrations and an existing one is migrated in place — there is no manual step and no separate dev backfill.
After changing a model, generate the matching migration from the diff and review it:
make migration m="add foo to chats" # autogenerate from the model change
# review the new file under precursor/backend/alembic/versions/, then commit it
make migrate # (optional) apply it to your local DB nowThe migration then applies to dev and prod alike on the next startup. Keep one migration per change. Autogenerate covers most cases — double-check column type changes, server defaults, and any data migrations by hand.
Continuous integration
Every PR runs .github/workflows/ci.yml:
- Lockfiles — every artifact must resolve to a public registry with a strong hash (see Lockfiles below).
- Backend —
uv sync --locked, then ruff check, ruff format check, mypy (strict), and pytest. - Frontend —
npm ci, then typecheck and build. - Docs site —
npm ciandnpm run docs:buildforwebsite/, so a broken docs build fails the PR instead of the deploy.
All jobs must pass before merge. Run make check locally first to catch failures early.
Lockfiles
uv.lock and the two package-lock.json files are committed, and must pin public artifacts (files.pythonhosted.org, registry.npmjs.org) with strong hashes. Regenerating them is CI's job, not yours.
Many managed devices route uv and npm through a corporate package mirror. Re-resolving there doesn't just relabel URLs, it weakens the lockfile: npm integrity comes back as sha1 instead of sha512, and uv drops the size/upload-time provenance — which in a diff looks like a harmless URL change. Rewriting the URLs back by hand is worse, since it pairs a public artifact with the weakened metadata. If you find uv.lock modified after a local command, revert it rather than committing it.
Enable the guard once
make hooks # git config core.hooksPath .githooksThe pre-commit hook then rejects any lockfile you'd commit with a proxy URL or a weak hash. make lockcheck runs the same check on demand.
Day to day, install from the lockfiles instead of re-resolving — make sync uses npm ci and exports UV_FROZEN=1. That flag matters more than it looks: every uv run re-locks by default, so make dev, make check, and make test would each rewrite uv.lock without it.
To change a dependency, edit pyproject.toml or package.json, commit that, then let a clean runner resolve it:
gh workflow run relock.yml --ref "$(git branch --show-current)"The Relock workflow regenerates the lockfiles, verifies they install and build, then pushes the result back to your branch (or opens a PR when run against main). Dependabot updates arrive the same way.
If npm ci can't find a version
Corporate mirrors lag the public registries, so a lockfile CI just produced may pin a version yours hasn't cached — npm ci then fails with a 404 for a single package. Install without consulting the lockfile instead:
npm --prefix website install --no-package-lockThere is no lockfile to write, so the committed one stays untouched. Your node_modules may differ slightly from CI's, which is fine for local work.
Adding a plugin
Plugins live in their own packages and register via [project.entry-points."precursor.plugins"]. See the plugin reference.
Documentation
Documentation is part of every change. When you add or change a user-facing feature, update the docs in the same PR — don't defer it. Use the decision checklist in .github/copilot-instructions.md to decide what to touch (a feature page, the landing grid, configuration reference, CHANGELOG.md, screenshots, …), and keep the [Unreleased] section of CHANGELOG.md current.
- In-repo docs live under
docs/and the top-level markdown files (README.md,CONTRIBUTING.md, …). - This showcase + docs site lives under
website/(VitePress) and is published to GitHub Pages automatically on push tomainvia.github/workflows/pages.yml.
To work on the site locally:
cd website
npm install
npm run docs:dev # live-reload dev server
npm run docs:build # production build → website/.vitepress/distThe site is also served in-app at /docs/, and precursor --dev starts a live VitePress server automatically. See Serving the docs in-app.
Screenshots
Screenshots in website/public/screenshots/ are theme-aware — each has a light file (foo.png) and a dark file (foo-dark.png), and the <Screenshot> component swaps them by site theme. If a UI change alters a screenshotted screen, retake both variants from a seeded demo instance with the account hidden (no resolvable token → "Guest") and no config warnings (fake missing config rather than using real secrets). Capture light + dark at 2× and clip out the persona footer when it would leak the account.