ADR 0065: The documentation site, an mdBook over docs/ on GitHub Pages
Date: 2026-10-14. Status: accepted; built 2026-10-14. The audience, the generator, and the hosting chosen by the
owner on 2026-10-14; the domain, wisp.pidster.com, the same day.
Context
wisp’s documentation is about 133,000 words of Markdown under docs/: the reference pages, a page per tool,
64 decision records, proposals, and reviews, read on GitHub. GitHub renders each page well but gives no order
through them, no search across them, and no way in for someone who has not yet installed wisp: docs/README.md
is an index for people who already know what they are looking for, and the pages are written as contracts, not
as paths through a task.
The owner chose three things:
- Users first, internals after. A reader meets new task-oriented guides, then the existing reference pages, then the internals: design, decisions, measurements, the roadmap, engineering, the release.
- mdBook, a Rust tool, so the site needs no Node toolchain. The repository already builds Rust (
tools/). - GitHub Pages through GitHub Actions, built on a Linux runner on each push to
main. The macOS CI stays disabled until a macOS 27 runner exists (ADR 0004); the docs need no macOS.
The site is served at https://wisp.pidster.com, on the owner’s pidster.com domain, rather than at
pidster.github.io/wisp/.
Decision
The book is docs/ itself. book.toml sits at the repository root with src = "docs", so mdbook build runs
from the root, the content folder holds only content (a book.toml inside it would be copied into the site), and
no page is copied or duplicated: the page GitHub shows and the page the site shows are the same file. The build
writes to book/, which git ignores. docs/SUMMARY.md is the table of contents:
| Part | Pages |
|---|---|
| Introduction | introduction.md, a landing page: what wisp is, the two faces, the safety model in five lines, where to go. docs/README.md stays the repository’s full index |
| Guides | New, under docs/guide/: install and first run, delegating from Claude Code and Codex, approvals and the sandbox, local models, configuration, troubleshooting. Each is a task’s path through the reference, linking to it rather than repeating it |
| Reference | wisp.md, tools/ (its README and every tool), mcp.md, trust.md, approval.md, logging.md, verification.md, context-management.md, backends.md, install-local-models.md, model-controls.md |
| Internals | The documentation index (README.md), objective.md, design.md, policy-and-sandboxing.md, engineering.md, release.md, measurements.md, roadmap.md, backlog.md, on-device-ai-todo.md, fm-cli.md, the local-model research pages, every decision in number order, the proposals, and the reviews |
| Changelog | changelog.md, which includes the root CHANGELOG.md ({{#include ../CHANGELOG.md}}, escaped here so this page does not include it; mdBook reads an include outside src) |
The proposals and reviews are in the book, under Internals. They are Markdown in docs/, ADRs and pages link to
them, and leaving them out would have meant rewriting those links to GitHub for no gain to a reader.
The configuration (book.toml, for mdBook 0.5.4, which refuses a key it does not know): the title wisp and a
description; site-url = "/", since the site is a domain’s root; git-repository-url and an
edit-url-template to the file on GitHub’s main ({path} is the file’s path in the repository);
create-missing = false, so a page the summary names but the tree lacks fails the build; search on;
no-section-label, since the decisions carry their own numbers. Two choices are not mdBook’s defaults:
- The
indexpreprocessor is off (use-default-preprocessors = false, with[preprocessor.links]named to keep includes). It renames everyREADME.mdtoindex.md, which would have putdocs/README.mdover the Introduction as the site’sindex.htmland left every link to aREADME.mdpointing at aREADME.htmlthat does not exist. Off,README.mdisREADME.htmland links resolve as written. docs/assets/mermaid-init.jsdraws the seven```mermaiddiagrams, which GitHub draws itself and mdBook shows as code: it loads mermaid 11.4.1 from jsDelivr on a page that has one, and leaves the source shown if that fails. No preprocessor to install.
The link rule. Every link in a page in the book is relative and stays inside docs/, or is absolute. A link
that left docs/ (to ../harness/…, ../CHANGELOG.md) or named a directory (decisions/) worked on GitHub and
would 404 on the site; each now goes to the file on GitHub (https://github.com/pidster/wisp/blob/main/<path>,
tree/main/ for a directory), which works in both. On 2026-10-14 that was 7 links in four pages (three directory
links, three source files, the changelog), and the 6 links in CHANGELOG.md, which are read from the root, from
changelog.md, and in GitHub’s release notes, where a relative link never resolved. Six bare placeholders in
prose (<name>, <question>), which mdBook warned of and GitHub hides as HTML, went into backticks.
The check. scripts/docs-links (POSIX sh and awk, like scripts/check’s other helpers; no mdBook, no
network) fails when a page under docs/ is missing from SUMMARY.md (a list in the script holds deliberate
exclusions; none today), when the summary names a page twice or one that does not exist, or when a link in a page
the summary lists, or in what it includes, does not resolve on the site: outside docs/, a directory, a path from
the root, a missing file, or a page not in the book. It reads Markdown links and images, reference definitions,
and HTML href, src, and srcset, and skips code. It runs in about 0.15 s, so scripts/check docs-links is
part of the gate (all). scripts/check docs-site runs it and then mdbook build, and fails on a build that
warns; it needs mdBook and stays out of the gate. On 2026-10-14 the gate’s check read 115 pages and 1,212 links,
and a separate one-off check of the built site found every relative href and src resolving, and every anchor.
The workflow. .github/workflows/docs.yml runs on a push to main that touches docs/**, book.toml,
CHANGELOG.md, scripts/docs-links, or the workflow, and by hand. On ubuntu-latest it downloads mdBook 0.5.4’s
x86_64-unknown-linux-gnu tarball from its GitHub release and checks it against a SHA-256 written in the workflow
(3f28de05…958cd7, the digest GitHub’s release API gives, read on 2026-10-14 and matched by downloading the file),
rather than run a third-party installer action; then scripts/check docs-site, actions/upload-pages-artifact,
and actions/deploy-pages. Permissions are contents: read for the build and pages: write and id-token: write
for the deploy alone; one concurrency group, pages, lets a deployment finish before the next. Actions are
pinned to their current major versions (checkout@v7, upload-pages-artifact@v5, deploy-pages@v5), not to
commit SHAs, as ci.yml pins its own.
The domain. GitHub serves the site at wisp.pidster.com once the owner, outside this change:
- adds a DNS
CNAMErecord forwisp.pidster.compointing atpidster.github.io; - verifies
pidster.comfor the account in GitHub’s Pages settings, which stops anyone else’s repository claiming a subdomain of it (a takeover, should the record ever outlive the site); - enables Pages for the repository with GitHub Actions as its source, sets the custom domain there, and enforces HTTPS once the certificate is issued.
No CNAME file goes in the site: a Pages site deployed by a custom Actions workflow takes its domain from the
repository’s settings, and ignores such a file. Nothing here calls GitHub’s API or changes DNS.
Consequences
- A new page is added to
docs/SUMMARY.mdas well as todocs/README.md(the release preflight checks the index; the gate now checks the summary). A link to anything outsidedocs/is absolute..claude/rules/docs.mdsays both. - The docs sweep before a release (
docs/release.md, step 0) also reads the guides and the Introduction against the code, since they restate what the reference pages say, and builds the site. - A broken site link is a failed commit, not a 404 found later. Anchors are not checked by the gate; the build’s pages carry mdBook’s heading ids, which match GitHub’s for the headings the docs use.
- The site depends on jsDelivr for its diagrams, and on mdBook’s release asset for its build; a newer mdBook means
changing the version and the digest in the workflow and the version in
scripts/checktogether. - The guides restate facts the reference owns (defaults, commands, messages). Where they disagree, the reference and the code win, and the guide is fixed.
- Until the owner enables Pages, the workflow’s build job still checks every push and its deploy job fails.
Amendment, 2026-10-15: a homepage, and a theme of wisp’s own
Context. The site went live on 2026-10-14 as mdBook’s default theme, with the Introduction as its front page.
The owner judged it not fit to release under: a reader arriving at wisp.pidster.com met a book’s first chapter in
a generic theme, with nothing of wisp’s identity. Two choices were the owner’s, on 2026-10-15: a homepage of its
own with the book under it, rather than a homepage inside the book or another generator; and wisp-tui’s
palette as the visual direction, dark first with a matching light theme. The repository’s social image, a glowing
spirit over a laptop at night, set on GitHub by the owner, is the character the site carries.
Decision.
- The site is a homepage with the book under
/docs/.site/home/holds a hand-written page (index.html,home.css, the picturewisp.jpgcut from the social image, andsocial.jpg, the whole image, for links shared elsewhere).book.tomlbuilds the book intobook/docs/withsite-url = "/docs/", andscripts/check docs-sitecopies the homepage,site/palette.css, andsite/mark.svgtobook/, with mdBook’s404.html, whose assets are named from/docs/, so it serves for a missing page anywhere. The workflow deploysbook/as before, and also runs on a change undersite/. The pages the book served at the root on 2026-10-14 (/guide/install.html) moved to/docs/; the site was a day old, and nothing outside it linked deeper than its root, so there are no redirects. - One palette.
site/palette.cssholds the colours both pages use:wisp-tui’s, as--tui-*in kebab case, and the web’s own (--web-*: the night ground, below the terminal’sSENT, and the light theme’s paper and inks, each at least 4.5:1 on paper).scripts/check palette, in the gate, now reads it too, and fails when a--tui-*colour differs from the Rust, as it already did for the Swift. - The book’s theme is
site/theme/, named bybook.toml’stheme: only the files it holds replace mdBook’s.wisp.csssets mdBook’s variables from the palette, so its five themes become two (lightandrustread as wisp’s light,navy,coal, andayuas its dark, so a theme a reader stored before is still one of ours); the theme picker is hidden, and the reader’s system setting chooses (default-theme,preferred-dark-theme). mdBook’s menu bar becomes the site’s bar, as on the homepage: fixed across the full width above the sidebar, with the mark and “wisp / Docs” in a column the sidebar’s width, so the sidebar’s edge runs up through it; the contents button and the search button, drawn as a search field; and on the right the homepage’s links and the repository and edit icons (the print icon is hidden).wisp.jsadds the mark, the links, and the field’s label; without JavaScript the bar is mdBook’s own and still works. On a phone the sidebar slides over the page rather than pushing it, bar and all, aside. A first version, which kept mdBook’s centred title and floating icons, was judged by the owner on 2026-10-15 to look apart from the layout.fonts/fonts.cssis empty, so mdBook copies and loads no web fonts. - The system’s own faces, and no third-party request from the homepage. The pages set the system font stack (SF Pro and SF Mono on a Mac) and load nothing from another host; the book still loads mermaid from jsDelivr on a page with a diagram, as above.
- The mark (
site/mark.svg, also the favicon) is a small vector wisp drawn from the character: a round body with two eyes and a tail that curls, in the palette’s glow-to-wisp blues. The picture is the character itself; the mark is what stands for it at 20 pixels. - The homepage says only what the docs say. Its sentences come from the Introduction, the guides, and the
reference: the install line, the requirements, the gate’s five steps, the MCP tools, the runtimes. The terminal
it shows is
wisp-tui’s own text, from its source: the⚙tool line and↳ oknote, the fold line,ran:, the approval dialog’s title,remembered as git push *, and its keys.scripts/docs-linksnow reads the homepage too: every link intodocs/must name a page inSUMMARY.md, and every other relative link a file the site puts beside it.
Consequences.
- A change to
wisp-tui’s colours is a change tosite/palette.cssin the same commit, or the gate fails. - A page the homepage links to cannot be renamed without the homepage, or the gate fails.
- When the docs say something new that the homepage restates (a tool, a requirement, the dialog’s wording), the
release’s docs sweep reads the homepage against them (
docs/release.md, step 0). - Checked by eye on 2026-10-15 at 1440 and 390 pixels wide, in the light and dark themes, with headless Chrome: the homepage and the book’s pages, with no horizontal scroll at phone width.