Second batch of accessibility, content and tooling improvements. - #192
Merged
Merged
Conversation
The site rode just-the-docs' `system-ui, -apple-system, "Segoe UI", ...` default, so every platform rendered a different typeface at different metrics. Inter is the brand face -- twinbasic.com has always named it first and never shipped a @font-face to deliver it. Three OFL faces, committed as subset .woff2 with their licences and regenerable by scripts/build_fonts.py from SHA-256-pinned upstream releases: Inter for all web text, PDF headings and diagram labels; Cascadia Mono for all code; Source Serif 4 for PDF body only. `opsz` is pinned out (~70 KB/face, more than trimming the charset would save); `wght` stays variable, which is what makes the two `font-weight: 350` rules resolve to a real 350. 219 KB on a cold visit, cached across 869 pages. Unified across web light and dark, the offline mirror, the PDF book, the DOT/Gantt/Mermaid diagrams and the diagram export buttons. Five things this surfaced that were already wrong: * <kbd> on eleven pages, and <pre>, were never on the mono stack. normalize puts pre/code/kbd/samp on generic `monospace` and JTD overrides only `code`, so those rendered in Courier New / Consolas. * The footnote back-link has never met WCAG 2.5.8 in any font -- 12.09x19 on Segoe UI, 12.09x15 on Liberation Sans, 15.97x17 on Inter. It passed on axe's spacing exception, and Inter's wider U+21A9 moved the centre enough on one footnote of one page to fail. Sized explicitly at 24x24 instead of restoring the clearance. * PNG export has never worked on the two Mermaid diagrams: Chromium taints a canvas that has had <foreignObject> drawn into it, and the throw happened inside an onload handler where nothing surfaced it. Not fixable by config -- `htmlLabels: false` keeps node labels in a foreignObject on Mermaid 11. It now fails loudly and names Download SVG as the alternative. * The PDF was embedding a Times New Roman subset for seven footnote arrows Source Serif lacks, and a Consolas subset for the <kbd> text. The book's chains are self-hosted all the way down now. * The forked paged.js rejected any @font-face not in state `loaded`, which a declared-but-unused face never reaches. It now rejects `loading` and `error` only. Two traps the wiring has to respect, documented at both sites: the dark compilation re-emits every JTD base rule at raised specificity, so the stacks must be passed to modules-dark.scss as well or dark mode silently keeps system fonts; and @font-face must be emitted exactly once, from the light compilation, hence the mixin. Mermaid sizes each node box to the text it measured, so re-pointing font-family in a committed export clips every label. scripts/ render_mermaid.mjs re-exports from the _Images/*.md sources with Inter loaded and asserts no label overflows. Diagram exports embed the faces they use as data: URIs -- an SVG in <img> renders in secure static mode and fetches nothing, so an exported PNG asking for Inter rasterised pixel-identical to one asking for a font that does not exist. The bytes come from cache; ~25 ms per click. Verified: build.bat and check.bat clean; full axe sweep over all 869 pages x 2 themes x 2 viewports, 3476 audits, 0 violations; PDF renders 1992 pages embedding all three faces.
Both files joined composite cache keys with a literal U+0000 in a template literal. The separator choice is right -- a NUL cannot occur in a path, URL or fragment, so keys cannot collide -- but written as a raw byte it makes the source not-plain-text: grep reports "Binary file matches" and hides the line, and file(1) reports "data". git was unaffected, since both NULs sit past its 8000-byte sniff window, which is why it went unnoticed through review. The escape produces the identical string, so this is a source-encoding change only. Both files are now JavaScript source, Unicode text, UTF-8 text.
checks.yml's fixture-built gate has been red since 58f9c6e. The font work put two <link rel="preload" href="/assets/fonts/..."> tags on every page; test/fixtures/check-src/ has no assets/fonts/, so each of its three pages gained two broken links and the broken count went 3 -> 9 against a hard-coded FIXTURE_BUILT_ONLINE.broken = 3. The two checker implementations agreed throughout -- what failed was the fixture's own expectation, which is the assertion working as designed. Stubs rather than a raised expectation: the checker only tests that a target exists, and six accidental broken links would dilute a category the fixture exists to hold at an exact, meaningful number. Note the fixture can break without being touched, since it is built by the real tbdocs and inherits whatever the page template emits unconditionally.
The page is advertised as the complete interface reference and received +3/-2 lines across a range that added four tasks and five modules. Corrects two statements that would misdirect: task priority claimed lower-number-wins (sab-scheduler keeps the largest pri it finds, and flush:i relies on it), and renderJoin was shown with expected = [], the pre-bug shape f177c28 fixed. Adds Section 5 for linkJoin / checkBook / checkReport, the vendorAssets seed, and export tables for check.mjs, check-tree.mjs, link-check.mjs, vendor-assets.mjs and dot-metrics.mjs. Documents the check that now runs inside flush:i, the four check* fields on SharedState, the four check BuildOpts, sitemapIncludes / searchIncludes / stripFontPreloads, and the stale expected lists on markdownInit, writeAssets and writePdf. writeOffline and writePdf are no longer terminal.
Worked Example B showed `if (!p) continue;` in a render submit callback. tbdocs.mjs throws there now, and WIP.md names it as a rule new fan-out code must not regress -- on the merge path, a missing piece is a bug, not a case to handle. Replaced, with a callout giving the reason. Also fixes the priority ordering (higher claims first) and the in-tree plugin count, which is fourteen since headingLevelNormalizePlugin, videoLinkPlugin and remoteImagePlugin landed.
…ists check.bat runs five steps; the page said three cheaper gates precede the axe scan and never named check_dot_fit.mjs at all, which 6170e07 added. Drops the claim that the Graphviz path carries "no in-tree patches" -- false since dot-metrics.mjs began writing Inter's advance widths into the WASM module's memory, and contradicted by Builder.md two pages over. Adds a short section on why that patch exists and what the fit gate proves.
The page promises "every executable in the documentation repository". Adds entries for check_tree_fresh, check_dot_fit, check_axe_patch_equiv, check_a11y_fingerprint, sweep_a11y, build_fonts, build_dot_metrics and lib/axe-scan -- two of them gates check.bat runs, two more gates CI runs. Fixes three errors: check.bat has five steps, not four; the PDF rename instruction pointed at .github/workflows/jekyll-gh-pages.yml, which does not exist; and "CI invokes it the same way" as build.bat was false for both workflows and contradicted by the next paragraph. Replaced with a table of what each caller actually passes. Also splits the a11y scan's description: lib/axe-scan.mjs defines the scan, check_a11y.mjs only reports it, and someone told to change the scan was being pointed at the wrong file.
Builder.md's module map, task lists and "what runs where" never mentioned check.mjs, link-check.mjs, check-tree.mjs or the three check tasks, though build.bat passes --check-audit-index on every run. Adds a Verification group, the Check section, dot-metrics.mjs, and the check that rides along inside flush:i. Figures: 31 static tasks not 28; five Gantt sections not four (index.md repeated the four-section list); ~34 modules; the scheduler SAB is 170 KB, measured, not 140 KB. PDF-Generation.md showed a book.bat command with docs/-relative paths and ^ continuations, stale since the batch files moved to the repository root in 8ac701b. Authoring.md gains the three opt-out frontmatter keys -- search_exclude in particular is load-bearing now that the build's link check honours it.
Every non-Core file path in "Where things live", the page-template examples and the per-symbol workflow predated 58a5e1c, which split the packages into Reference/Default/ and Reference/Built-In/. The file had no mention of either directory. Prefixed, with a banner noting the reorg moved files and not URLs -- the cross-section linking tables resolve against permalinks and were never affected -- and that Assert's folder is now TwinBasicAssertions. Contradictions, each against the tree: build.bat passes --check-audit-index, not --check; book.bat writes "twinBASIC Book.pdf"; STATE_AUDITS has two entries and so adds eight audits, which is the only figure consistent with the 60 the same section quotes; tbIDE has 23 CoClasses, as the file itself says seventeen lines later. Misattributions: font-weight 350 is asked for by the vendored layout.scss and navigation.scss, not custom.scss, which declares none; the two idiom anchors live in the published package pages, not in the WIP notes, and redirect_from cannot rescue a renamed fragment because redirects.mjs emits whole-page stubs; check_dot_fit runs in checks.yml but not the deploy workflow; check_links_diff calls the script side in-process; there are nine finding categories, not ten. Decimal is a full data type in twinBASIC -- the bullet said the reverse, contradicting the very page it tells contributors to consult. Adds AppGlobalClassObject, a thirteenth package that publishes nothing, with both causes recorded.
PLAN.md called Phase 10 both shipped and planned, described verification by diffing against Jekyll through verify-phase1..8.mjs / _triage.mjs / _diff.mjs -- none of which exist, and there is no Jekyll build left to diff -- listed a "dependencies" key package.json does not have, and never mentioned PLAN-sab-pull-scheduler.md, which is the scheduler the build actually runs. Banners rather than rewrites: the narrative is history, the forward pointers are what was missing. PLAN-13 sized svg-inline.js at ~80 lines; it is 338, having gained 177 in this range. PLAN-a11y's top summary still said six sample pages, with its own superseded note 271 lines below. PLAN-checks said both workflows run the same four gates; checks.yml runs six and the deploy workflow four, deliberately. builder/README.md told you to npm install in builder/, which has no package.json, pointed at docs/build.bat and friends a move ago, linked a deleted assets/README.md, omitted every check and fetch flag, stopped its plan list at PLAN-12, and claimed both CI workflows invoke check_links.mjs directly, which neither has since b97c75f.
Ten parsed flags appeared in no header block: --unminified, sweep_a11y's --root-dir/--out/--stock-axe/--recycle-every, pick_a11y_sample's --root-dir/--budget, and check_links_diff's --max-lines/--base-path-tree/ --build-base-path. --recycle-every occurred exactly once in the whole repository, on its own argv line. check_a11y.mjs said the sample is eleven pages; it is thirteen -- the figure was corrected six -> eleven in three files while the sample was being widened to thirteen in the same batch. check_links.mjs documented only exit code 2 of four. tbdocs.mjs listed 9 of 17 flags. tbdocs.mjs and check.mjs both still said 230 MB, the figure 9f6c38f corrected to ~270 MB everywhere else. probe-tabs-vs-procs printed a usage line naming a file that does not exist.
test/ had no README. test/fixtures/check-src/ is consumed only by check_links_diff.mjs, its per-category expected counts are hard-coded in that script, and nothing in the tree said so -- editing the fixture without updating them fails the gate by design. The sharper point, learned the hard way: the fixture is built by the real tbdocs, so it inherits whatever the page template emits unconditionally. Adding font preloads broke it in a commit that touched neither the fixture nor the checker. When that happens the fix is to add the stub the template now expects, not to raise the expected count.
33 lines that invited pull requests and never mentioned Node, npm ci, any of the four batch files, or where the developer documentation lives.
Companion to REVIEW-c9f2dfe0-1b6922b.md, covering the prose rather than the code. 36 findings, each verified against source before inclusion, plus the two live defects the amendment pass turned up: the fixture-built CI gate red since the font work, and AppGlobalClassObject publishing nothing while serving its raw markdown on docs.twinbasic.com.
A UTF-8 BOM decodes to U+FEFF, which node's utf8 reader hands back as the first character. gray-matter's test then sees --- instead of ---, reports no frontmatter, and the page is filed as a static asset: it vanishes from the nav, the sitemap and the search index, and its raw markdown is copied into the output tree and served verbatim, frontmatter keys and all. That happened to Reference/Built-In/AppGlobalClassObject/index.md and went unnoticed for months -- an excluded or misfiled page is not a missing one, so nothing reported it. Windows editors add a BOM without being asked, so this is fixed at the one place every .md and .html passes through rather than by policing the files.
"**/_*/**" existed to drop the Affinity sources in _Images, and took the 37 pages under Reference/Built-In/AppGlobalClassObject/_App/ with them. The twinBASIC interface really is named _App, after the COM hidden-interface convention, so the folder name and the build convention collided head-on. Measured before and after: pages 871 -> 908, nothing lost, static files unchanged, and every gained page under AppGlobalClassObject/_App/. The pattern matches a directory, so the five published underscore-prefixed pages under CustomControls/Framework/ were never affected. Adds **/*.af so a stray Affinity source filed outside _Images cannot ship either.
index.md carried the BOM and no permalink, so it derived /Reference/Built-In/AppGlobalClassObject/index.html -- a URL nobody chose, against which its own relative links did not resolve. Declares /tB/Packages/AppGlobalClassObject/ like every sibling package, and corrects the interface link from _App/_App to _App/, which is where that page's permalink puts it. Lists the package on Built-In.md, Packages.md and Reference/index.md, which have undercounted since it was written: ten built-in packages, thirteen in total.
discover.mjs falls back to "/" + srcRel + ".html" when permalink is
absent, which is only right on a site whose file tree mirrors its URL
tree. This one deliberately does not -- Reference/Built-In/CEF/ publishes
at /tB/Packages/CEF/ -- so outside Features/ a derived URL is structurally
wrong, and wrong silently: the page builds, links into it 404, and nothing
reports either. It is also the URL the IDE help system and in-source
[Documentation("...")] attributes resolve against.
Aborts in the same pass as the nav-parent check, which has the same
failure character, and covers every page rather than only nav-visible ones
-- a nav_exclude page is still reachable by URL.
906 of 908 pages already complied. The two that did not were publishing at
a .html URL none of their own siblings use; the check then found a third,
the test fixture's Kitchen.md. All three now declare one, and the output
path is unchanged in every case.
Authoring.md states the requirement where a contributor meets it, with the reason: the file tree does not mirror the URL tree, so a derived permalink would be wrong and wrong silently. Pipeline-Stages documents validatePermalinks beside the nav-parent check and corrects the Page table, which described the fallback as if it were reachable. WIP.md's status table gains the thirteenth package, and its note now records what was actually wrong -- the _* glob and the BOM -- rather than saying the package cannot publish.
indexed_from, exclude_from_docs and exclude_kinds are provenance for the package authoring pass, not build input. Together they say what a completeness check should expect: re-index a package from a newer build, and anything new not named in the exclude lists is a documentation gap. Without them a re-index cannot tell "not yet written" from "deliberately omitted". Nothing recorded this, and the keys read as stray metadata someone would eventually strip. WIP.md now carries the meaning, the six packages that use them, the 2026-06-04 commits that set them, and the instruction to bump indexed_from on re-index -- a stale value asserts a check was run against a build that no longer matches. Also notes that further package documentation work using these keys is in progress and not yet merged, so a package may already be written before anyone starts it again.
…l path An install path contains a username. It now takes the newest twinBASIC_IDE_BETA_<n> on %USERPROFILE%/Desktop, with --ide and TB_IDE as the overrides for an install kept elsewhere.
… for it parseCommandLine is in ide/main2.js, not main.js, and the trailing space was recorded as measured through GetCmdLine when it had not been. Both now come from an A/B on twinBASIC.exe's own Win32_Process.CommandLine: two cmd batch files differing by one byte, one opening the project and one drawing the dialog.
… file The parse runs before root.loadProject, so the path need not exist. A nonexistent path with a trailing space gives "Bad command line syntax" and without it gives ERROR_FILE_NOT_FOUND -- two different dialogs from one byte, which says the space changes the parse rather than the file lookup.
…ike found Bypassing the WebView is not possible: the compiler's port and pass key are minted by host objects inside the page, and --compiler= is an opaque handle rather than a port. Reusing a live IDE for a second project wedges its renderer, so a fresh IDE per project is the design and concurrency is the way to scale -- three projects take 26s sequentially and 10s in parallel. Also corrects a figure this session invented: a run is 8-11s and flat in project size, not the "roughly 40 seconds" previously committed unmeasured.
…nced six admonitions
…asured and landed
…he gate lists drifting again
…k holding a lone ``` swallowed every admonition after it
…s two the round did not anticipate
…fix pass damaging what it touched
…e one page it guarded, and publish the stall watchdog
….) arguments and report the rest with a reason
…nto, and discoverability flat across three re-runs
…give serve-mode and the offline copy a door, derive the enumeration total
… protocol and open the reference half
…vents a page, reconcile the event mechanism across five pages
…s a Class, confirmed against exported sample sources
…nd Assert, every syntax claim compiled
…c authors, and the parameter description already covers the behaviour
… output; document the measured 14-column print zone
…tate the error-9 hazard on LBound/UBound; fix a stray control char in tbrun's Debug.Cls check
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
eval/) that asks whether the documentation worksrather than whether it is accurate.
gate-list drift, page-count baseline and diagram fit.
{{tbdocs:<name>}}build-time counts** to keep build-related figures current.tbbuild.mjs,tbrun.mjsDebugobject, andWithEvents.