Skip to content

Documentation site

Issue 13 framed the choice as mdBook against MkDocs with Material, with rustdoc for the API either way. Both were built as working spikes over the same five pages of real Henad content rather than compared on paper. The comparison turned on a fact neither option's docs advertise: Material for MkDocs entered maintenance mode in November 2025 and reaches end of life on 2026-11-05, MkDocs 1.x is unmaintained, and MkDocs 2.0 removes the plugin system Material depends on and ships unlicensed. Zensical, Material's successor by the same team, built the identical spike tree with zero configuration changes, which is what made the format and the builder separable. mdbook test turned out weaker than the issue assumed: it needs an isolated --target-dir, a hidden extern crate line per doctest, and every fence tagged, and the guarantee it buys is available in either tool by including from files the workspace already compiles. The site now lives at docs/, built by Zensical from mkdocs.yml, with the agent records moved to agent-record/ because Zensical does not implement exclude_docs.

State before

docs/ was a mostly gitignored tree, whitelisted down to agent-record/ and authoring/. The only user-facing documentation was README.md and docs/authoring/primitives.md, and the README closed with "More docs will follow soon ;)". There was no site, no hosting, and no documentation job in CI.

What was done

The evaluation

Three spikes were built in the scratchpad, each rendering the same content: GitHub-flavoured alerts, the real primitives.md tables, a snippet included from a real .rs file, Rust and WGSL blocks, a mermaid diagram, MathJax, and three-way content tabs.

mdBook 0.5.4 Material 9.7.7 Zensical 0.0.56
Build, 5 pages, warm 0.02 s 0.24 s 0.28 s
Output size 1.2 M 2.7 M 704 K
Install one 10 M binary 32-package venv 10-package venv
> [!NOTE] native literal literal
Mermaid needs mdbook-mermaid native native
Content tabs needs mdbook-tabs native native
WGSL highlighting absent Pygments Pygments
Versioning none mike claimed

Two findings decided it.

mdBook's bundled highlight.js carries 48 languages and neither WGSL nor GLSL, so a project whose GPU documentation is mostly shaders would have to vendor a custom highlight.js build. Material and Zensical highlight server-side through Pygments, which has a WGSL lexer, and needed no configuration for it.

mdbook test was made to work and cost three concessions. It compiles every untagged fence as Rust, which primitives.md fails on immediately, since its plain fence of file paths parses as Rust. -L target/debug/deps fails in this workspace with E0464, seven libhenad_core-* candidates, so it needs a dedicated target directory. Edition 2024 needs a hidden # extern crate henad_core; per doctest, because mdbook test has no --extern.

The setup

docs/ is now the published site and is fully committed. The gitignored development documents moved to dev-docs/, and .gitignore lost the whitelist block in favour of ignoring that one directory plus the site/ output.

agent-record/ moved to the repository root. Zensical does not implement MkDocs 1.6's exclude_docs, verified against the installed package, so a hand-off note left under docs/ would be built into the site and its internal anchors validated as if it were a page.

docs/authoring/primitives.md became docs/reference/primitives.md, and its four GitHub alerts became Material admonitions. AGENTS.md and crates/henad-core/src/authoring/primitives/mod.rs were repointed.

Code examples are included from the workspace rather than retyped. game_of_life.rs gained one marker pair around step_cell, and both the landing page and the authoring overview pull it through pymdownx.snippets. Line-range includes work too and were rejected, since they drift silently where a named region errors.

