ruff format reflows long calls and normalises quotes, isort reorders the
imports of the documentation lint package, SIM117 folds nested `with`
statements into one parenthesised block, TC003 moves typing-only imports
under `if TYPE_CHECKING`, the JavaScript linter replaces a `var`, and the
YAML formatter rewraps five role files. None of it changes behaviour.
One entry does more than that: the formatter had reflowed two calls in
test_builder.py so their `# nocheck: cache-read` markers landed on the
closing parenthesis, where they suppress nothing and the cache-read lint
would have reported two real violations. Both calls are hoisted into a
variable so the marker sits on the reading line again.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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>
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>
test_brands, test_titles and test_yaml_source spelled out meta/main.yml,
meta/services.yml and meta/info.yml where utils.roles.mapping already names
them. test_file_spot reports exactly that, and it did: the three files were
its only findings in a full make test.
A hard-coded path keeps pointing at nothing once a metadata file is renamed,
and a fixture that writes to the wrong place fails somewhere else than where
the mistake is.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
One predicate now answers what no translator may be handed, and both ends of
the pipeline read it: the extraction withholds the message from the catalog,
the client refuses to send it. What the catalogs keep back is exactly what the
client would have declined.
Two shapes qualify. A message with no word is markup. A message that is only a
name is a label: a brand, this software, a credited person, or a bare URL.
Inside a sentence the name stays protected by the span machinery and the
sentence still needs translating.
Three details each come from a finding. The comparison runs against the prose,
because **Mastodon** is not the string Mastodon and prose() already strips the
emphasis. Case is ignored, because a page writes the name the way its sentence
needs it and **mailu** is still Mailu. The address is matched first, on the raw
text, because masking protects the address of mailto:a@b.c but leaves the
scheme behind and its prose reads as a word.
Measured over the docs domain: 22745 messages fall to 22402 with zero
warnings, and 145 of the 377 removed entries carried a corrupted brand that
was being shipped - Keycloak read Schluesselmantel, OpenProject read offenes
Projekt, micro read Mikrowelle.
SOFTWARE_NAME is read from the group_vars SPOT beside SOFTWARE_REPOSITORY. It
lived there already and no python had ever read it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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>
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>
Both extensions landed in bd15eae9fa without the mirrored unit test the
repository lint requires, which left that lint red.
The resolver's cases include the mistake its first version made: it
filtered on refdomain == 'doc', while myst builds a document reference
only when the target is_file(), so a directory arrives with refdomain
None and the transform did nothing at all. Reintroducing that filter
fails three of the six cases.
The parser's cases pin what the build relies on: one yaml-highlighted
literal block, no block quote, a title from the filename, and the orphan
docinfo as the first child, which is the only place sphinx reads metadata
from.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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>
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>
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>