Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
13 changes: 11 additions & 2 deletions .github/actions/run-gates/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,9 @@ runs:
run: node --test test/example-batches.test.mjs
# Unit tests for scripts/lib/tb-ports.mjs, which claims the DevTools ports
# the harness's IDEs use: two claims at once are disjoint, a port in use
# and a live lock are passed over, a stale lock is taken over. Starts no
# IDE. No browser, no built tree, about 2 s.
# and a live lock are passed over, a stale lock is taken over; and for what
# ending an IDE removes of its temp folder and profile. Starts no IDE. No
# browser, no built tree, about 2 s.
- name: Unit-test the harness's port claims (test/ports.test.mjs)
shell: bash
run: node --test test/ports.test.mjs
Expand Down Expand Up @@ -215,6 +216,14 @@ runs:
- name: Verify the book-coverage warnings (check_book_coverage.mjs)
shell: bash
run: node scripts/check_book_coverage.mjs
# Every NOTE and WARNING of a contributed package's section names the test
# cases behind it, and each package test project under test/package-tests/
# is in order: its cases named, well formed, in expect.json for the
# documented version and in its generated RunAll module. Sources only: no
# browser, no built tree, no install.
- name: Verify the package test markers and projects (check_package_tests.mjs)
shell: bash
run: node scripts/check_package_tests.mjs
# The symbol index (tB/symbols.json) is read by the IDE help add-in. A
# build that indexes the reference cleanly says nothing about the rules
# that did not fire on it, so these probes assert each against the case
Expand Down
2 changes: 1 addition & 1 deletion WIP.A11y.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ It has one blind spot: it compares a candidate against a baseline produced by th

It also compares *which* findings axe produces, never their shape. The `no-html` scheme passes the gate on every audit and would still crash the reporter, because `noHtml: true` makes `node.html` null and `check_a11y.mjs` calls `.slice()` on it. Treat the gate as necessary, not sufficient.

Cost attribution for the scan lives in [perf/ab-axe.mjs](perf/ab-axe.mjs), with [perf/probe-axe-dom.mjs](perf/probe-axe-dom.mjs) for DOM operation counts and [perf/probe-axe-scaling.mjs](perf/probe-axe-scaling.mjs) for the size curve. The investigation, [builder/PLAN-axe-perf.md](builder/PLAN-axe-perf.md), is complete. Its conclusion: one vendored source patch, `SOURCE_PATCHES['plain-color-fields']` in `axe-scan.mjs` (see above), is worth **26 %** across a realistic page set and **30 %** on large pages. It is gated audit-by-audit by [check_a11y_fingerprint.mjs](scripts/check_a11y_fingerprint.mjs) and verified value-by-value by [check_axe_patch_equiv.mjs](scripts/check_axe_patch_equiv.mjs) -- both are needed, because the fingerprint gate compares `incomplete` as a rule-id set and would not notice a colour error that shifted ratios without flipping a pass/fail. The patch asserts an exact occurrence count at each substitution point, so an axe-core bump fails loudly rather than silently reverting. Before touching the scan:
Cost attribution for the scan lives in [perf/ab-axe.mjs](perf/ab-axe.mjs), with [perf/probe-axe-dom.mjs](perf/probe-axe-dom.mjs) for DOM operation counts and [perf/probe-axe-scaling.mjs](perf/probe-axe-scaling.mjs) for the size curve. The investigation, [builder/PLAN-axe-perf.md](builder/PLAN-axe-perf.md), concludes that one vendored source patch, `SOURCE_PATCHES['plain-color-fields']` in `axe-scan.mjs` (see above), is worth **26 %** across a realistic page set and **30 %** on large pages. It is gated audit-by-audit by [check_a11y_fingerprint.mjs](scripts/check_a11y_fingerprint.mjs) and verified value-by-value by [check_axe_patch_equiv.mjs](scripts/check_axe_patch_equiv.mjs) -- both are needed, because the fingerprint gate compares `incomplete` as a rule-id set and would not notice a colour error that shifted ratios without flipping a pass/fail. The patch asserts an exact occurrence count at each substitution point, so an axe-core bump fails loudly rather than silently reverting. Before touching the scan:

