Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
c9f2dfe
WCAG 2.2 AA accessibility improvements
KubaO Sep 16, 2026
3db9794
Fix WCAG 1.4.3 contrast in both themes; unblind the a11y checker
KubaO Sep 16, 2026
0813181
Raise text contrast to WCAG AAA (1.4.6) in both themes
KubaO Sep 16, 2026
bc35524
Highlighter: tag every variable in a Dim/Const/ReDim list, not just t…
KubaO Sep 16, 2026
26fa4cc
Fix light-mode code font-size: emit tb-code-overrides after JTD defaults
KubaO Sep 16, 2026
cd8035e
a11y plan: capture shipped phases; add Phase 5 theme-toggle design + B19
KubaO Sep 16, 2026
cd5a3f3
Phase 5: 3-state (system/light/dark) theme toggle with no-JS progress…
KubaO Sep 16, 2026
56c5570
a11y Phase 3 polish: SVG-control target size + rule guard, chapter->s…
KubaO Sep 16, 2026
4019a87
docs: deprecate h1->h3 heading house style; template + guidance use #…
KubaO Sep 16, 2026
086e42f
docs: document a11y checker + theme toggle; add contributor authoring…
KubaO Sep 16, 2026
78566d1
a11y check: block search index during scan (27s -> 9s, identical resu…
KubaO Sep 16, 2026
dc1e02f
Update the parallel probe for the current paged.js location.
KubaO Sep 16, 2026
b57af1f
perf: restore probe-parallel's --disable-gpu pair; add browser-topolo…
KubaO Sep 16, 2026
780c77a
docs: vendor remote images; check.bat balks on off-box <img>
KubaO Sep 16, 2026
2d6aa2d
builder: fix offline write race on project-owned theme assets
KubaO Sep 16, 2026
efe4aaf
docs: replace YouTube embeds with vendored thumbnails linking offsite
KubaO Sep 16, 2026
8706926
builder: vendor remote assets at build time; CI never downloads
KubaO Sep 16, 2026
940d91f
ci: enforce asset vendoring - no downloads, remote-asset check on bot…
KubaO Sep 16, 2026
395e023
ci: make PR checks actually fire - target staging, drop path filter, …
KubaO Sep 16, 2026
7f87b9a
ci: run check_a11y.mjs in both workflows; sandbox flags for Linux run…
KubaO Sep 16, 2026
abd8ec2
Plan: axe-core performance evaluation (post source-review)
KubaO Sep 18, 2026
9b9e6a9
a11y check: close WCAG 2.1 tag gap, re-admit heading-order
KubaO Sep 18, 2026
74b3395
deps: pin axe-core to 4.13.0; plan notes the pin
KubaO Sep 18, 2026
656b1f5
perf: quote empty-string args in pin-cpu's /affinity relaunch
KubaO Sep 18, 2026
a0614a5
a11y perf: Phase 0 harness -- shared scan module, fingerprint gate, a…
KubaO Sep 18, 2026
9c722f1
a11y perf: Phase 1 attribution -- DOM-op probe, scaling curve; audit …
KubaO Sep 18, 2026
0f1a604
a11y perf: Phase 2 mechanism -- super-linear term is color-contrast's…
KubaO Sep 18, 2026
16ad61a
a11y perf: Phase 3 decision -- every gated lever is within noise; tak…
KubaO Sep 18, 2026
6a21217
a11y perf: plain-color-fields patch -- Color2 without WeakMap-emulate…
KubaO Sep 18, 2026
ca7db4e
a11y: adopt plain-color-fields in production; gate it in check.bat an…
KubaO Sep 18, 2026
f1397ec
gitignore: temporary root HANDOFF.md
KubaO Sep 18, 2026
3aa4d8c
a11y: full-site sweep tool, and derive the scan's page sample from co…
KubaO Sep 18, 2026
e3d767e
a11y: fix the six violation classes the full-site sweep found across …
KubaO Sep 18, 2026
c67abe4
a11y: widen the scan to 11 pages and gate the sample's construct cove…
KubaO Sep 18, 2026
76abd6c
perf: ab-css reads tb-highlight.css, not the rouge.css the Shiki migr…
KubaO Sep 18, 2026
7801a75
a11y perf: retract 'config-only is within noise' -- it is -10.7% on t…
KubaO Sep 18, 2026
c612726
plan: fold the link checker into the build's task graph, with axe/sam…
KubaO Sep 18, 2026
1036619
render: unwrap the paragraph around an inlined SVG -- a <div> cannot …
KubaO Sep 19, 2026
8cbecca
checks: fold the link checker into the build and make --check-html ac…
KubaO Sep 19, 2026
f177c28
scheduler: give renderJoin an expected list -- a zero dep count did n…
KubaO Sep 19, 2026
b97c75f
ci: build with --check instead of a second pass over the built trees
KubaO Sep 19, 2026
33624df
build: refuse to continue when a chunk's result is missing, everywher…
KubaO Sep 19, 2026
f52991b
docs: add footgun to the replace table and reword the three existing …
KubaO Sep 19, 2026
386d68d
a11y: give the heading-link hit box margin -- 3px cleared 24px on Seg…
KubaO Sep 19, 2026
106da80
a11y: inset the theme toggle's focus ring -- the aux nav's scroll con…
KubaO Sep 19, 2026
c672203
a11y: take the heading permalink icon out of the AT tree and the tab …
KubaO Sep 19, 2026
34ba350
a11y: give every page a collapsed section-links disclosure
KubaO Sep 19, 2026
e045ab5
style: fold the page bottom into one rule-divided block
KubaO Sep 19, 2026
9dd8fdb
style: align the footer row baselines, and stop styling items by element
KubaO Sep 19, 2026
3b927e5
style: make each footer row a single element type
KubaO Sep 19, 2026
1b6922b
a11y: audit the section-links disclosure open, not just closed
KubaO Sep 19, 2026
45e6336
Review a11y work thus far.
KubaO Sep 19, 2026
90dd617
a11y: assert exact substitution counts on the Color2 get/set patches
KubaO Sep 19, 2026
3187638
a11y: route the production scan through the scheme registry
KubaO Sep 19, 2026
f4cd53c
a11y: key the callout family on the admonition class
KubaO Sep 19, 2026
39e03ab
check: key brokenUnique on the pre-resolution target
KubaO Sep 19, 2026
7df8625
check: honour sitemap:false and search_exclude:true
KubaO Sep 19, 2026
d2c3a74
check: say why a cross-file check was skipped
KubaO Sep 19, 2026
1c2f895
build: audit the tree index on every build; gate check.bat on freshness
KubaO Sep 19, 2026
c0d5ff9
check: make the standalone link checker case-sensitive on Windows
KubaO Sep 19, 2026
f9ef5a4
build: make the three surviving merge-path tolerances loud
KubaO Sep 19, 2026
8c93e02
offline: stop counting the six phantom unresolved links
KubaO Sep 19, 2026
a7a040f
build: check the Gantt chart's bytes, not the ones it replaced
KubaO Sep 19, 2026
648b607
scheduler: put submit inside the abort path, and bind the barrier halves
KubaO Sep 19, 2026
040aea1
vendor: record the in-tree _sass patches
KubaO Sep 19, 2026
a581290
style: four fixes axe cannot see
KubaO Sep 19, 2026
3c0c6b6
vendor-assets: validate the response, and survive a dead network
KubaO Sep 19, 2026
fdf157c
a11y: tighten five sharp edges in the scan tooling
KubaO Sep 19, 2026
8eacc5c
check: run a fixture through the build's own checker
KubaO Sep 19, 2026
0e8753d
a11y: audit content disclosures, and the page that stacks them
KubaO Sep 19, 2026
95f19b5
check: ignore the fixture build's PDF tree too
KubaO Sep 19, 2026
b966f12
render: four narrow correctness fixes
KubaO Sep 19, 2026
9f6c38f
docs: correct the drift the link-check fold-in and sample widening left
KubaO Sep 19, 2026
da65d2b
docs: record what landed against the review plan
KubaO Sep 19, 2026
bdf0282
docs: point each review finding at the commit that resolved it
KubaO Sep 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 129 additions & 46 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
@@ -1,22 +1,33 @@
name: Run Checks

on:
workflow_dispatch:
# push:
# branches: [ main, dev ]
# paths: docs/**
pull_request:
branches: [ main ]
paths: docs/**
workflow_dispatch:
# `staging` is this repo's default branch and the one the deploy
# workflow publishes from, so that is what PRs target. Checking only
# PRs into `main` meant this never ran.
#
# Pushes are deliberately not listed: tbdocs-gh-pages.yml already
# builds and checks on every push to `staging` before it deploys, so a
# push trigger here would duplicate that work. This workflow's job is
# to catch the breakage BEFORE the merge.
pull_request:
branches: [ staging, main ]

# No `paths:` filter. It used to be `docs/**`, which skipped any PR that
# touched only builder/ or scripts/ -- exactly the code most able to break
# the build, the link checker, or asset vendoring. The whole run is a couple
# of minutes, so filtering buys little and silently drops real coverage.

# Check-only job: it never deploys, so it needs no Pages or OIDC rights.
permissions:
contents: read
pages: write
id-token: write

# Must NOT share the deploy workflow's "pages" group -- that made PR checks
# queue against production deployments. Keyed per PR, and superseded runs are
# cancelled when new commits arrive.
concurrency:
group: "pages"
cancel-in-progress: false
group: checks-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
# Build job
Expand All @@ -33,38 +44,110 @@ jobs:
cache-dependency-path: package-lock.json
- name: Install dependencies
run: npm ci
- name: Build with tbdocs
run: node builder/tbdocs.mjs --src docs
- name: Check links (check_links.mjs)
# Three passes run in parallel via /sep/:
# 1. Online (_site/): --fallback-extensions html mirrors GitHub Pages'
# extensionless-URL behaviour. No --base-path needed -- this
# workflow builds without --baseurl (contrast jekyll-gh-pages.yml).
# Integrity checks (--check-html/a11y/ids/sitemap/search) run here.
# 2. Offline (_site-offline/): --forbid catches any surviving
# https://docs.twinbasic.com/<path> link the offline rewrite missed
# (bare root URL is exempt). Integrity checks run here too (minus
# sitemap/search -- the offline tree has neither).
# 3. Book (_site-pdf/book.html): --no-fail makes failures informational
# (some links are not yet fully resolved).
run: >-
node scripts/check_links.mjs
--offline --include-fragments
--check-html --check-a11y --check-ids
--check-sitemap --check-search --check-canonical
--fallback-extensions html
--index-files 'index.html,.'
--root-dir docs/_site
docs/_site
/sep/
--offline --include-fragments
--check-html --check-a11y --check-ids
--index-files 'index.html,.'
--fallback-extensions html
--forbid 'https://docs.twinbasic.com'
--root-dir docs/_site-offline
docs/_site-offline
/sep/
--offline --no-fail --include-fragments
--root-dir docs/_site-pdf
docs/_site-pdf/book.html
# check_a11y.mjs drives axe-core inside headless Chromium. The
# build itself needs no browser -- only this scan does.
# fonts-liberation is installed explicitly, not incidentally. axe's
# target-size rule measures rendered boxes, and an inline element's
# measured height is its font's content area -- so the 24px floor is
# cleared or missed depending on what system-ui resolves to. At the
# mobile h3 size: Segoe UI 19px, Verdana and Tahoma 17px, Arial 16px,
# Liberation Sans / DejaVu Sans / Roboto 15px. The site's padding is
# calibrated against the smallest of those, so the runner has to have
# it. ubuntu-latest migrates to Ubuntu 26 on 19 October 2026; pinning
# the image would also work and expires differently, while this
# addresses the actual variable.
- name: Install Liberation fonts
run: sudo apt-get install -y --no-install-recommends fonts-liberation
- name: Install Chromium
# Same incantation as tbdocs-gh-pages.yml's install step, which
# is the proven-working one in this repo: --install-deps apt-installs
# the shared libraries Chromium needs, which requires root.
run: sudo npx puppeteer browsers install chrome --install-deps
- name: Build and check links with tbdocs
# --no-fetch-assets: CI must never download a referenced image. An
# author who wrote the markdown but forgot to commit the file would
# otherwise get a green build while the published site went on
# hotlinking a third party. A missing asset is a hard failure here,
# naming the file to commit. ($CI already implies this; the flag
# states it.) See builder/vendor-assets.mjs.
#
# --check runs the link and site-integrity check over the HTML the
# build already holds in worker memory, instead of writing three
# trees out and reading ~270 MB of them back through
# scripts/check_links.mjs. Same findings -- that equivalence is
# gated by scripts/check_links_diff.mjs, run by hand when either
# implementation changes. Do NOT also add a check_links.mjs pass
# over _site/: it would check the same bytes a second time.
#
# --check-audit-index adds the index audit on top: a diff of the
# tree index the build derives from its own records against what
# landed on disk. It is the one direction the findings comparison
# cannot see -- a spurious entry makes the oracle answer "exists"
# for a path that 404s in production, and on a clean site nothing
# links to a path that does not exist, so nothing else would
# notice. CI lost its previous coverage of this when the FsOracle
# pass was removed.
#
# It covers all three trees: _site/ (links, fragments, duplicate
# ids, well-formedness, remote <img src>, sitemap, search index,
# canonical URLs), _site-offline/ (the same minus sitemap and
# search, plus the forbidden-prefix rule that catches live-site
# links the offline rewrite missed), and _site-pdf/book.html
# (informational). A failing check never aborts the build -- a
# broken link still produces a site worth inspecting -- so the
# step fails on the exit code: 1 for link failures, 2 for
# integrity failures, 3 for both.
run: node builder/tbdocs.mjs --src docs --no-fetch-assets --check-audit-index
# The build above is the only pass over the site's HTML. This step is
# NOT a second one: it runs the differential harness against a
# nine-file synthetic tree that carries one fault of every kind, so
# scripts/check_links.mjs -- still the tool for a tree the build did
# not produce, and the oracle the fused path is defined against --
# cannot rot unnoticed. ~0.3 s, no site files touched.
#
# The full script-vs-fused comparison over the real trees stays a
# manual gate (`check_links_diff.mjs --a script --b fused`): running
# it here would mean checking every page twice, which is exactly what
# folding the check into the build removed.
- name: Verify the standalone link checker (check_links_diff.mjs)
run: node scripts/check_links_diff.mjs --case fixture --a script --b index
# The step above compares two front ends over a hand-written tree.
# This one compares the script against the BUILD's own checker, over
# a three-page tree the build produces from test/fixtures/check-src.
# That is the pass the harness exists for and the one nothing
# exercised: `--b fused` skipped the synthetic `fixture` case every
# time, because the build cannot check a tree it did not write. A
# regression in builder/check.mjs that stopped REPORTING a category
# would have left every gate green.
#
# One extra three-page build, ~1 s. It is here and not in the deploy
# workflow because this is the PR gate: catching it before a merge is
# the point, and the deploy workflow has a site to ship.
- name: Verify the build's own link checker (check_links_diff.mjs)
run: node scripts/check_links_diff.mjs --case fixture-built --case fixture-built-offline --a script --b fused
# check_a11y.mjs injects a PATCHED axe bundle (plain-color-fields,
# -26 % on a realistic page set -- see builder/PLAN-axe-perf.md), so the
# patch has to be verified before its results are trusted. The patch
# asserts its substitution targets and so fails loudly if an axe-core
# bump moves the code; this catches the other case, where the text still
# matches but the colour maths has changed. The fingerprint gate cannot
# see that -- it compares `incomplete` as a rule-id set.
#
# Cheap (one page, two bundles) and Chromium is already installed. The
# PR that bumps axe-core is exactly when it earns its place.
- name: Verify axe source patch (check_axe_patch_equiv.mjs)
run: node scripts/check_axe_patch_equiv.mjs
# The scan is eleven pages of ~1,160, so its page list decides what it can
# report at all. This fails when the site grows a markup construct no
# sample page carries -- the drift that let the previous six-page sample
# report a clean pass while 54 pages had violations in constructs it never
# saw. No browser, ~1 s.
- name: Verify a11y sample coverage (pick_a11y_sample.mjs)
run: node scripts/pick_a11y_sample.mjs --check
# Matches check.bat: the build's own link check gates this, and a
# link failure short-circuits before the (slower) browser scan runs.
# Scans _site-offline/, whose relative asset paths actually resolve --
# _site/ uses root-absolute URLs that render unstyled here, making
# every colour-contrast result meaningless.
- name: Accessibility check (check_a11y.mjs)
run: node scripts/check_a11y.mjs
98 changes: 62 additions & 36 deletions .github/workflows/tbdocs-gh-pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,53 +41,79 @@ jobs:
cache: 'npm'
cache-dependency-path: package-lock.json
- name: Install dependencies and Chromium
# fonts-liberation is installed explicitly, not incidentally.
# axe's target-size rule measures rendered boxes, and an inline
# element's measured height is its font's content area -- so the
# 24px floor is cleared or missed depending on what system-ui
# resolves to. At the mobile h3 size: Segoe UI 19px, Verdana and
# Tahoma 17px, Arial 16px, Liberation Sans / DejaVu Sans / Roboto
# 15px. The site's padding is calibrated against the smallest of
# those, so the runner has to have it. ubuntu-latest migrates to
# Ubuntu 26 on 19 October 2026; pinning the image would also work
# and expires differently, while this addresses the actual
# variable.
run: |
npm ci
sudo apt-get install -y --no-install-recommends fonts-liberation
sudo npx puppeteer browsers install chrome --install-deps
- name: Setup Pages
id: pages
uses: actions/configure-pages@v6
- name: Build with tbdocs
- name: Build and check links with tbdocs
# --url + --baseurl together make canonical / og:url / sitemap
# entries point at the actual deployment (e.g. on a fork that
# deploys to kubao.github.io/twinBASIC-docs/, both bits move
# so the page advertises its own URL rather than the config'd
# production host).
run: node builder/tbdocs.mjs --src docs --url '${{ steps.pages.outputs.origin }}' --baseurl '${{ steps.pages.outputs.base_path }}'
- name: Check links and site integrity
# Three passes run in parallel via /sep/:
# 1. Online (_site/): --fallback-extensions html mirrors GitHub Pages'
# extensionless-URL behaviour. --base-path strips the Pages baseurl
# (e.g. `/twinBASIC-docs`) from absolute URLs before resolving.
# Integrity checks (--check-html/a11y/ids/sitemap/search) run here.
# 2. Offline (_site-offline/): --forbid catches any surviving
# https://docs.twinbasic.com/<path> link the offline rewrite missed
# (bare root URL is exempt). Integrity checks run here too (minus
# sitemap/search -- the offline tree has neither).
# 3. Book (_site-pdf/book.html): --no-fail makes failures informational
# (some links are not yet fully resolved).
run: >-
node scripts/check_links.mjs
--offline --include-fragments
--check-html --check-a11y --check-ids
--check-sitemap --check-search --check-canonical
--fallback-extensions html
--index-files 'index.html,.'
--base-path '${{ steps.pages.outputs.base_path }}'
--root-dir docs/_site
docs/_site
/sep/
--offline --include-fragments
--check-html --check-a11y --check-ids
--forbid 'https://docs.twinbasic.com'
--fallback-extensions html
--index-files 'index.html,.'
--root-dir docs/_site-offline
docs/_site-offline
/sep/
--offline --no-fail --include-fragments
--root-dir docs/_site-pdf
docs/_site-pdf/book.html
# --no-fetch-assets: CI must never download a referenced image. An
# author who wrote the markdown but forgot to commit the file would
# otherwise get a green build while the published site went on
# hotlinking a third party. A missing asset is a hard failure here,
# naming the file to commit. ($CI already implies this; the flag
# states it.) See builder/vendor-assets.mjs.
#
# --check runs the link and site-integrity check over the HTML the
# build already holds in worker memory, across all three trees,
# instead of writing them out and reading ~270 MB back through
# scripts/check_links.mjs. Do NOT also add a check_links.mjs pass:
# it would check the same bytes a second time.
#
# --check-audit-index adds the index audit on top: a diff of the
# tree index the build derives from its own records against what
# landed on disk. It is the one direction the findings comparison
# cannot see -- a spurious entry makes the oracle answer "exists"
# for a path that 404s in production, and on a clean site nothing
# links to a path that does not exist, so nothing else would
# notice. CI lost its previous coverage of this when the FsOracle
# pass was removed.
#
# It also removes a synchronisation point that used to be easy to
# get wrong. The standalone pass needed --base-path to match this
# step's --baseurl, in a different step, by hand; the fused check
# reads the baseurl off the config it just built with. Only the
# online tree gets a base path -- the offline tree's links are all
# relative after the rewrite.
run: node builder/tbdocs.mjs --src docs --url '${{ steps.pages.outputs.origin }}' --baseurl '${{ steps.pages.outputs.base_path }}' --no-fetch-assets --check-audit-index
# Not a second pass over the site: a nine-file synthetic tree carrying
# one fault of every kind, so scripts/check_links.mjs -- still the
# tool for a tree the build did not produce -- cannot rot unnoticed.
# ~0.3 s. See the same step in checks.yml, which additionally runs the
# comparison against the build's own checker.
- name: Verify the standalone link checker (check_links_diff.mjs)
run: node scripts/check_links_diff.mjs --case fixture --a script --b index
# check_a11y.mjs injects a PATCHED axe bundle; verify the patch is still
# value-preserving before trusting what it reports. See the same step in
# checks.yml for why the fingerprint gate does not cover this.
- name: Verify axe source patch (check_axe_patch_equiv.mjs)
run: node scripts/check_axe_patch_equiv.mjs
# Fails when the site grows a construct no sample page covers; see the
# same step in checks.yml. No browser, ~1 s.
- name: Verify a11y sample coverage (pick_a11y_sample.mjs)
run: node scripts/pick_a11y_sample.mjs --check
# Same gate as check.bat and the PR workflow. Chromium is already
# installed above for the PDF render, so this costs only the scan.
- name: Accessibility check (check_a11y.mjs)
run: node scripts/check_a11y.mjs
- name: Render book PDF
run: |
mkdir -p _pdf
Expand Down
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,12 @@ node_modules/
/after-*/
/findoverflow-baseline/
indexer/.packages/

# Temporary session handoff notes. Root-anchored: only the repo-root file.
/HANDOFF.md

# scripts/check_links_diff.mjs builds test/fixtures/check-src into these.
/test/fixtures/_out/
/test/fixtures/_out-offline/
/test/fixtures/_out-pdf/
/test/fixtures/_out.json
Loading
Loading