Skip to content

Convert docs to AsciiDoc, localise into all five languages, add open-govpress rendering and publishing pipeline - #21

Open
736-c41-2c1-e464fc974 wants to merge 53 commits into
swiss:mainfrom
736-c41-2c1-e464fc974:main
Open

736-c41-2c1-e464fc974 wants to merge 53 commits into
swiss:mainfrom
736-c41-2c1-e464fc974:main

Conversation

@736-c41-2c1-e464fc974

@736-c41-2c1-e464fc974 736-c41-2c1-e464fc974 commented Sep 14, 2026

Copy link
Copy Markdown

Summary

This turns the guidelines into a single localised AsciiDoc source tree that renders itself. Today the repository holds English-only Markdown and ODT files and publishes nothing; after this it holds 15 documents that each carry all five languages in one file (de, fr, it, rm, en), renders them to 75 PDFs through open-govpress — the Swiss federal AsciiDoc rendering tool — and publishes them to GitHub Pages with a language chooser, a per-language index, and an archive of every past build.

Live preview of the result, built from this branch by the same workflow being proposed here:
https://736-c41-2c1-e464fc974.github.io/opensource-guidelines/

The four main pieces:

1. Markdown/ODT → AsciiDoc

  • Every .md and .odt document converted to .adoc and the originals removed. docs/en/ is flattened to docs/, since a per-language directory has nothing left to name once each file carries five languages.
  • The four OSS checklists become [%interactive] AsciiDoc checklists — 87 static glyphs turned into real fillable PDF AcroForm widgets, not just squares that look clickable.
  • Intra-repo link: targets point at .pdf, because they are followed inside the rendered PDFs next to their siblings; README.adoc keeps .adoc targets, being read on GitHub.

2. All five languages, in one file per document

  • A document is :lang: on line 1, a conditional doctitle, a shared attribute header (pared back in section 8 to only what a renderer actually reads), and five whole-body ifeval:: blocks in the order en, de, fr, it, rm. open-govpress render --lang pins {lang} as a locked attribute, which drives the guards.
  • TRANSLATING.md is the entry point for reviewers — it documents the file shape, the rules, the Kennzeichnung table and how to build.
  • Deliberately one file, not five copies: five copies drift apart the first time someone fixes a typo in only one of them.
  • The title-page Kennzeichnung is per-language and names the DTI sector per the Federal Chancellery's own pages (note the abbreviation differs: DTI de/en, TNI fr, TDT it and rm).
  • publiccode.yml gains rm-CH.

3. Diagrams that are text

The 21 diagram PNGs behind 105 image:: macros were language-blind — only their alt text was ever translated, so six of them shipped German text inside the English, French, Italian and Romansh PDFs. They are now:

  • [mermaid] blocks with translated labels — they diff, spell-check and are reviewable in a pull request. Authored bare, so open-govpress supplies the project palette.
  • three pictures that were tables all along → real AsciiDoc tables (they translate and are announced as tables by a screen reader);
  • the four-Cs gears → a generated SVG per language (tools/make-four-cs-svg.sh), so the labels are real text. The PNGs it replaces had no editable source in the repo at all.
  • The document map appeared 25 times across four documents and had diverged into two disagreeing copies; it now lives once per language in docs/partials/ and is included where shown.

docs/assets/ drops from 1.45 MB of bitmaps to 233 kB.

