Development
vdoc is a Python backend and a React frontend in one repository, plus this documentation site.
tox drives everything on the Python side, npm everything on the JavaScript side.
Layout
| Path | What lives there |
|---|---|
src/vdoc/ | The FastAPI application and the vdoc CLI |
src/ui/ | The frontend: Vite, React, MUI, TanStack Router and Query |
docs/ | This site: a Docusaurus project with its own package.json |
tests/unit/ | pytest, one module per module under test |
tests/integration/ | pytest against a running application |
tests/ui/ | Playwright, driving the frontend in a browser |
tests/resources/sample-docs/ | Real Sphinx projects the tests build and upload |
The frontend builds into src/vdoc/webapp/, which is why that directory is git-ignored and shipped
inside the wheel — a pip install vdoc carries the interface with it.
Frontend code imports a module from its own directory relatively, as ./Module, and every other one
from the root of src/ui/, as @/helpers/Module. The paths in tsconfig.app.json define @/,
and Vite, Vitest and Playwright all read it from there.
Setting up
uv sync
npm ci
Then a .env for local overrides. It is read by the development server and by the Playwright
tests, and everything in it beats the configuration file:
VDOC_CONFIG_FILE=vdoc.yaml
VDOC_DOCS_DIR=/path/to/some/documentation
VDOC_DATABASE_URL=sqlite:////path/to/some/vdoc.db
VDOC_API_USERNAME=someone
VDOC_API_PASSWORD=something
The credentials are there because uploads and the admin pages stay disabled with the default ones. They are what you log in with at localhost:8090/admin.
Running it
./start_dev.py
That starts uvicorn on 8080, reloading on changes under src/vdoc, and Vite on 8090, with /api
and /static proxied from Vite to uvicorn — so the frontend on
localhost:8090 talks to the backend you are editing, and both reload on
save.
Work against 8090. Port 8080 serves the web UI as it was last built by npm run build, which is
what a deployed vdoc answers with, but it knows nothing of the file you just saved.
Tasks
tox run -e lint # codespell, mypy, ruff
tox run -e py311-test # pytest, with coverage
tox run -e docs # license page, then build this site
tox run -e build # the wheel, depends on the UI and docs builds
npm run lint # biome, and prettier over markdown and YAML
npm run format # the same, writing
npm run test:unit # vitest
npm run test:e2e # playwright, starts its own dev server on 3000
tox run with no environment runs lint and the test matrix across every supported Python version.
Working on the documentation
The site is built with @voraus/docusaurus-theme,
which carries the voraus look and most of the configuration, so
docusaurus.config.ts
stays short. For writing, the development server beats a full build:
npm --prefix docs ci
npm --prefix docs start
Pages live in docs/docs/. The sidebar is generated from that directory, so a new page needs no
registration: the number prefix on a file name orders it and is stripped from the URL, and a
directory becomes a category with its index.mdx as the category page.
A few things fail the build rather than shipping broken: an internal link that points nowhere, a
missing image, and invalid MDX. Link between pages by file path, for example
[Configuration](03-configuration.md), so that a renamed page is caught rather than silently
404ing.
.md is parsed as CommonMark and .mdx as MDX. That is what keeps the generated license page —
which contains whatever third-party metadata says, including things that look like HTML tags — from
being able to break the build.
Changing the database
vdoc brings the database up to date with Alembic every time it
starts. The tables are defined in src/vdoc/db/tables.py. After changing them, write the migration
for it:
uv run --group lint alembic revision --autogenerate -m "Add a column for something"
Alembic compares the tables against the database named in .env, so that database has to be at the
current schema first, which running vdoc once does. The lint group provides ruff, which formats
the new revision. Read what it wrote before committing it. A test fails as long as the tables and
the migrations disagree.
On SQLite, the migrations run with the foreign keys off. Alembic alters a table by rebuilding it, and
dropping the old table would otherwise fire the ON DELETE action of every row that references it. A
migration that leaves a reference pointing nowhere fails, and vdoc rolls it back.
Releasing
Releases are automated with release-please: the
commit messages on main decide the next version, and merging the release pull request tags it.
The pipeline then builds the wheel, the Docker image and this site, publishes them, and deploys
docs.vorausrobotik.com.
The image is tagged with the version alone, such as voraus.jfrog.io/docker/vdoc:1.2.3. There is no
latest tag, and docs.vorausrobotik.com runs the exact version it was deployed with.
So commit messages matter — Conventional Commits, with
feat: and fix: being what moves the version.