5.3 Generating documentation
gxdoc generates static HTML documentation from a directory of Djot files. It is included with Gerbil and is also available as gerbil doc.
gerbil doc -o site documentation
# Equivalent:
gxdoc -o site documentation
With an explicit input directory, -o is required and project configuration is ignored. Put options before the directory, following Gerbil’s command-line convention. Without an input directory, the command reads gxdoc.pkg in the current directory. Use gerbil help doc or gxdoc -h for help.
5.3.1 Project configuration
To build a project without a build script, add a gxdoc.pkg file at its root. This is a property list read as data, not executable Scheme:
(output: "site"
mounts: (("doc" . "") ("src" . "reference"))
exclude: ("src/AGENTS.md"))
Then run either command from that directory:
gerbil doc
gxdoc
# Override the configured output directory:
gerbil doc -o /tmp/project-site
The supported fields are:
-
output:— a nonempty output-directory string; defaults to.site. -
mounts:— a list of(source . public)string pairs. Each source directory is relative to the project root, and each public directory is relative to the output root. The default is(("" . "")), publishing the project directory directly. -
exclude:— a list of exact, project-relative source filenames to omit; defaults to(). These are not glob patterns or directory exclusions.
All fields are optional, so () is a valid configuration for a project whose nonhidden .md files are all documentation. Use explicit mounts and exclusions when the repository also contains other Markdown or non-published files. An output path is relative to the current project directory unless absolute; command-line -o takes precedence.
Configuration must contain exactly one property list. Unknown fields, duplicate fields, invalid value types, and extra expressions are errors. The command does not evaluate the configuration, run a build script, or load package source code. It looks only in the current directory: no parent-directory search, installed-package lookup, or implicit configuration from gerbil.pkg. With neither an input directory nor a local gxdoc.pkg, it fails rather than scanning the repository by guesswork.
5.3.2 Live preview
On Linux, run the same publisher with a filesystem watcher and local HTTP server:
gerbil doc --serve
gerbil doc --serve --port 9000
gxdoc --serve --host 127.0.0.1 --port 9000 -o site documentation
Open the printed URL; the default is http://127.0.0.1:8080/. Keep the server bound to loopback unless you intend to expose the documentation to your network. This is a development server, not a production hosting service. Stop it with Ctrl-C.
An ordinary document save regenerates only that page. A referenced asset edit copies only that asset. Adding, removing, or renaming pages, changing a page title, a contents or manuals block, or a page’s level-two sections, or editing gxdoc.pkg rebuilds the site so navigation and numbering stay current. The watcher combines rapid save events, supports editor rename-style saves, and ignores generated output. It uses native inotify, not filesystem polling; macOS/BSD support through kqueue is deferred.
The preview injects a small EventSource client into HTTP responses. Server-Sent Events notify all open tabs after successful publication, including changes missed while disconnected or across server restarts. Responses disable caching, except for the bundled fonts, which are cached for a day. The client script is not written into generated HTML, so ordinary published documentation remains JavaScript-free. /__gxdoc/events and /__gxdoc/live.js are reserved preview endpoints.
Invalid markup or configuration leaves the last successful preview available and reports the error in the terminal. Local-link problems are reported without blocking the preview; fragment validation is deferred. Run ordinary gerbil doc before publishing for comprehensive cross-page validation. The preview removes obsolete files that it generated during the current session; it never serves unrelated files merely because they exist in the output directory.
5.3.3 Inputs and outputs
Write Djot in files with the .md extension. The command recursively finds those files, skipping hidden names, symbolic links, and the selected output directory. It reads documentation only; it does not import or execute package code.
Each document becomes a complete HTML page at the same relative path, with .html replacing .md. For example, documentation/guide/start.md becomes site/guide/start.html. README.md (case-insensitive) becomes index.html. An authored README.md and index.md in the same directory conflict rather than silently overwriting each other. The input needs a root README.md.
Start each document with a level-one heading and nest subsequent headings logically; the first level-one heading supplies the page title.
5.3.4 Manuals
Documentation is organized as manuals: titled, numbered books in a fixed reading order. A manual’s root page lists its chapters in a contents block, a Djot div containing a list of links:
# The Widget Manual
Widgets for every occasion.
::: contents
- [Getting started](start.md)
- [The API](api/README.md)
:::
A chapter may list its own sub-chapters the same way, so api/README.md can contain a contents block for its pages. Chapters are numbered by position (1, 2, 2.1, …); a page’s level-two sections are numbered beneath it (2.1.1, …) unless the page has chapters of its own. Every published page must appear in exactly one contents block: a page listed twice, a page listed nowhere, a cycle, or an entry that is not a document is an error. On the root page, the block is rendered as a numbered table of contents showing each chapter with its sections or sub-chapters.
A project with one manual uses its root README as the manual’s title page. To publish several manuals, make the root README a start page with a manuals block instead: level-two headings name categories, and each list item links a manual’s root page, followed on its next line by a short description:
# Gerbil
::: manuals
## Getting Started
- [The Gerbil Guide](guide/README.md)
Installing Gerbil, its tools, and a tour of the language.
:::
Each manual is numbered independently. A note block (::: note) becomes a margin note beside the text on wide screens and an inline note on narrow ones.
Every page has a manual rail: top (when there is a start page), prev, up, and next links in reading order, then the manual’s numbered contents with the current chapter expanded to its sections. On narrow screens the contents collapse behind a native details control. Pages end with previous and next links. Navigation uses ordinary links without JavaScript, and a skip link reaches the main landmark.
As each HTML page, referenced asset, or stylesheet is successfully written, the publisher prints a [Output to PATH] line to standard output. The messages are flushed immediately so progress remains visible when output is redirected. Files that fail validation or writing are not reported as completed.
The output includes gxdoc.css, a default stylesheet linked from every page, and the fonts it uses in gxdoc-fonts/: Piazzolla for text and Fira Mono for code, both under the SIL Open Font License, whose texts are published beside them. HTML contains no embedded CSS. Table alignment is represented by data-align attributes and styled by that stylesheet. The stylesheet and fonts are bundled with the tool, so generation does not require access to Gerbil’s source checkout.
Use an output directory dedicated to generated files. It may be inside the input directory, but cannot be the input directory or one of its ancestors. An ordinary build replaces matching generated files; unlike the live preview, it does not delete stale files after source documents are removed or renamed. Existing output symlinks are not followed when writing files.
5.3.5 Links and assets
Use ordinary relative links between source documents:
[Getting started](guide/start.md#Getting-started)

Local document links are rewritten to .html, preserving query strings and fragments. Referenced local images and other files are copied with their relative paths intact. Unreferenced files are not copied. Missing local targets, links outside the input directory, and conflicting output paths cause errors.
External URLs are left unchanged. Root-relative local page links are resolved against the publication, and generated links are relative so the site works beneath a URL prefix or from local files. Directory links resolve to their index pages. Existing local asset files take precedence over inferred document names; asset/page output collisions are errors.
The build validates local page targets and fragments, including same-page fragments and percent-encoded identifiers, before replacing published pages. Duplicate IDs and unresolved reference links or images are errors. Use the identifiers produced by Djot, including their case; old VuePress URLs and heading identifiers are not compatibility targets.
5.3.6 Building Gerbil’s website
From the Gerbil checkout, with the updated tool available on the Gerbil load path:
gerbil doc
gerbil doc -o /tmp/gerbil-site
The repository’s gxdoc.pkg selects doc/.site/ as the output. Open its index.html or serve that directory with a static web server. No Bash script, Makefile, npm, VuePress, or Org exporter is required.
The build publishes doc/ at the site root and colocated src/ pages under reference/. The root README is a start page listing four manuals: The Gerbil Guide, Gerbil Tutorials, The Gerbil Reference, and the Legacy Reference. Agent instructions and vendored fixture provenance are excluded. Referenced source assets are copied; unrelated source files are not executed or published. Legacy Org files remain downloadable source assets when referenced, not additional rendered pages.
5.3.7 Build-script interface
For an existing custom build script, the same publisher remains available as a procedure. The command and this interface share the generation, link validation, HTML, CSS, and asset handling:
(import (only-in :gerbil/tools/gxdoc generate-docs))
(generate-docs "." "site"
mounts: '(("doc" . "") ("src" . "reference"))
exclude: '("src/AGENTS.md"))
Source directories and excluded filenames are relative to the input root. Public directories are relative to the output root. Links in authored documents remain relative to their source files; the publisher rewrites them through the complete page map. An omitted mounts: publishes the input directory directly. Mounts cannot escape their root or overlap, and conflicting public paths are rejected.
5.3.8 Current limits
This is a publisher for trusted documentation, not an HTML sanitizer. Djot attributes and copied assets can contain active content. Review inputs before publishing documentation from another author.
Raw HTML is rejected, and arbitrary inline CSS is rejected rather than copied into pages. Alignment declarations are converted to data-align attributes. Write semantic Djot instead of embedding presentation markup.
This first version does not convert Markdown dialects, interpret Gerbil-specific annotations, resolve binding identities, or install documentation into a global registry. Those are separate stages of the documentation effort.