henad/
├── mkdocs.yml                      # new, Zensical/Material config
├── pyproject.toml                  # new, pins zensical
├── uv.lock                         # new
├── agent-record/                   # moved from docs/agent-record/
├── dev-docs/                       # moved gitignored dev docs, ignored
├── docs/                           # now the published site
│   ├── index.md                    # new
│   ├── benchmarks.md               # new, the README tables plus method
│   ├── comparison.md               # new, stub
│   ├── assets/                     # new, logo and favicon from assets/
│   ├── stylesheets/henad.css       # new, brand blue sampled from icon-256.png
│   ├── guide/
│   │   ├── index.md                # new
│   │   ├── installation.md         # new
│   │   ├── running.md              # new
│   │   ├── app.md                  # new, stub
│   │   └── models.md               # new
│   ├── authoring/
│   │   ├── index.md                # new, choosing a trait
│   │   ├── grid-models.md          # new
│   │   ├── agent-models.md         # new
│   │   ├── fields.md               # new, stub
│   │   ├── gpu-grid-models.md      # new
│   │   ├── gpu-agent-models.md     # new
│   │   ├── parameters-and-stats.md # new, stub
│   │   ├── performance.md          # new, stub
│   │   ├── determinism.md          # new
│   │   └── registering.md          # new
│   ├── reference/
│   │   ├── index.md                # new
│   │   ├── primitives.md           # moved from docs/authoring/, alerts converted
│   │   ├── cli.md                  # new, the flag table moved out of guide/running.md
│   │   └── environment.md          # new
│   └── developing/
│       ├── index.md                # new
│       ├── architecture.md         # new
│       ├── cpu-backend.md          # new, stub
│       ├── gpu-backend.md          # new, stub
│       └── contributing.md         # new
├── .github/workflows/docs.yml      # new, build on PR, deploy to Pages from master
├── .gitignore                      # docs whitelist block replaced
├── AGENTS.md                       # new "Documentation site" section, paths repointed
├── README.md                       # docs link replaces the placeholder
└── crates/
    ├── henad-core/src/authoring/primitives/mod.rs   # path repointed
    └── henad-models/src/game_of_life.rs             # snippet markers

Every parameter table on guide/models.md came from henad-cli --params rather than from reading the source, and the CLI flag table from --help.

Information architecture

The nav is five sections read top to bottom: Henad, Guide, Authoring, Reference, Developing. Authoring is its own section rather than a subsection of Guide or a set of entries under Reference, because writing a model is the primary journey and it is neither a task list nor a lookup table. Benchmarks and the engine comparison sit in the first section rather than under Developing, since they are what someone evaluating Henad reads first.

Nav tabs were tried and dropped. Of ten Material sites checked, five run tabs and five do not, and the split tracks size: every tabs-on site is over a hundred pages, where one column stops working. Both Astral projects, the closest comparators by language and audience, run without them. At 26 pages a single collapsible column shows all five section names at once, and a section header both navigates to its index and expands in place.

navigation.sections was tried alongside that and dropped too, since it renders sections as always-expanded groups and puts thirty rows in the sidebar. Without it the sections collapse, which is Ruff's shape. toc.integrate, also Ruff's, was tried and rejected: it works with navigation.indexes in Zensical, but on the Primitives page its eight headings push the rest of the tree off screen, so it costs the most sidebar exactly where it is meant to help.

State after

uv run zensical build reports no issues and writes 26 pages in around 0.2 s. Grid cards, admonitions, content tabs, mermaid, server-side WGSL highlighting, the snippet include and search all render, verified in a browser rather than in the HTML alone.

cargo fmt --all -- --check passes and henad-core and henad-models still compile.

The Pages deployment is written but not live. It needs Pages enabled on the repository with the source set to GitHub Actions, and micfong-z.github.io/henad/ pointed at it, which only the maintainer can do.

Issues found & future directions

henad.micfong.space/docs is not reachable with Pages alone, since DNS maps hostnames and not paths. It would need one host serving both, and the app cannot move to Pages, because the wasm thread pool needs Cross-Origin-Opener-Policy and Cross-Origin-Embedder-Policy headers that Pages cannot set. A Vercel rewrite proxying /docs/* to the Pages site is the only route to that URL, at the cost of a proxy hop. micfong-z.github.io/henad/ avoids all of it.

Zensical is 0.0.56 and its module system is not yet open to third parties, so a future need such as mike versioning may have no module. Material 9.7.7 builds the same tree unchanged and is the escape hatch, which the spike demonstrated rather than assumed. It is deliberately not in pyproject.toml, since carrying an unused 32-package tree to hedge a risk that has not arrived is not worth it.

Thirteen pages carry a "Not written yet" admonition naming what they will cover, which is enough for the nav to be real and not enough to deploy. The engine comparison, the app interface tour, fields, parameters and stats, writing fast models, and both backend pages are the substantial ones outstanding.

GridModel::step_cell's doc comment still says the engine runs it "sequentially on WASM", which the wasm parity work made false.

Manual notes (human)

See Record #20 for manual notes for records #15-#20.