Skip to content

Documentation development

The documentation site is generated with Material for MkDocs. Its source is the repository's docs/ directory and its navigation lives in mkdocs.yml.

Keep links to repository source files relative, so they work when the Markdown is read on GitHub or from a local checkout. During an MkDocs build, scripts/mkdocs_source_links.py rewrites existing links outside docs/ to the matching GitHub source URL.

Where a feature is documented

Directory Content
getting-started/ First steps: overview, first server.
guides/ Server workflows: routing, handlers, middleware, bodies, deployment.
protocols/ One page per protocol: TLS, HTTP/2, WebSocket, CONNECT.
modules/ One page per optional module: HTTP client, JWT, JSON and configuration files.
reference/ Exhaustive lists: build options, configuration fields, HTTP/1.1 conformance.
operations/ Running in production: observability, health probes, Kubernetes, benchmarks.
development/ Contributing: documentation, testing, server architecture.

A feature page explains, in this order when it applies: what the feature is for and how to enable it (CMake option, configuration), a first working example, how it behaves (including limits and error cases), its configuration, performance considerations, limitations, and links to its tests. Async handlers is a complete example. Describe the behavior of the code as it is, and verify each claim against the sources and tests rather than against older documentation.

When a page is renamed or moved, add an entry to redirect_maps in mkdocs.yml, so that published links keep working.

The header logo, docs/assets/logo.png, is derived from resources/logo-blue-violet.png: the symbol alone (without the wordmark, which the header already prints), cropped, downscaled to 72 px high and recolored white so that it stays legible on the purple header in both color schemes. Regenerate it from the source logo when that changes.

Preview locally

Create an isolated Python environment, install the documentation dependency, and start the live-reload server:

python3 -m venv /tmp/aeronet-docs-venv
/tmp/aeronet-docs-venv/bin/python -m pip install --requirement requirements-docs.txt
/tmp/aeronet-docs-venv/bin/python -m mkdocs serve

Open the local address reported by MkDocs. Use Ctrl+C to stop the server.

Validate a production build

/tmp/aeronet-docs-venv/bin/python -m mkdocs build --strict --site-dir /tmp/aeronet-docs-site

--strict turns MkDocs warnings into build failures, including navigation and Markdown-link issues that would otherwise reach the published site.

Publishing

The existing GitHub Pages workflow builds the site and overlays live benchmark dashboards at /benchmarks/. It also retains the source Markdown under /docs/ to avoid breaking existing direct links. Do not add a second Pages deployment workflow: GitHub Pages has one active deployment target, so independent workflows would overwrite each other's published artifact.

CI runs the Markdown example verifier, scripts/verify-md-code.py, on README.md and on every page under docs/ except ROADMAP.md, whose snippets sketch APIs that do not exist yet. It checks internal links, and compiles and links every C and C++ code block against the configured aeronet build, so keep each block a complete, compilable unit: a block without main() becomes a function body, and the aeronet namespace is in scope.

Periodic CMake option combinations

Most CMake options are independent, so the number of combinations is far too large for the regular CI, which only builds a curated set of them on every push and pull request. To catch breakage in the untested combinations on a best-effort basis, the .github/workflows/flags-combinations.yml workflow runs twice a week (and on demand through workflow_dispatch). Each run builds a wave of 20 option combinations with clang-21 in Debug mode and runs ctest on each. Bugs in option combinations are typically triggered by the interaction of a handful of options, so the waves are consecutive slices of a deterministic coverage-guided sequence generated by scripts/gen-flag-matrix.py: each combination is the best of 500 valid candidates, i.e. the one covering the most ON/OFF assignments of any 5 options that were not covered yet (or the least often). About 94 % of all 5-option interactions are covered after 60 combinations and 99.8 % after 120, roughly half of what uniform random sampling of the valid combinations needs on the current option set (measured by simulation). The sequence does not depend on test results, so the workflow keeps no state: the wave is selected by the run number of the workflow (or by the wave input), so that successive runs, scheduled or manual, test successive slices. After 420 combinations (21 waves) a new epoch starts with a fresh sequence, which covers all the feasible interactions again with a different set of combinations, so the process never ends. The optional random_count input adds random combinations, derived from the seed (the run number by default) and the wave. Both parts are reproducible with python3 scripts/gen-flag-matrix.py --wave-count <n> --wave <w> --random-count <r> --seed <s> (numpy is required; the wave and seed of a run are shown in its summary). When adding a new boolean CMake option, append its name to FLAGS in that script, and if the option has prerequisites that CMake enforces (a FATAL_ERROR or a cmake_dependent_option), encode them in is_valid() so that the generator never produces a configuration that cannot exist. The wave size must stay constant during a campaign. Adding or removing an option or changing a constraint changes the sequence but does not reset the wave number: set FIRST_RUN_NUMBER in the workflow to the next run number to restart from wave 0.

When legs fail, a final job sorts the failures into categories with scripts/report-flag-matrix.py. Each leg extracts the errors of its failed step from the step log: compiler and linker errors, CMake errors, failing GoogleTest cases, crashed or timed out tests. Every error gets a fingerprint that ignores line numbers, and legs sharing an error fall in the same category. For each category, the run summary shows the error excerpts, the commands that reproduce its failing combination with the fewest enabled options (building only the failing outputs, or running only the failing tests through GTEST_FILTER), and the smallest flag patterns shared by all its failing legs and by none of the passing legs of the run, a hint of the trigger. Scheduled runs, and manual runs with the report_issues input, also open one GitHub issue per new category, labeled flag-combinations. A category already reported gets a comment on its open issue instead, matched by the fingerprints stored in a hidden marker of the issue body. Close the issue once the bug is fixed: if the same errors come back, a new issue is opened that links to the closed one. The leg subcommand also accepts a job log downloaded from GitHub, to analyze an older run (see the script documentation).