Hurray Documentation Website — Specification
Status: Accepted. Decision recorded in ADR-028. This document specifies the concrete structure of the website; it is infrastructure spec, not part of the normative format specification under
docs/spec/.
This document uses RFC 2119 key words: MUST, MUST NOT, REQUIRED, SHALL, SHALL NOT, SHOULD, SHOULD NOT, RECOMMENDED, MAY, and OPTIONAL.
1. Scope and goals
The website is the public face of Hurray, modelled on arrow.apache.org: a mostly-technical
site for reference-implementation users, format implementers, and ML/inference engineers.
Goals, in priority order:
- Faithful, full-history versioned docs. Every released spec version is browsable, built from its own git tag.
- Simple, fully automated CI pipeline with no Node/npm toolchain.
- Easy to reorganize and extend — content is Markdown authored in place.
- Portable — a plain static tree deployable to GitHub Pages today and any static host later.
2. Toolchain
| Concern | Tool | Notes |
|---|---|---|
| Versioned technical book | mdBook | Spec + impl + cookbook + tutorials. Single Rust binary. Built-in per-book search. |
| Outer site shell | Zola | Landing, FAQ, blog, community. Single Rust binary. |
| Rust API reference | cargo doc | Per version, per crate; published under the version path. |
| Python API reference | pdoc + maturin | Per version; maturin builds that version's wheel, pdoc introspects it (ADR-038). |
| CI / deploy | GitHub Actions → GitHub Pages | Full static-tree deploy. |
Both site generators are prebuilt binaries pinned to explicit versions in the workflow. The build MUST NOT require a Node or npm toolchain, and every tool it installs MUST be pinned to an explicit version in the workflow — the two prebuilt binaries, the Rust toolchain already used by the workspace, and the Python packages named above. It MUST NOT install anything unpinned or resolve a dependency tree at build time.
3. Deployed URL scheme
The site deploys as a single static tree. Paths (relative to the Pages site root):
/ Landing / overview (Zola)
/faq/ FAQ (Zola)
/blog/ Blog index + posts (Zola)
/community/ Contributing, CoC, governance, mailing lists (Zola)
/docs/ → redirects to /docs/stable/
/docs/stable/ Latest stable release book (mdBook; copy of the stable tag build)
/docs/dev/ Book built from `main` (mdBook)
/docs/<version>/ Book built from tag <version> (mdBook) e.g. /docs/0.1.0/
/docs/<version>/api/ cargo doc for that version (rustdoc)
/docs/<version>/python-api/ pdoc for that version's `hurray` module (pdoc)
/docs/stable/api/ cargo doc for the stable release
/docs/dev/api/ cargo doc for `main`
/versions.json Version manifest (drives the dropdown)
- Version path segments MUST be the exact git tag name. The tag convention is
MAJOR.MINOR.PATCHwith no leadingv(e.g.0.1.0); release tags MUST follow it. - The API reference for a version MUST live under that version's
api/(Rust) andpython-api/(Python) subpaths so a single version prefix scopes the book and both API references. - Both API reference directory URLs MUST resolve to a landing page.
cargo docon a multi-crate workspace emits no rootindex.html, so the build MUST emit one;pdocemits its own. - The book MUST carry a navigation entry linking both API references, using links relative to the version path so each version links to its own.
/docs/MUST redirect to/docs/stable/(an emittedindex.htmlmeta-refresh is acceptable, since GitHub Pages does not honour symlinks).
4. Versioning policy
- A version is a spec version. Release tags follow the spec version. The set of
published versions is exactly the set of matching git tags, plus the special
dev(built frommain). stableis the highest non-prerelease semantic-version tag. Its build MUST be copied to/docs/stable/(a copy, not a symlink) on each deploy.- Default entry point. The site navigation "Docs" link and
/docs/MUST resolve to/docs/stable/. Thedevversion MUST be reachable and MUST be clearly labelled as unreleased in the version dropdown and via an in-page banner on everydevpage. - Bootstrap fallback. Until the first release tag exists, there is no stable build;
/docs/,/docs/stable/, and the "Docs" nav link MUST fall back to/docs/dev/. The workflow MUST detect "no release tags" and emit this fallback rather than a broken link. - Prerelease tags (e.g.
0.2.0-rc.1) MAY be published as their own version entry but MUST NOT be selected asstable. - Immutability. A published version path, once deployed for a given tag, MUST reflect
that tag's content; version builds MUST come from
git checkout <tag>, never frommain.
4.1 versions.json schema
A single manifest at the site root drives the version dropdown. It is regenerated on every deploy. Shape:
{
"stable": "0.1.0",
"dev": "dev",
"versions": [
{ "id": "dev", "label": "dev (unreleased)", "path": "/docs/dev/", "released": null, "prerelease": false, "dev": true },
{ "id": "0.1.0", "label": "0.1.0", "path": "/docs/0.1.0/", "released": "2026-08-01", "prerelease": false, "dev": false }
]
}
versionsMUST be ordered newest-first, withdevfirst.stableMUST name theidof the stable version, or benullbefore the first release.- Consumers (the dropdown script) MUST treat an absent/
nullstableby pointing atdev.
5. Repository layout
New site sources live under website/; published content stays authored in place under
docs/.
website/
├── book/
│ └── book.toml # mdBook config; src points at the curated doc tree
├── site/ # Zola project
│ ├── config.toml
│ ├── content/
│ │ ├── _index.md # landing
│ │ ├── faq/
│ │ ├── blog/
│ │ └── community/
│ ├── templates/
│ ├── sass/ or static/
│ └── static/
├── theme/ # shared tokens (colors, fonts) used to keep book + site coherent
├── build-site.sh # builds the whole published tree (shell + one book per version)
├── check-doc-links.py # CI check: internal .md links resolve on GitHub and on the site
├── check-rust-blocks.py # CI check: the cookbook's Rust blocks compile and run
├── check-coverage-matrix.py # CI check: regenerates the Implementation Status page from the
│ # real crates, the Python module, and the C header
├── build-prior-art-pdf.py # renders docs/prior-art.md to docs/prior-art.pdf (pandoc + typst)
└── coverage-matrix.toml # canonical spec-feature list (rows) and implementations (columns)
docs/
├── SUMMARY.md # mdBook table of contents — the single reorganization surface
├── spec/ # (existing) format specification
├── impl/ # (existing) implementation requirements
├── cookbook/ # (existing) cookbook entries
├── tutorials/ # (new) longer-form guided tutorials
├── adr/ # (existing) architecture decision records — published as appendix
├── prior-art.md # (existing) prior-art survey — published as appendix
├── prior-art.pdf # generated from prior-art.md; served alongside it by the book
└── figures/ # SVG diagrams — the one image format GitHub, mdBook, and the
# PDF pipeline all render without a preprocessor
- The book is a view over
docs/.book.tomlsetssrcto the doc tree and the book's navigation is defined solely bydocs/SUMMARY.md. Reorganizing the book is editingSUMMARY.md; adding a page is adding a Markdown file and oneSUMMARY.mdline. - The ADRs (
docs/adr/) anddocs/prior-art.mdMUST be published as a book appendix in v1, listed under an "Appendix" section inSUMMARY.md. - The Zola shell MUST NOT duplicate versioned technical content; it links into
/docs/.
6. Visual coherence
- The mdBook book and the Zola shell are themed independently (see ADR-028 Consequences).
- A shared set of design tokens (brand colors, typography, logo) under
website/theme/SHOULD be applied to both so navigation between shell and book feels like one site. - Both themes MUST support light and dark modes and MUST be responsive.
7. CI/CD pipeline
A GitHub Actions workflow builds and deploys the entire tree. It is stateless: it reconstructs all versions from git on every run.
Triggers: push to main, push of a release tag (semver MAJOR.MINOR.PATCH[-prerelease],
no leading v), and manual dispatch.
Stages (in order):
- Checkout with full history and tags (
fetch-depth: 0). - Install pinned tools: the Rust toolchain,
mdbook(pinned version),zola(pinned version), and Python withmaturinandpdoc(pinned versions). - Build the shell:
zola buildfromwebsite/site/into the output root (public/). - Build
dev: from the currentmaintree,mdbook build→public/docs/dev/,cargo doc --no-deps --workspace→public/docs/dev/api/, thenmaturin build+pdoc→public/docs/dev/python-api/. Stamp the "unreleased" banner. - Build each release version: for every release tag (semver, no leading
v), in a detached worktree at that tag,mdbook build→public/docs/<tag>/,cargo doc→public/docs/<tag>/api/, andmaturin build+pdoc→public/docs/<tag>/python-api/. The Python reference MUST be built from that tag's own tree, and MUST be skipped — not faked from another version — for a tag whose tree has nohurray-python. - Resolve stable: compute the highest non-prerelease tag; copy its build to
public/docs/stable/. If no release tag exists, makestablefall back todev. - Emit
public/versions.jsonand thepublic/docs/redirect tostable. - Deploy
public/to GitHub Pages (actions/deploy-pages).
The workflow MUST fail the build (not silently skip) if a tagged version fails to build, so
history stays trustworthy. Pull-request builds SHOULD build the shell + dev only (no full
history) for fast preview.
Note (non-normative): Stage 5 is O(number of release tags), and each version now costs a
cargo docplus a debug build of the Python extension. This is acceptable at current scale (zero tags today). When it becomes slow, switch to incremental builds that carry prior version outputs forward and rebuild only new/changed tags — see ADR-028.
7.1 Cookbook code blocks
Every fenced block in docs/cookbook/ is verified, in both languages, by the test and
python-conformance jobs in ci.yml — not by the docs workflow, because a block rots
when the API moves and that diff touches no Markdown.
| Language | Runner | Tiers |
|---|---|---|
| Rust | website/check-rust-blocks.py (rustdoc --test per page) | rust compiles and runs · rust,no_run compiles only · rust,ignore is skipped and MUST carry a comment saying why |
| Python | hurray-python/tests/test_cookbook_python_blocks.py | RUN executes every block · COMPILE parses only and MUST carry a reason |
The Python tiers are a table in the test module rather than a fence annotation, because
lang-tabs.js groups blocks by language: writing ```python,ignore would change
that tab's identity and break the page. A page with Python blocks that appears in neither
table fails the suite, so a new page cannot skip the decision.
Within a page, Python blocks share one namespace and one scratch directory: a page is read top to bottom, so a later block may use an earlier one's names and the files it wrote.
8. Search
- mdBook's built-in search is enabled per book, giving per-version search for free.
- Each API reference carries its own generator's search (rustdoc's, pdoc's), scoped to that reference and separate from the book's.
- Cross-version and whole-site search are out of scope for v1 (ADR-028).
9. Content model
| Section | Source | Owner |
|---|---|---|
| Landing / overview | website/site/content/_index.md | core |
| FAQ | website/site/content/faq/ | core |
| Blog | website/site/content/blog/ | core contributors only |
| Community (contributing, CoC, governance, mailing lists) | website/site/content/community/ | core |
| Spec / impl / cookbook / tutorials book | docs/ via docs/SUMMARY.md | per existing agent ownership |
| Appendix: ADRs + prior-art | docs/adr/, docs/prior-art.md via docs/SUMMARY.md | per existing agent ownership |
| Rust API reference | cargo doc output | generated |
| Python API reference | pdoc output, from the bindings' doc comments | generated |
| API reference index page | docs/api-reference.md via docs/SUMMARY.md | core |
10. Open questions
[OQ-1]: Resolved. Tag naming is
MAJOR.MINOR.PATCHwith no leadingv(e.g.0.1.0). See §4.
[OQ-2]: Resolved. ADRs and
prior-art.mdare published as a book appendix in v1. See §5, §9.
[OQ-3]: Branding — logo, color palette, and typography for the shared theme tokens. Deferred; to be decided before the theme is built.