Maintaining these pages

CNA snapshot c1c316b9  ·  Development › Maintenance & Releases  ·  source links pinned to c1c316b9

✓

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. cnahead at the root of the libcna.com repository holds a full 40-character commit and a newline: c1c316b9c7a846ce8002809c151fcd1af14942c9, on CNA's apple/m4-stabilization branch (9 October 2026). It is the documentation snapshot, not a release: CNA's product version string at that commit is still 0.1.0-alpha.1, and the tag v0.1.0-alpha.1 is 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. next and 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 a samples branch (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 cnahead without the matching prose, pinned links, constants and generated pages is a defect; see the procedure below.

Advance the documented revision

  1. Fetch and identify the intended CNA next commit. In your CNA clone (the site tooling defaults to a sibling directory ../cna, overridable with CNA_REPO, and only ever reads its Git objects), record the old and new full hashes and confirm ancestry with git 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.
  2. Review the diff, grouped by module, build selection, API and lifecycle. Measure it mechanically first, then read it:
    OLD=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/graphics
    Commit subjects carry task identifiers, so git log --first-parent --format=%s $OLD..$NEW shows 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 ledger 009d40f5-phase1-delta.md as the model; c1c316b9-phase4-delta.md records the latest move the same way. For each page the delta touches, run git 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).
  3. 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_TREE for check_snippet.sh, created by extract_cna_trees.sh). The retired-renderer scan reads the CNA Git objects at the alpha.1 baseline and at the commit in cnahead, so it needs no extracted tree (it still accepts --base and --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.
  4. 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.
  5. Write the full 40-character commit to cnahead, then recompute data/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.
  6. 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); the CNA_TARGET constant in validate_presentation.py; the snapshot patterns in check_facts.py; the diagram labels in make_architecture_diagram.py, then regenerate img/architecture-diagram.svg; and the date constants in site_dev.py and build_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.
  7. Regenerate, rebuild and re-index.
    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
    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.
  8. 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.

  1. known_issues.py merge, then build. merge reads the authored inputs in audit/data/bible/issues/ and writes issues-source.json and dispositions.json; build writes data/known-issues.json and every page under known-issues/.
  2. site_deep.py hubs and sync. Deep Dives and Known Issues hubs, sidebars, breadcrumbs and pagers.
  3. 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.
  4. site_dev.py sync and site_nav.py sync. Development and docs sidebars, breadcrumbs and pagers.
  5. backlinks.py apply. The marked “Deep dives on this topic” and “Known issues in this area” blocks on docs/ and development/ pages, recomputed from the built pages.
  6. issue_refs.py apply. Rewrites each hand-written issue:KEY comment-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.
  7. build_site_indexes.py, last. search-index.json and sitemap.xml from 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.json and, for changes after the independent audit, patches-adversarial.json, whose format is documented at the top of its section in scripts/known_issues.py), never issues-source.json, data/known-issues.json or the pages under known-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:KEY with the stable key (the exact form is in the docstring of scripts/issue_refs.py); regeneration rewrites the text and link. issue_refs.py check (inside validate_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 an issue:KEY token (see scripts/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-tree of the pinned commit.
  • CMake option inventory (CMake option index), from a syntactic scan of every option(), cmake_dependent_option() and set(... CACHE ...).
  • Test target and registration inventory (Test target index), from add_executable and add_test declarations.
  • Public header inventory (Public header index), the non-Internal headers under include/CNA and include/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.py reads 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) from dev_manifest.py and 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.

ScriptWhat it verifies
test_validators.pyRuns 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.pyParses 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.pyStructural 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, --phase4Compares 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.pyRecomputes 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.pyReads 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 checkThe 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 checkConservation 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.pyFails 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.pyDerives 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 checkThe 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 --checkWhitespace 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 cnahead and 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.json or a generated page, and is alpha.1 used 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.

The same subsystem is explained at four altitudes. These are the neighbouring pages at each one.

Tests and validation
Verification and known issues