4. Build, CI and publishing

  • devenv.nix / .envrc for local work: render-docs (all languages, or render-docs de rm) produces byte-identical output to CI, including the index pages — an index reimplemented in the workflow is an index that stops matching what you see before you push.
  • .github/workflows/render-docs.yml: renders the five languages as a matrix, merges them into one site, uploads PDFs as an artifact on every push and PR, and deploys to Pages on main.
  • Every build is kept: pushes to main land under commits/<sha>/ (newest 10) and own the site root, v* release tags land permanently under tags/<tag>/. Backed by a site-history branch, since a Pages deployment replaces the whole site and cannot read back what is published.
  • .github/workflows/lint.yml runs 13 pre-commit hooks — whitespace/EOF/symlink/executable checks, editorconfig-checker, nixfmt, yamllint, ripsecrets, shellcheck -x -o all, and a repository-local check-asciidoc that renders every document once per language (a conditional left unterminated in the Romansh block is invisible while parsing as English) and checks every cited reference key against the entries actually emitted — see section 6. typos and vale are deliberately excluded: they are English-language tools and four of five languages here are not English.
  • All shell lives in tools/*.sh rather than inside Nix strings, where shellcheck and editorconfig-checker cannot see it.

5. Tables that were never tables

The document set drew 585 tables, and only 60 of them hold tabular data. The
rest are FAQ question-and-answer pairs, glossaries, reference lists, project
fact-sheets, single-cell callouts, empty write-in boxes, and tables whose only
job was to indent something. They are tables because the source came out of
Word, not because the content has columns.

That is not only untidy. tr { break-inside: avoid } makes every table row
unbreakable, and open-govpress deliberately keeps it that way: a row that breaks
mid-cell is the one fragment shape its repair logic refuses to handle, and
Paged.js then abandons the rest of the document. So a page-tall FAQ answer was
an atomic block the layout could not place, and the remedy is the construct, not
a stylesheet.

Each family moved to the construct that already described it:

  • The Em002-6 FAQ — 48 tables per language whose first column held nothing
    but a localised literal Q/F/D and A/R — became [qanda.unnumbered].
    The convention was applied inconsistently, 190 of 245 questions bolded and 55
    not; the construct supplies the emphasis, so that inconsistency went with it,
    along with the legend table that documented the convention.
  • Glossaries, references and the ten Annex D project fact-sheets became
    description lists. Em002-6's glossary was already one, so this finishes a
    conversion someone had started.
  • The chapter-opening definition of digital sovereignty in Em002-7 became a
    sidebar, and a source attribution in Em002 became a NOTE:, which also
    deleted five hand-maintained "Source note:" prefixes.
  • The four checklists lost the wrapper tables whose first column — and third,
    in Em002-2.3 — was empty in every single row.

The 75 write-in boxes were the worst of it. A [cols="1"] table holding one
empty cell is an implicit header row, so asciidoctor emitted a <thead> with a
single empty <th> and no body, and the theme paints th black. Every box in
all five languages printed as a solid black rectangle — three of them on page
2 of the English Em002-2.1. They are now field::[], a real /Tx widget you can
type into and save. Together with the checkboxes, the checklists are fillable
rather than half-fillable.

The same implicit-header defect printed "Measure 1: Implement the Practical
Guidelines…" as a heading above Measures 3, 6 and 8 in Em002. Both are gone.

60 tables stay, and each earns it: Em002-5's ten-column target-group matrix,
Em002-4's morphological box, Em002-7's sovereignty perspectives, levels, grid,
use-case classification and WTO-threshold case distinction, Em002-3's two licence
comparisons, Em002-2's document and platform tables, and Em002-1's
constellation/impact pairs. Em002-3 and Em002-7 additionally cite "Table 1" by
number in running prose in all five languages, so converting those would have
broken the reference.

No prose was changed anywhere. Five of the eight commits leave the per-language
word census byte-identical; the other three differ only by markup that was
deliberately removed, {empty} guards that render as nothing, or the field and
rows tokens of the new macro.

This needs open-govpress 0.0.12, released for it: [qanda.unnumbered],
page-break protection for terms, questions and bordered blocks, the field::[]
/Tx widget, and tables that now repeat their header and hold their column
widths across a page break. One item was asked for and did not ship — the Romansh
admonition terminology in attributes-rm.adoc still needs TERMDAT or a native
speaker before a published document relies on it.

6. Lists kept in three places, and a gate that never checked

Three lists — abbreviations, glossary, references — were maintained by hand in
Em002, Em002-6 and Em002-7, and all three had drifted apart. They now live once, in
docs/partials/{abbreviations,glossary,references}.<lang>.adoc, and each document
includes the subset it needs by tag.

The drift is the reason it was worth doing, and it was not cosmetic:

  • The abbreviation lists agreed in English and in no other language.
  • The glossary disagreed on five entries in English and on up to eight per
    translation.
  • Of the seven reference keys appearing in more than one list, every one
    differed in title, in link form, or in both. Em002 writes link:url[text];
    Annex A writes +url+, which renders as inert text — and had lost [DigiV]'s
    URL entirely and the head noun from [BBL-CL], which began mid-phrase, "for
    Art. 9 EMOTA Blanket Exception", in all five languages.

The parse gate had never checked cross-references — and not for the reason its
own comment gave. tools/check-asciidoc.sh passed --out-file /dev/null, and
asciidoctor special-cases that path and returns before conversion, so no
conversion-stage diagnostic could reach it at any --failure-level. The comment
blamed the log level, which is the kind of wrong that stops the next person
retrying. It now converts for real, and a companion check compares cited keys
against defined ones per document per language, flattening the corpus with
asciidoctor-reducer so it applies the same tag selection that could silently drop
an entry.

That surfaced references that were broken in the published PDFs:

  • Em002-7 defined [KKB-MB] while citing [KBB-MB] six times without defining
    it
    — 30 bracketed labels pointing at nothing across the set. One information
    sheet, one URL, one letter apart; KBB is Kompetenzzentrum Beschaffungswesen Bund
    and the sibling keys are already [KBB-KV] and [KBB-Perimap].
  • BIT-KOM2023, BITCOM2024, PE1999, DigiO, OSI2019 and OSI were each
    cited with no entry anywhere, in all five languages. [OSI] deliberately became
    [Pe1999], not [OSI2024]: the sentence introduces the Open Source Definition's
    criteria, and [OSI2024] is an alphabetical licence index.

Em002-7's self-assessment grid is now fillable. It asks the reader to place a
technology on a five-by-five grid of sovereignty perspectives against maturity
levels, and gave them twenty-five empty cells — nothing to tick on paper, nothing
to click on screen. Every cell is now an interactive checkbox, 25 real /Btn
widgets per language, filled and read back through pdf.js. Its two reference tables
became description lists.

Every citation in the document set now resolves, and the gate proves it: exit 0
over the whole corpus with an empty allow-list. That has not been true before.

Needs open-govpress 0.0.15. 0.0.13 implements include::[tags=…], which the
partials depend on — 0.0.12 accepted the attribute and silently ignored it, the
dangerous direction, since stock asciidoctor honours it and a gate would go green
while the PDF shipped every entry in the file. 0.0.14 adds the three fixes in
section 7, and 0.0.15 corrects a paint defect it introduced with them: on a turned
page the app's own viewport backdrop reached the paper, because the print
stylesheet's reset was a less specific selector than the rule it meant to override
and @media adds no specificity. A quarter of every landscape page was grey. Both
defects were specified from this repo, with measurements, and both were fixed
upstream rather than worked around here.

7. Links that went nowhere, and a table that ran off the paper

Two defects that were in every published PDF and that no check could see, both
fixed by the renderer rather than by editing the documents.

Every inter-document link was dead. A relative link:em002-2.pdf[…] serialised
into the PDF as app://govpress/em002-2.pdf — the renderer's own private origin,
a scheme no PDF reader can open. 285 annotations per language, 1,425 across the
set. The fix makes a relative target pass through verbatim, so the 1,095 targets
authored here were already correct input; they were deliberately never rewritten
into absolute URLs, which would have had to be undone. Not one source line
changed and all of them now resolve
/URI (em002-1.pdf).

Em002-5's target-group matrix ran off the sheet. Ten columns against a 453.5 pt
measure: a narrow column gets 37.8 pt and Geschäftsleitung renders at 81.1 pt, so
the surplus was simply painted past the paper edge — nine words in German,
including X and (X) data cells, not merely a clipped heading. A reader could
not tell those columns were there.

The renderer now lets cell content break, and says when it had to. Em002-5 takes
the [.landscape] role instead, which turns the page that one table sits on
without changing the document type: a matrix whose headings are broken
mid-syllable is legible only in the sense that the glyphs are on the sheet. It
costs two to three pages — landscape buys width and spends height — and the
attribute sits on the table, not the document, because
:govpress-style: base-form-landscape would turn all eight pages of a prose
factsheet sideways and declare it a Basisformular, which it is not.

Both fixes were specified from this repo, with measurements, in
ai-prompt-wide-tables.md upstream.

8. Headers that repeated themselves

Two smaller cleanups to the thing every document carries at the top.

Seven of the nineteen header attributes never reached a reader.
:govpress-style: report makes open-govpress apply two attribute files of its
own before the document's header — styles/common.adoc and
styles/report.adoc, bundled in the app — and between them they already set
govpress-front-block, title-page, pdf-theme, title-logo, toc,
sectnums, toclevels, sectnumlevels and experimental. They are applied
softly, so a document's own header still outranks them; ours simply restated
values already in force, byte-identically, fifteen times over.

:toc:, :toclevels:, :sectnums: and :sectnumlevels: deliberately stay,
with a comment saying why they look redundant and are not: GitHub and GitLab
render every .adoc in the repository with plain Asciidoctor, which applies none
of open-govpress's style files, so without them the web view of the source loses
its contents list and its section numbering. The rule applied was drop what no
renderer reads
, not drop what the PDF does not need. :classification: stays
too, empty and commented, so a document answers the question rather than leaving
it unasked.

The repository URL was written out 70 times. :url-repo: was defined in all
15 documents and referenced by none, while the address itself appeared literally
in four different forms, in five languages, with nothing holding them together.
All 55 body occurrences now derive from the attribute, and its value is the
repository root rather than /tree/main — the only value all four forms can be
built from, and the form open-govpress's own showcase uses. The repository being
a fork makes the owner segment the part most likely to move, which is now written
once per document instead of seventy times across them.

The four checklists, index.adoc and the README template lose the attribute
rather than gaining a reference: none of them links to the repository at all, so
defining it there would have recreated in six files exactly the unused-attribute
problem the change exists to remove.

Pre-existing content bugs fixed along the way

None of these were introduced here; they were found by converting and then reading the text in five languages.

  • em002-1.md had a large duplicated content block — roughly the first third of the document repeated, in two different truncated forms. De-duplicated.
  • Broken .odt checklist links across several documents (typos, trailing spaces, a missing word in the filename).
  • Footnotes embedded in heading text, unsupported per the AsciiDoc docs, producing genuine duplicate HTML element IDs and a stray [N] leaking into the table of contents. Relocated to the first natural mention of the same term in the body.
  • Orphaned footnotes defined but never referenced, across nearly every document — dropped rather than force-inserted.
  • Defects baked into the diagram bitmaps, unfixable without an image editor: a box in Italian inside the English licence tree ("GPL o licenza permissiva"); "Verb sements must flow back?", a mangling of "Verbesserungen müssen zurückfliessen"; an Italian footnote column in the security-grounds diagram; a decision node with two branches both labelled "No", which cannot be followed; "Trnsparenz" and "Altrnativoptionen" in the sovereignty scale; and the morphological box screenshotted from PowerPoint with an object still selected, grab handles visible.
  • ODT-conversion artefacts in the English text, repaired per document as each was translated — Fehler! Verweisquelle konnte nicht gefunden werden., an empty in Section ) cross-reference, "aApplication owner", "the roject is Free", "must be uthorised", "Outcomes are feedback continuously", "thus subject to the legal requirements of a?", "(at noted in Poledna/…", a doubled "in principle", the misspelt opdencode.de, an eoperation.ch link contradicting eoperations.ch in the same document, a fedlex link whose display text pointed at /de while the target was /en, and a stray literal backslash before an en-dash.
  • Em002-5's figure caption described the Art. 9 EMOTA process while showing the four Cs; Em002-4's sole remaining figure was numbered 2 with no 1.
  • publiccode.yml's screenshot pointed at a deleted bitmap through a /blob/ URL, which serves an HTML page rather than an image.

Left for FCh to decide, as content rather than conversion: Em002-3 annex D.5 attributes the ASTRA.EMSG road-asset application to MeteoSwiss, though both its repository and its description point to the Federal Roads Office.

Verification

  • Every .adoc parses at zero errors/warnings, in all five languages, on every commit (check-asciidoc).
  • render-docs renders 15 documents × 5 languages = 75 PDFs end to end via the real open-govpress CLI — no diagram falling back to a source listing, no partial rendered as a document.
  • A dedicated content-completeness pass per document, comparing against the original source via git show, confirmed no content was lost — including the em002-1 de-duplication.
  • All 75 title pages carry exactly the two expected Kennzeichnung lines, and the longest (fr, 61 characters) still sets on one line.
  • Interactive checkboxes verified to emit real /AcroForm /Widget annotations with /TU tooltips matching each label; counts match the original glyph counts per file (25/14/9/39).
  • Both workflows have been run green on this fork, including the Pages deploy, the per-commit and per-tag archive, and the v2.0 release tag.
  • The table refactor holds five-language structural parity per document: a census of table fences, cols attributes, headings per level, checkbox items, footnotes, links and list terms moves by the same delta in en/de/fr/it/rm for every commit. The only cross-language differences left in the corpus are the localised abbreviation lists, which already differed before.
  • A per-language word census, taken before and after with cell markup stripped, shows no word count increased anywhereem002-2, em002-3, em002-4, em002-6 and em002-7 are byte-identical, and the only additions in the other two render as nothing.
  • 585 tables become 60; 1204 rendered pages become 1073, and no document grew in any language.
  • The 75 new /Tx form fields were filled in and read back with pdf.js in en and de — values return unchanged, multiline set exactly on the rows > 1 boxes — and the 435 /Btn checkboxes are unchanged at 25/14/9/39 per language.
  • Every citation in the corpus resolves. tools/check-asciidoc.sh now converts rather than stopping at parse, and exits 0 in all five languages with an empty known_unresolved allow-list — a stale entry in that list is itself fatal, so it cannot outlive the problem it documents.
  • The 35 lists replaced by tagged includes were checked from the rendered PDFs, not the source: all 35 come out identical before and after, entry for entry, in all five languages, with the single intended difference [KKB-MB][KBB-MB].
  • The word census is negative in every language for every document that gained an include — text moved out — and every word with a positive delta is include-directive syntax.
  • 1073 rendered pages become 1072. One document changes size: Romansh Em002-7 gains a page because Annex A got back the descriptions and URL it had lost.
  • Form fields survive the rebuild: 25 /Btn in Em002-7 and 87 /Btn + 15 /Tx across the four checklists, per language.
  • Zero glyphs fall outside their page in any of the 75 PDFs, against nine in the German Em002-5 before; and zero app:// URIs remain, against 285 per language. The published 0.0.15 asset was downloaded from the pinned URL and hashes to the pinned sha256, so the workflow's sha256sum -c will pass. Every landscape page of all 75 PDFs was sampled for the backdrop defect: worst coverage 0.03%, which is anti-aliasing on borders, against 25% of a turned page before the fix.

Repository settings this needs (one-time, by an admin)

Nothing here can be set from a pull request — these are repository settings, and the publish job stays red until they are in place. Merging without them is safe: the render job and the PDF artifact work regardless, only the Pages deploy fails.

1. Turn Pages on, sourced from Actions.
Settings → Pages → Build and deployment → Source: GitHub Actions (not "Deploy from a branch"). This is what creates the github-pages environment the deploy job targets.

2. Let release tags deploy, not just main.
Settings → Environments → github-pages → Deployment branches and tags. GitHub creates this environment restricted to the default branch, so the v* tag runs are rejected with a protection-rule error while main succeeds — the archive under tags/<tag>/ reaches the site-history branch but never the site. Add a rule of type Tag matching v*, so the list reads:

type pattern
Branch main
Tag v*

3. Leave the default GITHUB_TOKEN permissions alone — no change needed.
Settings → Actions → General → Workflow permissions can stay on the restricted "Read repository contents and packages permissions" default. Both workflows declare permissions: contents: read at the top and raise only what a specific job needs (contents: write on collect to push the site-history branch, pages: write + id-token: write on deploy). An explicit block in the workflow overrides the repo default, so there is nothing to loosen globally.

4. Check that no ruleset covers site-history.
The publish job force-pushes a single parentless commit to a site-history branch (created on first run — no need to make it by hand). An organisation ruleset matching ** or requiring pull requests on all branches will block that push. The branch is pure derived output, re-renderable from source at any time, so exempting it costs nothing.

Nothing needs to be done for pull requests: PR runs never deploy and never push — they only upload the site artifact.

Notes for reviewers

  • The open-govpress binary reference will need repointing. open-govpress itself lives at https://gitlab.com/swiss-armed-forces/cyber-command/cea/open-govpress. Since this repository is on GitHub and needs the binary available to Actions, the devenv and CI builds currently fetch a 0.0.7 Linux tarball re-hosted as a GitHub Release on this fork (open-govpress-v0.0.7), verified against a pinned SHA-256 — committing it is not an option (~112 MiB, over GitHub's 100 MB limit) and Git LFS is blocked on forks by GitHub policy. On merge, govpressUrl/govpressSha256 in devenv.nix and GOVPRESS_URL/GOVPRESS_SHA256 in .github/workflows/render-docs.yml should point at whatever the canonical publication channel turns out to be — ideally a GitLab release artifact from the upstream project, so there is no re-hosted copy to keep in sync.
  • The document revision is bumped to 2.0, tagged v2.0.
  • Suggested reading order: TRANSLATING.md, then one document (docs/em002-5.adoc is the smallest and was the proof of the mechanism), then tools/.

736-c41-2c1-e464fc974 and others added 30 commits September 14, 2026 14:56
Migrates all guideline documents, the index page, and the four OSS
checklists from Markdown/ODT to AsciiDoc. Internal cross-links and
.odt checklist filenames were normalized to kebab-case during the
conversion. Along the way this also fixes several pre-existing
content issues: a duplicated content block in em002-1, broken .odt
link filenames (typos/trailing spaces), and a couple of malformed
footnote markers.
Adds a devenv.nix/.envrc setup providing git, gh, asciidoctor, pandoc,
and an open-govpress wrapper (FHS-wrapped since the packaged Electron
binary can't run natively on NixOS) plus a render-docs script that
renders every tracked .adoc file to PDF. The open-govpress build is
downloaded on demand from a GitHub Release and verified against a
pinned sha256 rather than committed to the repo: at ~112MiB it clears
GitHub's 100MB hard limit on regular git objects, and this repo is a
fork, which GitHub's LFS policy blocks from uploading new LFS objects
at all.

Also expands each generated .adoc document's header with the full set
of open-govpress front-block attributes (govpress-style,
classification, title-logo-*, url-repo) that the render CLI reads
directly from the document.

Verified: devenv shell -- render-docs renders all 15 tracked .adoc
files to valid PDFs with zero errors.
Renders every tracked .adoc file to PDF via open-govpress, downloaded
from the same GitHub Release referenced in devenv.nix and verified
against the same pinned sha256, under Xvfb (Electron has no headless
mode). Runs on every push to main and on pull requests; PDFs are
always uploaded as a build artifact, and on push to main they're also
published to GitHub Pages with a generated index page. The download is
cached across runs by its checksum.

Requires a one-time manual step: enable Pages in repo Settings with
source set to "GitHub Actions".
Converts all 87 checkbox glyphs (☐) across the four OSS checklist
documents into AsciiDoc's [%interactive] checklist syntax. This
renders as real, clickable <input type="checkbox"> elements in HTML,
and open-govpress carries that through to genuine fillable PDF
AcroForm widgets (/Widget /Btn annotations with /TU tooltips matching
each label) rather than a static glyph.

Where a checkbox and its label lived in separate table columns (the
original ODT layout), merged them into a single list item so the
checkbox has real label text to attach to -- AsciiDoc's checklist
syntax doesn't register a bare "* [ ]" with no following text as a
checklist item at all.

Verified: checkbox counts match the original glyph counts exactly per
file (25/14/9/39), all four files render with zero errors, and the
actual PDF output via open-govpress contains real AcroForm checkbox
widgets, not just visual squares.
Per the AsciiDoc documentation, footnotes are not officially supported
in heading text and can produce incorrect/duplicate numbering. This
was confirmed in practice: three headings had a footnote macro
appended directly to the title (em002-4.adoc x2, em002-6.adoc x1),
which caused Asciidoctor to emit duplicate `id="_footnoteref_N"`
attributes (footnote 1 and 2 in em002-4.adoc were assigned twice) and
leaked a stray "[N]" into the auto-generated table-of-contents entry
for those sections. Relocated each footnote to the first natural
mention of the same term in the following body text, which resolves
to a clean, unique, sequential footnote numbering with no ID
collisions and no TOC leakage.

Also fixed a stray literal backslash before an en-dash in em002.adoc
(`\–`) that was rendering verbatim instead of being silently dropped,
since backslash-escaping only has meaning before AsciiDoc markup
characters (confirmed correct usage of the two neighboring `\|`
escapes, which legitimately escape a literal pipe inside a table
cell -- left those as-is).
Every document is about to carry all five languages in one file, so a
per-language directory has nothing left to name. Assets move with the
documents, which keeps every `image::./assets/...` macro valid without
touching a single one.

Intra-repo `link:` targets switch from .adoc to .pdf: these links are
read inside the rendered PDFs, sitting next to their siblings in the
published per-language directory, where an .adoc target resolves to
nothing. README.adoc keeps its .adoc target, being read on GitHub.

publiccode.yml gains rm-CH, and its screenshot follows the new layout on
main rather than a path pinned to a commit that predates the move.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Proves the mechanism the remaining documents will follow. `ifeval::` on
{lang} selects one body block of five; `open-govpress render --lang`
pins {lang} as a locked API attribute, so it outranks the document's own
:lang: and drives the guards. Verified: five 7-page PDFs, each with its
own title, TOC heading and admonition labels, and no leaked markup.

`:lang: en` moves to line 1, above the title. Attribute entries may
precede a doctitle, and this one has to: without it nothing defines
{lang} before the conditional title is read, so "auto" mode -- GitHub's
.adoc preview, the desktop app, a bare asciidoctor run -- would produce
a document with no title at all.

The four :title-logo-base:/-line1/-line2/-line3 attributes are deleted
rather than translated. Their presence is precisely what suppresses
open-govpress's own localisation (sniffStatesLogo), which is why the
title page read "Federal Chancellery FCh" in all five languages before
and now reads Departement/Amt, Département/Office, Dipartimento/Ufficio
and Departament/Uffizi. Note this trades the FCh naming for the tool's
generic placeholder; restoring it means putting that one string back
into the per-language chain by hand.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The render job becomes a matrix over en/de/fr/it/rm. Each renders the
same sources with its own `--lang` into out/<lang>/, which is what keeps
the outputs apart: `-o <dir>` names every PDF `<basename>.pdf` with the
language nowhere in the filename.

A new collect job merges the five artifacts into site/<lang>/ --
`merge-multiple: true` is load-bearing, without it each archive lands
under its own pdfs-<lang>/ -- and writes the language chooser. Pages now
deploys that tree instead of a flat pdfs/ directory.

The index pages move out of the workflow into tools/site-index.sh and
tools/site-root.sh so `render-docs` can build byte-identical ones
locally; an index reimplemented in the workflow is an index that stops
matching what you see before you push. site-index.sh labels each entry
with the document's title read from that language's ifeval:: block, and
lists only the PDFs actually present -- which is how a document that
opts out of a language with `:l10n-languages:` stays absent from it
rather than appearing as a blank PDF.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The index also drops the "(ODT, 92 kB)" annotations from its entries:
they described the ODT downloads on the FCh website, and these links now
point at the PDFs this repository renders.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Checkbox items keep their `[%interactive]` blocks and `* [ ]` markers so
they stay clickable in every language, and the NCSC and Wikipedia
footnote URLs follow the language where a translated page exists.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Bibliography entries keep their original-language titles, as citations
should; only the descriptive glosses, the act names and the abbreviation
table are translated, so EMOTA/EMBAG/LMETA/LMeCA and FOBL/BBL/OFCL/UFCL
each read correctly in their own version. Fedlex and admin.ch URLs follow
the language; the German-only intranet links do not.

Also fixes two artefacts of the original Markdown conversion that were
carried into the English text: a doubled letter in "aApplication owner"
and a stray footnote number in "open repository14".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Also repairs three artefacts the ODT-to-Markdown conversion left in the
English text: an untranslated "Section Fehler: Verweis nicht gefunden"
where the Open Hub section was meant, an empty "in Section )" cross-
reference, and a stray "-2" before a link macro.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ages

Also fixes two typos carried over from the ODT conversion: "the roject
is Free" in a cited section title, and a missing space after a full stop.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Also fixes four artefacts in the English text: two "Contribution
Contributing" headings missing their colon, a doubled "in principle",
the misspelt opdencode.de, and an eoperation.ch link that contradicted
the eoperations.ch used elsewhere in the same document.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Also fixes a missing full stop that ran two sentences together in the
multi-licensing note, and a stray section number left in the "(L) GPL-3.0
and IoT 7.4.4" heading.

Note for FCh, left unchanged because it is a content error rather than a
conversion artefact: annex D.5 attributes the ASTRA.EMSG road-asset
application to MeteoSwiss, though both its repository and its description
point to the Federal Roads Office.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Restructure docs/em002-4.adoc into the one-file/five-language shape:
a conditional doctitle, the shared attribute header, and one whole-body
ifeval:: block per language (en, de, fr, it, rm).

The embedded Developer Certificate of Origin quote block is verbatim
legal text and is deliberately left in English in all five versions.

Conversion artefacts repaired in the English source along the way:

* "must be uthorised by the project" -> "authorised"
* "Regarding the distribution key, see also Section *Fehler!
  Verweisquelle konnte nicht gefunden werden.*." -> a plain reference
  to the 'Cost allocation' section
* "Outcomes are feedback continuously to the community" -> "are fed
  back continuously"
* "_[IzCab2023]_" -> "_[IZqCab2023]_", matching the reference used
  elsewhere in the corpus
* a fedlex link whose display text pointed at /de while the target was
  /en -> both /en

Verified with open-govpress render --lang {en,de,fr,it,rm}: 32-34 pages
each, correct localised title, no leaked ifeval::/{lang} markup.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Restructure docs/em002-6.adoc into the one-file/five-language shape:
a conditional doctitle, the shared attribute header, and one whole-body
ifeval:: block per language (en, de, fr, it, rm).

The Q/A table markers are localised along with the text (Q/A -> F/A,
Q/R, D/R, D/R), as are the abbreviation tables, which list the
abbreviations actually used in each language version.

Conversion artefacts repaired in the English source along the way:

* "thus subject to the legal requirements of a?" -> "of Art. 9 EMOTA?"
* a stray asterisk after "Open (Government) Data*"
* a question with no question mark ("On what basis would a third party
  have to request the release of source code")
* "the DTI Section of the Federal Chancellery" -> "DTI Sector", the
  form used everywhere else in the corpus
* a duplicated sentence about each authority having a strategy /
  internal rules, merged into one

* "(at noted in Poledna/Schlauri/Schweizer" -> "(as noted in"

Verified with open-govpress render --lang {en,de,fr,it,rm}: 27-28 pages
each, correct localised title, no leaked ifeval::/{lang} markup.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Romansh block of docs/em002-6.adoc was the only one in the corpus
citing the act as LMETA; the other fourteen documents use EMBAG, which
is what TRANSLATING.md fixes as the Romansh short title. Align it, and
cite the ordinance as OEMBAG to match.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Reverts the earlier deletion of :title-logo-base:, :title-logo-line1:,
:title-logo-line2: and :title-logo-line3:. The documents state their own
Kennzeichnung again, as they did before this branch.

Because the documents state it, open-govpress does not localise it, so
lines 1 and 2 move into the per-language ifeval:: blocks alongside the
doctitle; base and the empty line3 stay in the shared header.

line2 now names the DTI sector rather than the generic "Office", per
the Federal Chancellery's own pages:

  en  Federal Chancellery FCh  / Digital Transformation and ICT Steering DTI
  de  Bundeskanzlei BK         / Digitale Transformation und IKT-Lenkung DTI
  fr  Chancellerie fédérale ChF/ Transformation numérique et gouvernance
                                 de l'informatique TNI
  it  Cancelleria federale CaF / Trasformazione digitale e governance
                                 delle TIC TDT
  rm  Chanzlia federala ChF    / Transfurmaziun digitala e direcziun
                                 da las TIC TDT

The sector abbreviation differs per language: DTI in German and English,
TNI in French, TDT in Italian and Romansh. The Romansh bodies used TDI
throughout; corrected to TDT to match bk.admin.ch.

TRANSLATING.md documents the table and cites the source page for each
language.

Verified: all 75 title pages (15 documents x 5 languages) carry exactly
the two expected lines, and the longest line (fr, 61 characters) still
sets on one line.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The sovereignty-level scale, the sovereignty matrix and the morphological
box were tables drawn as pictures. As PNGs they were German-only inside the
English, French, Italian and Romansh renders, and they carried defects that
could not be fixed without an image editor:

- "Trnsparenz" and "Altrnativoptionen" were baked into the level scale
- the morphological box was screenshotted from PowerPoint with an object
  still selected, so grab handles and a rotate handle were visible

Real tables translate, diff and spell-check. They are also announced as
tables by a screen reader rather than as one opaque image.

The new tables carry no block title: AsciiDoc auto-numbers those as
"Table N.", which would have renumbered the existing Table 1 and broken the
prose that cites it in five languages. They use the file's own convention
of an italic caption below instead.

Two content notes:

- the morphological box disagreed with its own prose on the lettering of
  the Support dimension; the prose is followed here
- the matrix cells are empty in the original too -- the picture carried
  only the row and column names

Also drops docs/assets/em002-1/media/image2.png, referenced by nothing.
Every document carries all five languages in one file, but the diagrams did
not: they were 21 language-blind PNGs behind 105 `image::` macros, so only the
alt text was ever translated. Six of them shipped German text inside the
English, French, Italian and Romansh PDFs, and several carried defects that
could not be fixed without an image editor:

- Em002-3's extended licence tree had a box in Italian in the English version
  ("GPL o licenza permissiva") and another reading "Verb sements must flow
  back?", a mangling of "Verbesserungen müssen zurückfliessen"
- Em002-2's security-grounds diagram carried an Italian footnote column
- Em002-2's third-party-rights diagram drew two branches both labelled "No"
  out of one decision, which cannot be followed. Read as No -> rights
  ownership, Yes -> further developments, matching its own footnote 1
- the document map existed in two diverged copies that disagreed about the
  checklist file format and about the entire right-hand column

Diagrams are now `[mermaid]` blocks whose labels are translated text: they
diff, they spell-check, and they are reviewable in a pull request. Blocks are
authored bare -- no `theme`, no `%%{init}%%` -- so open-govpress supplies the
project palette. `classDef` appears only where colour carries meaning the
structure does not: the licence-compatibility graph and the document map,
whose legend was a colour key.

Not everything became a diagram. Three pictures were tables all along and are
now tables; the footnote columns and legends drawn inside four diagrams were
always prose and are now prose.

The four-Cs gears cannot be expressed in Mermaid and stay a picture, but a
generated SVG rather than a bitmap: `tools/make-four-cs-svg.sh` emits one file
per language, so the labels are real text. The PNGs it replaces had no editable
source in the repository at all.

The document map appeared 25 times across four documents -- the duplication
that let the two copies drift apart. It now lives in `docs/partials/`, one file
per language, included where it is shown. `render-docs`, the workflow and
`site-index.sh` exclude that directory, because a fragment is not a document
and would otherwise render as a near-empty PDF with a row in the language
index.

Also:

- publiccode.yml pointed at a deleted bitmap through a `/blob/` URL, which
  serves HTML rather than an image. It now points at a rendered page.
- Em002-5's figure was captioned "Overview diagram of the OSS Article 9 EMOTA
  process" while showing the four Cs; the alt text now describes the figure.
- Em002-4's sole remaining figure was numbered 2 with no 1, since figure 1 is
  now a table. Renumbered; no prose cited it.

docs/assets/ goes from 1.45 MB of bitmaps to 233 kB, most of it the one
screenshot. Verified with `render-docs`: 15 documents x 5 languages, 75 PDFs,
no diagram falling back to a source listing and no partial rendered as a
document.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A Pages deployment replaces the whole site each time, and the workflow has
no way to read back what is already published, so nothing older than the
newest push to main could survive. Keeping earlier builds therefore needs
somewhere to remember them between runs: the `site-history` branch, cloned
by tools/site-publish.sh, overlaid with the build just rendered, and pushed
back. The deployment stays a snapshot; its contents accumulate.

A push to main keeps a copy under `commits/<short sha>/` and still owns the
site root, so the root goes on meaning "current head of main" as it always
has. A `v*` tag keeps a permanent copy under `tags/<tag>/` without
disturbing the root -- `open-govpress-v*` tags the renderer binary released
from this repo and is excluded by construction. Main builds are pruned to
the newest KEEP_COMMITS; releases are never pruned, because a release needs
a URL that keeps working.

Each publish is a single parentless commit. The PDFs are re-rendered on
every run and so are new blobs even when unchanged, so retaining history
would add the full weight of the site to the branch every time, and
deleting a pruned directory at the tip would reclaim none of it. The branch
is derived output, and nothing in it cannot be re-rendered.

This all works only because the generated site is position-independent:
site-root.sh emits `<lang>/`, site-index.sh emits bare PDF names and `../`.
Every link is relative, so a copy of the tree at any depth resolves without
rewriting.

Pull requests are untouched -- still the `site` artifact, still no deploy.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every row on the language chooser, the document listings and the archive was
an anchor styled `color: inherit; text-decoration: none`, so nothing marked
it as clickable until the pointer was already over it and the hover tint
appeared. Keyboard users got no cue at all.

Each row now carries an underlined label in a link colour, and the affordance
matches what the row actually does: a chevron where the row navigates, and a
PDF badge on the document listings, where the row hands over a file instead.
`:focus-visible` gets a visible ring, and the palette moves to custom
properties with a dark-scheme variant so the link colour stays legible either
way.

The back arrows are labelled rather than bare glyphs -- "All languages", and
its translation in each of the five languages on the document listings -- so
the link says where it leads. The archive link in the chooser footer is
likewise a coloured, underlined link rather than plain footer text.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This repository had no git-hooks block at all. loom has roughly 25 generic
hooks plus generated per-subdirectory ones, but almost all of those are
language hooks (Python, JavaScript, Helm) with nothing here to act on. What
follows is the subset that has something to check in a tree of AsciiDoc
documents, shell scripts and CI configuration.

Thirteen hooks: dos2unix, trim-trailing-whitespace, end-of-file-fixer,
check-executables-have-shebangs, check-shebang-scripts-are-executable,
check-symlinks, editorconfig-checker, nixfmt, check-json, yamllint,
ripsecrets, shellcheck and a repository-local check-asciidoc.

Deliberately left out: typos and vale. Both are English-language tools, and
four of the five languages here are not English, so they would report the
German, French, Italian and Romansh text as a wall of misspellings.

Three adjustments the hooks forced, each commented where it lives:

  - .envrc is excluded from check-shebang-scripts-are-executable. It is
    sourced by direnv, never executed; its shebang exists to get shell
    highlighting and the shellcheck directive on line 2.
  - The x-access-token URL in site-publish.sh carries a ripsecrets pragma.
    The token is interpolated at run time, so the literal is a URL shape
    rather than a credential.
  - yamllint runs with truthy check-keys disabled, because GitHub Actions'
    `on:` trigger key parses as the YAML 1.1 boolean true.

shellcheck runs as `-x -o all`, matching loom, which took 171 findings to
zero across the four existing scripts: 129 SC2250 (brace variable
references), 23 SC2312 (masked return values in command substitutions), 17
SC2292 ([[ ]] over [ ]), one SC2249 and one SC2035. The rewrite was checked
against recorded output of the previous revision across ten scenarios,
including the error paths: all ten are byte-identical, and the 31 generated
HTML files differ only in the CSS indentation that editorconfig-checker
required.

.editorconfig is loom's file plus two `indent_size = unset` sections. Strict
adoption flagged 22 violations, and those two cover the ones that are not
ours to reformat: the CC0 LICENCE text with its roman-numeral hanging
indents, and Markdown's list continuations, which align under the marker and
so are naturally odd. The four-space shell indentation needed no change.

check-asciidoc exists because git-hooks.nix ships no AsciiDoc linter of any
kind; the closest are prose linters. It parses every document once per
language rather than once, because the bodies are five ifeval:: blocks and a
conditional left unterminated in the Romansh block is invisible while parsing
as English. Batching one asciidoctor per language instead of one per
file-and-language keeps a full sweep near two seconds.

open-govpress and render-docs move out of devenv.nix into tools/. Shell
inside a Nix string is invisible to shellcheck and editorconfig-checker, and
between them those two scripts held most of the shell in this repository.
The wrappers now pass down only what has to come from Nix.

The lint workflow runs prek directly rather than `devenv test`: devenv
reports only that the git-hooks task failed, without naming the hook or the
reason, even with tasks."devenv:git-hooks:run".showOutput set.

The VS Code bundle is the editor counterpart of the hooks, so that what the
hooks reject is visible while typing. It sits behind env.cicd, which the lint
workflow sets, so CI does not download the editor closure on every push just
to run the hooks.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Brings in the git hooks adopted from loom, along with the build archive and
the link styling on the generated pages.

Three conflicts, all of them the same underlying change meeting the same
files from the other side. This branch added `':!docs/partials/*'` to the
`git ls-files '*.adoc'` listings so that include:: fragments are not treated
as documents; main rewrote every one of those listings for `shellcheck -o
all` and moved one of them out of devenv.nix entirely.

  - tools/site-index.sh: took main's rewritten loop, which reads the file
    list from a variable rather than a process substitution, and moved the
    pathspec onto that variable's assignment.
  - devenv.nix: took main's side, which is now a two-line wrapper. The
    listing this branch edited had moved to tools/render-docs.sh, so the
    pathspec and its comment went there instead.
  - .github/workflows/render-docs.yml merged without help.

check-asciidoc deliberately keeps listing the partials. The exclusions above
are about what counts as a *document* -- what gets rendered to a PDF and
takes a row in the language index -- whereas check-asciidoc only asks whether
a file parses, which a fragment can be asked as well as a document. All five
partials parse standalone in all five languages.

tools/make-four-cs-svg.sh arrived on this branch after main had already
tightened shellcheck to `-x -o all`, so the merge left it failing with 29
findings (26 SC2250, 3 SC2292) plus one editorconfig continuation indent.
Fixed here. The ten generated SVGs are byte-identical before and after, so
the graphics are untouched.

Verified by rendering English end to end: 15 documents, no partial rendered,
15 rows in the index.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A release tag points at the commit main has just published, so the tag
run asked Pages to deploy a build version it had already deployed. Pages
accepted it, reported success, and went on serving the earlier build --
the tag's archive reached the site-history branch but never the site.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
GitHub now forces node20 actions onto the node24 runtime and warns on
every run. Bumping to the majors that declare node24 natively -- rather
than being coerced into it -- keeps the actions running on the runtime
they were built and tested against.

upload-pages-artifact has dropped dotfiles from the tar since v4; the
published tree is only language directories, index.html, the archive and
build-meta.txt, so nothing it carried before goes missing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@736-c41-2c1-e464fc974 736-c41-2c1-e464fc974 changed the title Convert docs to AsciiDoc, add open-govpress rendering pipeline Convert docs to AsciiDoc, localise into all five languages, add open-govpress rendering and publishing pipeline Sep 16, 2026
736-c41-2c1-e464fc974 and others added 10 commits September 16, 2026 04:19
The tool is hosted on GitLab, under swiss-armed-forces/cyber-command/cea;
the comment named a github.com path that does not exist.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Published as open-govpress-v0.0.12 on this fork, the same binary-host
arrangement 0.0.7 uses: the electron-builder tar.gz is ~112MiB, over
GitHub's 100MB object limit, and a fork may not upload new LFS objects.
0.0.7 stays published -- the document tags still pin it, so deleting it
would make a historical checkout unrenderable.

The version, URL and sha256 are pinned in two places that have to agree.
Only the workflow's URL needed editing; devenv.nix interpolates
govpressVersion into both the tag and the filename.

Rendering is unchanged in shape -- all 15 documents in all five
languages, same `<basename>.pdf` naming -- but the new print styling
protects page breaks, so some documents grow a page or five (em002-4
goes 32 -> 37). Extracted text is word-for-word identical across the
bump; only pagination and the running headers move.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Em002 carried six tables per language that were not tabular data. The
objectives/measures matrix and the measure descriptions are label-and-prose
pairs, the two reference lists and the abbreviations are key-and-value, and the
source note is a single-cell box drawn around one sentence. They are tables
because the document came out of Word, not because the content has columns.

One of them was actively wrong in the published PDF. A first row followed by a
blank line is an implicit header row, and 0.0.12 repeats a table's header on
every continuation page -- so "Measure 1: Implement the Practical Guidelines for
Open Source Software in the Federal Administration" printed as a heading above
Measures 3, 6 and 8. It goes away with the table.

Judgement calls:

- The measure references inside the objectives -- "1)", "3)", "8)" -- stay
  literal text with their hard breaks. They point into the measure descriptions
  below, so a real numbered list would renumber them 1, 2, 3 and silently break
  every reference.
- The two reference lists are plain `::`, not `[horizontal]`. An hdlist has no
  `cols`, so its term column is auto-laid-out and the longest key sets it for
  every row: `[KBB-Perimap]` came out wrapped as `[KBB-` / `Perimap]`, which
  `cols="20,80"` had been preventing. Abbreviations keep `[horizontal]`, where
  the longest key is `HERMES` and aligning them is the point of the list.
- Every value sits on the line after its term. `[Em002]:: link:x[y]` on a single
  line parses as an unregistered block macro and the *entire list* disappears --
  exit 0, no warning, nothing in the output. The glossary in Em002-6 is already
  written the long way.
- `\|` became `|`. The backslash escapes a cell separator; outside a table it
  prints as a backslash.
- Two lead-in sentences per language name the construct, so "the following
  table" became the following overview / Übersicht / liste / elenco / glista,
  and the hand-written "Source note:" prefix gave way to the localised NOTE
  label -- five maintained strings fewer.
- The abbreviations list is one entry longer in German and one shorter in
  French, Italian and Romansh. That is not drift: abbreviations are localised,
  and it was already true before this change.

Verified: the constructs move in lockstep across all five languages (-12 table
fences, -6 cols attributes, +1 horizontal list, +1 admonition in each); the
per-language word census is unchanged apart from the dropped header rows, the
"Source note:" prefixes and the ten reworded lead-ins; 13 footnotes before and
after in every language; asciidoctor parses all five. open-govpress render:
en 21->17, de 21->18, fr 20->17, it 20->17, rm 20->17 pages.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Em002-1 carried nine tables per language that hold no tabular data. Definitions,
the twelve potentials, the twelve challenges, the alternativeTo software types,
the GitHub Insights and Open Hub panels and the three support variants are each
a label and a paragraph of prose about it -- two columns because the document
came out of Word, not because the content has any. The cols="1,3a" split then
squeezed every label into a third of the measure and let no row break across a
page.

Judgement calls:

- The constellation/impact table in 4.1 stays a table. It is the one construct
  here that is genuinely two-dimensional: eight parallel condition-and-consequence
  pairs under two named axes, both sides full prose of comparable weight.
  Converting it would drop the axis labels, and 0.0.12 repeating "Constellation |
  Impact" on pages 13, 14 and 15 is the behaviour working. The tallest row is
  ~16 lines against ~45 usable, so the unrepairable in-cell break paged.css warns
  about is not in reach. Its cramped cols="1,2a" split is left alone: widening it
  is a rendering change of its own.
- The numbered potentials and challenges keep their authored numbers, guarded by
  {empty}. "1. Digital sovereignty::" at line start is an ordered-list item, not
  a term -- asciidoctor takes it as an <li> and swallows the definition into it.
  "{empty}1. ...::" parses as a term and prints "1. Digital sovereignty"
  unchanged. A real ordered list would have moved the numbering from the source
  to the renderer, which no diff can show.
- All nine are plain `::`, not [horizontal]. An hdlist's term column is
  auto-laid-out and the longest key sets it for every row; here that key is
  "Open source software development (OSSD)".
- The one definition ending in a numbered list uses a bare `+` continuation.
  Nothing follows the list inside that definition, so there is no paragraph for
  the `+` to re-parent and no need for a `--` open block.
- No lead-in in any language names a table and the file contains no `\|`, so no
  prose was touched: the word census is identical apart from the guards.
- Two footnotes per language, _[St2024]_ in the digital-sovereignty entry and
  _[Gu2024]_ in section 6, close at the bracket of the reference they quote and
  spill the remainder into the body. That is in the published PDF today and is a
  separate fix.

Contrary to expectation there were no implicit header rows to reclaim here: all
nine tables write a row's two cells on two lines, so the line after |=== is never
followed by a blank. asciidoctor emits one <thead> per language before this
change and one after, both from the constellation table's explicit
options="header".

Verified: the constructs move in lockstep across all five languages (-18 table
fences, -9 cols attributes, +72 description terms, +6 bullets the cell marker had
been hiding); the per-language word census differs only by the 24 {empty} guards,
which render as nothing; asciidoctor parses all five. open-govpress render:
en 23->21, de 24->21 pages.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Annex D lists ten released projects, each as a two-column table with a fixed
six-field record: URL, federal authority, description, year, licence,
justification. Nothing in that is tabular -- there is no shared axis and no
second row to compare against, just one label-and-value pair per line, drawn as
a table because the document came out of Word. The section already says the
list will be replaced by metadata collected from publiccode.yml, and a
description list is a far better starting point for that than a grid: the field
names become terms rather than first-column cells.

Judgement calls:

- Plain `::`, not `[horizontal]`. An hdlist has no `cols`, so its term column is
  auto-laid-out and the longest label sets it for every row. The labels here are
  words, not abbreviations -- `Autoritad federala` (18), `Federal authority`,
  `Autorité fédérale`, `Autorità federale` (17), `Bundesbehörde` (13). Rendered
  with `[horizontal]`, English wraps to `Federal` / `authority` and Romansh to
  `Autoritad` / `federala`, the term column eats about a third of the text
  measure in every row, and the document stays 31 pages instead of 30. An hdlist
  is also still a table, so `tr { break-inside: avoid }` would keep D.2's
  four-line description and two-paragraph justification unbreakable.
- Every value sits on the line after its term, uniformly, including the bare `-`
  used for "no justification given" and the six URLs.
- D.2 is the only multi-block record. Its description keeps its ` +` hard break
  verbatim; its justification's second paragraph became a `+` continuation. No
  fact-sheet contains a nested list, so no `--` open block was needed.
- The two real tables stay tables. The licence/copyleft/special-features
  comparison and the use-case/licence/justification matrix are three columns of
  genuinely parallel data, and both carry hand-numbered captions -- Table 1 and
  Table 2 in five languages -- that a conversion would have had to renumber.
- Nothing else moved. No lead-in names the construct, there is no `\|` in this
  document, and no table here had an implicit header row: D.2's blank line is
  inside its last cell, not after its first row, so the header was never
  promoted and the published PDF was not affected.

Verified: the constructs move in lockstep across all five languages (-20 table
fences, -10 cols attributes, +60 description-list terms in each); the
per-language word census is byte-identical before and after, so no prose moved
at all; asciidoctor parses all five languages; the five
`licences-for-pure-usage` anchors and their ten references still resolve, and
both numbered table captions are untouched. open-govpress render: en 31->30,
de 31->30 pages.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…n lists

The eight tables in "Detailed content for the community concept" have three
narrow columns naming a combination of fundamental decisions and one wide
column of prose proposing what to write for it. That is a lookup keyed by a
combination, not tabular data: nothing is compared down a column, and the
three key columns exist only to be read across. Laid out as `cols="1,1,1,4"`
they gave each key a quarter of the measure, so "Federal government", "Each
their own" and "Terzas persunas" wrapped to two and three lines beside a
paragraph of prose, and the four-cell header reprinted on every continuation
page -- 17 times for 8 tables in English, 20 in German.

Judgement calls:

- Each row is one description list entry whose term is the decision triple,
  joined with " / ". The header row is not dropped but becomes an italic
  legend above each list -- "Product management / Supplier as / Development
  -- Proposal" -- so the triple stays decodable without a header. Every
  localised label survives; the word census is byte-identical in all five
  languages.
- The last row of the first table had an empty Proposal cell. It is a vertical
  merge the Word conversion lost: that table's third column is shifted one row
  down against its own prose, and shifting it back leaves the last row empty.
  The two rows are now consecutive terms sharing one description, which is how
  AsciiDoc spells a merged cell. A term with no description is not an option --
  Asciidoctor silently adopts the next paragraph as its description, which
  would have swallowed "The community concept should be a dynamic document".
  The shift itself is left alone; it is a content error for the authors.
- The "a)" "b)" "c)" runs inside the proposals stay literal text with their
  hard breaks. They are references into the [loweralpha] option lists of
  "Fundamental decisions when building a community" -- "Product management c)
  Open organisation" is option c there -- so a real list would renumber them
  and break every reference. Same for the "*Handling reported errors*" run-in
  headings, which mirror the sub-items of the concept structure in Section 4.
- Where a bullet list inside a value is followed by more prose, the list is
  wrapped in a `--` open block. A bare `+` after a list re-parents the next
  paragraph into the last <li>; it parses clean and the gate exits 0.
- Plain `::`, not `[horizontal]`. An hdlist has no cols and the longest term
  sets the term column for every row; "Terzas persunas / Incumbensà / Terzas
  persunas" would have set it for all forty.
- The morphological box stays a table. Dimension x options (a)-(e) is a real
  matrix: it is read down as well as across, and the cells are deliberately
  parallel between rows.
- Two terms per language begin with "- " ("- / Partner / -"). They are safe
  because neither is the first item of its list; as a first item the "- "
  would have parsed as a bullet.

Verified: the constructs move in lockstep across all five languages (-16 table
fences, -8 cols attributes, +40 dlist terms, +8 open-block fences in each);
the per-language word census is byte-identical before and after, in every
language -- the header labels moved into the legend rather than being deleted;
19 footnotes before and after in each language; asciidoctor parses all five.
open-govpress render: en 37->28, de 41->29, fr 37->29, it 37->29, rm 37->29
pages.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Em002-6 drew its entire body as tables: 48 two-column tables per language whose
first column held nothing but a localised literal "Q"/"F"/"D" and "A"/"R", a
legend table documenting that convention, and an abbreviation table. None of it
is tabular. A question-and-answer pair is what `[qanda]` is for, and a
key-and-value list is a description list; they were tables because the document
came out of Word.

The convention was also applied inconsistently: 190 of the 245 questions were
wrapped in `*...*` and 55 were not, so the same construct printed bold or plain
depending on which table you landed in. `[qanda]` supplies the emphasis, so the
bold markers are gone and the inconsistency with them.

Judgement calls:

- The Q/A legend table and its lead-in sentence are deleted rather than
  reworded. They document a convention that ceases to exist, and the paragraph
  above already stands on its own.
- Consecutive questions under one heading became a single list, not one list
  per question. A new list starts only after a heading, which is what `[qanda]`
  numbering would key off if it were ever turned on.
- Abbreviations keep `[horizontal]`: the longest key is `HERMES`, and aligning
  them is the point of the list.
- Six answers per language carry a nested list. Where content follows the list,
  the run is wrapped in a `--` open block -- a bare `+` after a list silently
  re-parents the next paragraph into the last item, and it parses clean. Where
  the list ends the answer, `+` continuation is enough.
- The "two types of violations" answer keeps its real ordered list: the
  "Answer to 1:" / "Answer to 2:" paragraphs below point at it, and the numbers
  are genuinely sequential, so nothing is renumbered.
- The abbreviation lists stay at 20 entries in German and English and 21 in
  French, Italian and Romansh. They are alphabetised by the localised
  abbreviation and some entries exist in one language only; that is not drift.
- The glossary is untouched. It was already a plain description list.

Verified: 250 tables removed, 120 `[qanda.unnumbered]` lists and 343
description terms added; the constructs move in lockstep across all five
languages (-100 table fences, -50 cols attributes, +24 qanda lists, +1
horizontal list, +8 open-block fences each). The only census divergence is the
description terms, +68 in English and German against +69 in French, Italian and
Romansh, which is exactly the one-entry difference in the localised
abbreviation lists. The per-language word census is unchanged apart from the 49
dropped cell markers in each language, the legend table and its lead-in, and
the abbreviation header rows; 12 footnotes before and after in every language;
asciidoctor parses all five. open-govpress render: en 28->20, de 29->21 pages.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…o lists

Em002-7 carried eight tables per language, three of which were not tabular data.
The chapter's definition of digital sovereignty is one sentence with a box drawn
round it, and the Annex A references and the Annex B glossary are key-and-value
lists. They are tables because the document came out of Word, not because the
content has columns. The five that really are matrices -- the sovereignty
perspectives, the sovereignty levels, the perspectives x levels grid, the
use-case classification and the WTO-threshold case distinction -- stay tables.

Judgement calls:

- The definition becomes a sidebar, not a NOTE. It is the chapter's operative
  definition of digital sovereignty, and an admonition label would demote it to
  an aside.
- Both lists are plain `::`, not `[horizontal]`. An hdlist has no `cols`, so the
  longest key sets the term column for every row, and keys like `[KBB-Perimap]`
  and `[BITKOM2024]` are long enough to wrap mid-token. The glossary values also
  carry hard breaks and several paragraphs, which an hdlist handles badly.
- Every value sits on the line after its term. `[A029]:: link:x[y]` on a single
  line parses as an unregistered block macro and the *entire list* disappears --
  exit 0, no warning. The glossary in Em002-6 is already written the long way,
  and this one now matches it.
- `\|` became `|`, twice per language, in [DigiV] and [OSBA-VK]. The backslash
  escapes a cell separator; outside a table it prints as a backslash.
- Nothing was reworded. No lead-in or caption in any language names one of the
  three converted constructs, so the word census comes out byte-identical.
- Tables 1 and 2 are left exactly as they were. Section 5 cites "Table 1" by
  number in running prose and both tables carry hand-numbered italic captions,
  so converting either would break the reference.
- No implicit header row to fix here: the thead count is 5 before and after, and
  all five belong to tables that stay.

Verified: the constructs move in lockstep across all five languages (-6 table
fences, -3 cols attributes, +37 dlist terms, +2 sidebar fences in each); the
per-language word census is completely unchanged; asciidoctor parses all five;
8 tables per language become 5. open-govpress render: en 28->27, de 30->28
pages.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Em002-5 carries the only block title on a table in the document set. It renders
as "Table 1. / Tabelle 1. / Tableau 1. / Tabella 1." in italic above the table,
where Em002-2, Em002-3 and Em002-7 hand-write their captions in plain or italic
text below. Nothing was wrong with the output -- what was wrong is that the
anomaly read as an oversight, so the next pass over these tables would have
"fixed" it and quietly lost the construct.

Judgement calls:

- The block title stays. 4d1056c avoided them because auto-numbering would
  renumber the tables whose numbers the prose cites -- true of Em002-7, which
  cites "Table 1" by number in all five languages. Em002-5 has one table, no
  hand-written number, and no prose in any language that cites one.
- It is also the better construct here. Asciidoctor emits it as
  `<caption class="title">`, the table's accessible name, and open-govpress
  supplies both the number and the localised word for "Table" from its resource
  bundle. Converting would buy visual uniformity with five new hand-maintained
  translatable strings -- the opposite of the five this refactor removed from
  Em002.
- There is no single convention to converge on in any case: Em002-2 writes
  "Table N - X", Em002-3 "Table N: X", Em002-7 "_Table N: X_", and Em002-1,
  Em002-6 and the four checklists caption none of their tables at all.
- All three tables in Em002-5 and Em002-2 stay tables. The target-group matrix
  is genuinely two-dimensional -- nine target-group columns against fourteen
  documents, with X, (X), bold and section references in the cells. The two
  Em002-2 tables are closer to key-and-value, but their header rows name the
  columns in a way a description list cannot, and Table 1 already breaks across
  a page with its header correctly repeating.
- The rationale goes in the source rather than only here, as a comment above the
  English block title with a localised pointer above the other four -- the same
  shape as the comments in docs/partials/document-map.*.adoc.

Verified: comments only, no construct delta in any language; the word census
differs only by the words of the five comments; asciidoctor parses all five;
open-govpress render en 8 and de 9 pages, unchanged, with pdftotext output
identical to the baseline apart from the title-page build hash.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The four checklists drew two constructs out of tables, and neither is tabular.
Each `== Checklist` section was one `[cols="1,6"]` table -- `[cols="1,6,1"]` in
Em002-2.3 -- whose first column, and third where it had one, was empty in every
single row. The table did nothing but indent content that is already an
`[%interactive]` checkbox list and prose, at the cost of a seventh of the text
block and a border around every question.

The other one was actively wrong in the published PDF. The 75 write-in areas are
`[cols="1"]` tables holding a single empty `a|` cell, and a first row followed by
a blank line is an implicit header row even with no `options="header"` -- so
asciidoctor emitted a `<thead>` with one empty `<th>` and no body rows at all,
and the theme paints `th` with `--table-header-bg: var(--ch-black)`. Every box in
all five languages printed as a solid black bar; three of them on page 2 of the
English Em002-2.1 alone. They are now `field::[]`, which stamps a real `/Tx`
widget a reader can type into and save.

Judgement calls:

- `rows` comes from what each prompt asks for: 1 for a name, 3 for a contact or
  repository block, 4 for a description or for free-text comments. Em002-2.1
  gets 1/4/3/4, Em002-2.3 gets 1/3/3/4/4, the other two 1/3/4.
- `rows=1` is written out rather than left to the macro's default, which is also
  1. The number is the judgement being recorded, and a reader comparing the 75
  boxes should not have to know the default to see one was made.
- The fields stay anonymous and every prompt stays prose above the box. A label
  is escaped plain text, so it cannot carry the ` +` hard breaks half these
  prompts use, and folding the prompts in would have meant 375 new translated
  strings for no gain.
- Nothing inside a cell was re-indented or re-wrapped. The content was already
  flush left, so promoting it out is a pure deletion of wrapper lines: 75
  inserted lines, all of them `field::[rows=N]`, against 995 deleted ones, all of
  them `|===`, `|`, `a|`, `[cols=...]` or blank.
- Em002-4.1's wrapper carried two rows that were empty in both columns. They were
  spacing, and they go with the table rather than becoming blank paragraphs.
- Several checklist questions still tell the reader that something "can be named
  in the textbox" where that row never had one. Left alone: this converts the 75
  boxes that exist, and adding boxes is the document owners' call.

Verified: asciidoctor parses all four in all five languages; 10 ifeval and 10
endif per file; compare-census reports the same delta in en/de/fr/it/rm with no
DIVERGES -- table fences -10/-8/-12/-8, cols attributes -5/-4/-6/-4, fields
+4/+3/+5/+3 per language -- and 06_checkbox, 08_ulist and 17_footnote all
unchanged. The word census differs only by the tokens `field`, `rows` and the
numerals 1/3/4 the new macros introduce: not one word of prose, in any language.
435 `* [ ]` items before and after. In the rendered PDFs, per language: /Btn
widgets 25/14/9/39 before and after, /Tx widgets 0 -> 4/3/5/3, all empty,
multiline set exactly on the rows>1 boxes, and pdf.js fills all 15 and reads them
back unchanged. open-govpress render, identical in all five languages: Em002-2.1
5->5, Em002-2.2 6->5, Em002-2.3 5->5, Em002-4.1 6->6 pages.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@olibrian

Copy link
Copy Markdown
Collaborator

Thanks for the MR. Great! @elbaer will be doing a review.

736-c41-2c1-e464fc974 and others added 13 commits September 17, 2026 14:26
Published as open-govpress-v0.0.13 on this fork, the same binary-host
arrangement 0.0.7 and 0.0.12 use: the electron-builder tar.gz is ~112MiB,
over GitHub's 100MB object limit, and a fork may not upload new LFS
objects. Both predecessors stay published -- commits and document tags
still pin them, so deleting either would make a historical checkout
unrenderable.

The version, URL and sha256 are pinned in two places that have to agree.
Only the workflow's URL needed editing; devenv.nix interpolates
govpressVersion into both the tag and the filename.

Rendering is unchanged in shape: all 15 documents render in en, same
`<basename>.pdf` naming, and em002-1 -- the one document rendered under
both versions before the rest were overwritten -- comes out at the same
21 pages.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three citations in Em002-1 named keys that are defined nowhere in the document
set, in any language, so they printed in the published PDFs as bracketed labels
with no matching entry in any reference list: BIT-KOM2023 for BITKOM2023,
BITCOM2024 for BITKOM2024, and PE1999 for Pe1999. Each was wrong in all five
languages, once per language.

- BIT-KOM2023 was copied out of the BITKOM2023 entry's own body, which carries a
  stray "BIT-KOM" from a line break in the source PDF. The following paragraph
  already cites the same guide correctly.
- BITCOM2024 keeps its year: the footnote on the same line gives the URL and
  section of the 2024 Annex A entry, so BITKOM2024 is the target, not the 2023
  entry in the general references.
- BITKOM2023 and BITKOM2024 are the same Bitkom guide at the same URL, recorded
  under two years in two different reference lists. Left as is; harmonising them
  is an editorial decision, not a broken reference.
- BITKOM2024 is defined only in Em002-7's Annex A, so it resolves within the
  document set but not within Em002-1 alone. Left for the owner.
- Em002-1's bare [OSI2019] is also undefined and is left alone here. It does mean
  the licence list, so OSI2024 is the likely target, but it also wants the italic
  markers the house style uses and belongs in its own change.
- The two footnotes per language that close early on a bracketed citation are a
  separate known defect and are untouched.

Verified: asciidoctor parses all five languages; 10 ifeval and 10 endif;
compare-census reports every construct delta zero in all five languages, as a
pure key substitution should; the word census differs by five tokens per
language and identically across them (bit 1>0, kom2023 1>0, bitcom2024 1>0,
bitkom2023 1>2, bitkom2024 0>1) with no other word moved. The Pe1999 fix is
case-only and therefore invisible to the census by construction, so it was
checked in the rendered PDF text instead. open-govpress render: en 21->21,
de 21->21 pages. 15 lines changed, 3 per language, none reflowed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Em002-7 asks the reader to place a technology on a five-by-five grid of
sovereignty perspectives against maturity levels, and then gives them a table
whose twenty-five cells are empty -- not empty as in "fill this in", empty as in
there is nothing there. On paper there is no box to tick and on screen there is
nothing to click, so the one part of the document that is a form was the one part
that could not be used. The two reference tables above it are not tabular either:
a one-character index, a short name and a sentence, with the sentence squeezed
into the last of three columns and the prose reduced to under half the measure.

The grid keeps its table -- it is a real matrix -- but every cell becomes an
`[%interactive]` checkbox, and the header now names each level instead of
printing a bare 0 to 4, so the reader never has to flip back to the scale. The
two reference tables become description lists.

Judgement calls:

- The cell body is `* [ ] {empty}`, not `* [ ]`. Asciidoctor emits no list item
  for a checklist entry with empty principal text, so `* [ ]` alone silently
  produces no <input> and nothing for the form stamper to find -- a grid of
  twenty-five nothings that parses clean and exits 0. `{nbsp}` also works but
  leaves a trailing space that pushes the box off-centre.
- Level 0's short name is truncated at the slash in the grid header --
  "Externally determined", not "Externally determined / non-sovereign". The
  alternative phrasing is a definition, not a column label, and the full form is
  one line away in the list above. Every short name is lifted verbatim from its
  own language's levels list; none was translated afresh.
- Both lists are plain `::`, not `[horizontal]`. The longest term is
  "4 -- Souverain sur le plan systémique", nowhere near short enough for an
  hdlist's auto-laid-out term column.
- The standalone caption under the levels table is dropped rather than folded in.
  In all five languages the lead-in sentence two lines above already contains it
  word for word. The matrix caption stays: that table still exists.
- The widgets carry generic accessible names ("Checkbox 1" ... "Checkbox 25"),
  because a bare box has no label text. The meaning is in the row and column
  headers, which the table structure already exposes.
- [DigiO] was cited in the English, French and Italian prose and defined nowhere;
  the entry has always been [DigiV], which German and Romansh already cited. Only
  the citations are corrected here -- the definition lives in Annex A, which is
  being centralised separately.
- Left alone: the sentence after the grid still speaks of "the empty cells",
  which is now half true. Rewording it in five languages is a prose change, not a
  table refactor, and would have hidden in this diff.

Verified: asciidoctor parses all five languages; the constructs move in lockstep
(-4 table fences, -2 cols attributes, +25 checkboxes, +10 description-list terms
in each); the word census differs only by the four dropped header rows, the five
dropped captions, the level short names now appearing a second time in the grid
header, twenty-five {empty} placeholders and the three DigiO citations. Each
rendered PDF carries exactly 25 /Btn widgets where it carried none, all on-page,
all still ticked after pdf.js fills, saves and reopens them, with the header row
repeating across the page break in en, fr, it and rm. open-govpress render:
en 27->27, de 28->28, fr 29->29, it 28->27, rm 28->27 pages.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…entries

tools/check-asciidoc.sh passed `--out-file /dev/null`, and asciidoctor
special-cases that one path: lib/asciidoctor/convert.rb reads `when '/dev/null'
then return load input, options`, so it returned the loaded document without ever
converting it. The gate was parse-only by construction and no conversion-stage
diagnostic could reach it at any --failure-level, unresolved <<xref>>s above all.
The header comment blamed the log level instead -- right about the symptom, wrong
about the cause, and wrong in the way that stops the next person from trying.

Nothing at all connects a citation to its reference entry. `[St2024]` is literal
text and `[St2024]::` is a description-list term; a citation whose entry was never
written renders as a bracketed label pointing at nothing, in silence. With the
reference lists moving into partials selected by `tags=`, an entry can now also go
missing because a selector did not pick it -- silently again, in asciidoctor and
open-govpress alike, at every log level.

Judgement calls:

- `--out-file -` with stdout discarded, plus `--verbose` and
  `--failure-level=INFO`. An unresolved cross-reference is logged at INFO, so INFO
  has to be fatal and has to be logged at all; asciidoctor has no --log-level and
  -v is its only lever on the logger. Converting for real and throwing the HTML
  away costs nothing measurable -- the parse is the expensive half.
- The 175 DEBUG lines per run that -v turns on are filtered, not explained away in
  a comment. They come from open-govpress's own extensions, which plain
  asciidoctor does not have; DEBUG sits below INFO and can therefore never change
  the exit code, so they are noise by construction and enough of it to train
  people to stop reading this gate. The filter keys on the level label, not the
  three messages, so a fourth extension needs no change here.
- Still one asciidoctor per language, not per file-and-language. Ruby startup
  dominates at 0.29s an invocation whatever the document's size, so batching costs
  2.5s where 100 runs cost 29s. The price is one message: the INFO line for a
  dangling xref names the key and nothing else. Worth it -- the key is its own
  locator via `git grep -n '<<key'` -- and WARN and ERROR, which is nearly
  everything else, still name file and line.
- The citation check lives here rather than in a sibling script, because a sibling
  would need wiring in devenv.nix and an unwired checker is a half-done one.
- It flattens the corpus with asciidoctor-reducer rather than matching `ifeval::`
  and `tags=` by hand. Reducer applies the same tag selection that will silently
  drop an entry, so a mis-tagged include shows up as a citation losing its
  definition instead of being reproduced by a second implementation with the same
  blind spot.
- Always the whole tracked corpus, whatever arguments were passed: only Em002 and
  Em002-7 carry reference lists and every other document defers to them in prose,
  so a per-document universe of entries would report every citation in Em002-1.
- OSI (Em002-3) and OSI2019 (Em002-1) stay in a known_unresolved list rather than
  making the check advisory. Everything else is fatal today; those two await an
  owner decision, and an entry that no longer matches is reported as stale, so the
  list cannot quietly outlive the problem it documents.
- TRANSLATING.md's link rule said relative `.pdf` targets are followed inside the
  rendered PDFs. The rationale holds, the behaviour does not: they serialise as
  `app://govpress/...` and are dead. Stated as what is true today and what 2271c81
  will make true, rather than quietly rewriting 1,100 targets around a bug that is
  already fixed upstream.

Verified: check-asciidoc.sh exits 0 over all 20 tracked .adoc in all five
languages in 5.1s, against 2.5s parse-only before; prek run check-asciidoc
--all-files passes. In a scratch copy it exits 1 on `<<no-such-anchor>>` in all
five languages, where the old invocation exited 0; on a fabricated [Nope2026]; on
an entry left out by `tags=x,y`, which the parse half still reports nothing about;
and on a stale known_unresolved entry. It finds exactly two unresolved citations,
OSI in em002-3 and OSI2019 in em002-1, in all five languages and nowhere else,
with no false positives, and the same result under en_US.UTF-8 and C. The
tag-separator figures in TRANSLATING.md are measured through open-govpress render:
the same fragment, 2 pages with `tags=a,b` and 7 with `tags=a;b`, no warning
either time. shellcheck -x -o all and editorconfig-checker pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…rtials

Three lists were kept by hand in Em002, Em002-6 and Em002-7, and all three had
drifted. The abbreviation lists agreed in English and in no other language. The
glossary disagreed on five entries in English and on up to eight per translation.
Of the seven reference keys that appear in more than one list -- not three: Annex
A also duplicates four entries from Em002's tools list -- every single one
differed in title, in link form, or in both. Em002 writes link:url[text]; Annex A
writes +url+, which renders as inert text and has lost [DigiV]'s URL entirely.

docs/partials/{abbreviations,glossary,references}.<lang>.adoc now carry the union,
tagged per consuming document. References needs three tags, not two, because Em002
prints two separate reference sections with the document map wedged between them.

Judgement calls:

- Abbreviations needed tags after all. The two lists are identical only in
  English; Em002 has 20 en, 21 de, 19 fr/it/rm and Em002-6 has 20 en, 20 de,
  21 fr/it/rm, because the two documents genuinely cite different things. A plain
  include would have silently changed four of five languages.
- HERMES takes Em002-6's italic book title. OU/OE/UO takes Em002-6's "federal
  office", which is what an organisational unit is here. OSSD takes Em002's
  capitalisation, matching every neighbour in its list.
- The glossary follows Em002-6, which Em002-7's own Annex B names as the general
  glossary. Em002-7 wins only for Branch, which ends its sentence, and Market
  analysis, which keeps its hard break.
- [KBB-KV] takes Em002-7's wording: Em002 left it in German in all five languages
  and doubled a word. The other six shared references keep Em002's, which carry
  the URLs Annex A dropped or made inert.
- Both [KKB-MB] and [KBB-MB] are kept here. They are the same sheet at the same
  URL and folding them touches citations in two other documents, so it is its own
  change.
- [BITKOM2023] and [BITKOM2024] are the same Bitkom guide at the same URL under
  two keys, cited both ways from Em002-1. Both kept, pending a ruling on which
  year is right -- picking one would assert an edition nobody here can verify.
- Where the two documents use different headwords for one concept -- AV/AO,
  Kollaboration/Collaboration, Subscription/Subskription and their Romance
  equivalents -- both entries survive, one per tag. Unifying them is a translation
  decision, not a formatting one.
- Six abbreviation entries are emitted twice in fr, it and rm: the two lists sort
  them differently and no single order satisfies both. They collapse once the
  source lists are alphabetised, which neither currently is.

Verified: 15 files, 5 languages, 3 lists, tag names parallel across all five with
no overlapping or unclosed region. 35 of 35 consumer/language selections render
under asciidoctor --safe -v --failure-level=INFO with no warning and reproduce
their document's list contiguously and in order; English matches the recorded
pre-change lists at 20/20/12/21/19/27/16 entries. 45 of 45 open-govpress render
--lang <xx> --strict checks pass, including that the two abbreviation selections
coincide in en and diverge in de, fr, it and rm. A deliberately wrong tag name is
caught: WARNING tag not found, exit 1.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
[OSI2019] in Em002-1 and [OSI] in Em002-3 were cited with no reference entry
anywhere in the corpus, so both rendered as a bracketed label with nothing behind
it. [KKB-MB] in Em002-3 named the same information sheet at the same URL as
[KBB-MB], one letter apart -- KBB is Kompetenzzentrum Beschaffungswesen Bund, and
the sibling keys are [KBB-KV] and [KBB-Perimap], so KKB was the typo.
check-asciidoc.sh has carried OSI and OSI2019 in known_unresolved since it learned
to check citations; that list is now empty.

- [OSI2019] becomes [OSI2024], the Open Source Initiative's licence list by name,
  because the sentence is about "approximately 80 licences recognised by the OSI"
  and that entry is the list of them. Nothing dated 2019 exists.
- [OSI] becomes [Pe1999], not [OSI2024]: the sentence introduces the five criteria
  of the Open Source Definition, and [OSI2024] is an alphabetical licence index,
  not a definition. Em002-1 already cites [Pe1999] for exactly this content.
  Perens wrote the Open Source Definition and the OSI adopted it, so the
  surrounding prose attributing it to the OSI stays correct, and reads correctly
  in all five languages -- the key is a trailing parenthetical in each.
- Both Em002-1 citations take the italics the house style uses; _[Pe1999]_ sits in
  the same sentence and was already italic. Em002-3's were italic already.
- The footnote:[https://opensource.org/] on the Em002-3 citation stays put. The
  prose still attributes the definition to the OSI, so the link is still right.
- known_unresolved keeps its mechanism as an empty array rather than being
  deleted. The next citation an owner has yet to rule on needs the park and the
  stale-entry guard both, and the comment now carries a worked template instead of
  a TODO.
- Em002-3's _[Pe1999]_footnote:[...] still prints its underscores literally --
  constrained emphasis cannot close against a following word character. That is
  how _[OSI]_footnote: rendered too; left alone here so this stays a key
  substitution.

Verified: tools/check-asciidoc.sh exits 0 over the whole tracked corpus in all
five languages with known_unresolved empty; in a scratch copy it still exits 1 on
a fabricated [Nope2026] and on a stale known_unresolved entry, so neither half of
the mechanism was weakened. compare-census.sh reports every construct delta as
zero in all five languages for both documents. The word census differs by exactly
20 lines, 4 per language: osi2019 -> osi2024, kkb -> kbb, osi 3 -> 2, pe1999 +1.
shellcheck -x -o all and editorconfig-checker pass. Rendered en and fr: em002-1
21 and 21 pages, em002-3 30 and 31, all four unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Em002, Em002-6 and Em002-7 still carried the abbreviation, glossary and reference
lists that 6dec878 extracted into docs/partials/, so the union was written down
twice and none of the drift that commit reconciled had reached a single PDF. All
35 lists -- three documents, five languages -- are now one include of their own
language's partial, selected by tag.

Em002-7 also defined [KKB-MB] and cited [KBB-MB] six times without defining it:
six bracketed labels pointing at nothing, per language, in every shipped PDF. KBB
is Kompetenzzentrum Beschaffungswesen Bund and Annex A's own neighbours are
[KBB-KV] and [KBB-Perimap], so [KKB-MB] was the deviant, and the two were one
information sheet at one URL.

Judgement calls:

- [KBB-MB] survives the fold verbatim. [KKB-MB] had nothing it lacked -- same
  sheet, same URL, written as +url+, which renders as inert text -- so the merge
  is a deletion plus a tag, not a rewrite.
- The surviving entry gains ref-em002-7 where it already stands rather than
  moving, because it sits between [BBL-CL] and [KBB-KV] in the file, which is
  exactly the slot [KKB-MB] held in Annex A. Annex A's order is unchanged.
- [horizontal] stays in the consuming document, on the line above the include. The
  attribute carries across the include and across the leading // tag:: comment,
  and it is the document's layout choice, not the list's.
- One tag per include, never two. tags=a,b is neither an error nor a warning and
  silently means tags=a, so the spelling that cannot be got wrong is the one used
  everywhere.
- Em002's "Visual overview of OSS tools:", document map and "Figure 3:" caption
  stay in Em002. They sit between the two reference sections and belong to neither
  list.
- Romansh Em002-7 grows by one page. Annex A gains two to four rendered lines in
  every language -- the [BBL-WL] and [BBL-CL] descriptions and [KBB-Perimap]'s URL
  that 6dec878 restored -- and only Romansh crosses a page boundary.
- Em002-2 and Em002-3 cite the same sheet and are not touched here; those
  citations are fixed in their own changes.

Verified: 35 of 35 lists replaced, 10 ifeval and 10 endif per document, +3/+2/+2
includes per document in all five languages, 5/5/0 [horizontal] kept, 0 comma tag
separators. tools/check-asciidoc.sh exits 0 over the whole corpus in 5.5s and prek
passes; in a scratch copy it exits 1 on the [KKB-MB] citation put back, and on one
misspelled tag, which it catches twice -- WARNING tag not found and three Annex A
citations losing their entries. compare-census.sh: 09_dlistterm -66/-67/-65/-65/
-65, -32/-32/-33/-33/-33 and -37 uniform, the two DIVERGES lines decomposing
exactly as 19+27+abbr and abbr+12 over the abbreviation lists' own per-language
lengths of 20/21/19/19/19 and 20/20/21/21/21; 18_link -28/-1/-1 uniform;
11_horizontal unchanged. The word census is negative in every language -- 1463/
1419/1542/1498/1532, 598/551/681/640/654 and 1099/989/1217/1140/1204 words out --
and every word with a positive delta is include-directive syntax: all 25 added
lines in Em002 and Em002-6 are includes, and Em002-7's five others differ from
their predecessors by KKB->KBB alone. Extracted from the PDFs, all 35 rendered
lists are identical before and after in all five languages, entry for entry, with
the single intended difference [KKB-MB] -> [KBB-MB]; the English lists still match
the recorded pre-change lists at 19/27/20 and 20/12 and 16/21 entries. Within
Em002-7 alone, [KBB-MB] goes from cited 6 times and undefined to cited 7 times and
defined, in all five languages. Page counts 17/20/27 en, 18/21/28 de, 17/21/29 fr,
17/21/27 it, 17/21/27->28 rm.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Em002-2 names the procurement information sheet three times per language as
++[++KKB-MB++]++ -- the brackets escaped, so it prints as the literal text
[KKB-MB] rather than parsing as anything. That is why nothing caught it: the
citation check in check-asciidoc.sh reads bracketed citations, and an escaped
one is not a citation. With [KKB-MB] folded into [KBB-MB] everywhere else, these
fifteen references would have pointed at a key that no longer exists in any
reference list.

- Only the key changes. The escaping stays, because these are prose mentions of
  a document alongside ++[++BBL-WL++]++, not citations meant to resolve, and
  unescaping them would be a different decision about how Em002-2 refers to the
  rest of the set.
- Em002-2 defines no reference list of its own and cites no literature, so it
  gains nothing from the partials and is not wired to them.

Verified: 15 occurrences to 0, and 15 new ++[++KBB-MB++]++ in their place;
asciidoctor parses all five languages; compare-census.sh reports every construct
delta as zero in all five; the word census differs by one token per language,
kkb 3 -> kbb 3, and nothing else. After this, KKB-MB appears nowhere in docs/.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every rendered document carried a byte-identical 19-attribute header, and seven
of those lines never reached a reader. `:govpress-style: report` makes the tool
apply two attribute files of its own before the document's header -- bundled in
app.asar as styles/common.adoc and styles/report.adoc -- and between them they
already set `govpress-front-block: report`, `title-page`, `pdf-theme: basic`,
`title-logo: logo-ch-black.svg`, `toc`, `sectnums`, `toclevels: 3`,
`sectnumlevels: 3`, `experimental` and more. They are applied softly, so a
document's own header does outrank them; ours simply restated values already in
force. The two files are byte-identical in open-govpress 0.0.12 and 0.0.13.

Judgement calls:

- `:toc:`, `:toclevels:`, `:sectnums:` and `:sectnumlevels:` stay, and gain a
  comment saying why they look redundant but are not. GitHub and GitLab render
  every .adoc in the repository with plain Asciidoctor, which applies none of
  open-govpress's style files: without these four the web view of the source
  loses its contents list and its section numbering. The rule applied throughout
  is drop what no renderer reads, not drop what the PDF does not need.
- `:title-logo-base: black` goes because it restates the default -- with the
  attribute absent the converter's black-or-color normaliser returns black -- and
  `:title-logo-line3:` because it is empty and `:title-logo-line1:` already
  selects the explicit-lockup branch.
- `:experimental:` goes even though the web view would read it: no document uses
  kbd:, btn: or menu:, so it is inert in both pipelines.
- `:classification:` stays, empty, with a comment recording that this is
  deliberate. It renders exactly as omitting the line would; it is kept so a
  document answers the question rather than leaving it unasked.

The cost, recorded because it is real: the documents now depend on the tool
continuing to supply `title-logo` for this type. A future open-govpress that
stopped would drop the coat of arms from the title page silently, rather than
fail.

Verified against the working tree before the change, at open-govpress 0.0.13:

- Plain `asciidoctor --safe -a lang=...` output is byte-identical for all 15
  documents in both en and de -- 30 of 30 renders -- so the GitHub and GitLab
  view does not move.
- Rendering all 15 in en gives the same page count for each (211 pages) and every
  page pixel-identical at 60 dpi except page 1, which differs only in the
  `Version 2.0-<checksum>` string the tool derives from the source bytes and
  which therefore must change. Confirmed per document by diffing pdftotext output
  with that one token masked.
- check-asciidoc.sh passes; de renders with the Kennzeichnung intact.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`:url-repo:` was defined in all 15 documents and referenced by none, while the
repository URL was spelled out literally 70 times across them -- four different
forms of the same address, in five languages, with nothing holding them
together. The attribute is open-govpress's own documented idiom for exactly this
("Your own attributes are allowed and save repetition", showcase.adoc), so it was
already the right tool, just unwired.

All 55 body occurrences now derive from the attribute:

  {url-repo}                     5   Em002, the plain GitHub URL in prose
  {url-repo}/releases            5   README
  {url-repo}/tree/main          40   the README link every document carries
  {url-repo}/tree/main/docs      5   Em002-5

Judgement calls:

- The value becomes the repository root rather than staying at /tree/main. The
  root is what showcase.adoc uses for this attribute, and it is the only value
  all four forms can be built from -- at /tree/main the five /releases links and
  the five bare ones in Em002 would have had to stay literal. The repository
  being a fork makes the owner segment the part most likely to move, which is
  precisely the part now written once.
- The four checklists, index and README_template lose the attribute instead of
  gaining a reference. None of them links to the repository at all, so defining
  it there would recreate in six files the unused-attribute problem this commit
  exists to remove. Two header shapes, each byte-identical within its group: nine
  documents that link to the repository and six that do not.
- Substitution is safe in both macro forms present, `https://...[README]` and
  `link:https://...[README]`, because attributes are substituted before macros
  are parsed.

Verified against the tree before the change, at open-govpress 0.0.13: plain
asciidoctor output byte-identical for all 15 documents in en and de, so every
substituted link resolves to the same string it did before; all 211 rendered
pages pixel-identical except each title page's source checksum; check-asciidoc.sh
passes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Published as open-govpress-v0.0.14 on this fork, the same binary-host
arrangement 0.0.7, 0.0.12 and 0.0.13 use: the electron-builder tar.gz is
~112MiB, over GitHub's 100MB object limit, and a fork may not upload new LFS
objects. All three predecessors stay published -- commits and document tags
still pin them, so deleting any of them would make a historical checkout
unrenderable.

Three things this release fixes for this repo, all of which were wrong in the
published PDFs:

- A wide table no longer runs off the paper. Em002-5's ten-column target-group
  matrix was clipped at the sheet edge in every language; German lost nine
  words, including X and (X) data cells, not merely a truncated heading. Cell
  content can break now, and a new build-time info diagnostic names any table
  that only fits because of it.
- [.landscape] turns the page a single block sits on, without changing the
  document type. Applied to the matrix in its own commit.
- Relative link: targets reach the PDF verbatim instead of serialising as
  app://govpress/em002-2.pdf, the renderer's private origin. That retires 285
  dead annotations per language with no source change -- which is why the
  1,095 .pdf targets here were deliberately never rewritten around the bug.

The version, URL and sha256 are pinned in two places that have to agree. Only
the workflow's URL needed editing; devenv.nix interpolates govpressVersion into
both the tag and the filename.

Verified: the published asset was downloaded from the pinned URL and hashes to
the pinned sha256, so the workflow's `sha256sum -c` will pass. Rendering is
unchanged in shape -- all 15 documents render in en with the same
<basename>.pdf naming.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The matrix sets fourteen documents against nine target groups, and ten columns
do not fit A4 portrait: at 453.5pt of measure a narrow column gets 37.8pt, and
"Geschäftsleitung" renders at 81.1pt. Until 0.0.14 the surplus was simply
painted past the sheet edge -- nine words in German, including X and (X) cells,
so a reader of the published PDF could not tell those columns were there.

0.0.14 stops the clipping on its own by letting cell content break, and says so:
"only fits because its words are broken. [.landscape] gives it a turned page."
This takes the turned page, because a matrix whose headings are broken
mid-syllable is legible only in the sense that the glyphs are on the sheet.

- The attribute goes on the table, not the document. :govpress-style:
  base-form-landscape would turn all eight pages of a prose factsheet sideways
  and would also declare the factsheet a Basisformular, which it is not.
- Landscape costs height: 700.2pt of measure instead of 453.5, but a 595pt sheet
  instead of 842. The matrix now runs across three turned pages, four in English,
  and the document grows from 8 pages to 10, 11 in English. That is the price of
  not breaking the headings.
- Landscape alone would not have been enough. An 81.1pt word still exceeds the
  46.3pt content box a 1/12 column gets even on a turned sheet -- the two fixes
  are complementary, and it is 0.0.14's word breaking that makes the column fit
  at all.

Verified: renders clean in all five languages with no info or warning; portrait
before, 842x595 for the matrix, portrait after; zero glyphs beyond the page
width in any language, against nine in German before. Pages 8->11 en and 8->10
de, fr, it and rm. asciidoctor parses all five; construct census delta zero in
every language and the word census unchanged, an attribute line carrying no
prose.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Published as open-govpress-v0.0.15 on this fork, the same binary-host
arrangement 0.0.7 and 0.0.12 through 0.0.14 use: the electron-builder tar.gz is
~112MiB, over GitHub's 100MB object limit, and a fork may not upload new LFS
objects. All four predecessors stay published -- commits and document tags still
pin them, so deleting any of them would make a historical checkout unrenderable.

0.0.14 introduced [.landscape] with the geometry right and the paint wrong. The
app's backdrop is `:root > body { background: #d0d0d0 }`, scoped that way so it
cannot tint the phantom html/body clones Paged.js inserts into every page after
the first; the print block reset it as plain `body`, and @media adds no
specificity, so 0-0-1 lost to 0-0-2 and the reset never applied. It stayed
invisible for as long as every sheet was portrait, because .pagedjs_page paints
white over the whole of one -- a turned sheet is 297mm wide while that element is
still the document-global 210mm, so 87mm of backdrop reached the paper and
`printBackground: true` wrote it into the PDF.

In this repo that was a grey band down the right third of all four turned pages
of Em002-5, in every language, in the PDFs published from the last push.

No document changed. Em002-5's [.landscape] attributes were already correct, and
the exposure is exactly one release wide: before [.landscape] there was no sheet
wider than the page element.

Verified: the published asset was downloaded from the pinned URL and hashes to
the pinned sha256, so the workflow's `sha256sum -c` will pass. Across the whole
rendered corpus -- every landscape page of all 75 PDFs sampled -- the worst
#d0d0d0 coverage is 0.03%, which is anti-aliasing on borders, against 25% of the
upstream showcase's own turned page under 0.0.14. Zero clipped glyphs, zero
app:// URIs, 1082 pages before and after with no document changing size, the
form fields intact at 25 /Btn in Em002-7 and 87 /Btn plus 15 /Tx across the
checklists, and check-asciidoc.sh exits 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants