Skip to main content

Working on the docs

This page documents the documentation pipeline itself — site structure, hand-written vs auto-generated pages, generator internals, build + deploy, and how to add new content.

Two surfaces, one project

SurfaceLives atPurpose
docs/spec/Repo rootInternal design / RFC territory. Authoritative for why. Plain Markdown, read on GitHub.
website/Repo rootUser-facing documentation site (this site). Authoritative for how. Docusaurus 3 + MDX.

Rule of thumb. If a change affects user behavior, update the site. If it changes design rationale, update the spec. Big features often touch both — do it in the same PR.

Site structure

website/
├── docusaurus.config.ts Site metadata, plugins, nav, footer, prism/mermaid config
├── sidebars.ts The sidebar tree — every page referenced here
├── package.json Node deps (Docusaurus 3.10, local search, mermaid)
├── src/
│ ├── pages/index.tsx Custom landing page (hero + features + install)
│ ├── components/ React components used by pages
│ └── css/custom.css Infima overrides, brand palette, dark-mode tuning
├── static/ Images, diagrams, favicon
└── docs/
├── introduction/ 4 pages — what is versionx, comparisons, status, principles
├── get-started/ 7 pages — install, quickstart, first release, migrations
├── guides/ 7 task-oriented guides
├── reference/ Auto-generated reference pages
│ └── cli/ One page per versionx subcommand (auto-gen)
├── integrations/ MCP (per agent), JSON-RPC, HTTP API, Actions
├── sdk/ Rust SDK embedding + cookbook + docs.rs pointer
├── contributing/ Dev env, workspace tour, this page, etc.
└── roadmap.md User-facing version-sliced roadmap

Hand-written vs auto-generated

Hand-written (most pages)

Plain Markdown with YAML frontmatter:

---
title: My new page
description: One-sentence summary used in search + link previews.
sidebar_position: 3
---

# My new page

Content goes here in GitHub-flavored Markdown + MDX.

Rules:

  • Frontmatter is required. title and description at minimum. sidebar_position when ordering matters.
  • Use .md, not .mdx, unless you need React components or front-matter imports. Docusaurus 3 processes both through MDX, but .md reads as more human-friendly source.
  • MDX escapes: {, }, <, and > outside code blocks get parsed as JSX. Wrap literal braces in backticks (`{path}`) or escape with \{ / \}. Same with </> — use &lt; / &gt; or wrap in backticks.
  • Admonitions use the :::note / :::tip / :::warning / :::danger fenced blocks.
  • Code blocks always carry a language tag so Prism highlights correctly (bash, toml, rust, json, console, etc.).

Auto-generated (Reference)

Reference pages come from the source of truth. Hand edits get clobbered on the next cargo xtask docs run, and CI fails when committed content drifts from what generators produce.

Page / directorySource of truthGenerator
reference/cli/*.mdclap derive in crates/versionx-cli/src/main.rs (via versionx --help-json)cargo xtask docs --only cli
reference/versionx-toml.mdcrates/versionx-config/src/schema.rscargo xtask docs --only config
reference/events.mdEvery .info("kind") / .warn("kind") / Event::new("kind") site across cratescargo xtask docs --only events
reference/exit-codes.mdCoreError enum in crates/versionx-core/src/error.rscargo xtask docs --only exit-codes
integrations/mcp/tool-catalog.mdTool descriptor() fns in crates/versionx-mcp/src/tools/*.rscargo xtask docs --only mcp
integrations/json-rpc-methods.mdmethods {} and notifications {} const blocks in crates/versionx-daemon/src/protocol.rscargo xtask docs --only rpc

Every generated file starts with:

{/* GENERATED by `cargo xtask docs --only <name>`. Do not edit by hand. */}

Run all generators at once:

cargo xtask docs

Generator internals

Implementation in xtask/src/docs/. Shared helpers in syn_util.rs.

docs-cli

Invokes versionx --help-json (a hidden root flag that emits the complete command tree as JSON) and walks the resulting tree. Produces a root page plus one file per top-level subcommand. Sub-subcommands render inline on the parent page.

docs-config

Parses schema.rs via syn, walks every pub struct and pub enum, extracts field names, types, serde attributes (default, flatten, rename, skip_serializing_if), and doc comments. The root VersionxConfig renders at ##, everything else at ###.

docs-events

Event kinds aren't enumerated as Rust variants — they're dot-separated strings passed to EventSender::emit / .info / .warn / Event::new(...). The generator scans every .rs file under crates/ with a regex and collects the string-literal first argument. Tests get filtered out by cutting at the \n#[cfg(test)]\nmod tests marker. Results group by top-level category.

docs-exit-codes

