Commit Graph
12 Commits
Author SHA1 Message Date
kevinveenbirkenbachandClaude Opus 5 46445552b8 fix(docs): emit the Architecture section only where its page is written
`sphinx -b gettext -q -W` turns a warning into a failure, and
`make i18n-extract` had been dying on 512 of them. 505 were one defect
repeated: the README template emitted an Architecture section for every
role, while `generate_architecture_page` writes ARCHITECTURE.md only for
invokable ones. 101 roles therefore linked five times each to a page
that does not exist. The section now carries the same
`application_invokable` gate as the page, measured from 101 mismatched
roles to 0.

The remaining seven were separate markup defects, each fatal on its own
under -W. architecture.py's four-entry block was an indented paragraph
that docutils read as a definition list ending abruptly, so it becomes a
literal block. branding.py held a double-backtick literal spanning four
lines, which RST does not allow; it becomes a literal block too.
container_extra_hosts.py wrote `Kwargs:`, which napoleon does not know -
a blank line is measurably not enough, the section has to reach
napoleon, so it is `Keyword Args:`. postgres_libraries.py needed one
blank line before its bullet list, and the cleanup skill an H1 above its
first `##`.

conf.py gains two settings. autodoc_mock_imports covers
`ansible.module_utils.kcadm_json`: that import is correct for an Ansible
module, which resolves module_utils only inside a play, so autodoc on
the control host is what has to yield, not the module.
napoleon_use_ivar stops an `Attributes:` section from emitting
`.. attribute::` directives that collide with the dataclass fields
autodoc already documents - eight duplicate-object warnings, reproduced
and cleared on demand.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-18 01:24:35 +02:00
kevinveenbirkenbachandClaude Opus 5 9dc7f0cbc2 feat(web-app-docs): configure the Sphinx project, copyright and author
conf.py hardcoded all three. They now come from services.docs.sphinx, so an operator changes them where every other role value lives instead of in a python file inside files/.

The author needed a constant. INVENTOR joins ORGANIZATION in group_vars/all/00_general.yml; the name had been written out once in 14_about.yml, which now reads the constant so it stays in one place.

project drops the descriptive suffix and is SOFTWARE_NAME. copyright carries no copyright sign: sphinxawesome_theme/footer.html:14 renders '© {{ copyright }}', so a sign in the value would print twice.

conf.py reads the three with os.environ[...] rather than .get(), because they are required: a missing value must stop the build instead of silently restoring an old literal. DOCS_DESIGN_TITLE keeps its guarded .get() because it is optional.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-15 22:55:43 +02:00
kevinveenbirkenbachandClaude Opus 5 1fe353c225 feat(web-app-docs): render the documentation under the configured design title
web-svc-design declares title: true and web-app-docs does not override it, so resolve_branding yields the role's README H1. Nothing applied it: neither files/Dockerfile nor templates/design.js.j2 set a title, so the merged design spec asserting it in the tab, the header, the breadcrumbs and the mobile drawer would have run and failed.

Pass the resolved title to the container as DOCS_DESIGN_TITLE and let conf.py set html_title from it, which is the one value sphinxawesome derives all four of those surfaces from. The assignment is guarded on a non-empty value, so a deployment with the replacement disabled keeps Sphinx's own default built from project and release.

