I need to update libcna.com after CNA changes

CNA snapshot 009d40f5  ·  Development › Maintainer Handbook  ·  source links pinned to 009d40f5

✓

Evidence basis: source-verified at the pinned commit. Claims on this page were checked by reading the CNA source at commit 009d40f5; unless a sentence says otherwise, nothing here was built or executed. Read from this repository's scripts, data files, audit records and the previous update's ledger; the scripts were read but not run, and the commands shown for computing the delta are illustrative.

libcna.com documents one immutable CNA snapshot, not a moving branch. When CNA moves, the site is updated by advancing that snapshot through a fixed sequence: choose the commit, compute the delta, map it to pages, recompute every canonical number, re-read the changed implementations, update the user pages and the Development pages, move the pin everywhere at once, regenerate the derived files and run the validators. This page is that sequence for a human maintainer, grounded in this repository's own scripts and records. It describes the procedure that produced the current snapshot (009d40f5); the scripts named here were read but not run for this page, and the repository does not automate the reading of source, which remains the maintainer's job. The pin policy itself is on Maintaining these pages.

⚠

Never update prose from filenames or commit messages. A commit subject says what its author intended, not what the code does at the new commit. Every sentence about behaviour, ordering, ownership, defaults or counts must be re-derived from the source at the new revision, or left on the old pin until it is.

What is pinned, and where

Fixed pointWhere it lives
The documented CNA commit (currently 009d40f5dd085c4e674d3479675fac84b12b3e0a, branch next, 2026-09-24)cnahead: exactly the 40-hex SHA and one newline, read by site_dev.py, check_source_links.py, validate_presentation.py and others
Canonical numbersdata/current-facts.json: every value carries the method that recomputes it; external values sit under external_pinned with their own revision
The audit recordaudit/009d40f5-phase1-delta.md (source boundary, mechanical delta, workstream-to-page map, validation evidence, post-TARGET check), audit/data/facts/ (per-area fact sheets and the editorial guide) and audit/data/phase2-editorial-guide.md. Nothing under audit/ is served as site content
The page set of the Development areascripts/dev_manifest.py (data only: groups, pages, order)
Presentation baselinesaudit/data/phase1-baseline-inventory.json and phase2-baseline-inventory.json with their dispositions files

Two revisions matter and must not be confused. TARGET is the CNA commit the pages describe. The snapshot is not the product release: CNA_VERSION_STRING is still 0.1.0-alpha.1, the GitHub default branch still points at the tag commit, and the working checkout may be past TARGET (the ledger records 7 later commits that were deliberately not documented). External projects are pinned separately, each to its own revision.

Step 1: choose the new CNA commit

  1. Pick a settled commit on next. The record shows how the last pin was chosen (the tip of next on 2026-09-24, which was the integration merge merge(OpenGL4ModernGraphics)) but states no selection rule; prefer a commit on the integration line over a branch tip that others are still committing to. Confirm the old pin is its ancestor (git merge-base --is-ancestor) and that it is reachable from the public next branch, so the deep links resolve for readers.
  2. Extract a read-only tree with git archive to a directory outside /tmp (the ledger placed /rv/tmp/libcna-v2/cna-target there because the shared build rules forbid clones under /tmp). third_party/ and vendor/ are not extracted; read those with git show <sha>:path. Keep the alpha.1-era base tree: check_retired_renderers.py derives the retired set from it.
  3. Never read the working checkout. Every scripted read uses Git objects or the extracted tree, and CNA is never checked out, reset or written.

Step 2: compute the delta

The ledger records these metrics for BASE to TARGET; the commands below compute them and are not scripted in the repository (not executed here). Run them against Git objects with the old pin as OLD:

git -C cna rev-list --count OLD..NEW                # commits
git -C cna rev-list --count --first-parent OLD..NEW # first-parent commits
git -C cna rev-list --count --merges OLD..NEW       # merges
git -C cna diff --shortstat OLD NEW                 # files, insertions, deletions
git -C cna diff --name-status OLD NEW               # A / M / D / R per path
git -C cna diff --name-only OLD NEW | awk -F/ '{print $1"/"$2}' | sort | uniq -c | sort -rn | head -30

