ADR-0030: A documentation website built from docs/ — MkDocs with Material, no plugin, one page per language¶
Status: Accepted (2026-10-05, asked by the author: « a real site, pages, a menu on the left with sub-menus », reusing the same files so nothing is maintained twice) Date: 2026-10-05
Context¶
The GitHub Pages site (ADR-0022) had two hand-written pages: a showcase and the web installer. Everything else was Markdown read on github.com: long pages, each holding the English text and then the French one (« ## Version Française »), with no menu across pages and no search. The showcase already repeated parts of the README by hand.
Checked on 2026-10-05:
- MkDocs 1.6.1 (PyPI, 30 August 2024) has had no release since. The Material theme announced a « MkDocs 2.0 » that breaks every theme and plugin (Material's blog, 18 February 2026, shown by Material 9.7.7 itself at each build).
- Material for MkDocs 9.7.7 (PyPI, 17 July 2026) is in maintenance since 5 November 2025, with fixes promised for at least 12 months (announcement). Its authors move to Zensical (0.0.67 on 30 September 2026), which reads
mkdocs.ymlbut does not run MkDocs plugins (compatibility); nothing confirmed two languages with a language selector there. - The usual i18n plugin, mkdocs-static-i18n, is frozen « because of an unmaintained and uncertain MkDocs upstream » (repository).
Decision¶
docs/stays the only source. No file moves, so every link already given (README, Home Assistant packages and dashboard, blueprint, forum posts) keeps working on github.com. The site is built from those files at deploy time; nothing is copied into the repository.tools/site/construire.pydoes the work, MkDocs only lays out plain Markdown. For each language it writes the pages, then runs MkDocs with Material in strict mode:- a bilingual file is cut at « ## Version Française »: English under
en/, French underfr/(same paths, so the language selector andhreflanglead to the same page). A file written in one language (langue:in the menu:arcade.mdin French, the ADRs in English) is shown on both sites with a note saying so; - links: a published page → its page on the site; an anchor of the other language's heading (a French paragraph pointing to an English title of the same file, fine on GitHub) → the heading at the same rank; an image of
docs/→ copied with the page; any other file of the repository → github.com; a missing file → the build fails; - a block between
<!-- hors-site -->and<!-- /hors-site -->is read on GitHub only (« these pages are also a website »); - each page gets a description (its first real paragraph) for search engines.
- One menu,
tools/site/menu.yml, English and French titles side by side. A file ofdocs/is either in it or listed on purpose intests/test_site_doc.py(HORS_SITE: test inventory, 1.x changelog, press drafts). - No plugin besides Material's search, no MkDocs hook. The language split, the links and the anchors are ours; the theme override (
tools/site/theme/) only adds the sharing tags, page-levelhreflangand a language selector that keeps the page. Another generator that readsmkdocs.yml(Zensical, a MkDocs 1.x fork) would only have plain Markdown to lay out. - Pinned with hashes.
tools/site/requirements.txt(generated byuv pip compile --generate-hashes), installed with--require-hashes --only-binary :all:bysite.yml, whose job can write the public site.requirements-dev.txttakes the same two versions for pytest. - Same deployment as before.
site.ymlbuilds the documentation, thentools/publication/pages.py assembler --doccopiesen/,fr/and the English404.htmlnext to the showcase, the installer and the channel folders. It refuses a documentation that would overwrite anything else: published firmwares read their updates atstable/andbeta/. Onesitemap.xmlfor the whole site (MkDocs' own are dropped), image URLs resolved from each page. - Look: the showcase's colours, system fonts (no request to Google Fonts), light and dark following the system.
Alternatives rejected¶
- Splitting each file in two (
x.mdandx.fr.md) in the repository: simpler build, but every link already published and the « one file, both languages » habit of this repository would change, for no gain to the reader. - mkdocs-static-i18n: frozen, and ignored by Zensical.
- Starlight, Docusaurus, VitePress: native i18n, but a Node toolchain beside a Python one, and the same Markdown would need front matter in every file.
- A hand-written generator: no dependency, but search, the mobile menu and dark mode would have to be written and kept.
Consequences¶
pytestbuilds the whole site (~3 s): a broken link or anchor indocs/turns thepythonjob red on the pull request, before the deployment would stop onmain.tests/test_site_doc.pyalso checks the language split, the links, the page tags (title, description, canonical,hreflang, sharing image) and the hashes.- More commits of
mainare deployed (site.ymlnow runs fordocs/**andtools/site/**). GitHub Pages keeps one deployment per commit (ADR-0022): a release must still target a commit that was never deployed, usually the merge of itschore(release)pull request, which does not touchdocs/. - A new page: write it in
docs/(English, then « ## Version Française »), add it tomenu.yml. - Material's maintenance ends around November 2026 without a promise after: the versions stay pinned, and the switch is a configuration change, not a rewrite.
Amendment (2026-10-05): the README is the home page, the showcase is gone¶
Asked by the author the same day: the site's address still opened the showcase, without the menu.
- The root of the site sends to
en/orfr/, after the browser's language (web/index.html,location.replace); without JavaScript it shows both links. It keeps its title, description,canonical, Open Graph and thehreflangof both languages, with itself asx-default. README.mdis the home page of both languages (page: README.mdinmenu.yml, written asindex.md): one text, read on GitHub and on the site. The showcase was a second copy of it, kept in step by hand. The README took its content, shorter: pitch, install links, what the screen does with a link to each page of the user manual, rain and warnings, gallery. Badges and « this page is also a website » arehors-site. Its<title>(TITRES_ACCUEILinconstruire.py) and the JSON-LDSoftwareSourceCodecome from the theme override.docs/README.md, the contents of the documentation, becomesdocumentation/.- What left the README: the long feature list (in
docs/screens.mdand the user manual), the arcade table (docs/arcade.md), the key design decisions (docs/architecture.md, whose countstests/test_doc_comptes.pynow reads there), the voice section (docs/voice_assistant.md), the repository layout (CARTOGRAPHIE_TAB5.md; the README's copy still counted 45 UI components) and the full personal note (docs/story.md). - Pictures: only the sharing image keeps a descriptive name under
images/(IMAGES); the others are the documentation's own, underen/images/andfr/images/, listed insitemap.xmlwith their page. The demo video is a plain link: no thumbnail is loaded from YouTube. site.ymlalso runs forREADME.md.tests/test_site_doc.pychecks that the menu starts with the README, the home page's title and JSON-LD, and the root's links.