Skip to content

Set up a docs site (mkdocs) for in-depth documentation - #93

Merged
MJohnson459 merged 2 commits into
mainfrom
worktree-docs-site
Aug 2, 2026
Merged

Set up a docs site (mkdocs) for in-depth documentation#93
MJohnson459 merged 2 commits into
mainfrom
worktree-docs-site

Conversation

@MJohnson459

Copy link
Copy Markdown
Contributor

Set up a mkdocs (Material) documentation site so the in-depth docs have a
single navigable home, without moving them away from the code.

Branch: worktree-docs-site (committed, not pushed), one commit ed9af75.

What landed

  • mkdocs.yml + a docs pixi feature/environment (no-default-feature, like
    lint): pixi run docs live-serves, pixi run docs-build builds with
    --strict. The lock diff is purely additive -- 1 removed line, the diff
    header -- so the robot/default env is untouched.
  • .github/workflows/docs.yml: --strict build on every PR, deploy to GitHub
    Pages on main (upload-pages-artifact + deploy-pages).
  • docs/hooks/repo_links.py: mounts 24 markdown files that live with the code
    (the five package READMEs, mote_arm/BENCH.md, the sub-package READMEs,
    design/{README,BOM,ASSEMBLY,WIRING}.md and the five design research notes)
    as site pages at build time, and rewrites their relative links -- to another
    site page where the target is one, to a GitHub blob/tree URL where it is
    source the site does not serve. Figures from outside docs/ are copied in
    under figures/ so the site renders its own pages without reaching back to
    GitHub. Anything unresolvable is left as written, so it becomes a mkdocs
    warning and fails --strict. A mounted page's edit pencil opens the file it
    was copied from.
  • Nav covers getting started, hardware/BOM, the robot (bringup, sites/maps/
    zones, missions, map cleanup, Foxglove, chaos), perception + inference
    server, the SO-101 arm, fleet (runbook, architecture, both contracts, server
    pipelines, all six verification ledgers), simulation + its three harnesses,
    and the tuning notes.
  • Three new pages, where no single existing file covered what the nav needed:
    docs/index.md, docs/getting-started.md, docs/simulation.md, plus
    docs/robot/sites.md for sites/maps/zones.
  • README: docs badge, and deep links into the site from the setup, perception,
    fleet and contributing sections.

Nothing was deleted or moved. Every tracked .md except the root README and
CLAUDE.md is reachable from the nav (verified programmatically against
git ls-files '*.md').

Two real bugs the strict build found

  • mote_perception/README.md linked the inference-server doc twice as
    ../../docs/inference-server.md, one level above the repo root -- broken on
    GitHub too. Fixed.
  • Headings are slugged GitHub's way (pymdownx.slugs, set in on_config
    rather than YAML because the tag check-yaml refuses to load). Every heading
    link in the repo was written against GitHub's renderer, and the two sluggers
    disagree wherever a heading has an em dash -- which is most of them here.
    Without this, ~10 cross-references landed at the top of the page instead of
    the section. validation.anchors: warn now keeps that true.

Verified

  • pixi run docs-build: 49 pages, zero warnings under --strict (with
    omitted_files, absolute_links, unrecognized_links and anchors all set
    to warn).
  • pixi run docs: serves on http://127.0.0.1:8000/Mote/ (the site_url path);
    home, robot/sites/, fleet/ and arm/bench/ all 200.
  • Rendered in a browser: home page, and a mounted research page with its
    copied-in figures, tables, code blocks and rewritten source links.
  • pixi run lint: 12/12 hooks pass.

Needs a human

GitHub Pages has to be enabled in repo settings with Source: GitHub Actions
before the deploy job can publish; until then the PR build check still runs.
The site URL assumed throughout is https://clachdev.github.io/Mote/.

The depth was there but scattered: five package READMEs, ten files under
docs/fleet, the inference-server doc, the design notes and the tuning runs,
each reachable only by knowing it existed. The README refresh deliberately
kept the README to sell-and-orient, which left nothing that could absorb it.

`pixi run docs` serves a mkdocs Material site and `pixi run docs-build` builds
it under --strict; a docs workflow gates every PR on that build and deploys
main to GitHub Pages. Both live in their own pixi environment with no default
feature, so the robot env is untouched and a contributor building the site
solves mkdocs rather than ROS -- the lock diff adds the docs environment and
removes nothing.

