Maintaining these pages
Evidence basis: source-verified at the pinned commit. Claims on this page were checked by reading the CNA source at commit c1c316b9; unless a sentence says otherwise, nothing here was built or executed. The procedure describes the libcna.com scripts and data files as they exist at the pinned snapshot; they were read, not re-run, for this page.
Documentation freshness is a reproducible human workflow: choose a CNA revision, inspect its diff and the contracts it touches, update the reviewed prose, regenerate the indexes, validate the links and facts, and commit the new revision marker together with the documentation. This page states the pin policy that governs the whole Development area, then the procedure for advancing the pin, what stays hand-reviewed and what is generated, what each checker verifies, and how to preview and review. It describes the libcna.com repository as it is at snapshot c1c316b9; every script named here lives in that repository's scripts directory (its inventories and ledgers in the audit directory), and all were read, not re-run, for this page. The task recipe for a CNA change is Update libcna.com after CNA changes.
Pin policy
- The pin is one file.
cnaheadat the root of the libcna.com repository holds a full 40-character commit and a newline:c1c316b9c7a846ce8002809c151fcd1af14942c9, on CNA'sapple/m4-stabilizationbranch (9 October 2026). It is the documentation snapshot, not a release: CNA's product version string at that commit is still0.1.0-alpha.1, and the tagv0.1.0-alpha.1is only the historical baseline the snapshot is compared with. - Everything factual is tied to that commit. Source links in the Development area are blob or tree links carrying the full hash (never a moving branch) and are checked against the Git tree at that commit; the generated references are computed from that commit's Git objects; the user documentation states the same commit in each page footer; and the canonical numbers (14 renderer identities in 12 families, 3 platforms, 3 audio implementations, the test inventory, the C ABI version) live in
data/current-facts.json, checked against the pages. - CNA moves faster than the pin.
nextand the maintainer's own CNA checkout keep advancing, often on a working branch. When this snapshot was pinned, that checkout was 17 commits past it on asamplesbranch (renderer and effect fixes found by the sample ports), and none of those commits is documented. Pages never describe behaviour from commits after the pin; the update recipe includes a check that no post-pin task identifier or feature leaked into the text. - Readers compare before they trust. Before using a trace for a new patch, compare your branch with the snapshot and re-read the changed paths (the comparison commands), and treat every path and symbol as a navigation hint on a different revision.
- External projects are pinned separately. Bindings, sample projects and the hosted demos are recorded at their own revisions in
data/current-facts.json(external_pinned) and are evidence for those revisions, not for the snapshot. - The pin moves atomically or not at all. A new
cnaheadwithout the matching prose, pinned links, constants and generated pages is a defect; see the procedure below.
Advance the documented revision
- Fetch and identify the intended CNA
nextcommit. In your CNA clone (the site tooling defaults to a sibling directory../cna, overridable withCNA_REPO, and only ever reads its Git objects), record the old and new full hashes and confirm ancestry withgit merge-base --is-ancestor. Decide the new commit deliberately: a batch of integrated work is easier to audit than a moving head, and the whole area is re-verified against it. - Review the diff, grouped by module, build selection, API and lifecycle. Measure it mechanically first, then read it:
Commit subjects carry task identifiers, soOLD=c1c316b9c7a846ce8002809c151fcd1af14942c9 NEW=<the new 40-character commit> git -C ../cna merge-base --is-ancestor $OLD $NEW && echo "ancestor" git -C ../cna rev-list --count $OLD..$NEW git -C ../cna rev-list --count --first-parent $OLD..$NEW git -C ../cna rev-list --count --merges $OLD..$NEW git -C ../cna diff --name-status $OLD $NEW -- cmake CMakePresets.json .github/workflows git -C ../cna diff --stat $OLD $NEW -- modules/runtime modules/platform modules/graphicsgit log --first-parent --format=%s $OLD..$NEWshows the campaigns that landed. The Phase-1 audit derived its workstream table this way: campaigns and merges to the affected pages, using the Phase-1 ledger009d40f5-phase1-delta.mdas the model;c1c316b9-phase4-delta.mdrecords the latest move the same way. For each page the delta touches, rungit diff $OLD $NEW -- <path>for every source path the page links (they are the token links, so the old hash in the page lists them). - Make a read-only tree of the new commit for the check that reads files. Only the snippet compile check uses an extracted worktree of the pinned commit (
CNA_TARGET_TREEforcheck_snippet.sh, created byextract_cna_trees.sh). The retired-renderer scan reads the CNA Git objects at the alpha.1 baseline and at the commit incnahead, so it needs no extracted tree (it still accepts--baseand--target, and ignores an extracted target tree stamped with another commit). Everything else also reads Git objects and never checks out, resets or cleans anything in the CNA repository. - Re-read the affected implementations; never update prose from filenames or commit messages alone. A renamed file proves nothing about behaviour, and a commit subject is a claim. Re-verify each affected claim (paths, symbols, ordering, ownership, counts, test names and registration, CMake defaults) against the new tree, and keep the distinction between source-verified, test-present and observed on every page.
- Write the full 40-character commit to
cnahead, then recomputedata/current-facts.json(identities and families, platform and audio counts, test files and definitions, workflow files, XNA counts, C ABI version, headers and routes, commit statistics) from the new tree, and update the per-area fact sheets and the delta ledger kept in the audit directory that record how each number was derived. - Replace the old hash and the constants that embed it. Hand-written pinned links in the user documentation; the footer sentence and date in
site_nav.py(and the copy baked into every page); theCNA_TARGETconstant invalidate_presentation.py; the snapshot patterns incheck_facts.py; the diagram labels inmake_architecture_diagram.py, then regenerateimg/architecture-diagram.svg; and the date constants insite_dev.pyandbuild_site_indexes.py. A search of the repository for the old hash (outside the audit directory) is the completeness test: every remaining hit must be a deliberate historical reference. - Regenerate, rebuild and re-index.
A built page carries the hash inside its expanded links. Rebuilding from the fragment re-expands every source-link token against the new tree and fails the build for a path that no longer exists; a page edited in place needs the scripted replacement above and then the link checker, which verifies each pinned path.python3 ./scripts/generate_dev_reference.py # the five generated reference pages, from the pinned Git objects python3 ./scripts/site_dev.py build --src <fragments> development/<page>.html # authored pages, tokens re-expanded and re-validated python3 ./scripts/site_dev.py sync # sidebar, breadcrumb and pager regions of every Development page python3 ./scripts/build_site_indexes.py # search-index.json, sitemap.xml and dateModified stamps bash ./scripts/validate_all.sh # every site validator - Inspect the generated diff, then run the checks. Read what the generator changed (counts, options that appeared or vanished, modules added), fix the prose that depended on it, and run the per-page validator on every touched Development page before the full run.
Revision changes are atomic. Do not commit a new cnahead with old prose or old pinned links. The checkers reject mismatched markers: check_source_links.py treats a Development-page link pinned to any commit other than cnahead, or to a moving ref, as an error, and validate_presentation.py requires cnahead to equal the commit its own constant names. Commit the pin, the prose, the generated pages and the indexes together.
Routine maintenance without any agent tooling
Everything below runs with Python 3, Git and a text editor; no AI assistant, no .claude directory and no session state is needed or read. Two clones sit side by side: this repository and CNA (../cna, or set CNA_REPO; it is only read). The page source is the repository itself: authored pages are edited in place, and no page fragments are kept.
# 1. edit the canonical source (authored HTML, or the Known Issues data described below)
./scripts/regenerate_site.sh # 2. regenerate every generated part, in dependency order
./scripts/validate_all.sh # 3. run every validator (overall: PASS, PASS WITH OPTIONAL EXTERNAL CHECKS SKIPPED, or FAIL)
git diff --stat # 4. read what changed, then commit sources and generated files together
./scripts/regenerate_site.sh --check # optional: does the committed HEAD regenerate to itself? (uses a temporary worktree; your tree is untouched)
On a current checkout, regeneration changes nothing: running it and then git status shows a clean tree, and the --check mode reports drift if a generated file was edited by hand or a source was changed without regenerating. The retired-renderer scan reads the CNA Git objects directly, so validate_all.sh always runs it and fails, rather than skips, when no CNA clone is found; read-only extractions of CNA revisions are needed only by the snippet compile check, and ./scripts/extract_cna_trees.sh creates them under ~/.cache/libcna-com (or export CNA_BASE_TREE and CNA_TARGET_TREE). validate_all.sh skips the Phase-2 conservation check (developer_ledger.py) when the retired developer.libcna.com working tree is not next to this repository, and the Bible inclusion-graph check when the Bible repository is absent; every other validator runs from this repository and the CNA Git objects. A skipped stage is listed by name at the end, and the final status then reads overall: PASS WITH OPTIONAL EXTERNAL CHECKS SKIPPED, never a bare PASS; --require-external turns a skip into a failure.
Regeneration order
The order is a dependency chain, not a preference; scripts/regenerate_site.sh carries it with comments.
known_issues.py merge, thenbuild.mergereads the authored inputs inaudit/data/bible/issues/and writesissues-source.jsonanddispositions.json;buildwritesdata/known-issues.jsonand every page underknown-issues/.site_deep.py hubsandsync. Deep Dives and Known Issues hubs, sidebars, breadcrumbs and pagers.generate_dev_reference.py. The five reference pages, rewritten whole from the pinned CNA Git objects. This drops their backlink blocks, so it must come before step 6.site_dev.py syncandsite_nav.py sync. Development and docs sidebars, breadcrumbs and pagers.backlinks.py apply. The marked “Deep dives on this topic” and “Known issues in this area” blocks ondocs/anddevelopment/pages, recomputed from the built pages.issue_refs.py apply. Rewrites each hand-writtenissue:KEYcomment-marked reference to the entry’s current visible id and link. It needs the entries from step 1 and runs after the backlink blocks exist.build_site_indexes.py, last.search-index.jsonandsitemap.xmlfrom the finished pages’ metadata.
apply_expansions.py apply and apply_page_fixes.py apply are not part of the routine. An expansion block is rewritten from its source under audit/data/bible/expansions/, which would revert later page fixes; the validators run their check modes instead.
Editing Known Issues
Two identifiers exist on purpose. The stable key is the entry’s identity that does not move: for the earlier audits the original id (the CNA-BUG-nnn, CNA-GAP-nnn and other numbers those audits allocated, including ones whose entry has since been retired or reclassified), for entries the independent audit added a temporary id NEW-AUDIT-nn. The visible number (CNA-BUG-nnn, CNA-GAP-nnn, CNA-PLAT-nnn, CNA-VGAP-nnn) is generated: ids of the earlier audits never change, later ids are allocated in order and can shift when an entry is reclassified. dispositions.json (adversarial_audit) maps stable key to current id.
- Change an entry, or add, reclassify, fold or retire one: edit the authored inputs (
verified-*.json,patches.json,merge-map.jsonand, for changes after the independent audit,patches-adversarial.json, whose format is documented at the top of its section inscripts/known_issues.py), neverissues-source.json,data/known-issues.jsonor the pages underknown-issues/. Then regenerate and validate. Only issues that exist at the pinned commit are published; there is no “fixed” status, so a fixed issue is retired with evidence. - Refer to an issue from prose: write the link inside an HTML comment pair named
issue:KEYwith the stable key (the exact form is in the docstring ofscripts/issue_refs.py); regeneration rewrites the text and link.issue_refs.py check(insidevalidate_all.sh) rejects an unmarked link to, or mention of, an entry whose number can shift. Inside an issue’s own text the same key is written as anissue:KEYtoken (seescripts/known_issues.py). - Backlinks on other pages come from each issue’s related links through
backlinks.py; nothing is edited by hand.
Edit the source, not the output
Do not edit by hand: known-issues/** and data/known-issues.json; the five pages under development/reference/; the sidebar, breadcrumb and pager regions of Development, Deep Dives, Known Issues and docs pages; the comment-marked p3:deep-links and p3:issue-links blocks; the text inside issue:KEY comment markers; search-index.json and sitemap.xml; img/architecture-diagram.svg (from make_architecture_diagram.py). Authored prose, everything outside those regions and the constants listed in Advance the documented revision are edited directly. The next regeneration overwrites a hand edit of generated output, and regenerate_site.sh --check reports it.
What validation proves
validate_all.sh passing is structural and source-level evidence: HTML and metadata are well formed, links and anchors resolve, source links exist at the pinned commit, canonical numbers match, generated regions are current, and no page lost content without a recorded reason. It does not show that CNA behaves as a page says at run time: a source-verified statement was read, not executed; a named test is registered, not passed; and no validator renders a page. The four levels stay separate: source verification (reading the pinned tree), structural validation (validate_all.sh), runtime or test evidence (only where a page says something was executed) and browser QA (below). The evidence labels are explained in Verification page and each page’s evidence box.
Browser QA
Run it after a change to a page’s markup, CSS or JavaScript, and before publishing a broad change; it is not needed for a text-only fix. Serve the repository and point browser_qa.py (headless Chrome through websocket-client) at it:
python3 -m http.server 8000 &
python3 scripts/browser_qa.py check http://localhost:8000/development/maintenance.html --width 1400 --scheme dark
python3 scripts/browser_qa.py sweep urls.txt --out qa-shots # every URL at 1400 and 390, light and dark
It reports horizontal overflow, header rows, console errors, failed images and the lowest text contrast. Low-contrast findings on inherited earlier-phase elements are known presentation debt, not a regression of the change being checked.
Publishing
The site is static and is published from the repository root by GitHub Pages (CNAME names the domain). Before pushing: regenerate_site.sh, validate_all.sh reporting overall: PASS, a clean git status, and browser QA when markup or styling changed. A new cnahead is committed only together with its prose, pinned links, constants and generated files.
What remains manually reviewed
Architecture, lifecycle, ownership, workflows, invariants, debugging advice, threading statements and every evidence label are authored prose. Generation intentionally does not synthesize architectural explanations: a human must trace the changed symbols, read the current source and decide whether each sentence, each test name and each ordering claim is still true. Two kinds of edit deserve extra care. A statement of absence (“no test asserts”, “no workflow names”) can be invalidated by any new file, so it needs a fresh search at the new commit. A statement of count or default should come from the generated pages or data/current-facts.json, not from memory; the fact checker fails a page whose whole-registry count disagrees with the canonical value.
What is generated
- Module inventory and entry files (Module index), with header, source and test counts and the renderer directories, from
git ls-treeof the pinned commit. - CMake option inventory (CMake option index), from a syntactic scan of every
option(),cmake_dependent_option()andset(... CACHE ...). - Test target and registration inventory (Test target index), from
add_executableandadd_testdeclarations. - Public header inventory (Public header index), the non-Internal headers under
include/CNAandinclude/Microsoft. - Selection axes (Selection axes index), derived from the identity, registry, platform and audio CMake files so the counts used across the site have one mechanical source. The generator refuses to write it when those sources disagree with each other or with the canonical counts.
- Client-side search index and sitemap from the visible metadata of every page (
build_site_indexes.pyreads each page's title, description, canonical URL and keywords), and the page furniture of Development pages (sidebar, breadcrumb, pager, table of contents, evidence box and related-pages block) fromdev_manifest.pyand each page's metadata. - The architecture diagram
img/architecture-diagram.svg, whose labels are facts from the fact sheets (make_architecture_diagram.py).
Generated pages carry a Generated page notice and should be changed through their generator, except when fixing the generator's output. They are syntactic: they locate declarations and files and do not establish behaviour, build success or test results. The repository also keeps records that are not served (the site's _config.yml excludes the audit and scripts directories and plan.md from the published output): plan.md (the audit summary and how to read it), the Phase-1 ledger 009d40f5-phase1-delta.md, the pin-move ledger c1c316b9-phase4-delta.md, the presentation baselines and comparisons, the per-area fact sheets and editorial guides, and developer-absorption-phase2.md, the Phase-2 absorption ledger that records, for each unit of source material folded into the Development area, where its knowledge went and what was verified, corrected or dropped.
Checks performed
validate_all.sh is the single entry point. It runs every validator below and prints overall: PASS, PASS WITH OPTIONAL EXTERNAL CHECKS SKIPPED or FAIL; the retired-renderer scan reads the CNA Git objects and is never skipped. It was read, not run, for this page.
| Script | What it verifies |
|---|---|
test_validators.py | Runs first. Feeds the semantic checks below the exact stale sentences an independent review found on published pages (a retired renderer inside a longer list or a table row, retired platform backends in a source tree or a “native sibling” link, a CI statement the workflows contradict, test-file counts of an earlier snapshot, a ledger token beside a false list), the sentences a second review found after that remediation (a generated sentence that presented the retired Win32, X11 and Wayland values as host-conditional implementations, “EasyGL serves five”, an inverted compiled-effect count, a stale format complement, a test-file count under the wrong name), the sentences a third review found (an 18-enumerator renderer enum with ordinals to 17, “three Direct3D identities”, a maturity and category split of the earlier registry, compiled-effect counts that mixed the capable set with its opt-in part, “seven renderers” for an eight-identity test gate) and the legitimate sentences of the same vocabulary (SDL’s Direct3D 12 or Wayland driver, a Windows host, native handles, Weston and Xvfb, numbers attributed to alpha.1); each must be rejected or accepted as recorded. |
validate_site.py | Parses every HTML file (and with html5lib installed, a strict HTML5 parse of every page), rejects duplicate ids, checks JSON-LD blocks, requires a title, description, canonical URL matching the file path and og:title and og:description, resolves every local link and #fragment, and checks that search-index.json and sitemap.xml contain every public page exactly once (only 404.html and search.html are excluded). |
validate_presentation.py | Structural checks on owner-designated content: cnahead equals its constant plus one newline, the homepage Quick Stats, the showcase's real-XNA verification section, the Speedy Blupi section and its primary call to action, and that no baseline primary call to action was lost or downgraded without a recorded disposition. |
compare_presentation.py and --phase2, --phase3, --phase4 | Compares the working tree's presentation inventory (sections, calls to action, links, cards, images, stats, tables) with a recorded baseline and fails on an unexplained loss; the Phase-2 mode compares against the inventory taken before the Development area and uses a stricter size guard (a reduction over ten per cent of a page or block), and the Phase-3 and Phase-4 modes apply the same engine to the inventories taken before the later updates, each with its own dispositions file under audit/data/. |
check_facts.py | Recomputes the renderer identity, family and GL-profile counts, the platform and audio implementations, the three test-inventory figures, the C ABI version, the route count and the derived values listed below from the CNA Git objects and fails when data/current-facts.json disagrees; then checks that those numbers appear on the pages that must carry them, that superseded numbers are not presented as current, and that a whole-registry phrasing of them on any page (Known Issues included, a number attributed to an earlier revision in its own clause excepted) equals the derived value, including impossible combinations such as more files with a counted macro than test files. It also derives the compiled-effect sets (identities and families that can run compiled effects and, separately, the part behind default-OFF options; the identities that never can; those a default configuration refuses), the families that classify texture and render-target formats, the GraphicsRendererType enumerators and ordinals, every identity’s declared maturity and category, the Direct3D, browser-only and non-web EasyGL subsets, the identities the unbound-texture test gate pins, and the examples/ file counts, and judges the subset and complement counts the site repeats by what each sentence claims: EasyGL identities, compiled-effect capability versus its opt-in part versus default refusal (identities, families and options counted separately), the identities outside the format classifiers, maturity and category totals, files named *_test.cpp versus all example sources, and any “other N” larger than the whole. These rules recognise named concepts in the phrasings the site uses; they do not parse English, so a number outside those concepts, or a sentence no rule recognises, is not checked, and a passing run does not show that every number on the site is current. It rejects an evidence callout that says “at the pinned commit” while naming an older commit, and C release-gate counts written as bare “unmapped symbols” or missing exports. |
check_source_links.py | Reads Git objects. In the Development area every link into the CNA repository must be a blob or tree link pinned to cnahead whose path exists at that commit (or a commit link reachable from it); a moving ref or another commit is an error. Elsewhere TARGET-pinned links are validated and moving refs are counted. Links into pinned binding repositories are validated against the local sibling checkout when it exists. |
site_dev.py check | The manifest in dev_manifest.py against the disk: every planned Development page exists and no Development page is missing from the manifest. |
developer_ledger.py check | Conservation of absorbed knowledge during the Phase-2 absorption: every unit has an action, every destination page exists, every source heading is mapped and every mapped destination anchor exists, cited source paths are carried or explained, and page size and code tokens are not silently thinned. |
check_retired_renderers.py | Fails if public content names a renderer identity CNA has retired. The set is derived at run time from the CNA registries, read from the Git objects at the alpha.1 baseline and at cnahead (no extracted tree is needed), and is never stored in the repository. It matches every spelling of a retired identity: the CMake name, its module-directory and prose forms (with a hyphen, a space or no separator) and a CamelCase prefix of a class name, while a lower-case continuation such as gdi32 is not a match. A semantic pass then reads every page's visible text and rejects a retired prose name that a list puts beside current renderers, that a renderer/family/identity clause, the previous sentence or a list’s heading introduces, that is the subject of a renderer/family predicate, or that fills a table cell in a renderer column; SDL’s own Direct3D 12 driver and other API or driver nouns stay legal. |
check_retired_platforms.py | Derives the retired CNA platform values from cmake/PlatformSelection.cmake at cnahead and rejects their selection, compile definitions, classes and source directories, a list that puts them beside SDL3, HEADLESS or TERMINAL (inline, one per list item, or in a platform-implementation table), a sentence about CNA’s platform implementations that names one of them, a “native sibling”, and a CI statement that contradicts the window systems platform-ci.yml at cnahead runs SDL3 on. SDL’s video drivers, host operating systems, native handles, Xvfb and Weston stay legal. |
tombstones.py check | The noindex transition pages kept at the URLs of retired pages are exactly what tombstones.py build writes, point at current pages, are linked from nowhere and are absent from the sitemap and the search index. |
git diff --check | Whitespace errors in the change. |
Two more tools run per page or per change rather than in validate_all.sh. check_dev_page.py <page> validates one Development page while it is written (strict HTML5, ids, JSON-LD, metadata, that every local link and fragment resolves with links to manifest pages not yet built reported as pending, that every CNA link is pinned to the snapshot and exists there, no retired identity, alpha.1 only as history, no first-person voice and no vocabulary of the earlier standalone manual). check_snippet.sh syntax-checks a C++ snippet against the pinned headers with g++ -std=c++23 -fsyntax-only and writes nothing. browser_qa.py (headless Chrome over the DevTools protocol, pointed at a running static server) reports horizontal overflow, header rows, console errors, failed images and the lowest text contrast per URL, viewport and colour scheme.
Local preview
cd libcna.com
bash ./scripts/validate_all.sh
python3 -m http.server 8000
# open http://localhost:8000
No compilation and no package manager are involved: the site is static HTML, CSS and a little JavaScript, published from the repository root by GitHub Pages. The pages use relative links, so most of the site also renders from file://, but a local server matches how the pages are served and is what browser_qa.py expects. The validators need Python 3 with beautifulsoup4 (the fact and presentation checks) and, for strict parsing, html5lib; the extracted TARGET tree is needed only for the retired-renderer scan and the snippet check.
Review checklist
- Can a newcomer find the owner of each changed subsystem, from the change map and the module index?
- Do all capability and completeness claims have code plus test evidence, and is each evidence label (source-verified, tests present, and any recorded-by-CNA level) the right one? “The implementation exists” is not “the behaviour is proven”, and a named test is registered, not passed.
- Are source links pinned to
cnaheadand alive, with no line-number anchors? - Do the commands exist in the current presets and scripts at the pin?
- Are known gaps stated, scoped and evidenced, not disguised as planned features and not copied from a stale note?
- Does every number come from
data/current-facts.jsonor a generated page, and isalpha.1used only as history? - Does the text name no retired renderer identity in any spelling, and does it keep to the snapshot framing (a development snapshot on a development branch, not a release)?
- Does the site remain useful with JavaScript disabled, and does the page hold at phone width?
Related: Generated references, Getting started as a maintainer and the Maintainer Handbook.
Related pages
The same subsystem is explained at four altitudes. These are the neighbouring pages at each one.
- Architecture
- Architecture overview
- Internals
- Repository map
- Maintainer workflow
- Update libcna.com after CNA changes · Getting started as a maintainer · Maintainer Handbook
- Tests and validation
- Verification and known issues
- Reference
- Generated references · Module index · CMake option index