The designer side solved this in roles/web-app-sphinx/templates/Dockerfile.j2 with -D html_title at image-build time (35de016936). That cannot work here: web-app-docs does not build Sphinx into the image, it runs infinito_docs.server, which builds each version on demand, so only a runtime value reaches that build. conf.py already reads DOCS_VERSION from the environment the same way.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-15 22:39:07 +02:00
kevinveenbirkenbachandClaude Opus 5 d206f2593e feat(web-app-docs): render mermaid fences as diagrams
Every role README carries a ```mermaid flowchart and the documentation
showed it as source. A post-transform now replaces those literal blocks with
<pre class="mermaid"> in html builds, which the runtime picks up; every other
builder keeps the fence as it was.

sphinxcontrib-mermaid injects an inline init script, which this deployment's
CSP refuses, so the runtime and its initialisation are two linked files
instead: mermaid-init.js calls mermaid.initialize with securityLevel strict
and follows the reader's colour scheme. The diagram source is html-escaped
before it is embedded, or an & or < in a node label would break the page.

The runtime is fetched at image build time through ADD --checksum, so a
changed upstream file fails the build rather than shipping. Version and
digest are not ARG defaults, which test_arg_without_default forbids; they
travel meta/services.yml -> vars/main.yml -> compose.yml.j2 the way
DOCS_IMAGE and DOCS_VERSION already do, and an update block registers the
pin with the npm version watcher instead of exempting it.

Verified by building a page with sphinx: the diagram arrives as
<pre class="mermaid"> with its source escaped, a bash fence stays a
highlighted block, and both scripts are linked. A missing mermaid.min.js does
not fail that build, so a local run without the image stays green.

scripts/lint/mermaid.sh keeps validating diagram syntax through mmdc, which
npm installs unpinned. It therefore checks against a newer mermaid than the
11.4.1 readers get, and a diagram using syntax from in between would pass the
lint and render blank.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-26 01:02:30 +02:00
kevinveenbirkenbachandClaude Opus 5 4bf9dc9577 refactor(web-app-docs): group the untranslated-markup extensions in a package
untranslated_markup.py and untranslated_yaml.py shared a name prefix in one
directory, which test_prefix_grouped_files reads as a folder in disguise. They
are now extensions/untranslated/markup.py and yaml.py, beside the __init__.py
that extensions/local/ already carries for the same reason.

A module named yaml.py inside a package does not shadow PyYAML, because
imports are absolute: all nine extensions conf.py registers still import, and
the docs-role unit suite stays green.

The unit tests move with them, keeping the parents[9] root derivation their
sibling package uses at the same depth.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-26 00:57:03 +02:00
kevinveenbirkenbachandClaude Opus 5 57610ff0e5 feat(docs): keep a word-free message out of the catalogs
A table cell holding only a link has nothing to translate, yet it became a
msgid like any sentence: 2999 of the docs domain's 25742 messages, 11.7
percent. They can never be filled, so they count as pending for good and
skew every completeness figure.

The messages come from sphinx's own extract_messages, which is what the
gettext builder collects. That is not cosmetic: sphinx takes a paragraph's
rawsource, so judging astext() would read 'e2e/' out of [`e2e/`](e2e/),
find a word in it and let the markup through. A test now pins that
difference, because the first version of the test could not see it.

The predicate is has_words, the one the translation client already applies
before it sends a batch, so extraction and translation share one definition
rather than two that drift.

Measured: 25742 messages fall to 22745, no surviving msgid loses its
translation, and 2924 of the 2948 removed entries carried their own source
as their translation.

The import is guarded because the site builds arbitrary refs and one older
than utils/i18n/ cannot supply the predicate; the transform then does
nothing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-25 20:12:22 +02:00
kevinveenbirkenbachandClaude Opus 5 d8caa45420 fix(docs): resolve a directory link to its index before its README
Sphinx prefers nothing: dir/index and dir/README are two independent
documents. The preference lives wherever something maps a folder onto a
page, and this repository had two such places disagreeing.
local.file_headings has always hidden README.md from a directory carrying
an index.rst, while yesterday's resolver saw README only, so a link led to
the page the sidebar hides.

Both now try index.rst, index.md, README.md, README.rst in that order. The
extension is renamed accordingly, and the tuple lives once in the lint
package rather than a third time.

That also repairs two links this branch degraded: roles/ holds an index.rst
and no README, so a README-only rule reported every reference to it as
broken and they were turned into code spans.

Three links stay code spans on purpose. Their text names something the
target is not - a suite, a file - so resolving them would restore a
misleading reference rather than a working one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-25 20:08:26 +02:00
kevinveenbirkenbachandClaude Opus 5 3b81b4de2e fix(docs): stop the documentation toolchain from emitting warnings
apidoc now excludes roles/ and library/ the way it already excluded tests/.
A role name carries hyphens and is therefore not an importable module path,
so automodule reported 44 invalid signatures and rendered nothing; library/
holds ansible modules whose ansible.module_utils imports exist only inside a
play. Measured: 168 generated pages drop to 151, and the 17 that go consist
of a heading and automodule directives on unresolvable paths.

The role page generator fits every section underline to its title. pandoc
counts one character fewer than docutils where a heading ends in a stray
variation selector left behind by a removed emoji. It also escapes inline
markup in role metadata, because one description reads (*.parent) and opened
an emphasis that never closed.

The index generator underlined a 21-character caption with 19 characters.

conf.py adds the tel scheme, without which the phone number in SECURITY.md
is an unresolvable reference, and the extract copies the image assets into
its conf directory the way the site builder does, without which every run
reports a missing html_static_path and favicon.

Four tests pinned values this changes. The fifth, the extension count in
test_conf, asserted nothing the loop below it does not already prove and
broke on every legitimate addition; it now asserts the list is non-empty, so
that loop cannot run over nothing.

With this, make i18n-extract domain=docs finishes with zero warnings under
the -W gate.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-25 19:30:04 +02:00
kevinveenbirkenbachandClaude Opus 5 bd15eae9fa fix(docs): let the build resolve yaml pages and directory links
The gettext build emitted 23251 warnings. Three causes carried 22424 of
them, and each is answered with the mechanism sphinx supplies.

conf.py read .yml and .yaml through the reStructuredText parser, where
yaml indentation is a block quote: 16107 warnings, and the pages had no
title either. A parser renders them as the literal block they are, and
marks them orphan because a yaml file is reference material rather than a
chapter, which drops another 3062 toctree warnings.

myst builds a document reference only when the link target is_file(), so
every link to a directory fell through as unresolvable. A post-transform
ahead of myst's resolver points such a link at the directory's README,
but only where that README exists, which leaves the genuinely dangling
ones reported. Together with myst_heading_anchors, which was never
configured, that clears 2921 missing cross-references and 320 python
references.

Measured per step over four full builds: 23251 -> 7130 -> 4068 -> 827.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-25 17:47:07 +02:00
kevinveenbirkenbachandClaude Opus 5 6a23926648 feat(docs): build and serve a translated site per language
Every version whose tree holds a locale/<code>/LC_MESSAGES/docs.po with at
least one translation now gets one site per such language next to the
English one, built with `-D language=<code> -D html_copy_source=0` after
the English site is published. Sphinx reads one docs.po per language
(locale_dirs, gettext_compact = "docs"); YAML pages stay untranslated
through an extension that runs before the Locale transform.

The deployed working tree is built as the unlisted version `deployed` from a
snapshot baked into the image, because the git mirror only holds what is
pushed to main. The server answers /<version>/<code>/ and
/api/languages/<version>, a language without a site is a 404 rather than the
English page, and a switcher on every page lists the built languages and
keeps the page path. min_storage grows to 13GB for the translated sites.

The 47 docs.po catalogs hold the 24869 documentation messages; German is
partially machine-translated so far. The raw-docker and primary-domain lints
skip .po files, which carry the Markdown's own commands and hosts as
translation sources.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-22 18:56:06 +02:00
kevinveenbirkenbachandClaude Opus 5 7026565f86 fix(web-app-docs): group the local navigation extensions in their own package
The prefix-grouped-files lint rejects two siblings sharing the local_
prefix. They now live in extensions/local/ and import nav_utils
absolutely, which TID252 requires for a parent-package import.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-21 19:27:23 +02:00
kevinveenbirkenbachandClaude Opus 5 6eb9a59d14 feat(web-app-docs): merge infinito-sphinx and serve every release on demand
The Sphinx configuration, extensions and generators lived in the separate
infinito-sphinx repository, whose image baked a snapshot of core at build time
from a base image name core no longer publishes. Only core consumed it, and its
generators read core's role layout, so the two repositories drifted without any
CI covering the pair.

The tooling now ships in roles/web-app-docs/files/, and web-app-sphinx is
renamed to web-app-docs. The container is a small service instead of a static
image:

- it keeps a git mirror of the repository in the docs_sites volume;
- /latest/ is the last commit of the default branch, rebuilt whenever a fetch
  finds a new one while the previous build stays online;
- every release tag is selectable in the sidebar switcher and on /versions/,
  and is built the first time someone opens it, behind a progress bar;
- replicas share the volume: the one holding builder.lock fetches and builds,
  the queue and the build states are files beside the sites, so another
  replica resumes the queue when the builder dies.

The theme rewrites every page serially after the build, which took about 75
minutes for the 4544 pages; that pass now runs on the -j workers with the
theme's own function. sphinx-apidoc skips tests/, because
tests/utils/bare_bootstrap_probe.py installs an import blocker when autodoc
imports it and killed the Sphinx workers. The roles overview is generated by a
separate process, so autodoc resolves utils from the version being built, not
from main. The role pages now show galaxy_info.description instead of the
top-level key that never existed.

The Sphinx packages join the dev extra, because the mirrored unit tests of the
tooling import them. min_storage now budgets the built image, the latest site
and a running build's scratch instead of the old image's size. The Playwright
suite is split into front page, versions and on-demand build files; the last
one selects an unbuilt tag in the overview and asserts the build gets queued.

Operators must rename the web-app-sphinx inventory group to web-app-docs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-21 17:39:54 +02:00