Documentation Site Operations#
This page is the runbook for the documentation site at https://www.optiland.org/docs/. It describes how the site is built, deployed, validated and rolled back. Writing documentation itself is covered in Contributing to Optiland.
Architecture#
optiland/optiland (docs/, notebooks, docstrings)
│ pull request / push to master
▼
GitHub Actions "Docs" workflow
│ Sphinx build → validate_build.py → prepare_vercel_output.py
▼
Dedicated Vercel project (the docs origin, static files only)
│ external rewrite /docs/:path* → <origin>/:path*
▼
https://www.optiland.org/docs/… (optiland/optiland-website, Next.js)
Single source of truth. Every page comes from
docs/inoptiland/optiland(.rstpages,.ipynbnotebooks committed with their outputs) or from Python docstrings (API reference viaautodoc/autosummary). The website repository holds routing only; it never contains documentation content.Canonical URLs.
docs/conf.pysetshtml_baseurltohttps://www.optiland.org/docs/; every page carries a matching<link rel="canonical">, the sitemap uses the same base, and the origin publishes arobots.txtthat disallows crawling so only first-party URLs are indexed.Build identity. Every build writes
_meta/build.json(source commit, version, build time). The footer of every page links to it.Version.
releaseis derived from the installed package metadata (versioningit), so a build frommasteris labelled with the honest development version (for example0.6.2.post49+g<sha>). It is never hardcoded.Code highlighting. Colours come from the
a11y-high-contrastPygments styles selected indocs/conf.pyplus a few overrides indocs/_static/optiland-docs.css. Pygments only marksdef/classnames, so the local extensiondocs/_ext/optiland_pygments.pyextends thepython,pyconandipython3lexers to tag function and method calls as well; it is registered like any other extension inconf.py.
The workflow#
.github/workflows/docs.yml runs:
buildOn every pull request that touches
docs/**,optiland/**,optiland_gui/**,pyproject.toml,uv.lock,.readthedocs.yamlor the docs tooling, and on every push tomaster(API pages depend on docstrings, so master is always rebuilt). Steps: create the environment fromdocs/build-environment.ymlwith micromamba, build withscripts/docs/build_docs.py, validate withscripts/docs/validate_build.py, package withscripts/docs/prepare_vercel_output.py, upload thedocs-siteartifact.screenshotsRenders representative pages (landing light/dark, quickstart on a phone viewport, an API page, a notebook, search) with Playwright and uploads them as the
docs-screenshotsartifact for review. Non-blocking.previewFor pull requests from branches of this repository: deploys the validated artifact as a Vercel preview and smoke-tests it. Forks never receive secrets; they still get the full build and validation.
deployOn push to
master: asserts the artifact was built from the pushed commit, deploys it to the docs origin project withvercel deploy --prebuilt --prod, then runsscripts/docs/smoke_test.pyagainst the origin alias and againsthttps://www.optiland.org/docs/(with retries while CDN caches refresh). A failed build never reaches deployment; the previous production deployment stays live. Thedocs-productionconcurrency group prevents an older run from finishing after a newer one.linkcheckandproduction-smokeWeekly (and on manual dispatch):
sphinx -b linkcheckfor external links (third-party failures are reported, first-party failures are blocking) and a smoke test of the production route.
What blocks a deployment#
scripts/docs/validate_build.py fails the build on:
missing required pages or assets (
index.html,searchindex.js,objects.inv,sitemap.xml,404.html,_meta/build.json, the key narrative pages, the JupyterLite bundle);any internal
href/srcthat does not resolve inside the build, including anchors;duplicate element ids on a page;
canonical URLs or sitemap entries not rooted at the first-party base;
absolute links that escape
/docs/;links to
optiland.readthedocs.io;a stale or unknown version string;
a partial sitemap (a sign of an incremental build);
any Sphinx warning that is not matched by
scripts/docs/warnings-allowlist.txt.
The allowlist is intentionally short and every entry carries a reason. Fix warnings at the source; extend the allowlist only when the warning cannot be fixed in this repository. The validator reports allowlist patterns that no longer match so they can be removed.
One-time setup#
- Vercel
Create a dedicated project for the docs origin (static, no framework preset, no build command; deployments arrive prebuilt from CI). Note the ids of the project and of the owning team or personal account: running
vercel linkagainst the project writes both to.vercel/project.jsonasprojectIdandorgId. Assign a stable production alias, for exampleoptiland-docs.vercel.app.- GitHub
Add
VERCEL_TOKEN,VERCEL_ORG_ID(theorgId; a personal account has one too, no team required),VERCEL_DOCS_PROJECT_ID(theprojectId) andDOCS_ORIGIN_URL(the production alias, without a trailing slash) as repository Actions secrets. Create thedocs-productionenvironment and restrict its deployment branches tomaster;docs-previewneeds no setup — GitHub creates it on the first preview run and the job reads the repository secrets. Scoping the secrets to the two environments instead of the repository also works.- Website
In
optiland/optiland-websitesetDOCS_ORIGIN(Preview and Production environments) to the production alias.next.config.mjsrewrites/docs/:path*to it, redirects/docsto/docs/and every legacy/tutorials/*URL to its canonical page (config/legacy-doc-redirects.json), andmiddleware.tsexcludes/docsso documentation requests never touch Supabase.
Rollback#
- Documentation content or build regression
Revert the offending commit on
master; the next workflow run redeploys. To restore the previous deployment immediately without a rebuild, promote it in the Vercel dashboard or runvercel rollbackagainst the docs project (vercel rollback --token=… --scope=<team>; pick the previous production deployment fromvercel ls). The website needs no change.- Routing regression on optiland.org
Redeploy the website with the previous
next.config.mjs(or unsetDOCS_ORIGINto disable the proxy). Legacy redirects are plain config and can be reverted the same way.- Read the Docs fallback
The Read the Docs project keeps building from the same
docs/conf.pyanddocs/build-environment.ymlduring the stabilization period and can be linked publicly again if a severe problem occurs.
Legacy URLs#
https://www.optiland.org/tutorials/<slug>(the former website tutorials) redirect permanently, in one hop, to the canonical notebook page; the manifest with the reason for each mapping lives in the website repository (config/legacy-doc-redirects.json) and is validated byscripts/validate-legacy-redirects.pythere.After the stabilization window, configure Read the Docs redirects so that
/en/latest/*and/en/stable/*land onhttps://www.optiland.org/docs/*(forced exact redirects that strip the/en/<version>/prefix).
Local builds#
See Contributing to Optiland for the day-to-day workflow. The CI-equivalent commands are:
python scripts/docs/build_docs.py --fresh
python scripts/docs/validate_build.py docs/_build/html \
--warnings docs/_build/warnings.log \
--allowlist scripts/docs/warnings-allowlist.txt
On hosts where jupyterlite-xeus cannot build the WebAssembly kernel
environment (for example Windows), add --skip-jupyterlite to the build
and --allow-missing-jupyterlite to the validator. Published builds always
include JupyterLite.
Versioning#
/docs/ follows master and is redeployed on every merge. Versioned
snapshots (/docs/stable/, /docs/0.6/) are a later phase; do not
publish such paths before the deployment and redirect strategy for them
exists.