The site does not move any documentation. A package README is read in a
checkout and on GitHub at least as often as on a site, so it stays with its
package and docs/hooks/repo_links.py mounts it at build time -- 24 files, from
the package READMEs to design/BOM.md to the hardware research notes -- and the
edit pencil on a mounted page opens the file it came from. Every tracked
markdown file except the README and CLAUDE.md is now reachable from the nav.

Mounting is what makes the links the interesting part. A copied file's relative
links point into the repo, so each one is resolved against the file's real home
and rewritten: to another site page where the target is one (a mounted file or
anything under docs/), and to a GitHub blob URL where it is source the site
does not serve. A figure from outside docs/ is copied in rather than linked, so
the site never reaches back to GitHub to render its own pages. Anything the
rewriter cannot resolve is left exactly as written, which makes it a mkdocs
warning and fails --strict rather than shipping as a 404.

Two things fell out of turning that on. Headings are slugged GitHub's way
(pymdownx.slugs, set in the hook because spelling a Python object in YAML needs
a tag check-yaml refuses to load): every heading link in the repo was written
against a file rendered on GitHub, and the two sluggers disagree wherever a
heading contains an em dash, which is most of them here. And
mote_perception/README.md's two links to the inference-server doc pointed at
../../docs/, one level above the repo -- broken on GitHub too, and found by the
first strict build.

Three pages are new, because no single file covered what the nav needed to
point at: a landing page, getting-started, and simulation and sites/maps/zones
guides that tie the existing material together.
Comment thread .github/workflows/docs.yml Outdated

concurrency:
group: pages-${{ github.ref }}
cancel-in-progress: true

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cancel-in-progress: true applies to the whole workflow-level concurrency group (pages-${{ github.ref }}), which also covers the deploy job below it (.github/workflows/docs.yml#L44-L56). Since every push to main lands in the same group, a second push to main while a previous run's deploy (the actual actions/deploy-pages publish) is still in flight will cancel it mid-deploy. GitHub's own Pages "starter workflow" deliberately keeps cancel-in-progress: false for exactly this reason — cancellation is only safe on the PR-only build path, not a path that includes a live production deploy.

Suggest scoping cancellation to non-main runs, so PR builds still cancel superseded runs but a live deploy on main is never killed:

Comment thread docs/hooks/repo_links.py
return config


def on_files(files, config):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_figures is a module-level dict (docs/hooks/repo_links.py#L75) populated by insertion inside _resolve (docs/hooks/repo_links.py#L103), which runs from on_files on every rebuild. MkDocs caches hook modules for the life of the mkdocs serve process, so this dict persists across live-reload rebuilds rather than resetting. If a mounted file's figure reference is edited out, or a figure is renamed/deleted, while pixi run docs is running, the stale entry survives and figure.read_bytes() (docs/hooks/repo_links.py#L174-L175) raises FileNotFoundError out of the hook, crashing the dev server.

Reset it at the top of on_files so each rebuild starts clean:

Comment thread .github/workflows/docs.yml Outdated

concurrency:
group: pages-${{ github.ref }}
cancel-in-progress: true

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Correcting my comment above — the suggestion block was dropped. Here's the concrete fix, scoping cancellation to non-main runs so PR builds still cancel superseded runs but a live deploy on main is never killed:)

Suggested change
cancel-in-progress: true
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}

Comment thread docs/hooks/repo_links.py
return config


def on_files(files, config):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Correcting my comment above — the suggestion block was dropped. Here's the concrete fix, resetting _figures at the top of on_files so each rebuild starts clean:)

Suggested change
def on_files(files, config):
def on_files(files, config):
_figures.clear()

…ilds

Two defects from review, both in the paths that only run outside a single
`mkdocs build`.

The workflow-level concurrency group covers the deploy job as well as the
build, so `cancel-in-progress: true` meant a second push to main could cancel
`actions/deploy-pages` half-way through publishing. Cancellation is now scoped
to non-main refs, so a superseded PR build still dies and a deploy never does
-- which is why GitHub's own Pages workflow declines to cancel at all.

`_figures` is module state, and mkdocs loads a hook module once per process
(`Hooks._load_hook` is lru_cached), so under `mkdocs serve` it accumulated
across every rebuild instead of describing the current one. Rename or delete a
figure mid-session and the stale entry outlives it, so the copy-in pass reads a
path that is gone and takes the dev server down with FileNotFoundError.
Reproduced by building twice in one process with the figure deleted between:
build 2 raises before the clear, and succeeds after it.
@MJohnson459
MJohnson459 merged commit 97e71a7 into main Aug 2, 2026
5 checks passed
@MJohnson459
MJohnson459 deleted the worktree-docs-site branch August 2, 2026 20:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant