Multi-version docs: local preview#
Note
This page documents the preview workflow on the
feat/versioned-docs-preview branch. The full release plan is in
MADDENING/docs/developer_guide/versioned_docs.md.
The version switcher is off by default — CI keeps deploying the
single-version site. To preview the multi-version layout locally,
opt in with DOCS_MULTIVERSION=1 (the Makefile targets below do this
for you).
Quick start#
cd MICROROBOTICA/docs
# One-shot build of every version + serve locally
make preview-versions # builds, then serves _build/html on :8000
# Open http://localhost:8000/maddening/ in a browser.
# The version switcher is in the top navbar.
The server is scripts/pages_server.py, which resolves a URL the way
GitHub Pages does (/x serves x.html, /dir redirects to /dir/,
/x.html/ is a 404; its docstring lists each rule with what
microrobotica.org answered). python3 -m http.server does not serve
x.html for /x, so the extensionless links MIME’s errors print
cannot be checked with it.
sphinx-build must be reachable. The Makefile auto-detects the
sibling shared venv (../../.venv); otherwise activate your docs
venv first (source ../../.venv/bin/activate) or pass an explicit
make … SPHINXBUILD=/path/to/sphinx-build.
The output structure mirrors the production target:
_build/html/
├── index.html ← MICROROBOTICA latest
├── _static/switcher.json ← MICROROBOTICA version list
├── sitemap.xml ← MICROROBOTICA sitemap
├── maddening/
│ ├── index.html ← MADDENING latest (= main tip)
│ ├── _static/switcher.json ← canonical MADDENING version list
│ ├── sitemap.xml ← MADDENING sitemap (latest only)
│ ├── v0.1/index.html ← MADDENING v0.1.0 tag
│ └── v0.2/index.html ← MADDENING v0.2.0 tag
└── mime/
├── index.html ← MIME latest (= the 0.3.0 pages)
├── _static/switcher.json ← MIME version list (docs/_switchers/mime/)
├── 0.3.0/index.html ← MIME 0.3.0 release pages
├── v0.1/index.html ← MIME v0.1.0 tag
└── v0.2/index.html ← MIME v0.2.0 tag
Every version’s HTML references /maddening/_static/switcher.json via
a root-relative URL, so v0.1 pages pick up the switcher dropdown even
though v0.1’s source tree predates the switcher.
MIME 0.3 on: the release’s pages#
MIME’s documentation is generated from 0.3 on: `mime docs build –out
MIME_RELEASESindocs/Makefilelistslabel:tagpairs (0.3.0:v0.3.0.dev1).make mime-pagesdownloads each release’s asset,https://github.com/Microrobotics-Simulation-Framework/MIME/releases/download/<tag>/mime-docs-<version>.tar.gz(<version>is the tag without itsv), unpacks it into_build/mime-pages/<label>/(ignored by git; never commit MIME’s pages here) and builds it to/mime/<label>/. The label is the release number with nov, because MIME’s errors link tohttps://microrobotica.org/mime/<release>/pages/errors/<code>.MIME_LATESTis built a second time to/mime/, and the switcher marks it as that release (DOCS_SWITCHER_MATCH). MIME’smainis the 0.3 line from its release on, solatestno longer follows it.The switcher list is
docs/_switchers/mime/switcher.json, here, since the generated pages have no_static/.Fallback (no asset yet, or to read a change before it is released): in a MIME checkout whose environment has MIME’s
verifyextra,uv run --no-sync mime docs build --out <dir>, thenmake all-versions MIME_PAGES_DIR=<dir>. Such pages print every evidence count as “not read” (no lane report); the release asset, built in MIME’s CI, reads them.make check-mime-docs(a step of the docs workflow): every code in MIME’s catalogue (pages/errors/index.md) has its page under/mime/<release>/and/mime/, its extensionless URL answers with it under GitHub Pages’ rules, and every link of the release’s pages lands, anchor included. After a deploy, the workflow samples the same extensionless URLs on microrobotica.org (scripts/check_mime_docs.py live).The pages are built with the
htmlbuilder: GitHub Pages serves/x/bundle.referenced-copyfrombundle.referenced-copy.html, dots and all, with no redirect.dirhtmlwould put a 301 on every error link and writespages/errors.md(reading an error) andpages/errors/index.md(the catalogue) to the samepages/errors/index.html, losing the first.
Iterating on docs without committing#
The default make all-versions builds every version from its remote
git ref. When you’re editing a docs file on the latest line and want
to see the change without committing, point the “latest” tier at your
local working copy:
make preview-local MADDENING_LOCAL=/home/you/MSF/msf/MADDENING
# Edit MADDENING/docs/... files, then re-run — <30 s incremental.
Older versions (v0.1, v0.2) still come from git tags — they’re frozen
and don’t need re-builds unless you nuke _build/.
What’s running#
Knob |
Default |
Multi-version build |
|---|---|---|
|
unset |
|
|
unset |
the build label ( |
|
|
|
|
from caller |
|
|
unset → submodule path |
tempdir worktree |
navbar shows version switcher |
no |
yes (rendered, fetches JSON at page-load) |
The switcher JSON contract: a flat list of
{version, name, url, preferred} rows. version is the value that
DOCS_VERSION must match for the theme to highlight the row as
“current”. preferred: true marks the stable release (the one new
visitors should land on if they have no preference).
Canonical URLs and the sitemap#
Multi-version docs publish the same page at several URLs
(/maddening/quickstart.html, /maddening/v0.2/quickstart.html,
/maddening/v0.1/quickstart.html). To stop search engines treating
those as duplicate content, the latest version is declared
canonical:
conf.pysetshtml_baseurlwithout a version segment (.../maddening/, never.../maddening/v0.2/). Sphinx derives every page’s<link rel="canonical">from it, so a versioned page emits<link rel="canonical" href=".../maddening/<page>">— it canonicalises to the latest copy of that page.The sitemap is generated by the latest build only.
conf.pydrops thesphinx_sitemapextension whenDOCS_VERSION != latest, so/maddening/v0.1/and/maddening/v0.2/ship nositemap.xml. Only/maddening/sitemap.xml(latest) exists, and that is the oneseo/sitemap_index.xmlreferences.seo/robots.txtkeepsAllow: /— crawlers must be able to reach the versioned pages so they can see the canonical link and consolidate. Don’tDisallowthe version subdirs.
Net effect: search engines index one copy of each page (the latest), and the version switcher is the only way readers reach older docs.
Adding a version#
MADDENING/docs/_static/switcher.json is the source of truth for the
MADDENING dropdown; MICROROBOTICA/docs/_static/switcher.json is the
(single-entry) list for the root, and MIME’s list is
MICROROBOTICA/docs/_switchers/mime/switcher.json from 0.3 on. A MIME
release from 0.3 on adds its label:tag to MIME_RELEASES, moves
MIME_LATEST to it, and adds its row to that list; a later
pre-release of the same release (0.3.0.dev2) changes only the tag.
When a project cuts a release:
Tag the release in that project’s repo (e.g.
v0.3.0).Add a row to that project’s
switcher.json—versionis the label, and movepreferred: trueto the new stable release.Update the project’s
*_VERSIONSline indocs/Makefile, e.g.MADDENING_VERSIONS := v0.1:v0.1.0 v0.2:v0.2.0 v0.3:v0.3.0 latest:origin/main.Rebuild.
The latest row builds from origin/main; the canonical
switcher.json it ships is whatever is on main, so commit the
switcher.json edit to main.
Cleaning up#
make clean # rm -rf _build/
make clean-worktrees # remove /tmp/msf-docs-worktree-*
(WORKTREES=<dir> puts the worktrees somewhere other than /tmp, for
both targets.)
clean-worktrees is safe to run anytime — it removes the git
worktrees the multi-version build creates under /tmp/. Subsequent
builds re-create them on demand.
Knobs that won’t affect production#
Everything in this preview is gated on env vars that default to “production single-version”:
make all(no-versionssuffix) — unchanged from v0.1 behaviour.CI workflow (
.github/workflows/docs.yml) — untouched.The
docs/conf.pyswitcher block is wrapped inif _multiversion:so it’s a no-op withoutDOCS_MULTIVERSION=1.
To actually ship multi-version, the steps are in
MADDENING’s versioned_docs.md
§ “Rollout sequence” — three small CI changes once you’re ready.
Troubleshooting#
“sphinx-build not found” — activate the docs venv
(source ../../.venv/bin/activate) or pass
SPHINXBUILD=/path/to/sphinx-build.
“projects/maddening submodule missing” — run
git submodule update --init --recursive.
“worktree add failed for … @ origin/…” — the submodule’s local
clone hasn’t fetched that branch yet. Run
git -C docs/projects/maddening fetch origin --tags and retry.
“worktree at /tmp/… has no docs/index.md (ref too old?)” —
the ref you’re trying to build predates the current docs layout.
Drop that row from MADDENING_VERSIONS in the Makefile.
Switcher shows nothing in the dropdown — open the browser
DevTools network tab and check that /maddening/_static/switcher.json
returns 200. It’s served from _build/html/maddening/_static/; the
“latest” build is what writes it.
Switcher shows but the current version isn’t highlighted —
the version field in switcher.json has to match DOCS_VERSION
exactly. The Makefile sets DOCS_VERSION to the LABEL part of
the <label>:<ref> pair (e.g. v0.1, v0.2, latest).