Skip to content

Development workflow ​

Branch, commit, PR ​

  1. Open (or claim) an issue describing the change.
  2. Branch from main: git checkout -b feat/short-description.
  3. Keep commits focused. Conventional commit prefixes (feat:, fix:, chore:, docs:) are encouraged.
  4. Open a PR using the template; reference the issue with Closes #N when 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:

bash
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 now

The 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 ci and npm run docs:build for website/, 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

bash
make hooks        # git config core.hooksPath .githooks

The 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:

bash
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:

bash
npm --prefix website install --no-package-lock

There 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 to main via .github/workflows/pages.yml.

To work on the site locally:

bash
cd website
npm install
npm run docs:dev        # live-reload dev server
npm run docs:build      # production build → website/.vitepress/dist

The 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.

Released under the MIT License.