- **Audit cost is super-linear in page size** -- `k = 2.73` on real pages, almost entirely inside `color-contrast`. The four largest pages in the site cost ~5.9x an average sample page each.
- **Every axe-core upgrade needs the fingerprint gate run across it** -- axe ships new and revised WCAG rules between minors, and the source patches are pinned to the bundle's current text.
Expand Down
25 changes: 17 additions & 8 deletions WIP.Build.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,9 @@ Three `.py` files stay, and none is an oversight:
account of the blocked JavaScript port.
- **`scripts/subset_font.py`** cuts each font an SVG screenshot embeds down to the
characters it draws (`shoot_docs --svg`, [WIP.Screenshots.md](WIP.Screenshots.md)). It is
fontTools, the subsetter `build_fonts.py` already relies on. harfbuzzjs was tried first and
removed (owner: "don't mess with harfbuzz"); it is the library whose CFF2 defect keeps
`build_fonts.py` in Python. It loads a font without recalculating glyph bounds, because the
fontTools, the subsetter `build_fonts.py` already relies on. It does not use harfbuzzjs
(owner: "don't mess with harfbuzz"), the library whose CFF2 defect keeps `build_fonts.py` in
Python. It loads a font without recalculating glyph bounds, because the
IDE's icon font stores loose glyph boxes and recalculating them moved icons a pixel, and it
keeps the hinting (owner's decision) and the font's timestamp, so a picture's SVG is the same
bytes from one run to the next.
Expand All @@ -141,7 +141,7 @@ a change to `src/` is packed again by hand. See [Tools and
Scripts](docs/Documentation/Tools-IDE.md#imagestrip).

**Its own unit tests are twinBASIC `[TestFixture]` modules in `src/Sources/Tests/`**, with
the images they build in `TestImages`, run by `tbrun --tests` (owner, 2026-10-10), which reads
the images they build in `TestImages`, run by `tbrun --tests` (owner), which reads
every `.twin` under `Sources/`. `TestImages.Cat` copies each `ParamArray` element through a
`Variant` before assigning it to an array, around a twinBASIC defect queued in
BUGS-TO-REPORT.md.
Expand All @@ -153,7 +153,7 @@ build without one rule) fails it. The two are kept in step by hand: a change to
strips is a change to both. `compactImage` in the same module is what the SVG screenshots
(`scripts/svgshot/snapshot-svg.mjs`, [Pictures as SVG](#pictures-as-svg)) embed their images
through, so a change to what it strips reaches their SVGs on the next `shoot_docs --svg` run.
What both strip was decided by the owner, 2026-10-10:
What both strip was decided by the owner:

- **An embedded image keeps its pixels and any notice of whose it is, and nothing else.**
The IDE's icons carry GIMP's EXIF, IPTC and XMP profiles (12 KB around a 28-pixel icon),
Expand Down Expand Up @@ -413,6 +413,15 @@ admonition's lines by their `>` markers and strips them, and a masked fence
inside an admonition takes its markers with it into the stash. It asks
`blockRegions`, with the same parser, which lines are code instead.

It also reads the label, the rest of the `[!TYPE]` line (`lib/callout-labels.mjs`),
and throws on one it cannot parse: it used to drop that text silently, which is
how a typo in a label would vanish. A package version in a label is checked against
`page.packageVersion`, the section's `indexed_from`, which the main thread stamps
on every page before the render chunks go to the workers, since a chunk need not
hold the section's landing page. The code-region gate calls the chain with no
`packageVersion` at all, which skips that check rather than failing every
labelled page: "" means a page in no section.

### Whitespace inside inline code is content

A documented value can be a padded string: [Partition](docs/Reference/Default/VBA/Interaction/Partition.md)
Expand Down Expand Up @@ -723,8 +732,8 @@ build.
CI workflows: over a hundred probes on fixtures, no tree, no install. The scanner's probes are
the traps the packages' `.twin` sources contain, the community packages' included, and what
a declaration's record says (signature, attributes with their arguments, description, return
type, accessor, the project name). A block of them holds the forms an audit of `parseTwin`
found it losing without a word (lists after `Inherits` and `Implements`, a qualified CoClass
type, accessor, the project name). A block of them holds the forms `parseTwin` could lose
without a word (lists after `Inherits` and `Implements`, a qualified CoClass
interface, `[Hidden(False)]`, modifier-named and initialised fields, brackets in a Type, a
result type before `Overrides`, an unmodified `Declare`, and the `#If` branches as
alternatives), each with the compiler's answer in its comment, checked with `tbrun --tests`
Expand Down Expand Up @@ -1148,7 +1157,7 @@ SVGs: online and book do, offline (and so the help archive) does not. Why:
and the index audit agree without knowing about pictures. That adds a few hundred literal
patterns, so `offlineExcluded` (and its copy in `check-tree.mjs`) compares a pattern with no
wildcard as a string instead of compiling it. The archive is a stored zip, 37 MB without
the SVGs, and the 310 of them (2026-10-10) would add 18 MB.
the SVGs, and the 310 of them would add 18 MB.
- **A picture's SVG is never inlined.** `figurePlugin` inlines a diagram's SVG in its figure; a
picture's (`isPictureSvg`: an `.svg` with its `.png` beside it) stays an `<img>` in the
figure's container, and `tbdocs.mjs` does not put it in the `svgContentsMap` the workers
Expand Down
39 changes: 17 additions & 22 deletions WIP.ExamplesBuild.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ const lang = tok.info ? tok.info.trim().split(/\s+/)[0] : "";
```

The first whitespace-separated token is the language and the rest is discarded, so anything
after `tb` is invisible. This was verified against the real pipeline (`createMarkdownIt`
after `tb` is invisible. This holds in the real pipeline (`createMarkdownIt`
plus `initHighlighter`, not a bare markdown-it; see WIP.md's [Source
dashes](WIP.md#source-dashes)). Four properties are probes that ride along on every run:

Expand Down Expand Up @@ -158,7 +158,7 @@ judgement rather than with a silence. An unknown reason is refused the way a bad
and `inert` beside `check_build` is refused as a contradiction.

The census prints three numbers: marked, inert by reason, and **undecided**. Only the last
is a backlog, and it is the one to drive to zero. The inert count should sit still.
is a backlog, and it is the one to keep at zero. The inert count should sit still.

**`blocked` is the one reason that is not about the fence**, and **nothing carries it
today**. It marks correct code that a *product* defect stops compiling. It exists so that
Expand Down Expand Up @@ -330,9 +330,8 @@ the repository's to commit: four of the nine ship no licence file, and WinReg em
one project, VBA_JSON's `Dictionary` class hid the `Scripting.Dictionary` that WinReg's
samples name.
- **A project does not see the names a package's own references declare.** WinReg embeds
WinDevLib, and its samples' `REGTYPES`, `BOOL` and `RegOpenKeyEx` (and, before 1.0.1.1,
`HKEY_CURRENT_USER`, which WinReg's own `RegKeyspace` now supplies) were unresolved
until `contributed-WinReg` referenced WinDevLib (at the version WinReg embeds, 8.8.0.507)
WinDevLib, and its samples' `REGTYPES`, `BOOL` and `RegOpenKeyEx` are unresolved unless
`contributed-WinReg` references WinDevLib (at the version WinReg embeds, 8.8.0.507)
and the Scripting Runtime itself. The WinReg landing page tells the reader the same.
- **An error inside an embedded package is the package's, and fails nothing**
(`splitPackageFaults`). WinReg 1.0.1.1 and ArrayListLib 1.6.151.1 do not compile on BETA
Expand Down Expand Up @@ -412,37 +411,34 @@ form with a `TextBox` on it, and impossible to compile alone, because the design
than the code declares `Text1`. Declaring those instances lets the compiler check what the
sample asserts: that the member exists, that it takes those arguments, that the types line up.

Against a bare template 99% of the 263 `Reference/Built-In` fences fail; with every
package referenced 78%; with the stage set 67%.

**The list is written out rather than inferred, and that is the point.** A rule over "an
identifier ending in a digit" would also declare `Var1`, `Arg1`, `Line2`, `SQLITE3`, `VBA7`,
`MySub1` and `IShellView2`, none of which is a control. The stage set is the tool's one
deliberate fiction, so it stays explicit and greppable.

**Stage entries are the biggest lever.** A flat tail of undeclared *names* is not a flat
tail of *work*: 45 undeclared VB control instances (`lblName`, `mnuFileSaveAs`, `optPlain`,
`dlgOptions`, `fraLeft`), each on one or two pages, cost one file edit and fixed 22 samples
in `Default/VB`.
tail of *work*: a VB control instance (`lblName`, `mnuFileSaveAs`, `optPlain`,
`dlgOptions`, `fraLeft`) used on one or two pages costs one file edit and fixes every
sample that names it.

## Batching, which is the whole cost question

One project per fence is unaffordable. What makes it tractable is that **IDE cost is flat in
project size**: what is paid for is startup, not compilation.

**Fill the lanes, not the batches.** Filling each batch to `--batch` before opening another
put 120, 55, 4 and 3 samples on four lanes, and a run takes as long as its biggest batch. The
batcher sizes to `ceil(total / jobs)` instead.
would put 120, 55, 4 and 3 samples on four lanes, and a run takes as long as its biggest
batch. The batcher sizes to `ceil(total / jobs)` instead.

### A sample can pass on its neighbour's declarations

Several samples share a generated project, and a `module`-slot sample's declarations are
**visible to every other sample in it**. So a sample can compile because an unrelated one
defined what it referenced. Which samples share a project depends on the selection, so the
same page can pass one run and fail the next. `Tutorials/Testing-with-Assert.md` defines
`PadLeft` in one fence and tests it in three others: a `--propose` survey compiled all 30 of
its fences together and passed, while the gate (marked fences only) put the definition in a
different project and the tests failed on `Unrecognized symbol 'PadLeft'`.
`PadLeft` in one fence and tests it in three others: a `--propose` survey compiles all 30 of
its fences together and passes, while the gate (marked fences only) can put the definition in a
different project, where the tests fail on `Unrecognized symbol 'PadLeft'`.

`projname=` is the answer. It is markup rather than a heuristic because the grouping is a
fact about the page that no scan recovers:
Expand Down Expand Up @@ -619,8 +615,8 @@ the Form page's `True`).
both. No template changes `project.warnings` today (the defaults leave every warning on),
so the attribute is insurance. An error canary puts an error in every batch, so `tbbuild`
never exits 0. An undefined-symbol canary is only the warning TB0002 under **Option
Explicit off**, where the name is declared implicitly, and stopped a full run on
`Core/Deftype.md:56`, a sample that compiles, in the `[implicit]` template. A batch that
Explicit off**, where the name is declared implicitly (a sample that compiles, such as
`Core/Deftype.md:56` in the `[implicit]` template, would fail it). A batch that
crashes the compiler returns no rows at all, canary included; `runBatch` hands a crash to
`isolateCrash` before it reads the canary, so that case never reaches it.

Expand Down Expand Up @@ -824,8 +820,7 @@ that fails only the second is one LLVM cannot generate code for.
**Count diagnostics, then try the fix.** A guess from counting diagnostics is not a result.
"`[Me]` wants a fourth slot" (27 samples, TB5025): a bare `Class`
wrapper fixes **5**, because `Me` becoming legal does not make `Me.Caption` resolve: the
wrapper has no `Caption` (`TB5027 [tbx_…] does not contain 'Caption'`). The largest win was
the stage set.
wrapper has no `Caption` (`TB5027 [tbx_…] does not contain 'Caption'`).

### The Class row, and why it needs a base

Expand Down Expand Up @@ -933,7 +928,7 @@ Language rules:

### What cannot be marked

The 52 `inert` fences are not programs: syntax skeletons with
The `inert` fences are not programs: syntax skeletons with
`<placeholder>` names (`Interfaces-CoClasses.md` writes `Inherits base_interface`),
property-assignment lines shown outside their `With` (`WinServicesLib/ServiceManager.md`
documents each property as `.Name = "..."`), continuation fences that deliberately reuse the
Expand All @@ -958,6 +953,6 @@ a reader writes.
one line each, because its purpose is "the control instances the samples assume". The
invented *classes* go in `hidden` fences, because a class is not a control instance and a
page is where it belongs. The line between the two is a judgement, not a rule.
- Which pages should carry `check_run` (33 samples do)? A sample states its output in its own
- Which pages should carry `check_run` (88 samples do)? A sample states its output in its own
text, as a trailing comment on a `Debug.Print` line or as the comment lines under
`' Output:`; its comparison is only as useful as the pages that state a printed value.
8 changes: 4 additions & 4 deletions WIP.Fonts.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,12 +131,12 @@ There is a secondary design point for upstream HarfBuzz --- a subsetter that can
CFF2 instance metrics should refuse rather than emit a font with mismatched outlines and an
invalid `head` --- but the actionable report is harfbuzzjs's.

**Not yet reported upstream (last checked 2026-09-21).** A full sweep of both trackers
**Not yet reported upstream.** A full sweep of both trackers
(every open issue and PR, closed-issue term searches, Discussions, and the `subset-font` /
`subfont` / `cn-font-split` wrappers) found nothing. Two look-alikes are different bugs: #6068
`subfont` / `cn-font-split` wrappers) finds nothing. Two look-alikes are different bugs: #6068
(HVAR advances frozen on partial instancing; fixed) and the open #4029 (glyf `hhea` bounds
not recomputed when the metrics map is empty --- the same shape, a different table). Both
repos' `main` still carried the defect when checked.
repos' `main` carried the defect when last checked.

**Status.** Decided: wait for the harfbuzzjs fix rather than take the Pyodide route or
leave the script Python for good. harfbuzzjs 1.6.2 is a pure HarfBuzz 14.5.0 bump
Expand Down Expand Up @@ -170,7 +170,7 @@ built by fontTools and are correct. When the port happens:
The only thing that meets all six is fontTools itself under Pyodide (`@web-alchemy/
fonttools`, ~15 MB, pinned to a stale 4.56.0) --- CPython in WASM, which satisfies "no
Python" as a technicality and nothing more.
- One workaround was proposed and **declined**: stop pinning `opsz` on Source Serif, keep
- One workaround is **declined**: stop pinning `opsz` on Source Serif, keep
the axis, and have `print.css` ask for `font-variation-settings: "opsz" 11`. It would
sidestep the bug and cost ~70 KB in a PDF-only face, but it changes what the generator
produces to suit a tool defect. Do not re-propose it; the pin stays. Switching Source
Expand Down
Loading
Loading