Parses error.rs via syn, walks CoreError variants, and reads each variant's #[error(...)] template attribute.

docs-mcp

Reads tools/mod.rs to discover tool module names (via regex), then parses each <name>.rs with syn to find the descriptor() function. Walks its body for a ToolDescriptor { ... } struct literal and extracts name, title, description, mutating fields.

docs-rpc

Parses protocol.rs, locates the methods and notifications inline modules, and collects every pub const NAME: &str = "value" inside them. Doc comments on each const become the entry description.

table_escape

All generators go through xtask::docs::syn_util::table_escape before writing into Markdown tables. It replaces | with \|, {/} with \{/\}, </> with &lt;/&gt;, and collapses newlines. Without this, MDX tries to parse literal {path} as JSX and literal <kind> as an opening tag.

Build + deploy

Local

# Run generators
cargo xtask docs

# Serve with hot-reload
cd website
npm install # first time
npm start # http://localhost:3000/versionx/

# Production build
npm run build # static output in website/build/
npm run serve # serve the production build locally

CI

.github/workflows/docs.yml has four jobs:

  1. regenerate — runs cargo xtask docs, then git diff --exit-code -- website/. Any drift between committed and generated content fails the build. This is what keeps the auto-gen contract honest.
  2. buildnpm ci + npm run build + upload the build/ output as a Pages artifact.
  3. lychee — broken-link check over the built HTML, advisory (non-blocking).
  4. deploy — on pushes to main, publishes the artifact to GitHub Pages.

Triggered by changes under website/**, crates/**/*.rs, xtask/**, or the workflow / lychee.toml themselves.

Pages configuration

  • Source: GitHub Actions (not the classic /docs branch-based deploy).
  • URL: https://kodydennon.github.io/versionx/.
  • Enabled via the GitHub API (gh api -X POST repos/.../pages -f build_type=workflow). Branch policy permits main.
  • Custom domain deferred — move to versionx.dev or similar later with a one-line docusaurus.config.ts edit + a website/static/CNAME file + DNS.

Adding a new page

  1. Decide which section it belongs to.
  2. Create website/docs/<section>/<slug>.md with frontmatter.
  3. Register it in website/sidebars.ts. Sidebar order is explicit — add your entry in the right place in the array.
  4. Link to it from neighboring pages' "See also" sections if relevant.
  5. Local build: cd website && npm run build. Fix any MDX errors.
  6. Commit.

For Reference pages, edit the source of truth instead (e.g., a new clap flag, a new config field, a new error variant, a new MCP tool) and run cargo xtask docs — the generator handles the rest.

Adding a new section to the sidebar

Edit website/sidebars.ts. Sections are category objects:

{
type: 'category',
label: 'My new section',
link: { type: 'doc', id: 'my-section/overview' },
items: [
'my-section/overview',
'my-section/page-two',
// ...
],
},

Every id must correspond to a real doc file. Docusaurus fails loudly if it can't resolve one.

Updating the landing page

The landing page is a custom React component at website/src/pages/index.tsx — not one of the doc pages. It pulls in:

Edit freely, but keep the install snippet and the versionx demo block in sync with actual CLI output.

Updating the theme / brand

website/src/css/custom.css owns the Infima overrides — colors, fonts, headings, dark-mode tuning. The brand palette is a deep slate base with a burnt-amber accent. Light and dark variants are both defined.

Style guide (brief)

  • Plain voice, no marketing puff in Reference.
  • No headings deeper than ### outside Reference pages.
  • Code blocks always have a language tag.
  • Imperative command examples (versionx plan, not "you would run versionx plan").
  • Avoid "awesome", "powerful", "blazing fast".
  • Every Guide opens with "You'll learn" (3 bullets) and "Prerequisites".
  • Every Reference item includes a "Since" version where applicable.
  • Every page ends with "See also" — 2–4 internal links.

Troubleshooting

MDX compilation failed: "Could not parse expression with acorn" Literal { or } outside a code span. Wrap in backticks or escape with \{ / \}.

MDX compilation failed: "Expected a closing tag for <foo>" Literal <foo> in text. Wrap in backticks, or escape as &lt;foo&gt;.

Drift-check failed in CI A source file changed but the generated page wasn't regenerated. Run cargo xtask docs locally, commit the result.

npm run build warns about onBrokenMarkdownLinks Docusaurus v4 migration notice. Safe to ignore until v4; the option will move to markdown.hooks.onBrokenMarkdownLinks.

Local dev server doesn't show new content Clear the cache: npm run clear then npm start.

Search returns nothing The local search plugin builds its index at npm run build time, not in dev mode. Run npm run build && npm run serve to test search.

See also