---
orphan: false
---
# Versioned documentation: design and rollout
```{versionadded} v0.2
The per-version `switcher.json` scaffolding lands with v0.2. The
companion changes to MICROROBOTICA's umbrella `conf.py` + docs CI
are required to make the switcher live on the published site —
that work has since landed in MICROROBOTICA's umbrella docs.
```
## Settled design decisions
These are the choices made for the v0.2 rollout. Captured here so
that the rationale survives turnover; revisit them when the v0.3
docs cycle starts.
| # | Decision | Choice | Reason |
|---|---|---|---|
| 1 | What does `latest` point at? | **Most recent git tag** (not main tip) | New visitors land on stable; bleeding-edge work moves to `/dev/`. Read-the-Docs convention. |
| 2 | Where does `switcher.json` live? | **In each project's repo** (`MADDENING/docs/_static/switcher.json`) | Projects progress somewhat independently; each owns its release cadence. |
| 3 | Version label convention | **`vMAJOR.MINOR`** for minor releases (each new minor demotes the previous), **`vMAJOR.MINOR.PATCH`** when a patch carries breaking changes worth a distinct switcher entry (e.g. v0.2.1's edge-validation flip), **`vMAJOR.MINOR-dev`** for in-flight branches | Pins to semver; the `-dev` suffix marks pre-release without polluting the tag namespace; the PATCH entries are used sparingly so the switcher doesn't bloat. |
| 4 | `preferred: true` row | **Always the latest stable tag** | Standard PyData theme convention; what new readers should see by default. |
| 5 | Does MICROROBOTICA + MIME get versioned too? | **Yes — all three** | MICROROBOTICA ships a C++ API that breaks compatibility on minor bumps; MIME's nodes evolve alongside MADDENING. Each release tag freezes its own docs. |
| 6 | CI build strategy | **Cache + only rebuild changed versions** (option C from the brainstorm) | Old tagged builds are immutable artefacts; rebuild only on tag-cut, cache forever. `main` + the latest tag rebuild on every push; older tags reuse a `gh-pages-versions` artefact. |
| 7 | Old version's incompatible `conf.py` | **For now, build all versions with the current umbrella `conf.py`**; switch to per-version pinned requirements once we have 5+ versions | Cheap until incompatibility actually bites; the pin-file pattern is a known fallback. |
| 8 | Glossary across versions | **Per-version (frozen at release)** | Each version stays accurate to its source; readers searching old docs see the terms they actually shipped with. |
| 9 | Switcher fallback when JSON unreachable | **Show a debug banner + console warning** | Hidden silent failures are the worst kind; make broken deploys visible immediately. |
| 10 | URL shape for older releases | **Subdir** (`/maddening/v0.1/`) — not subdomain | Single TLS cert, single GitHub Pages host, fits the multiproject build pipeline. |
These shape the rollout described below. The
[`multiversion_preview.md`](https://github.com/Microrobotics-Simulation-Framework/MICROROBOTICA/blob/feat/versioned-docs-preview/docs/developer_guide/multiversion_preview.md)
guide in MICROROBOTICA covers the local-iteration workflow that
implements them.
The three MSF projects (MADDENING, MIME, MICROROBOTICA) are
published from a single sphinx-multiproject build at
. Today that build emits a single
"latest" version of each project — there's no `v0.1` URL you can
deep-link to. When v0.2 lands as the new stable release, v0.1
documentation effectively disappears.
This page is the rollout plan for keeping every supported version
addressable at a stable URL with a UI version picker.
## The shape of the live site (target)
```
https://microrobotica.org/ — MICROROBOTICA latest
https://microrobotica.org/v0.1/ — MICROROBOTICA v0.1
https://microrobotica.org/maddening/ — MADDENING latest (== current main)
https://microrobotica.org/maddening/v0.2/ — MADDENING v0.2 (stable)
https://microrobotica.org/maddening/v0.1/ — MADDENING v0.1
https://microrobotica.org/mime/ — MIME latest
https://microrobotica.org/mime/v0.2/ — MIME v0.2
```
* `//` always points at `latest` (= the most recent tagged
release, not `main`). `main` content lives at
`//dev/` if someone wants pre-release docs.
* Each version is a frozen build of the docs in that release's
`docs/` tree against the Sphinx config at that tag. Switching
versions doesn't switch *just* the content — the navbar, theme,
and inventory all freeze together.
## How a user sees it: PyData theme's version switcher
PyData Sphinx Theme ships a built-in [version
switcher](https://pydata-sphinx-theme.readthedocs.io/en/stable/user_guide/version-dropdown.html)
that reads a JSON file at build time and renders a dropdown in the
navbar. We use it because the umbrella site already runs PyData
theme (`MICROROBOTICA/docs/conf.py` line ~70).
Each project ships its own `switcher.json` at
`docs/_static/switcher.json`, e.g. {download}`MADDENING's
<../_static/switcher.json>`:
```json
[
{"version": "latest", "name": "latest (main)",
"url": "https://microrobotica.org/maddening/"},
{"version": "v0.2", "name": "v0.2 (stable)",
"url": "https://microrobotica.org/maddening/v0.2/", "preferred": true},
{"version": "v0.1", "name": "v0.1",
"url": "https://microrobotica.org/maddening/v0.1/"}
]
```
The umbrella `conf.py` wires it into the theme:
```python
html_theme_options.update({
"switcher": {
"json_url": f"{_BASEURLS[project]}_static/switcher.json",
"version_match": os.environ.get("DOCS_VERSION", "latest"),
},
"navbar_end": ["theme-switcher", "version-switcher", "navbar-icon-links"],
})
```
`DOCS_VERSION` is set by the CI build per tag — `latest` for the
`main` build, `v0.2` for the `v0.2.0` tag, etc.
## How a build produces all the versions
```{mermaid}
flowchart LR
Tags["git tags
v0.1.0 · v0.2.0 · ..."] --> Matrix
Main["main branch"] --> Matrix
Matrix["docs.yml matrix:
{ tag in tags, 'main' }"] --> Build
Build["checkout @ tag/main
DOCS_VERSION=tag make all
OUT=_build/html/{project}/{tag}/"] --> Merge
Merge["merge into root _build/
aliases: latest → newest tag"] --> Deploy
Deploy["github-pages deploy"]
```
The umbrella `Makefile` already builds each project sequentially
(`make all` → `_build/html/{maddening,mime,microrobotica}/`). The
multi-version extension is:
1. Loop over `git tag --list 'v*'`.
2. For each tag, `git worktree add /tmp/wt-$tag $tag`, then `cd
docs && DOCS_VERSION=$tag make all OUTDIR=_build/html-$tag/`.
3. For `main`, the build is the same but with `DOCS_VERSION=latest`.
4. After all builds, symlink `_build/html/latest →
_build/html-$latest_tag/` and copy each `_build/html-$tag/` into
`_build/html/{project}/$tag/`.
5. Each project's root `switcher.json` lives at
`_build/html/{project}/_static/switcher.json` and is the same
file in every version's output (so the dropdown lists every
version regardless of which one the user is currently on).
## Why this design over alternatives
| Option | Pros | Cons | Verdict |
|---|---|---|---|
| **sphinx-multiversion** | Automatic git-tag enumeration; single command. | Each historical tag must build with the *current* `conf.py`; breaking config changes cascade across history. | Rejected — we expect `conf.py` to evolve. |
| **Read-the-Docs** | Hosted, automatic. | Not the umbrella architecture; would need a separate domain per project. | Rejected — keep the unified microrobotica.org URL. |
| **Manual switcher.json + CI matrix** *(chosen)* | Each version builds against its own `conf.py`; switcher UI freezes whatever we ship; clean rollback. | Switcher JSON has to be updated for every release. | **Chosen.** |
| **Single-version + 404 page** | Cheapest. | Loses backwards-discoverable docs. | Rejected — v0.1 users still need their docs. |
## Rollout sequence
```{note}
The first three steps are MADDENING-side; the rest require
coordinated changes on MICROROBOTICA's umbrella repo.
```
1. **(this commit)** — `docs/_static/switcher.json` in each project
lists `latest` + every released tag. Today only the v0.2 row
has a target (we haven't built v0.1 with this infra yet); v0.1
resolves to a 404 until step 4.
2. **(this commit)** — Document the design in this page so the
handoff is in version control, not in someone's head.
3. **(MICROROBOTICA umbrella PR)** — add `switcher` +
`navbar_end` to `html_theme_options` in `conf.py`. Set
`DOCS_VERSION` from env var with `latest` default.
4. **(MICROROBOTICA umbrella PR)** — extend `Makefile` with a
`versions` target that loops over `git tag` per project and
builds each into the right subdirectory.
5. **(MICROROBOTICA docs.yml)** — switch the workflow from
"build once" to "matrix of (project, tag), then merge into a
single artifact". Cache builds aggressively because most tags
won't change between deploys.
6. **(release process)** — every release adds a new entry to its
project's `switcher.json` and a new git tag. The next CI run
picks it up automatically.
## How a developer triggers a version-specific local build
Until the CI changes land, the per-version path is manual:
```bash
# Build v0.2 docs from a checkout at the v0.2.0 tag
git worktree add /tmp/maddening-v0.2 v0.2.0
cd ../MICROROBOTICA/docs
DOCS_VERSION=v0.2 make maddening # → _build/html/maddening/
# Same for main → _build/html-main/
git worktree add /tmp/maddening-main main
DOCS_VERSION=latest make maddening
```
The switcher will appear in the navbar; clicking the v0.1 entry
404s until v0.1 is built into the right subdir.
## What goes in a new release's switcher.json
When cutting a new release N:
1. Update `docs/_static/switcher.json`:
* Add a new entry for version N marked `"preferred": true`.
* Flip the previous stable's `"preferred"` to `false`.
* `latest` always points at `//` (the unversioned
URL, which the CI maps to the newest tag).
2. Tag the release.
3. Push.
4. CI picks up the new tag, builds it into the right subdir, and
the switcher picks it up automatically on the next umbrella
build.
## Open questions
* **MIME's switcher.json** lives at `MIME/docs/_static/switcher.json`
and is authored as of the v0.2 cycle (single `latest` entry today;
populates per-version rows as MIME's own release cadence kicks in).
* **MICROROBOTICA's switcher** is also authored at
`MICROROBOTICA/docs/_static/switcher.json` — same scaffolding,
same per-project release lifecycle.
* **Do we want a `dev/` alias?** Probably useful for users
tracking unstable APIs. Easy add: `/dev/` always maps to the
most recent `main` build alongside the tag-pinned versions.