Then classify: new or removed modules and renderer families, changed public headers, CMake and preset changes, workflow changes, new tools, the product version string (CMakeLists.txt) and the C ABI version (abi.h). The Task-ID campaigns in commit subjects (the ledger's histogram) help locate change areas, and the top-level integration merges bound them; they do not decide facts. Also run the post-TARGET check the ledger ends with: git log NEW..HEAD in the checkout, and confirm nothing from it leaked into any page.

Step 3: map changed paths to pages

  • The module index and Source ownership say which module and which Development page own a changed path; scripts/generate_dev_reference.py holds the module-to-internals-page dictionary, so a new module needs an entry there and in dev_manifest.py.
  • Every source citation in a Development page is a token link (a source-file or directory token in the fragment), so it appears in the built HTML as github.com/libcna/cna/blob|tree/<sha>/<path>. Intersecting those paths with git diff --name-only OLD NEW lists the pages that cite a changed file, and a file that is byte-identical between the two revisions cannot invalidate a claim that depends only on it. No script does this intersection; write it, or grep. An unchanged file still deserves a spot-check of any load-bearing claim, because the claim may always have been wrong.
  • Phase-1 user pages cite less precisely; use the ledger's workstream table (workstream to updated pages) as the starting map and the fact sheets for the area.
  • Removed and renamed paths. check_dev_claims.py and check_dev_page.py fail on a path absent at TARGET; renames need the page prose, not just the link, corrected.

Step 4: recompute every canonical fact

Recompute each entry of data/current-facts.json with its recorded method against the new tree and compare; do not carry a value forward. The recorded methods:

Fact (value at TARGET)Recorded method
Renderer identities (25), families (21), 2D-only (7)The public list in cmake/RendererIdentities.cmake, cross-checked against the C++ enum, the registry map and the C ABI table by scripts/check_renderer_identities.py; families are directories under modules/renderers that own a src/; 2D-only by ThreeD false in code
GraphicsCapability members (19)modules/graphics/include/CNA/GraphicsCapability.hpp
Compiled-effect identities (14 in 10 families)FNA3D always on plus the nine default-OFF *_COMPILED_EFFECTS options
Platform (7) and audio (4) implementationscmake/PlatformSelection.cmake and cmake/AudioPlatformSelection.cmake value lists
C++ test source files (904, 871 with a counted macro) and static definitions (12,610)rg --files -g '*.cpp' -g '!third_party/**' filtered to paths with a test or tests segment, and rg -n '^\s*(TEST|TEST_F|TEST_P|TYPED_TEST|TYPED_TEST_P)\s*\(' over *.cpp outside third_party; the same method reproduces the earlier revision's 568 and 8,263 exactly, which is what makes the two comparable
CI workflow files (20; 28 jobs)The files under .github/workflows
XNA types (331) and members (3,627), oracle scenes (39), XNB readers (61)tools/audit_xna_runtime_surface.py census (representation only); tools/xna-oracle/scenes/*.scene non-recursive; the content fact sheet
C ABI (0.29.0), headers (61), routes (4,055)abi.h; ls modules/c-api/include/CNA/C; the declared route names equal the recorded export baseline
Pinned external values (cna-samples 153/87/40, cna-examples, demos, bindings' ABI 0.21.x and revisions)Read from each repository at a recorded revision; audit/data/facts/08-ecosystem-external.md lists the commands (for example row counts of the sample plan's inventory table). They are evidence for that revision, never TARGET facts

Update the facts file with the new values (and its snapshot block: SHA, date, commits since the tag), then the places that state them. scripts/check_facts.py holds a regex list keyed to specific pages and hard-coded drift constants (for instance the production-module count and the pinned-sample string), so those constants are edited in the same change. Do not write a whole-registry count into prose without a gate: the identity and family counts are held on every page, and a number with no owner goes stale.

Step 5: re-read the affected implementations

  • Follow the editorial rules in audit/data/facts/00-editorial-guide.md (snapshot framing, canonical numbers, the retired-renderer rule, truthfulness) and, for Development pages, the Development editorial guide. Distinguish representation from behaviour, “builds” from “passes” from “verified”, and CNA-recorded evidence from anything the site ran; say “not executed” where it matters.
  • Consumer projects (samples, bindings, demos, Speedy Blupi) are evidence for their own pinned revision. Never write “latest binding plus latest CNA works”.
  • Check every code example against the new headers with scripts/check_snippet.sh (syntax-only compile against the extracted tree; generated headers such as CNA/Version.hpp are unavailable, so snippets that include them cannot be checked this way).
  • Record genuine mistakes found on already-published pages in the delta ledger, with evidence, instead of silently rewriting history.

Step 6: update the user pages

  • Preserve human value. Update the numbers inside existing sections; do not remove substantial sections, downgrade a primary call to action, replace a specific demo URL by a homepage, or shrink a protected page. The protected pages are the homepage, demos, showcase, videos, features, about, documentation, tutorials, architecture, roadmap, network and contact.
  • Compare against a baseline. scripts/compare_presentation.py knows two baselines (--phase2 selects the second and applies a stricter 10% shrink guard); a further update needs a new baseline inventory generated with scripts/inventory_presentation.py --rev <git-rev> --out … before any edit, and a mode or constant that points the comparison at it. Anything flagged is restored or dispositioned with a reason; the baseline and scripts are never edited to silence it.
  • New pages are generated with scripts/site_nav.py new-docs or new-tutorial (shared navigation, footer and JSON-LD), must be linked from a hub, and the sidebar is synchronised with site_nav.py sync; scripts/update_tutorial_hub.py reconciles the tutorial hub with the files on disk.
  • Framing. Say “this snapshot” for anything true at TARGET, use alpha.1 only for history, never write a version the source does not carry, and keep clone instructions on next (sharp-runtime also on its next).

Step 7: update the Development pages

  1. Edit the affected fragments. scripts/site_dev.py build --src DIR <page> renders a page from DIR/<path minus .html>.body.html plus .meta.json, expands its source-file, directory and page-link tokens and fails the build if a source path is absent at the pinned commit or a hand-written CNA link is not pinned to it. Source links are never edited as raw URLs and carry no line numbers. The repository at this revision does not contain a fragment directory: the input is whatever --src names, so decide and record where the fragments live before relying on a rebuild; otherwise the built HTML is edited in place, and only the sidebar, breadcrumb and pager regions (<!--dev:...--> markers) are machine-owned.
  2. Regenerate the reference pages: python3 scripts/generate_dev_reference.py rewrites the five inventories under development/reference/ (modules, CMake options, test targets, public headers, selection axes) from Git objects. They are syntactic (declarations and file shapes); they locate things and prove no behaviour.
  3. Sync the chrome: python3 scripts/site_dev.py sync rewrites sidebar, breadcrumb and pager on every Development page; site_dev.py check compares the manifest with the files on disk.
  4. Per-page checks: python3 scripts/check_dev_page.py <page> (strict HTML5, ids, links and fragments, pinned source links, no retired renderer, no bare alpha.1 as current state, no first-person voice) and python3 scripts/check_dev_claims.py (repository paths, preset names, CNA_* options and code identifiers cross-checked against the TARGET tree; errors are missing paths or presets, warnings are unknown names to review).

Step 8: move the pin atomically

The pin moves in one change with the prose it justifies. A new cnahead with old prose or old links is wrong, and validators reject the mismatch. Everything that carries the pin or the date:

CarrierWhat to change
cnaheadThe new SHA plus one newline
Footers, header meta and JSON-LDAbout 300 HTML pages carry a footer of the form “Documentation snapshot for CNA commit … (date), a post-alpha.1 development snapshot”, and dateModified values; regenerate or replace deliberately, and only after the page's content was audited (the index builder's own comment says a page regenerated without a content audit must be removed from the blanket date rule deliberately)
Source linksDevelopment pages get them from tokens; Phase-1 pages hold hand-written blob/<sha>/… links whose paths must exist at the new commit (check_source_links.py reports paths absent at TARGET and, on deep pages, moving refs)
data/current-facts.jsonSnapshot block, recomputed facts, external pins
Script constantsCNA_TARGET in validate_presentation.py; TODAY in site_dev.py (JSON-LD and page dates); UPDATED in build_site_indexes.py (search and sitemap dates); the constants in check_facts.py; the default extracted-tree paths in check_dev_page.py, check_dev_claims.py, check_retired_renderers.py, dev_pack.py and check_snippet.sh (or export CNA_TARGET_TREE where the script honours it)
plan.md and the audit ledgerRepoint the “current authoritative audit” section; start a new ledger for the new pin rather than overwriting the old one
search-index.json, sitemap.xmlRegenerated, never edited

Step 9 and 10: regenerate, then validate

python3 scripts/generate_dev_reference.py
python3 scripts/site_dev.py sync
python3 scripts/build_site_indexes.py
bash scripts/validate_all.sh

validate_all.sh runs every validator and prints an overall verdict; a failed validator does not stop the rest:

ValidatorEnforces
validate_site.pyEvery HTML file parses (strict HTML5 where available), no duplicate ids, JSON-LD parses, title, description, canonical and Open Graph tags, every local link and fragment resolves, and search-index.json and sitemap.xml match the pages exactly. It does not check external URLs
validate_presentation.pyOwner-designated content: cnahead equals the pin, the homepage Quick Stats, the real-XNA verification block, the Speedy Blupi section and its primary call to action, and every baseline primary CTA
compare_presentation.py (Phase-1 and --phase2)No baseline heading, CTA, link, card, image, table or block is lost without a recorded disposition; the second mode also guards against shrinkage
check_facts.pyThe canonical numbers appear on the pages that must carry them; alpha.1-era counts (50 and 46, 8,263, 21 workflow files) do not appear as current; whole-registry counts on any page match the facts
check_source_links.pyDevelopment links are pinned to TARGET and their paths exist; a moving ref there is an error; binding-repository links at a 40-hex commit exist in the local sibling checkout
site_dev.py checkThe manifest matches the Development pages on disk
developer_ledger.py checkThe absorption ledger's conservation invariant (run only when its units file exists)
check_retired_renderers.pyNo retired renderer identity in public content; the set is derived from the CNA registries at run time and stored nowhere in the repository; skipped if the extracted tree is absent
git diff --checkNo whitespace errors

Browser QA

Serve the site with python3 -m http.server and point scripts/browser_qa.py at it (it drives headless Chrome over the DevTools protocol and needs websocket-client): shot for one screenshot, check for horizontal overflow, header rows, console errors, failed images and lowest text contrast, and sweep for every listed URL at 1400 and 390 px in light and dark. The Phase-1 pass found 103 of 173 pages overflowing at phone width before the layout fix, so run the sweep on any page whose tables, code or headings changed. Automated checks and screenshots do not replace reading the changed pages; check both colour schemes.

Commit discipline and what not to do

  • Commit by workstream with the pin change and its prose together, as the ledger's checkpoint table records (nothing pushed until the owner decides). Keep generated files (indexes, reference pages) in the same commit as their inputs.
  • No post-target facts. Anything from commits after the pin, or from a checkout that moved, stays out; repeat the post-TARGET check before finishing.
  • No retired-renderer names anywhere public, not even as “removed”; a name outside the 25 is a configure-time error and may be said so without naming names. Do not build a retired-renderer catalogue.
  • No unpinned deep links. Do not use next, develop, master or HEAD in a deep page, and do not link the tag when the snapshot is meant.
  • Do not import another site's status labels as product status, do not silence a validator by editing the baseline or the script, and do not present a moving external number as a TARGET fact.
  • Do not build in /tmp or the session scratchpad; the extracted tree is read-only and lives outside it.

Review checklist

  • The delta was computed from Git objects, and every changed path that a page cites was re-read at the new commit.
  • Every canonical fact was recomputed with its recorded method; the facts file, the gates and the pages agree.
  • The pin, footers, dates, script constants and links moved together; validate_all.sh reports an overall PASS; the changed pages were viewed in a browser.
  • A new ledger records the source boundary, the delta, the page map, the validation evidence and anything left hedged.

Related: Make a release, Known uncertainty and history, Generated references and the Maintainer Handbook.

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

Tests and validation
Testing