diff --git a/.github/actions/run-gates/action.yml b/.github/actions/run-gates/action.yml
index 290e63a41..a00c889b1 100644
--- a/.github/actions/run-gates/action.yml
+++ b/.github/actions/run-gates/action.yml
@@ -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
@@ -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
diff --git a/WIP.A11y.md b/WIP.A11y.md
index b8aae7312..0e8241028 100644
--- a/WIP.A11y.md
+++ b/WIP.A11y.md
@@ -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.
diff --git a/WIP.Build.md b/WIP.Build.md
index e91dc2bb5..4d0d7ebc4 100644
--- a/WIP.Build.md
+++ b/WIP.Build.md
@@ -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.
@@ -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.
@@ -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),
@@ -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)
@@ -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`
@@ -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 `` in the
figure's container, and `tbdocs.mjs` does not put it in the `svgContentsMap` the workers
diff --git a/WIP.ExamplesBuild.md b/WIP.ExamplesBuild.md
index d8534142b..50a3a4172 100644
--- a/WIP.ExamplesBuild.md
+++ b/WIP.ExamplesBuild.md
@@ -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:
@@ -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
@@ -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
@@ -412,18 +411,15 @@ 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
@@ -431,8 +427,8 @@ One project per fence is unaffordable. What makes it tractable is that **IDE cos
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
@@ -440,9 +436,9 @@ Several samples share a generated project, and a `module`-slot sample's declarat
**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:
@@ -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.
@@ -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
@@ -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
`` 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
@@ -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.
diff --git a/WIP.Fonts.md b/WIP.Fonts.md
index be97efef4..bb5059c4a 100644
--- a/WIP.Fonts.md
+++ b/WIP.Fonts.md
@@ -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
@@ -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
diff --git a/WIP.Harness.md b/WIP.Harness.md
index 973c148a5..08608065c 100644
--- a/WIP.Harness.md
+++ b/WIP.Harness.md
@@ -144,8 +144,8 @@ comment in the file:
`E.[Hidden]` and `E.Hidden` are 0, `[Description("x")]` is a member of that name, and
`[NoSuchAttribute]` too. Code after the group (`[Hidden] Red = 5`) is refused, TB5182 at
`Red`. So the census records no attribute for such a group (`scripts/lib/census-scan.mjs`,
- probes in `check_twin_parsers.mjs`); it once did for the 35 `[_]` members VBCCR's
- `LongPtr` Enum declares in the samples.
+ probes in `check_twin_parsers.mjs`), the 35 `[_]` members VBCCR's `LongPtr` Enum declares
+ in the samples included.
- **A comment can sit anywhere**: inline `/* voffset &H00A8*/ Property Get X()` before a
declaration (DAO.twin), a trailing `' NOTE: ...` after a `]`, a whole `'` line *between*
two attribute groups (VBA/Strings.twin; 142 sites), or a `#If` between an attribute and
@@ -352,7 +352,7 @@ is written to stdout or stderr, ever**, it exits 0 with the `.exe` written when
in code nothing calls, and for an error the build reaches --- an unknown procedure or type,
an undeclared variable, no `Sub Main` --- it does not exit at all: it sits on a "Please
wait…" dialog at 100% forever. It is silent, falsely green, and hangs on the one case worth
-catching. Measured all three ways, on BETA 983 and 995 (the BUGS-TO-REPORT entry on
+catching. All three hold on BETA 983 and 995 (the BUGS-TO-REPORT entry on
`--buildAndExit32`).
**It runs the IDE on a private Windows desktop, and that is not decoration.** A build tool
@@ -388,7 +388,7 @@ reports. Two rules keep the cause readable. With its streams redirected, PowerSh
progress records and errors to stderr as CLIXML (`#< CLIXML`, as `tb-registry.mjs` also
found, below), so the script silences progress and writes a failure itself, as plain UTF-8
text. And a Win32 error read from PowerShell is not the call's: PowerShell makes calls of its
-own before the next statement runs, and a `CreateProcess` that had set 3 was reported as 203,
+own before the next statement runs, so a `CreateProcess` that set 3 reads as 203,
"The system could not find the environment option that was entered". Each call is made, and
its error read, in C#.
@@ -449,8 +449,8 @@ Seven rules about the harness, each a comment in the file:
helper the IDE is not a descendant of anything the harness spawned, so the helper reports
the pid on stdout and the harness kills that.
-**A crashing probe is a real risk.** Four `Debug.ExecuteHostCommand` argument-shape probes in
-one project took the compiler down repeatedly. Keep a question that might crash the compiler
+**A crashing probe is a real risk**: `Debug.ExecuteHostCommand` argument-shape probes take the
+compiler down repeatedly. Keep a question that might crash the compiler
in a project of its own, so the answer is attributable and one bad probe cannot cost the
other thirty their run.
@@ -548,8 +548,7 @@ Rules it follows:
failed due to licence error" and builds nothing. `#appOverlay` is a transparent element
over the whole page, shown while `lockUICount` is above zero: by every modal dialog, the
"Compiling..." and project-loading progress dialogs among them, and by an open menu. A press
- then fails with "its centre is covered by #appOverlay" (both were seen once each in a full
- `bug_repro verify`). A licence still unset after the wait throws; an overlay still up is
+ then fails with "its centre is covered by #appOverlay". A licence still unset after the wait throws; an overlay still up is
left to `click`, which names it.
- **A click that fails saves a picture of the page** (`pagePicture` in `tb-click.mjs`, CDP
`Page.captureScreenshot`, which works on the private desktop) under `%TEMP%\tb-click\`, and
@@ -911,7 +910,7 @@ copy. Put back as found, it would point `.twinproj` files at a folder that has b
and other sessions run `examples.bat` while add-in tests run copies, so the overlap is
ordinary. `startTidy` notes whether the association it recorded names the temp folder, and if
it did, `finishTidy` puts it back with every path to a copy's `twinBASIC.exe` replaced by
-`findIde()`'s, the newest install on the Desktop (owner, 2026-10-10): what that install's IDE
+`findIde()`'s, the newest install on the Desktop (owner): what that install's IDE
writes when it is next started. With no install outside the temp folder it leaves the
association as the IDEs set it and says so.
@@ -1196,7 +1195,7 @@ Eight rules:
gone. So a script that edits and then sets a breakpoint calls `waitForEditAnswer` first, as
`shoot_docs.mjs`'s `breakpointOn` and the `debugging` lane do. It waits until the file is no
longer `pendingChangeConfirmation`. A person rarely presses F9 that fast, and the defect is
- not queued (owner, 2026-10-12).
+ not queued (owner).
- **A click checks where its press lands** (the `assert` lane of
`ide-test.bat` shows it). The page can change between the call that aims and the press, a few
milliseconds to a hundred later on a busy page. In an editor the debugger has just
@@ -1382,7 +1381,7 @@ probe lanes:
list --- a check of a live site has to attach to it separately. The scenario
turns on `Runtime` to record page exceptions, which is how P12's `TypeError` is seen. Its
last test is not a numbered probe: a third button opens two tool windows given no id,
- which turn out to be one window.
+ which are one window.
- [test/addin/symbols.test.mjs](test/addin/symbols.test.mjs), P5: no add-in. It opens the
project in [test/addin/probes/symbols](test/addin/probes/symbols), which references tbIDE
and is never built, and asks the compiler about names in it the way the IDE's own code
diff --git a/WIP.HelpAddin.md b/WIP.HelpAddin.md
index 962722cee..f196abb0b 100644
--- a/WIP.HelpAddin.md
+++ b/WIP.HelpAddin.md
@@ -599,7 +599,7 @@ has the harness side of each item.
under the runner leaves its registry entries to the runner, which sweeps only the lanes'
folders.
-Stage 1's acceptance: both scenarios pass under `addin-test.bat`, and the whole registry,
+Stage 1 holds when both scenarios pass under `addin-test.bat` and the whole registry,
`IDESettings` included through hashes, is identical around the run.
- **Sample 10:** toolbar button, then its tool window, then a message box (the three-button
@@ -854,8 +854,8 @@ not: a restart saves a harness about a second against opening a new IDE.
rounds, and rows of 22.75 px overflowed a list of seven by a pixel each, which showed
its scrollbar. The vertical scrollbar is left on the list view's default, shown only
when there is something to scroll.
- - **The line between the list and the page** (Wayne, 2026-10-09: nothing ended the list
- and began the page) is the top border of `#helpPage` and `#helpSummary` while
+ - **The line between the list and the page** (Wayne: it ends the list and begins the
+ page) is the top border of `#helpPage` and `#helpSummary` while
`#helpResults` shows (a sibling rule on the list's inline `display`):
`2px solid color-mix(in srgb, var(--themeToolWindowBodyForeColor) 40%, transparent)`,
the theme's text colour at 40%, so it follows every theme with no rule of its own. The
@@ -931,7 +931,7 @@ not: a restart saves a harness about a second against opening a new IDE.
`Page.getFrameTree` reads its URL. The offline tree cannot be used: a page served over
http cannot frame a `file://` URL. So the lane needs `build.bat` first.
3. **Context: which `Add`?** Tested by the same lane on BETA 995. **The route is the
- page's socket** (the owner, 2026-10-04): the add-in's own parser would have to be close
+ page's socket** (owner): the add-in's own parser would have to be close
to a whole twinBASIC parser, so it waits, and the socket stands until upstream gives the
API a call for it. For the name under the cursor, F1 asks the compiler's hover about
the character the name starts from (`Words.NameColumn`); a selection is looked up as it
@@ -967,16 +967,16 @@ not: a restart saves a harness about a second against opening a new IDE.
the project's `Beep`.
- The socket code is in files of its own, `CompilerHover.twin` and `hover.js`, without
comments, so that it can be replaced whole once the API has a call.
-4. **A name with no page**, tested by the same lane on BETA 995, at the owner's
- request (2026-10-04): a user who documents their own code with `[Description]` sees it,
- and is not sent to a page for another name. When the hover names a package the index
+4. **A name with no page**, tested by the same lane on BETA 995 (owner): a user who
+ documents their own code with `[Description]` sees it, and is not sent to a page for
+ another name. When the hover names a package the index
does not document --- the project, another package, a type library --- F1 shows, in
place of the frame, the declaration, the description and where the name is declared,
all from the hover (P15), read by `Declared.ReadSummary`; a name with none says
`No description.`. *Open in browser* is disabled meanwhile, and showing a page puts the
frame back. The saved page is unchanged, so a restart shows the last page.
- `ReadHover` also reads `in library `, a class's or module's: a class of the
- project named as a documented one no longer finds that one's page.
+ project named as a documented one does not find that one's page.
- On a procedure's name where it is declared, the hover is the code-generation block, so
F1 says `No help for 'Beep': it is declared here`.
- **The lane's own race.** F1 on the name whose page shows already sets the same `src`
@@ -984,12 +984,11 @@ not: a restart saves a harness about a second against opening a new IDE.
`f1Shows` marks the page before pressing F1 and waits for a page without the mark. A
page showing again after a summary is waited for too, since its `src` can already be
right.
-5. **Offline help**, tested by the lane `help-offline` on BETA 995, at the owner's
- choice (2026-10-05): the add-in's own server, written in twinBASIC, serving one archive
- embedded in the DLL. The owner's other choices: the archive is a resource; the
- documentation build makes it (owner, 2026-10-06); the pane uses
- it when the DLL has it, and the live site otherwise; the symbol index stays a resource of
- its own.
+5. **Offline help**, tested by the lane `help-offline` on BETA 995 (owner's choice): the
+ add-in's own server, written in twinBASIC, serving one archive embedded in the DLL. The
+ owner's other choices: the archive is a resource; the documentation build makes it; the
+ pane uses it when the DLL has it, and the live site otherwise; the symbol index stays a
+ resource of its own.
- **The archive** is `add-in/Resources/HELP/site.zip`, gitignored, written from
`docs/_site-offline` by the `helpArchive` task of a build of the documentation, once
the offline tree is complete and before the Gantt chart goes into `BuildInfo.html`
@@ -997,7 +996,7 @@ not: a restart saves a harness about a second against opening a new IDE.
skips it, and CI never passes that, since the download carries the archive) (about
26 MB, 1,475 files; the DLL builds with it for
win32 and win64). An add-in built without it serves nothing
- and uses the live site, as before. The offline tree is the one served: its links are
+ and uses the live site. The offline tree is the one served: its links are
relative, so they work on any origin, and its pages hold only their part of the nav
tree.
- **No inflating in twinBASIC.** A deflated entry is sent as it is, between a 10-byte
@@ -1023,15 +1022,15 @@ not: a restart saves a harness about a second against opening a new IDE.
offline-only case asks the server from Node: a page's gzip body and a stored image
byte for byte, a folder's 301, a 404 and `HEAD`.
6. **Hover help**, tested by the `help` and `help-offline` lanes on BETA 997, with
- the owner's choices (2026-10-06): links in the IDE's hover under the mouse, behind one
+ the owner's choices: links in the IDE's hover under the mouse, behind one
*Hover help* checkbox in the settings panel (increment 8), off by default and kept with `SaveSetting`
(`Hover`, `On`). It shows a link per page the name has, at most five, labelled with the
name, its kind and where it is, as in *Help: MsgBox function (VBA.Interaction)* (the
- owner's wording, 2026-10-07; `SymbolIndex.Place`); a click shows that page in the pane,
+ owner's wording; `SymbolIndex.Place`); a click shows that page in the pane,
as F1 does. A name with no page shows nothing, since the IDE's hover already shows its
declaration and description. `SymbolIndex.Pages` is the lookup F1 and hover help share.
`HoverHelp` raises `Pick` with the page's path.
- - **At the cursor there is nothing** (owner, 2026-10-07). A widget above the line, added
+ - **At the cursor there is nothing** (owner). A widget above the line, added
through the public API once the cursor had rested on a name (P16), is not used: it
covers the line above whenever typing pauses on a name, does not look like the IDE's own
hover, and gives nothing the mouse hover and F1 do not. P16 says what it costs, should
@@ -1041,7 +1040,7 @@ not: a restart saves a harness about a second against opening a new IDE.
an earlier instance of the add-in left in `window.tbDocsHoverHelp`, and registers new
ones while the box is ticked. The provider is for `"twinbasic"`, so the links are the
hover's first lines, next to the name, where a long hover shows them without being
- scrolled (the owner's choice, 2026-10-06; a provider for `"*"` puts them below the
+ scrolled (the owner's choice; a provider for `"*"` puts them below the
compiler's text). The provider asks the compiler's hover itself, passes the
line, the column and the hover to the add-in, and waits up to 4 s for the markdown it
answers with (`window.tbDocsHoverHelp.answer`). The links point at the live site; a
@@ -1059,9 +1058,8 @@ not: a restart saves a harness about a second against opening a new IDE.
move it against the window's right edge before the mouse rests anywhere; and a hover
left open covers the toolbar, so each mouse case moves the mouse away at its end.
`test/addin/hover.mjs` holds the helpers both lanes use.
-7. **A detached window**, tested by the `help` and `help-offline` lanes on BETA 997,
- at the owner's request (2026-10-07): the pane can be moved to another monitor. The
- owner's choices: the add-in shows a window of its own, a twinBASIC Form holding the
+7. **A detached window**, tested by the `help` and `help-offline` lanes on BETA 997
+ (owner's request): the pane can be moved to another monitor. The owner's choices: the add-in shows a window of its own, a twinBASIC Form holding the
WebView2 package's control (P19), rather than a `window.open` from the page, which would
be page internals and which the host most likely sends to the browser (the host DLL
names `NewWindowRequested` and imports `ShellExecuteA`; not tried, since it might open
@@ -1070,7 +1068,7 @@ not: a restart saves a harness about a second against opening a new IDE.
**Attach** in the window's title bar hides the window and shows the pane. The window is
made once and then hidden and shown, so its page is kept. It has no owner window and
shows in the taskbar.
- - **The title bar** (owner, 2026-10-07) is one row drawn by the page, in place of the
+ - **The title bar** (owner) is one row drawn by the page, in place of the
Windows caption, which is light even in the dark theme. It is the IDE's tool-window
header: `.sectionHeader`, with `--themePanelHeaderBackColor` and `...TextColor`, the
theme's `...Border*`, `...Padding` and `...FontSize`, weight 600, the 6-dot grip at the
@@ -1175,8 +1173,8 @@ not: a restart saves a harness about a second against opening a new IDE.
caption (no read-back of the DWM attribute), Alt+F4, the registry read, maximizing on
a monitor other than the primary one (the lab machine has one), and the move
itself, whose loop needs the real mouse, which the harness's desktop has none of.
-8. **Settings**, tested by the `help` and `help-offline` lanes on BETA 997, at the
- owner's choice (2026-10-07): a gear at the right end of the pane's bar, and on the detached
+8. **Settings**, tested by the `help` and `help-offline` lanes on BETA 997 (owner's
+ choice): a gear at the right end of the pane's bar, and on the detached
window's title bar (in its bar under the Windows caption), opens a drop-down panel of
settings, one label row each. A click outside it, Esc, or the gear closes it; changing a
setting leaves it open. Each view has its own panel; the settings themselves go through
@@ -1186,7 +1184,7 @@ not: a restart saves a harness about a second against opening a new IDE.
`blur` closes the panel as well. The lanes' pane frame is on the IDE page's site, so one
case points it at `127.0.0.1` to check the shade with a cross-site frame.
9. **Only the packages the project references**, tested by the `help` and
- `help-offline` lanes on BETA 997, at the owner's choice (2026-10-07): F1, the hover links
+ `help-offline` lanes on BETA 997 (owner's choice): F1, the hover links
and the search offer the language's own entries and the packages the project references;
an *All packages* row in the settings panel, off by default and kept with `SaveSetting`
(`Packages`, `All`), offers every package. `LoadPackages` reads the references at
@@ -1198,7 +1196,7 @@ not: a restart saves a harness about a second against opening a new IDE.
F1 on a name only an unreferenced package documents says *No help for 'X' in the packages
this project references*.
10. **How a user gets an add-in with the archive.** The project file is a download, and it
- is the offline add-in (owner, 2026-10-09). Every docs build, CI's included, packs
+ is the offline add-in (owner). Every docs build, CI's included, packs
`add-in/` into `tB/IDE/AddIns/downloads/tbDocsHelp.twinproj` in the online tree
(`builder/addin-project.mjs`, the `addinProject` task), and *Getting the add-in* links
to it first, the source route second. It holds the files git tracks, the symbol index
@@ -1207,7 +1205,7 @@ not: a restart saves a harness about a second against opening a new IDE.
serves the pages itself with no internet; CI passes no `--no-help-archive` for that
reason. A build that wrote no archive packs none. The offline tree holds no copy, and
its links to the file go to the website. The reader still builds the two DLLs in the
- IDE. **Not done:** the DLLs themselves, built in CI --- the owner's plan (2026-10-07) is
+ IDE. **Not done:** the DLLs themselves, built in CI --- the owner's plan is
to build them there with everything else, once the compiler runs on Linux there.
Why the pack is made the way it is: [WIP.Build.md](WIP.Build.md#the-help-add-ins-project-file).
@@ -1283,9 +1281,9 @@ the route once the socket is gone, if the API never gains a call for it:
Recommended, and not yet confirmed:
- **Page internals are for probes only, with two exceptions the owner made**: increment 3
- asks the compiler's hover through the page's socket, until the API has a call for it
- (2026-10-04), and increment 6's mouse route registers a Monaco hover provider through the
- page (2026-10-06, P17). Otherwise the shipped add-in uses the public API and
+ asks the compiler's hover through the page's socket, until the API has a call for it,
+ and increment 6's mouse route registers a Monaco hover provider through the
+ page (P17). Otherwise the shipped add-in uses the public API and
`ShellExecuteW`, and whatever the API lacks is requested upstream. P4 showed the route
exists: any inline handler can reach the page's globals, `openEditors`, the socket to the
compiler and `hostAppObject` among them. `raiseEvent` in a list view's items is
diff --git a/WIP.IDE.md b/WIP.IDE.md
index fbb4d9e6f..ed7be4b79 100644
--- a/WIP.IDE.md
+++ b/WIP.IDE.md
@@ -3,14 +3,14 @@
The IDE section (`docs/IDE/`, `/tB/IDE`) is being rewritten so that every page is fit for
the book. The goal is that `docs/_book.yml` holds no IDE entry under `left_out:`. This file
covers the structure, the conventions, where each fact comes from, and how a chapter is
-written. Owner decisions, 2026-10-11:
+written. Owner decisions:
- task chapters plus a reference part;
- permalinks flattened, with redirects;
- the how-to material in `Features/Compiler-IDE` moved here;
- `docs/IDE/_Images/*.af` deleted.
-The section is not bound to the structure it had. The existing pages are raw material.
+The section is not bound to its current structure. The existing pages are raw material.
## What "book-ready" means for a page
@@ -45,7 +45,7 @@ children. Second person ("you") is allowed here, as in tutorials.
and the keyboard shortcuts, under `/tB/IDE/Reference/…`. Each entry gives the item, its
shortcut, one or two sentences, and a link to the chapter that explains it.
-- The keymap is shown as a table, not the JSON dump `Menu/Window.md` holds now. The table can be
+- The keymap is shown as a table, not the JSON dump `Menu/Window.md` holds. The table can be
generated from `main.js` if keeping it by hand proves error-prone. Decide that when the page is
written.
- Webpage and the other panes that are not in a chapter get a reference entry.
@@ -98,7 +98,8 @@ for contributors.
repeats the picture. A reference entry says what the item does. An item with nothing to say
beyond its label is left to the picture.
- **Headings** in sentence case, with no icon and no shortcut inside them.
-- **The section names no build**, except in a defect callout (owner's standing rule). Lines such
+- **The section names no build**, except in a defect callout's label, `> [!NOTE] BETA 1005`
+ (owner's standing rule; WIP.md rule 6). Lines such
as "Components list coming soon" go, unless the IDE itself shows that text and the page says
so.
- **Defects the twinBASIC developers already know of** (owner) are not queued, but a page that
diff --git a/WIP.Indexer.md b/WIP.Indexer.md
index 766cc0f5e..e871cd4d4 100644
--- a/WIP.Indexer.md
+++ b/WIP.Indexer.md
@@ -36,14 +36,14 @@ opens from a deeply nested clone otherwise.
from the working tree. A `sync` against a newer build rewrites the working-tree snapshots, and
the baseline has to stay what the pages say until the pages and the new snapshots are
committed together. It reads with a buffer of 1 GB because VB's snapshot is larger than the
-1 MB `execFile` default, which once made VB fall back to an audit.
+1 MB `execFile` default, and VB falls back to an audit with the smaller buffer.
-**The manifests live in the store, not in the repository.** They were committed once, and two
-things went wrong. After a pull, the manifest said the new build had been read while the store,
-which is not committed, held old sources or none, so `sync` printed "Up to date" over stale
-data. And a `sync` limited to some packages (`--package`) moved the whole manifest to the new
-build, so the packages it skipped looked current and were not. Now the manifest says only what
-was last fetched, a clone has none, and its first `sync` reads every package. Whether a package
+**The manifests live in the store, not in the repository.** A committed manifest goes wrong
+two ways. After a pull it says the new build has been read while the store, which is not
+committed, holds old sources or none, so `sync` prints "Up to date" over stale data. And a
+`sync` limited to some packages (`--package`) moves the whole manifest to the new build, so
+the packages it skipped look current and are not. So the manifest says only what was last
+fetched, a clone has none, and its first `sync` reads every package. Whether a package
is current is judged from what the store holds: its sources, an `api.json` in the format this
indexer writes, of the version the source has now, written by the scanner this checkout has.
@@ -51,8 +51,8 @@ indexer writes, of the version the source has now, written by the scanner this c
them*. The store records, beside each `api.json`, a SHA-256 over `scripts/lib/twin-api.mjs`,
`scripts/lib/twin-declarations.mjs`, `indexer/lib/api-model.mjs` and
`indexer/lib/vb6-preprocess.mjs` (line ends made alike, so a CRLF and an LF checkout agree).
-Without it a scanner fix left every store's `api.json` "current" and `analyze` compared output
-of the old scanner. With it, `sync` indexes the package again from the stored sources, with no
+Without it a scanner fix would leave every store's `api.json` "current" and `analyze` would
+compare output of the old scanner. With it, `sync` indexes the package again from the stored sources, with no
download and under the version it had. That is why the store keeps each package's `Settings`
file (the project name). The list is every module `api-model.mjs` imports, and the unit test
fails when an import is added and the list is not; `package-files.mjs` is not on it, because
@@ -65,11 +65,10 @@ paths are relative to `Sources/`.
## One scanner
**`parseTwin` in `scripts/lib/twin-api.mjs` is the only scanner**; the indexer has no lexer or
-extractor of its own. It had them once, and they misread what the shared scanner reads right:
-a field named for a keyword opened a container nothing closed, a `Public CoClass` was missed,
-so were Delegates and a `/*Private*/ Module`, and the values of a nested Enum and the fields of
-a nested Type were dropped. On the same input `parseTwin` found a superset. Two scanners also
-drift: the symbol index (`builder/package-api.json`) and the indexer would disagree about
+extractor of its own, which would misread what the shared scanner reads right: a field named
+for a keyword opening a container nothing closes, a `Public CoClass`, Delegates and a
+`/*Private*/ Module` missed, and the values of a nested Enum and the fields of a nested Type
+dropped. Two scanners also drift: the symbol index (`builder/package-api.json`) and the indexer would disagree about
one package, and each fix would be made twice. The scanner is pinned by
`scripts/check_symbol_index.mjs`, `scripts/check_twin_parsers.mjs` and
`test/indexer.test.mjs`; its header comment lists the ways to misread this corpus.
@@ -114,8 +113,8 @@ Branches of an `#If` are read as alternatives that each start from the block str
`format` changes whenever the shape does, and `sync` indexes a package again when the store
holds another number. Format 3 merges the declarations in the branches of an `#If`
(`#If VBA7 Then ... As LongPtr #Else ... As Long`) into **one member with a `signatures` list**,
-a condition on each. Before, the branches were separate members with no condition, so an
-`#If` pair looked like overloads and a changed condition changed nothing the differ could see.
+a condition on each. Separate members with no condition would make an `#If` pair look like
+overloads and hide a changed condition from the differ.
The differ reads the merged form, with the condition in front of each signature, so that a
changed condition, a new branch or an edited signature each modifies the one symbol of that
name and kind. The rules for what counts as an alternative (same block, name, kind, accessor
@@ -125,14 +124,13 @@ overloads) are in the README.
**A change to the fingerprinted code or to `format` means regenerating the committed snapshots
from the build they record (`indexed_from`), in the same commit as the change.** If it does
not, `analyze` compares the new scanner's output with the old scanner's and reports the
-scanner's changes as changes to the API. The BETA 997 snapshots were regenerated this way when
-the scanner changed. It needs that build's install, `sync --ide `:
+scanner's changes as changes to the API. It needs that build's install, `sync --ide `:
`--release` downloads only the newest release, and a plain `sync` reads the newest install,
which would write the snapshots of another build.
## Decisions of the owner
-- **Snapshots are committed only for the documented packages, to start with**: 14 files, 3.2 MB
+- **Snapshots are committed only for the documented packages**: 14 files, 3.2 MB
(about 350 KB compressed). The contributed packages and the extra CEF builds are indexed into
the store and left out of the repository. A package joins when its section does; `sync`
copies a snapshot for a documented package it indexed in that run, and gives a documented
@@ -174,12 +172,10 @@ which would write the snapshots of another build.
`builder/package-api.json` is the input of the help add-in's symbol index
([WIP.HelpAddin.md](WIP.HelpAddin.md#stage-3-the-symbol-index-generated-by-the-docs-build)).
-`twin-index derive` writes it (`lib/package-api.mjs`), and `scripts/build_package_api.mjs`,
-which wrote it before, is gone. That tool exported every package through the compiler
-harness's impexp into a cache and read the exported folders; `derive` reads the `.twinproj`
+`twin-index derive` writes it (`lib/package-api.mjs`). It reads the `.twinproj`
files with the reader `sync` uses and parses them with `parsePackageFiles`, the parse
-`buildApi` makes, so the two outputs come from one reading. Its output was byte-identical to
-the old tool's at BETA 997, and it takes half a second with no cache.
+`buildApi` makes, so the two outputs come from one reading. It takes half a second with no
+cache.
- **It reads the packages, not the snapshots or the store.** An `api.json` sorts types and
members by name and drops line numbers, and `apiSnapshot` needs declaration order: the
@@ -193,9 +189,8 @@ the old tool's at BETA 997, and it takes half a second with no cache.
- **It stays committed.** It is a build input `check_tree_fresh.mjs` watches, and CI has no
packages to derive it from. `derive --check` is outside every gate for the same reason.
- **A scanner change can change it**, as it can the snapshots. The `[ ]` Enum member (a
- bracket group alone on a line is a member) gave VB's `ReportBaseCtl.ReportSizeConstants`
- its 17 values at BETA 997, the first named `" "`, which the committed file lacked until it
- was derived again.
+ bracket group alone on a line is a member) gives VB's `ReportBaseCtl.ReportSizeConstants`
+ its 17 values at BETA 997, the first named `" "`.
## `draft` and the doc-draft workflow
@@ -205,8 +200,8 @@ the old tool's at BETA 997, and it takes half a second with no cache.
- **Placement is read from each section, not from a convention**, because the sections do not
share one: the built-in packages keep enumerations in `Enumerations/`, the contributed ones
beside the classes; a class is `X/index.md` with member pages, `X.md` with its members
- inline, or `_App/_App.md`. The draft workflow this replaced derived every path from one rule
- (`/.md`, `.md`), which matched no section exactly. Every rule in
+ inline, or `_App/_App.md`. One rule for every path (`/.md`, `.md`) matches no
+ section exactly. Every rule in
`draft-plan.mjs` is a majority over the section's own pages.
- **How well it places, checked by leaving each symbol out** of every section with a store
package (the thirteen built-in and default ones at BETA 997, and the nine contributed ones of
@@ -221,12 +216,12 @@ the old tool's at BETA 997, and it takes half a second with no cache.
folder the check had itself taken away. The four members that miss are documented unlike
the others of their kind in their type, or not at all. The rule that places a type in the
folder of the type returning it is limited to folders that already keep a type, because
- without that limit it moved Printer under Printers, Dictionary under JsonConverter and
+ without that limit it would move Printer under Printers, Dictionary under JsonConverter and
eight others that the sections keep at their roots.
- **A new section borrows its model pages from its group.** Its pages have no sibling of
- their kind to follow, so the plan of the first package drafted into a new section
- (tbRegExp) gave every task `model: null`, and the agents would have invented a page
- layout. A page of a kind its section lacks now takes a model from the group's largest
+ their kind to follow, so the plan of the first package drafted into a new section would
+ give every task `model: null`, and the agents would invent a page
+ layout. A page of a kind its section lacks takes a model from the group's largest
section that has one, of the same shape: a folder's `index.md` for a class that gets a
folder, never a one-page class.
- **A permalink is computed only where the rule holds for every existing page** of the
@@ -257,12 +252,12 @@ the old tool's at BETA 997, and it takes half a second with no cache.
- **The agents are Sonnet**, as the content agents of the contributed packages were; the
drafts are read against the sources before they are committed, which no model choice
replaces.
-- **The first run, tbRegExp 1.0.5.0 (28 pages, 5 batches), took 7 agents, 7 minutes and
- 0.96 M subagent tokens**, and its 30 samples compiled and ran on the first Check. The
- review still found what no compile finds: the drafts state only what the code says, so
+- **A batch of 28 pages in 5 batches takes about 7 agents, 7 minutes and 0.96 M subagent
+ tokens** (tbRegExp 1.0.5.0), and its samples compile and run on the first Check. The
+ review still finds what no compile finds: the drafts state only what the code says, so
where the package differs from the object it replaces (VBScript's **RegExp**, run under
- `cscript`) nothing said so, and claims read from the code but never run (`$0`, a lone `$`)
- needed a probe page. Running the doubts is now the Check agent's (above); the review
+ `cscript`) nothing says so, and claims read from the code but never run (`$0`, a lone `$`)
+ need a probe page. Running the doubts is the Check agent's (above); the review
still reads the pages against the sources.
- **It is not in the published docs.** `draft` is (Tools-Build), because its plans serve a
person writing pages by hand; the workflow is described here and in `WIP.md`, because
@@ -287,6 +282,6 @@ the old tool's at BETA 997, and it takes half a second with no cache.
describes a third party's HTTP API, which is neither a reason nor a decision, and folding 239
lines of it into this note would bury both. Its one reader is `lib/twinserv-client.mjs`, which
calls only `query` and `download`; the rest (login, publish, version check, the IDE-to-compiler
- commands) is there for whoever writes a publisher tool. It was reverse-engineered from
- BETA 983 (June 2026) and keeps no copy of the IDE files it came from. Check it against the
+ commands) is there for whoever writes a publisher tool. It is reverse-engineered from
+ BETA 983 and keeps no copy of the IDE files it came from. Check it against the
current IDE before relying on anything but those two endpoints.
diff --git a/WIP.PackageTests.md b/WIP.PackageTests.md
new file mode 100644
index 000000000..f3b301cc0
--- /dev/null
+++ b/WIP.PackageTests.md
@@ -0,0 +1,102 @@
+# Package test cases --- why they are built this way
+
+What to do is in [WIP.md](WIP.md) (*Where things live*, the contributed packages) and in
+[test/package-tests/README.md](test/package-tests/README.md). This file says why.
+
+## What the test cases are for
+
+A contributed section's NOTEs and WARNINGs are claims about someone else's code: that a member
+raises 53, that a match is cut short, that a dialog ignores a property. Each was true of one version
+when an agent checked it, and nothing kept that evidence: the probe pages were deleted after use.
+So the claims are written down as cases that persist, run again whenever the package's version
+moves, and say which claims no longer hold. The owner wants the same cases to be usable by the
+package's author, which decides most of what follows.
+
+## A case checks what is correct (owner)
+
+A case asserts the behaviour the package should have, not the behaviour it has. To the author a
+red case is a defect to fix and a green one is done, with a message that reads as a bug report.
+The documentation needs the opposite question answered too (does the page still describe the
+package?), and `expect.json` answers it: it records, per case and architecture, the exact failure
+report or that the case never returns, on the documented version. The runner's three verdicts
+come from comparing the two: *documented*, *fixed* (the callout goes, by the rule that the
+documentation is not a record of past defects), *changed*.
+
+The failure report is compared exactly, after the line number and the assertion prefix are
+stripped, so a defect that changes shape (a different wrong value) is *changed*, not
+*documented*.
+
+## Expect, not Assert (owner)
+
+twinBASIC's own form is a `[TestFixture]` module of `[TestCase]` Subs checked with the Assert
+package, and `tbrun --tests` runs those in the compiler's test mode. But in BETA 1005 the IDE has
+no way for a person to run a test case, and outside test mode a failed `Assert` stops the run at
+the error panel, uncatchable by `On Error` (WIP.Harness.md, *Running a project's test cases*). An
+author who opens the project would see the first failure only. So the cases check through
+`Expect.twin`:
+
+- **Under `RunAll`** (the project's `Sub Main`, which an author runs with F5), `Expect` collects:
+ `Expect.Done` records the case's report, and `RunAll` calls every case under
+ `On Error Resume Next` and prints `PASS`, `FAIL` or `SKIP` for each.
+- **Under `tbrun --tests`**, `Expect.Done` ends a failed case with `Assert.Strict.Fail` and the
+ whole report as its message. An `Assert` failure is the one thing a case's own
+ `On Error Resume Next` cannot hide; an `Err.Raise` from `Done` would be swallowed by it and
+ the case would pass. Checked on BETA 1005: the message comes back whole in the response's
+ `body.error`.
+- Every check of a case runs, and `Done` reports all failed ones, so one run of a case shows
+ every defect it covers.
+
+The `[TestFixture]` and `[TestCase]` attributes stay, so the cases are in twinBASIC's own form
+for whatever runner the IDE gains. `RunAll.twin` is generated rather than written, because a
+hand-kept list of calls drifts from the cases; the gate refuses a stale one.
+
+## A case that never returns
+
+Some defects are a loop that never ends (BigNumberLib's modular inverse of zero, TBMANLIB_Xml on
+some malformed text). In test mode such a case holds the compiler: `tbrun` gives up on it after
+its timeout, and every case after it would time out too. So the runner, on a case that does not
+return, records it and runs the cases after it again in a new IDE; once `expect.json` says
+`hangs`, the case is run alone with a short timeout, and `RunAll` skips it, because an author's
+IDE would hang.
+
+## The download carries no package (owner)
+
+Embedding the package in the published file would mean either the build fetching from TWINSERV
+(the deploy then depends on that server) or committing every documented version's `.twinpack`
+(about 35 MB, some of it WinDevLib, and four packages declare no licence that allows passing them
+on). The owner chose neither.
+
+The published project keeps the template's reference to the package and has no `Packages/`
+folder, which makes it a **linked** reference. Checked on BETA 1005 (private APPDATA, private
+desktop):
+
+- With the package's `.twinpack` in `%APPDATA%\twinBASIC\packages`, the project compiles. The IDE
+ finds the file by the package's project id, whatever its name, and takes the version from the
+ file: a 1.7.0.0 file under a reference that says 1.8.0.0 compiles against 1.7.0.0.
+- Without it, every name of the package is TB5079, and Enabled Libraries shows
+ `[LINKED-PACKAGE] ... ERROR unable to load this reference`; its FIX link is not implemented.
+- A file put in the folder while the IDE runs is used after Restart Compiler.
+- Nothing downloads a missing package by itself. The IDE's only download path is the Available
+ Packages tab (`importPackage` in `main.js`); a project load calls only `/packages/checkVersions`,
+ and only for a reference with a `publisher` field. `path32`/`path64` set to a file path or a
+ TWINSERV URL, or a `.twinpack` beside the project, are not used.
+
+So the reader puts the documented version's file in the packages folder (or adds the package from
+Available Packages with Embedded cleared) and restarts the compiler; an author points the folder at
+their own build. The runner embeds the package from `%TEMP%\tb-twinserv` instead, as
+`check_examples` does, so it never touches a packages folder.
+
+## Markers, and a gate that only tightens
+
+A callout names its cases in an HTML comment on the line above it. The marker lives with the
+callout, so moving or rewording the callout keeps its tie, and it is invisible on the page. The
+cases name nothing back: a case file referring to a page and a heading would break on every edit
+of the page. The gate checks both directions (an unmarked callout, an unnamed case) from the
+sources alone, so it runs in `test.bat` and CI, where the cases themselves cannot run.
+
+The sections were written before any project existed, so the gate starts with every section in
+`PENDING` and fails on a section that has a project and is still listed, or has none and is not:
+the list can only shrink, one package at a time.
+
+`none -- ` exists for a callout no program can check (a fact about the IDE, a licence); it
+keeps the decision explicit and visible in review, where a missing marker would be ambiguous.
diff --git a/WIP.Screenshots.md b/WIP.Screenshots.md
index 920c05857..5dd1c5079 100644
--- a/WIP.Screenshots.md
+++ b/WIP.Screenshots.md
@@ -2,8 +2,7 @@
The documentation's IDE screenshots go stale with every beta. `scripts/shoot_docs.mjs` retakes
them from a live IDE: every picture of the IDE the documentation holds, annotations included,
-and the program windows and web pages that sit beside them. The owner's decisions behind it are
-dated 2026-10-08 onward.
+and the program windows and web pages that sit beside them. The owner's decisions behind it are marked (owner).
## What there is to retake
@@ -18,7 +17,7 @@ Ten images under `docs/`, each for a stated reason:
- **The six GitHub walkthrough pictures** of `Documentation/Building.md` (compare-changes,
create-pull-request, merge-pull-request, confirm-merge, choose-workflow, run-workflow): the
- owner's to retake by hand, with a signed-in account (owner, 2026-10-09); no agent touches them
+ owner's to retake by hand, with a signed-in account (owner); no agent touches them
or their prose. They show base `main` and the old workflow name, and confirm-merge an e-mail
address, until then.
- **`packPublishPackage1`**: `packagePublisherPublish` opens the sign-in form when no publisher
@@ -27,7 +26,7 @@ Ten images under `docs/`, each for a stated reason:
- **`Tutorials/WebView2/Images/tbWebView2InAForm.gif`** is skipped (owner).
- **`favicon.png`** is never changed by this work (owner).
-## Decisions (owner, 2026-10-08)
+## Decisions (owner)
- **One tool.** `shoot_docs.mjs`; the help add-in's pictures are one setup among the others.
- **2x pictures, shown at half size**: every retaken picture gets `{:width="W" height="H"}` (half
@@ -48,15 +47,14 @@ Measured on BETA 997:
`body.scale150`, 22.67 px menu rows). `--force-device-scale-factor=1` in the WebView2 browser
arguments (`Lane`'s `browserArgs`) gives `scale100` and integer sizes;
`Emulation.setDeviceMetricsOverride` at `deviceScaleFactor: 2` then gives a clean 2x. Menus and
- dialogs come out at the CSS sizes of the pictures taken before the fix to the pixel (File
- 189x336, Manage Keyboard Shortcuts 1016x736).
+ dialogs come out at their 1x CSS sizes (File 189x336, Manage Keyboard Shortcuts 1016x736).
- **No-project start.** `Lane.open` always opens a project. `Lane.openNoProject()`
(`launchOnDesktop` with no argument, then `attachIde`) starts the IDE as it shows its splash and
then New / Open Project, with every menu in its no-project state. The lane's copy of the
install leaves out `projects\` (29 MB): New Project then shows 2 templates and no samples, so
a setup that shows them copies `projects\` into the lane's copy.
-- **Theme** (owner, 2026-10-09). Every picture is taken in the dark theme
- (`tbTheme_SwitchToDarkMode`) as `X.png`, byte for byte the picture it always was, and in the
+- **Theme** (owner). Every picture is taken in the dark theme
+ (`tbTheme_SwitchToDarkMode`) as `X.png`, and in the
**Light** theme (`tbTheme_SwitchToLightMode`, not Classic) as `X.light.png` beside it. The
site shows the light one in its light theme and the dark one in its dark theme, following the
toggle and, with no choice stored, the OS; pages are not edited, the build pairs the files by
@@ -82,7 +80,7 @@ Measured on BETA 997:
finished) — the same section;
- the Windows user name: the visible-text check (text boxes included) covers every picture,
run before the first capture and again when a capture is chosen, with the SVG under
- `--svg`: the page can draw again in between, as Project Settings did, putting the real
+ `--svg`: the page can draw again in between, as Project Settings does, putting the real
build path back over the template a shot had set in the page only (a shot that sets a
value types it, `input` and `change`, so the page keeps it).
@@ -94,10 +92,10 @@ Measured on BETA 997:
- **Cut-out**: a style sheet sets `html, body, #bodyInner` transparent and `body *` hidden
except the kept elements (`#rootMenu`, `#contextMenu`, `#contextMenuSUB`, or an
annotation layer), plus `Emulation.setDefaultBackgroundColorOverride` to alpha 0; both undone
- after. Verified: corners alpha 0, interior 255, the rounded bottom corners partial.
-- **Quiet page**: the existing style sheet against carets and animations goes into every shot,
- opaque ones included (the probe's IDE Options sample showed a caret without it).
-- **Write only on change**: kept from the current tool. The tool prints, for each picture, the
+ after. Corners are alpha 0, the interior 255, the rounded bottom corners partial.
+- **Quiet page**: the style sheet against carets and animations goes into every shot,
+ opaque ones included (IDE Options shows a caret without it).
+- **Write only on change.** The tool prints, for each picture, the
`{:width height}` its page should have and warns where the page disagrees; it does not edit
markdown.
- **A capture is not byte-stable.** `Page.captureScreenshot` of an unchanged screen alternates
@@ -124,8 +122,8 @@ Measured on BETA 997:
the element allows: the WebView2 download page's Version arrow (`#version
.px-dropdown__icon`, 8 by 9) changes a pixel by more than the noise between any two
captures, animations and transitions off. A shot without it compares byte for byte.
-- **Settle by condition, not by sleep.** Fixed sleeps were most of a run (a closed menu leaves a
- 2x8 px empty `#contextMenu`, so a wait for it to vanish always ran out its 2 s; ~3,000 calls).
+- **Settle by condition, not by sleep.** Fixed sleeps would be most of a run (a closed menu leaves a
+ 2x8 px empty `#contextMenu`, so a wait for it to vanish always runs out its 2 s).
A menu is closed when it holds no items; a dialog is still when a MutationObserver has seen no
change and its images and fonts are loaded; a floated panel when the IDE's `.flashElement` class
is gone; a help page when its fonts, images and body are settled. The page size is set only once
@@ -224,7 +222,7 @@ Exit codes: 0 every picture written or unchanged, 1 a picture failed, 2 the tool
light picture is narrower again (1498). Each comes out within the 32-pixel tolerance, so the
run passes, but the file can be rewritten for the width alone.
-## Standing rules for what a picture shows (owner, 2026-10-09)
+## Standing rules for what a picture shows (owner)
- **A state is reproduced as the IDE reaches it, never by editing what it draws.** The tool
inspects the IDE's page first to see how a state can be reached.
@@ -254,7 +252,7 @@ the page once real ones are shown, as History's times are.
Setup `code-chapter` (three parts): FeatureTour, the feature map's Inventory project, with
`test/shots/codechapter/` staged onto it (`Helpers`, `Ev`/`Ev2`, `Watcher`) and the project's
warnings set so that TB0016 is a hint and TB0012 information. Its 20 pictures are under
-`docs/IDE/Code/Images/`, plus `Menu_Edit` and `Menu_View`, now taken with the code focused.
+`docs/IDE/Code/Images/`, plus `Menu_Edit` and `Menu_View`, taken with the code focused.
- **Each picture starts from the default layout and its own tabs** (`chapterTabs`), and closes
Monaco's find widget first: the editor is one for every file, so a widget left open shows over
@@ -302,7 +300,7 @@ global object and WinNativeCommonCtls (for the Toolbox's COMMON CONTROLS group).
Command1, Check1, Frame1 holding Text2, and the control array Command2(0) and (1); `MyReport`
has a Label in Report Header and a Line and a Label in Report Detail. Its pictures are under
`docs/IDE/Forms/Images/` and `docs/IDE/Reports/Images/`, plus `Menu_Format_HorizontalSpacing`,
-now taken with Command1, Check1 and Command2(0) selected (the page's own example of **Left** 10,
+taken with Command1, Check1 and Command2(0) selected (the page's own example of **Left** 10,
140 and 250), so that Make Equal is live. `Designer_PackageNeeded` is taken in setup `settings`,
whose project does not refer to the VB package.
@@ -317,7 +315,7 @@ whose project does not refer to the VB package.
annotated picture, each label and arrow of the overlay from `layerBoxes`; the overlay's own
box is too big for it). The form, its menu bar and its readouts are compared only 2 pixels in
from their edges, a readout only between its rounded ends: the ground shows through their
- edges and shadows, and a 2-pixel band there failed Designer_Locked now and then. A cut-out
+ edges and shadows, and a 2-pixel band there fails Designer_Locked now and then. A cut-out
(`keep`) avoids the ground altogether.
- **The container's red outline is an animation** (`borderBlink`, the outline red half the
time), which the quiet style sheet stops where the outline is transparent. Controls_Container
@@ -383,7 +381,7 @@ whose project does not refer to the VB package.
`nameUserAsUserIn(c, ".debugConsoleOuter")` replaces the name there in the page; the console
is cleared with its button once the compiler is back.
- **The tooltips** (`Services_Unavailable_Tooltip`, `Services_Operational_Tooltip`) are
- **replicas** (owner, 2026-10-10). A native
+ **replicas** (owner). A native
`title` tooltip never appears on a private desktop: neither a CDP mouse move
over the badge nor a `WM_MOUSEMOVE` posted or sent to any of the page's three windows
(`Chrome_WidgetWin_0`, `Chrome_WidgetWin_1`, `Chrome_RenderWidgetHostHWND`) made a new
@@ -392,16 +390,16 @@ whose project does not refer to the VB package.
page, holding the badge's own `title` read live, styled by `TOOLTIP_CSS`, placed as Windows
places a tooltip by the cursor (cursor taken at the badge's middle; the badge is at the
window's bottom, so it goes above), captured alone and removed, in the badge's own setups
- (`project` for OPERATIONAL, `no-project` for UNAVAILABLE). Calibrated against the January
- pictures (154x73 and 158x73 px) by rendering the same text in puppeteer's Chrome at 1x: Segoe
- UI 12 px (9 pt), 16 px lines, `#242424` for the 1 px border **and** the text (the January
- pictures' darkest text pixel is 36 grey levels, not black), 7 px side padding, 3 above and 4
- below, the width rounded up to whole pixels. Result in a 1x render with the January text: the
- same size to the pixel and a mean luminance difference of 0.4 grey levels (maximum 9). In the
- IDE at 2x, shown at half size, OPERATIONAL is 158x73 like the January picture (its text a
+ (`project` for OPERATIONAL, `no-project` for UNAVAILABLE). The style is fitted to a real
+ Windows tooltip (154x73 and 158x73 px) by rendering the same text in puppeteer's Chrome at 1x: Segoe
+ UI 12 px (9 pt), 16 px lines, `#242424` for the 1 px border **and** the text (a real
+ tooltip's darkest text pixel is 36 grey levels, not black), 7 px side padding, 3 above and 4
+ below, the width rounded up to whole pixels. That gives the same size to the pixel and a mean
+ luminance difference of 0.4 grey levels (maximum 9). In the
+ IDE at 2x, shown at half size, OPERATIONAL is 158x73 (its text a
little softer than a 1x ClearType picture: mean difference 8 after a box-filter to 1x).
**The text is the IDE's live one**: with no project the badge's title reads each service
- `Not initialized` (the January picture says `Disconnected`, the state for 0.1 s between
+ `Not initialized` (`Disconnected` is the state for 0.1 s between
compilers), so the UNAVAILABLE picture is 155x73 and its alt text says Not initialized. The
replica does not follow the theme, so the Light pass takes the dark pixels and keeps no
`.light.png` file.
@@ -506,11 +504,10 @@ Taken on BETA 1005, in both themes where the theme shows.
on load says 1.0.0.10 is available. OLEGuids replaces WinDevLib in the page (WinDevLib
publishes weekly, so its line would change every run, and it is 23 MB).
- **`a1331a0e`** is the Enabled Libraries tab with the embedded package's tick ringed: BETA 1005
- no longer asks "Also remove the imported package ...", and `Updating a package.md` says the copy
- is deleted at once.
-- **`packPublishPackage1`** kept: `packagePublisherPublish` opens the sign-in form when no
+ deletes the copy at once and asks nothing, and `Updating a package.md` says so.
+- **`packPublishPackage1`** is not retaken: `packagePublisherPublish` opens the sign-in form when no
publisher is signed in, before the confirmation box.
-- **`e749e10f`**: the gear is the Project Explorer's (the old picture's toolbar gear is gone);
+- **`e749e10f`**: the gear is the Project Explorer's;
the dialog is shown whole with the gear beside it (2324 px wide; a smaller page does not make
the dialog narrower, it covers the gear). The full dialog fills Available COM References
(hidden, its text in the page), whose paths name the user: the shot waits for that list and
@@ -567,7 +564,7 @@ Taken on BETA 1005.
- `test/addin/help.test.mjs` fetches `/favicon.png` as its sample PNG, so `favicon.png` must stay
a PNG.
-## Machine state in the pictures (owner, 2026-10-08)
+## Machine state in the pictures (owner)
- **The IDE's settings** (View's ticks, IDE Options' values, the debugger options, user panel
layouts and keyboard groups) are set to their defaults **in the page only** — `liveIDEOptions`
@@ -576,10 +573,9 @@ Taken on BETA 1005.
- **About's licence line**: the tool waits for `licenceIsSet` before opening About, so it shows
the edition, never NOT READY.
- **The language tick** stays on whatever language the IDE runs in.
-- **`llvmdoc2`** shows the default thread count, 1 (the picture's earlier 10 was its author's
- setting).
+- **`llvmdoc2`** shows the default thread count, 1.
-## Project-setup pictures (owner, 2026-10-08)
+## Project-setup pictures (owner)
Project Settings, the panels, Project Explorer, Sample 6, Sample 15 and Global Search. In
BETA 997 Sample 6's control images are in the CustomControls package's Miscellaneous folder, not
@@ -593,7 +589,7 @@ the project's, and the tutorial says so.
value is changed to match.
- **An old-design picture is retaken in today's look**, with alt text and prose rechecked.
-## Designers, code and composites (owner, 2026-10-09)
+## Designers, code and composites (owner)
- **Composites** lay their parts out on the IDE's background in the picture's theme, with a gap,
code on the left and the panel on the right, one red arrow across.
@@ -650,13 +646,13 @@ the project's, and the tutorial says so.
the PNG was just captured from, and the build shows it in place of the PNG
([WIP.Build.md, Pictures as SVG](WIP.Build.md#pictures-as-svg)).
-**Owner's decisions (2026-10-09 and 10):**
+**Owner's decisions:**
- Text is real text, in per-picture subsets of the fonts the IDE drew it with, cut by fontTools
- (`scripts/subset_font.py`), hinting kept. Glyph outlines were rejected (they render poorly);
- harfbuzzjs was tried and removed ("don't mess with harfbuzz").
-- The PNG and the SVG are both committed. One SVG per theme: a single SVG switching on CSS
- variables and a `#light` fragment was proposed and dropped.
+ (`scripts/subset_font.py`), hinting kept. Glyph outlines render poorly and are not used;
+ nor is harfbuzzjs ("don't mess with harfbuzz").
+- The PNG and the SVG are both committed. One SVG per theme, not a single SVG switching on CSS
+ variables.
- Pages name `X.png` and the build substitutes; `png` in the attribute block keeps the PNG.
- `picture_svg` in `_config.yml` per output: online and book on, offline (and so the help
archive) off.
@@ -675,12 +671,12 @@ the PNG was just captured from, and the build shows it in place of the PNG
drawn as replicas of what Chromium paints: a closed `