I need to update libcna.com after CNA changes
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 point | Where 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 numbers | data/current-facts.json: every value carries the method that recomputes it; external values sit under external_pinned with their own revision |
| The audit record | audit/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 area | scripts/dev_manifest.py (data only: groups, pages, order) |
| Presentation baselines | audit/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
- Pick a settled commit on
next. The record shows how the last pin was chosen (the tip ofnexton 2026-09-24, which was the integration mergemerge(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 publicnextbranch, so the deep links resolve for readers. - Extract a read-only tree with
git archiveto a directory outside/tmp(the ledger placed/rv/tmp/libcna-v2/cna-targetthere because the shared build rules forbid clones under/tmp).third_party/andvendor/are not extracted; read those withgit show <sha>:path. Keep the alpha.1-era base tree:check_retired_renderers.pyderives the retired set from it. - 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.pyholds the module-to-internals-page dictionary, so a new module needs an entry there and indev_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 withgit diff --name-only OLD NEWlists 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.pyandcheck_dev_page.pyfail 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) implementations | cmake/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 asCNA/Version.hppare 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.pyknows two baselines (--phase2selects the second and applies a stricter 10% shrink guard); a further update needs a new baseline inventory generated withscripts/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-docsornew-tutorial(shared navigation, footer and JSON-LD), must be linked from a hub, and the sidebar is synchronised withsite_nav.py sync;scripts/update_tutorial_hub.pyreconciles 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 itsnext).
Step 7: update the Development pages
- Edit the affected fragments.
scripts/site_dev.py build --src DIR <page>renders a page fromDIR/<path minus .html>.body.htmlplus.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--srcnames, 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. - Regenerate the reference pages:
python3 scripts/generate_dev_reference.pyrewrites the five inventories underdevelopment/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. - Sync the chrome:
python3 scripts/site_dev.py syncrewrites sidebar, breadcrumb and pager on every Development page;site_dev.py checkcompares the manifest with the files on disk. - 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) andpython3 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:
| Carrier | What to change |
|---|---|
cnahead | The new SHA plus one newline |
| Footers, header meta and JSON-LD | About 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 links | Development 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.json | Snapshot block, recomputed facts, external pins |
| Script constants | CNA_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 ledger | Repoint the “current authoritative audit” section; start a new ledger for the new pin rather than overwriting the old one |
search-index.json, sitemap.xml | Regenerated, 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:
| Validator | Enforces |
|---|---|
validate_site.py | Every 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.py | Owner-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.py | The 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.py | Development 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 check | The manifest matches the Development pages on disk |
developer_ledger.py check | The absorption ledger's conservation invariant (run only when its units file exists) |
check_retired_renderers.py | No 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 --check | No 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,masterorHEADin 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
/tmpor 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.shreports 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.
Deep dives on this topic
Long-form pages that explain the exact semantics, invariants and evidence behind this subject.
- Project records as evidence: plans, ledgers, versions, handoffs and re-audits — How to use and write CNA's plans, handoffs, audits and gates as evidence: roles, stable task IDs, executable ledgers, version coordinates, defect-ledger rules and documentation re-audits.
Related pages
The same subsystem is explained at four altitudes. These are the neighbouring pages at each one.
- Architecture
- Architecture overview
- Internals
- Generated references
- Maintainer workflow
- Maintaining these pages · Make a release · Known uncertainty and history
- Tests and validation
- Testing
- Reference
- Module index · Source ownership