Skip to content

Commit 65f61ff

Browse files
committed
docs: cut over from MkDocs to Zensical
Atomic cutover: dependency group, lockfile, both workflows, the Makefile targets and the removal of mkdocs.yml land together, so no commit has docs CI or `make docs` pointing at a config that does not exist. Dependencies: mkdocs, mkdocs-material and pymdown-extensions are dropped for zensical plus the shared stridelabs-docs-theme (git dependency pinned to v0.2.2 -- a public repo rather than a registry, so PRs from forks and local clones can still build docs). pymdown-extensions now arrives transitively; pinning it twice invites conflicts. `grep -c mkdocs uv.lock` is 0. Workflows: adds Docs PR Build, which did not exist -- docs previously built only on push to main and without --strict, so a broken link or anchor could land on main and be caught at deploy time or not at all. Both workflows now build --strict and watch uv.lock, so a theme bump redeploys. Action pins bumped and each ref verified through the git refs API; setup-uv is pinned to v9.0.0 because it publishes no moving v9 tag and actionlint does not catch that. `make docs` / `make docs-serve` now use --locked, matching CI: the theme tag is mutable, so the lockfile's resolved commit SHA is the real integrity check. --strict required a small content fix that MkDocs had been warning about without failing: out-of-tree links to tools/, linux/, and packaging/ became GitHub blob URLs, and the DL-12 heading anchor gained the date suffix that the generated id actually uses. Verified with the locked toolchain: identical 27-page set and all 374 heading anchors preserved, theme markup present, appcast.xml still copied through, and zero requests to Google Fonts.
1 parent bdc4f25 commit 65f61ff

13 files changed

Lines changed: 231 additions & 465 deletions

File tree

.github/workflows/docs-pr.yml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: Docs PR Build
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- 'docs/**'
7+
- 'zensical.toml'
8+
- 'pyproject.toml'
9+
- 'uv.lock'
10+
- '.github/workflows/docs-pr.yml'
11+
12+
permissions:
13+
contents: read
14+
15+
concurrency:
16+
group: docs-pr-${{ github.ref }}
17+
cancel-in-progress: true
18+
19+
jobs:
20+
build:
21+
runs-on: ubuntu-latest
22+
timeout-minutes: 10
23+
steps:
24+
- uses: actions/checkout@v7
25+
with:
26+
persist-credentials: false
27+
# setup-uv publishes no moving major tag -- v9 does not resolve.
28+
- uses: astral-sh/setup-uv@v9.0.0
29+
- run: uv python install 3.13
30+
- run: uv sync --locked --group docs
31+
- run: uv run --locked zensical build --strict

.github/workflows/docs.yml

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ on:
55
branches: [main]
66
paths:
77
- 'docs/**'
8-
- 'mkdocs.yml'
8+
- 'zensical.toml'
99
- 'pyproject.toml'
10+
- 'uv.lock'
1011
- '.github/workflows/docs.yml'
1112
workflow_dispatch:
1213

@@ -23,13 +24,14 @@ jobs:
2324
build:
2425
runs-on: ubuntu-latest
2526
steps:
26-
- uses: actions/checkout@v6
27+
- uses: actions/checkout@v7
2728
with:
2829
persist-credentials: false
29-
- uses: astral-sh/setup-uv@v7
30+
# setup-uv publishes no moving major tag -- v9 does not resolve.
31+
- uses: astral-sh/setup-uv@v9.0.0
3032
- run: uv python install 3.13
31-
- run: uv sync --group docs
32-
- run: uv run mkdocs build
33+
- run: uv sync --locked --group docs
34+
- run: uv run --locked zensical build --strict
3335
- uses: actions/upload-pages-artifact@v5
3436
with:
3537
path: site-build
@@ -41,5 +43,6 @@ jobs:
4143
runs-on: ubuntu-latest
4244
needs: build
4345
steps:
44-
- id: deployment
46+
- name: Deploy to GitHub Pages
47+
id: deployment
4548
uses: actions/deploy-pages@v5

CHANGELOG.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,43 @@ builds the DMG + `.deb`s and publishes to the apt repo. Bump
99
`[workspace.package].version` in `Cargo.toml` to match before tagging (the
1010
release workflow asserts they agree).
1111

12+
## Unreleased
13+
14+
### Changed
15+
16+
- **Docs site migrated from Material for MkDocs to
17+
[Zensical](https://zensical.org)**, the successor from the same team.
18+
Material entered maintenance mode in November 2025 and now warns on every
19+
build that MkDocs 2.0 will remove the plugin and theming systems with no
20+
migration path. `mkdocs.yml` is replaced by a native `zensical.toml`;
21+
documentation content is unchanged aside from a handful of links that
22+
`--strict` now validates (out-of-tree paths became GitHub blob URLs; one
23+
stale heading anchor was corrected).
24+
25+
The look now comes from the shared
26+
[stridelabs-docs-theme](https://github.com/charliek/stridelabs-docs-theme)
27+
package rather than per-repo config, so restyling every site is a version
28+
bump instead of an edit in each repo. Fonts are self-hosted by the theme, so
29+
the site no longer requests anything from `fonts.googleapis.com` or
30+
`fonts.gstatic.com`.
31+
32+
Working notes previously withheld via MkDocs `exclude_docs` moved to
33+
`discovery/` at the repo root — Zensical has no equivalent, and leaving
34+
them under `docs/` would have published them.
35+
36+
Verified against the pre-migration build: identical 27-page set and all
37+
374 heading anchors preserved, so existing deep links still resolve.
38+
`docs/appcast.xml` still copies through to the published site. Page
39+
`<title>` now derives from the page `<h1>` rather than the nav label, which
40+
is the one intentional difference.
41+
42+
### Added
43+
44+
- **`Docs PR Build` workflow.** Docs previously built only on push to `main`,
45+
and without `--strict`, so a broken link or heading anchor could land on
46+
`main` and be caught at deploy time or not at all. Both workflows now build
47+
`--strict` and watch `uv.lock`.
48+
1249
## v0.0.17 — 2026-07-31
1350

1451
Agents move out of the palette and into the sidebar. Every running agent now

CLAUDE.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,44 @@ wrapper small.
241241
and before the single-instance lock, so it never touches a running
242242
instance.
243243

244+
## Docs
245+
246+
**Not part of `make check` or the `ci-success` gate.** The docs site has
247+
its own toolchain (uv/Python) and its own CI workflows; the Rust/Swift
248+
gates do not cover it. Run it for commits touching `docs/`,
249+
`zensical.toml`, `pyproject.toml`, `uv.lock`, or either docs workflow —
250+
both workflows trigger on those shared inputs (and each additionally on
251+
its own file), because a dependency or lockfile change can break the
252+
build just as easily as a content change:
253+
254+
```bash
255+
make docs # uv run --locked zensical build --strict
256+
make docs-serve # preview on http://127.0.0.1:7070
257+
```
258+
259+
The site is [Zensical](https://zensical.org) (not MkDocs — migrated 2026-08),
260+
configured in `zensical.toml`, built into `site-build/`. `--strict` fails
261+
on broken links and anchors and is what both CI workflows run, so run it
262+
locally before pushing docs changes. Note `zensical serve --strict` is
263+
unsupported; verify strictness via `build`.
264+
265+
The look comes from the shared
266+
[stridelabs-docs-theme](https://github.com/charliek/stridelabs-docs-theme)
267+
package, pinned by tag in `pyproject.toml`. Palette, fonts and feature
268+
toggles live there, not here — do not add `theme.palette`, `theme.features`,
269+
or a `[project.theme.font]` table to `zensical.toml`. The last is the
270+
sharp edge: it re-enables Zensical's Google Fonts `<link>` on every page
271+
while the theme's self-hosted faces keep loading anyway.
272+
273+
Working notes in `discovery/` live outside `docs/` on purpose. Zensical
274+
has no `exclude_docs` equivalent; files under `docs_dir` are published.
275+
276+
Two gotchas worth knowing: Zensical **silently ignores unknown config
277+
keys** even under `--strict`, so a green build does not prove a config
278+
edit did what you meant; and the `pymdownx.emoji` callables live in the
279+
`zensical.extensions.emoji` namespace — the Material for MkDocs
280+
`material.extensions.emoji` namespace aborts the build.
281+
244282
## Build
245283

246284
- libghostty-vt is pinned to a specific Ghostty commit in

Makefile

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -259,11 +259,11 @@ check: fmt-check clippy themes-check test ## Pre-push gate: fmt-check + clippy
259259
# ---- docs -------------------------------------------------------------
260260

261261
.PHONY: docs docs-serve
262-
docs: ## Build the mkdocs site into site-build/
263-
uv sync --group docs && uv run mkdocs build
262+
docs: ## Build the docs site into site-build/ (same as CI)
263+
uv sync --locked --group docs && uv run --locked zensical build --strict
264264

265-
docs-serve: ## Serve the docs locally (mkdocs serve)
266-
uv sync --group docs && uv run mkdocs serve
265+
docs-serve: ## Serve the docs locally with live reload
266+
uv sync --locked --group docs && uv run --locked zensical serve
267267

268268
# ---- clean ------------------------------------------------------------
269269

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ cd mac && swift build # or: ./mac/scripts/bundle.sh release →
5656

5757
## Documentation
5858

59-
The full site lives under `docs/` and builds with `mkdocs-material` (`make docs-serve`http://127.0.0.1:7070):
59+
The full site lives under `docs/` and builds with [Zensical](https://zensical.org) (`make docs-serve`http://127.0.0.1:7070):
6060

6161
- [Installation](docs/getting-started/installation.md) — toolchain + build
6262
- [First Run](docs/getting-started/first-run.md) — launch behavior + where state lives

RELEASING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ Inspect or edit at https://github.com/charliek/roost/rules.
176176
## The appcast lives where
177177

178178
The Sparkle appcast is at `docs/appcast.xml`, served by GitHub Pages from
179-
`https://charliek.github.io/roost/appcast.xml` via `docs.yml`'s mkdocs
179+
`https://charliek.github.io/roost/appcast.xml` via `docs.yml`'s Zensical
180180
deploy. The `appcast` job mutates that file in place, commits it as the
181181
release-bot, and pushes to main; `docs.yml` redeploys Pages shortly after.
182182

docs/development/setup.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,16 +83,16 @@ Mac tests are under `mac/Tests/RoostTests/`; they cover the workspace state mach
8383

8484
## Documentation site
8585

86-
Markdown sources live in `docs/`. mkdocs-material builds them through `uv`:
86+
Markdown sources live in `docs/`. [Zensical](https://zensical.org) builds them through `uv`:
8787

8888
```bash
89-
make docs # static site under site-build/
89+
make docs # static site under site-build/ (`zensical build --strict`)
9090
make docs-serve # live-reload server at http://127.0.0.1:7070
9191
```
9292

93-
`uv sync --group docs` runs automatically; no global Python install needed beyond the `uv` binary.
93+
`uv sync --locked --group docs` runs automatically; no global Python install needed beyond the `uv` binary. `zensical serve --strict` is unsupported — verify with `make docs`.
9494

95-
The voice for new docs is set in `mkdocs.yml`: professional + direct (no marketing), tables for option lists, code blocks with language hints, admonitions only for important notes/warnings, copy-pasteable examples, one topic per page.
95+
The voice for new docs is set in `zensical.toml`: professional + direct (no marketing), tables for option lists, code blocks with language hints, admonitions only for important notes/warnings, copy-pasteable examples, one topic per page.
9696

9797
## Bumping the pinned Ghostty SHA
9898

docs/development/test-automation.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
# Test automation & scripting architecture (plan)
22

33
Status: **largely implemented** (2026-05). The functional E2E harness
4-
(pytest) is built and headless in CI as [`tools/roosttest/`](../../tools/roosttest/README.md);
4+
(pytest) is built and headless in CI as [`tools/roosttest/`](https://github.com/charliek/roost/blob/main/tools/roosttest/README.md);
55
the tooling is reorganized into three layers (see
6-
[`tools/README.md`](../../tools/README.md)); and `tab.dump`, `wait`, and
6+
[`tools/README.md`](https://github.com/charliek/roost/blob/main/tools/README.md)); and `tab.dump`, `wait`, and
77
the `palette.*` ops shipped. The plan below is kept as the design
88
rationale — the "Gap" entries it lists are mostly closed now. The Lua
99
scripting layer (§ below) remains the open piece. The north star is
@@ -119,7 +119,7 @@ across both implementations?*
119119
| IPC surface | `roost-ipc`: tab/project CRUD, set-state, notify, focus, send, resize, reorder, screenshot, claude-hook, identify — **now also `tab.dump` (content), `palette.*` (UI-action), and `roostctl wait`**. | Copy/paste + live `events.subscribe` still unimplemented. |
120120
| Event stream | UIs consume an **in-process** event bus. `events.subscribe` over the wire is **stubbed not-implemented on both UIs** (`mac/Sources/Roost/IPCHandlerImpl.swift`, `crates/roost-linux/src/ipc.rs`). | External clients can't wait on events yet (the pytest harness condition-waits via polling instead). |
121121
| Render state | `roost-vt` `RenderState.walk(|cell| …)` yields `Cell { text: String /*grapheme*/, fg, bg }` + cursor; mirrored 1:1 in `mac/Sources/Roost/RenderState.swift`. Both UIs walk it to draw. | **Now exposed over IPC as text via `tab.dump`** (viewport only; scrollback is a follow-up). |
122-
| Tooling | Three layers (see [`tools/README.md`](../../tools/README.md)): `tools/roosttest/` (pytest, IPC, **in CI**), `tools/screenshot/` (bash + roostctl + pngtool, visual), `tools/input/linux/` (uinput/clipboard, real input). | Real-input (Layer 3) + visual (Layer 2) are local-only; a Mac CGEvent injector is still to come. |
122+
| Tooling | Three layers (see [`tools/README.md`](https://github.com/charliek/roost/blob/main/tools/README.md)): `tools/roosttest/` (pytest, IPC, **in CI**), `tools/screenshot/` (bash + roostctl + pngtool, visual), `tools/input/linux/` (uinput/clipboard, real input). | Real-input (Layer 3) + visual (Layer 2) are local-only; a Mac CGEvent injector is still to come. |
123123

124124
This unified design is now realized: the pytest harness (`tools/roosttest/`)
125125
is Tier 1, and the screenshot + input harnesses are reorganized by layer.
@@ -281,7 +281,7 @@ compat (optional fields). Document each new op in
281281
### 5.6 Hermetic runs, harness flags & the skip policy *(2026-05)*
282282

283283
The pytest harness is parameterized + configured by these knobs (full
284-
operational detail in [`tools/roosttest/README.md`](../../tools/roosttest/README.md)):
284+
operational detail in [`tools/roosttest/README.md`](https://github.com/charliek/roost/blob/main/tools/roosttest/README.md)):
285285

286286
**Make targets.** `make e2e` / `e2e-gtk` / `e2e-mac` are the quick local
287287
runs (reuse a running UI if present). `make e2e-gtk-ci` / `e2e-mac-ci`
@@ -396,7 +396,7 @@ network in the exposed `roost` table.
396396

397397
**Decision: pytest drives the tests; Lua is a scoped user-scripting
398398
surface, not the test mechanism** (see
399-
[vision.md DL-12](vision.md#dl-12-pytest-drives-the-tests-lua-is-a-user-scripting-surface)).
399+
[vision.md DL-12](vision.md#dl-12-pytest-drives-the-tests-lua-is-a-user-scripting-surface-2026-05-26)).
400400
The analysis that led there is kept below; the key insight that made it
401401
low-stakes is that E2E robustness lives in the *affordances*, not the
402402
runner.

docs/getting-started/installation.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -204,8 +204,8 @@ done (above) and [`nfpm`](https://nfpm.goreleaser.com) on `PATH`:
204204
./linux/scripts/build-deb.sh 0.0.1-dev
205205
```
206206

207-
See [`linux/README.md`](../../linux/README.md) and
208-
[`packaging/nfpm.yaml`](../../packaging/nfpm.yaml) for what the
207+
See [`linux/README.md`](https://github.com/charliek/roost/blob/main/linux/README.md) and
208+
[`packaging/nfpm.yaml`](https://github.com/charliek/roost/blob/main/packaging/nfpm.yaml) for what the
209209
package contains.
210210

211211
## CLI on PATH

0 commit comments

Comments
 (0)