Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
c1a170d
WNCC ListView.TextBackground: no visible effect on BETA 1005 (the opa…
KubaO Oct 14, 2026
8e76a21
WNCC Slider.BackColor: the control's background, shown only at its ne…
KubaO Oct 14, 2026
49bf02f
Queued slider-backcolor-no-repaint and listitem-backcolor-default-ref…
KubaO Oct 14, 2026
e66ae16
Build-time download placeholders: {{tbdocs:size:<path>}} for a publis…
KubaO Oct 14, 2026
78b29e7
Downloads page: the tools, the offline copy and PDF, and the package …
KubaO Oct 14, 2026
a538eb7
Never "cut" a release: publish it; the rule is in both vocabulary tab…
KubaO Oct 14, 2026
28afa0e
tbdocs: helpArchive, writePdf and checkBook run on workers; bookPaylo…
KubaO Oct 14, 2026
1b65078
Docs: helpArchive, writePdf and checkBook on workers, bookPayload, ma…
KubaO Oct 14, 2026
ea2798d
impexp packFiles packs a project from files in memory; the add-in pro…
KubaO Oct 14, 2026
3fba25f
Attributes: [PropertyPage] entry; the sites the BETA 1005 sweep accep…
KubaO Oct 14, 2026
0e5a0f2
tbrun --tests: a lone CR ends a line when compile cases are numbered,…
KubaO Oct 14, 2026
874bae6
VB6 import lane (vb6-import): the import keeps every Attribute line; …
KubaO Oct 14, 2026
769fbfb
Help archive leaves out Documentation Development, Videos and Challen…
KubaO Oct 14, 2026
fc68d17
WebView2 tutorial's animation is an animated WebP (1.08 MB, the GIF 3…
KubaO Oct 14, 2026
e50938a
Help archive leaves out every downloads folder, and the build refuses…
KubaO Oct 14, 2026
19fe95e
IUnknown: an Interface declared Extends Nothing with IUnknown's ident…
KubaO Oct 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 43 additions & 2 deletions .github/workflows/tbdocs-gh-pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ on:
branches: ["staging"]

# Allows you to run this workflow manually from the Actions tab.
# The manual path additionally cuts a GitHub release with the
# The manual path additionally publishes a GitHub release with the
# offline-browsable site copy attached as a zip; pushes to
# `staging` only deploy to Pages.
workflow_dispatch:
Expand Down Expand Up @@ -197,6 +197,39 @@ jobs:
# Workflow-internal hand-off to the release job; the
# release itself carries the long-lived copy of the zip.
retention-days: 7
# Every file the Downloads page offers from this site -- each
# same-site <a> on it with the `download` attribute -- goes into
# the release beside the zip and the PDF, so a download added to
# that page reaches the release with no change here.
- name: Collect the Downloads page's files
if: github.event_name == 'workflow_dispatch'
env:
OUT: ${{ runner.temp }}/downloads
run: |
node -e '
const fs = require("fs"), path = require("path");
const html = fs.readFileSync("docs/_site/Downloads.html", "utf8");
const out = process.env.OUT;
fs.mkdirSync(out, { recursive: true });
let n = 0;
for (const [tag] of html.matchAll(/<a\s[^>]*>/g)) {
if (!/\sdownload(?=[\s>=])/.test(tag)) continue;
const href = /\shref="([^"]+)"/.exec(tag)?.[1];
if (!href || /^[a-z]+:/i.test(href)) continue;
const rel = decodeURIComponent(new URL(href, "https://site/Downloads").pathname).slice(1);
fs.copyFileSync(path.join("docs/_site", rel), path.join(out, path.basename(rel)));
console.log(rel);
n++;
}
if (n === 0) throw new Error("no downloads found on docs/_site/Downloads.html");
'
- name: Upload downloads workflow artifact
if: github.event_name == 'workflow_dispatch'
uses: actions/upload-artifact@v7
with:
name: twinbasic-docs-downloads
path: ${{ runner.temp }}/downloads
retention-days: 7
- name: Upload book PDF workflow artifact
uses: actions/upload-artifact@v7
with:
Expand All @@ -216,7 +249,7 @@ jobs:
uses: actions/deploy-pages@v5

# Release job -- runs only on manual dispatch, after a successful
# Pages deploy. Each manual deploy cuts a new GitHub release whose
# Pages deploy. Each manual deploy publishes a new GitHub release whose
# tag tracks a publicly visible documentation snapshot at
# https://docs.twinbasic.com, with the offline-browsable copy of
# the site attached as twinbasic-docs-offline.zip.
Expand All @@ -238,6 +271,11 @@ jobs:
uses: actions/download-artifact@v8
with:
name: twinbasic-docs-book-pdf
- name: Download downloads workflow artifact
uses: actions/download-artifact@v8
with:
name: twinbasic-docs-downloads
path: downloads
- name: Compute release tag and name
id: tag
env:
Expand Down Expand Up @@ -265,9 +303,12 @@ jobs:

**PDF book:** download `twinBASIC Book.pdf` for an offline-readable, printable copy of the full reference.

**Tools and test projects:** the other files are the ones the [Downloads](https://docs.twinbasic.com/Downloads) page offers, as they were at this snapshot: the help add-in's project, the three editions of impexp, and the contributed packages' test projects.

Source commit: ${{ github.sha }}.
files: |
twinbasic-docs-offline.zip
twinBASIC Book.pdf
downloads/*
fail_on_unmatched_files: true
make_latest: 'true'
296 changes: 296 additions & 0 deletions BUGS-TO-REPORT.md

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions WIP.A11y.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ which refuses a tree older than the sources that produced it, then
[scripts/pick_a11y_sample.mjs --check](scripts/pick_a11y_sample.mjs) --- see
[Choosing the sample](#choosing-the-sample) --- and then
[scripts/check_a11y.mjs](scripts/check_a11y.mjs), which runs puppeteer +
axe-core over fourteen sample pages against WCAG 2.0/2.1/2.2 at Level A + AA
axe-core over sixteen sample pages against WCAG 2.0/2.1/2.2 at Level A + AA
(plus the `heading-order` best-practice rule) and exits non-zero on any
violation. It requires `build.bat` to have produced an up-to-date `_site/`.

Expand Down Expand Up @@ -55,7 +55,7 @@ axe excludes `aria-hidden` subtrees from rule evaluation wholesale, so the icon

**A closed `<details>` is invisible to the scan.** Its subtree is `notRendered`, so axe never walks it -- the same property that makes the section-links disclosure cheap is what hides it. A defect inside it (for example `target-size` on every link) would pass `check.bat`, because the state a reader sees after one click is never audited.

`STATE_AUDITS` in [scripts/lib/axe-scan.mjs](scripts/lib/axe-scan.mjs) closes that: entries layered onto the page x theme x viewport matrix that apply a DOM mutation from `PAGE_STATES` after navigation and before the audit. Two entries, each run across both themes and both viewports --- eight extra audits on top of the 56 the fourteen sample pages produce, for the 64 the scan reports.
`STATE_AUDITS` in [scripts/lib/axe-scan.mjs](scripts/lib/axe-scan.mjs) closes that: entries layered onto the page x theme x viewport matrix that apply a DOM mutation from `PAGE_STATES` after navigation and before the audit. Two entries, each run across both themes and both viewports --- eight extra audits on top of the 64 the sixteen sample pages produce, for the 72 the scan reports.

Three things about it are essential:

Expand All @@ -69,7 +69,7 @@ Syntax-highlight token colours are kept above 4.5:1 automatically: [builder/high

## Choosing the sample

The scan audits fourteen pages out of ~1,160, so its page list decides what it can report at all -- and a page list that stops being representative fails *silently*. A hand-picked list goes stale as the site grows around it: a sample with no table, image, `<details>` widget or video card leaves `image-alt`, the table rules and `scrollable-region-focusable` in the run options with nothing to run on, and the gate reports a clean pass.
The scan audits sixteen pages out of ~1,160, so its page list decides what it can report at all -- and a page list that stops being representative fails *silently*. A hand-picked list goes stale as the site grows around it: a sample with no table, image, `<details>` widget or video card leaves `image-alt`, the table rules and `scrollable-region-focusable` in the run options with nothing to run on, and the gate reports a clean pass.

The full-site sweep ([scripts/sweep_a11y.mjs](scripts/sweep_a11y.mjs) — every page, both themes, both viewports, 3,476 audits, ~20 min) is the check on the sample. It shows which constructs the sample has to contain: table wrappers (`scrollable-region-focusable`), footnote back-links (`link-in-text-block`), the FAQ's `<summary>` elements at the phone viewport (`target-size`), heading structure (`heading-order`), diagrams (`role-img-alt`) and code-block colours (`color-contrast`).

Expand Down
9 changes: 6 additions & 3 deletions WIP.Build.md
Original file line number Diff line number Diff line change
Expand Up @@ -784,8 +784,8 @@ The decisions, each with what it rules out:
commit behind the add-in being edited. `Resources/HELP/` is also refused by name, in
case it is ever added. A file git does not track is named in the build's output.
- **The archive in the project is the one this build wrote, handed over in memory.**
The `helpArchive` task returns the zip it wrote, and `addinProject` waits for it and
packs those bytes as `Resources/HELP/site.zip`. Reading the file back from
The `helpArchive` task (a worker task) returns the zip it wrote, as bytes, and
`addinProject` waits for it and packs those bytes as `Resources/HELP/site.zip`. Reading the file back from
`add-in/Resources/HELP/` could pick up the archive of an earlier build whenever this
one wrote none (`--no-help-archive`, `--serve`, another `--dest`, a fixture): that
case packs no archive, and the summary line says `without the offline archive`.
Expand Down Expand Up @@ -831,7 +831,10 @@ The decisions, each with what it rules out:
`deriveTreeRels` takes (`onlineOnlyRels`), so the link check resolves the page's link to
it and the publish sweep sees it. If the task stops writing,
`--check-audit-index` reports the file as indexed but not on disk.
- **The pack is `impexp.mjs`'s `importProject`, in process.** `builder/` may not import
- **The pack is `impexp.mjs`'s `packFiles`, in process and in memory.** It packs the
files as `importProject` packs them from a folder (`test/addin-project.test.mjs` pins
the two to the same bytes), with no copy through `%TEMP%`: staging the 53 MB there
took 200 ms to 1.9 s on a busy disk, against about 100 ms in memory. `builder/` may not import
`scripts/` (`biome.jsonc`), because `check_tree_fresh.mjs` would not see a change to
what the build imports. `impexp.mjs` is the one exception, by name in the rule: it
imports nothing, the build already publishes it through `bundle_extra`, and
Expand Down
66 changes: 63 additions & 3 deletions WIP.Harness.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,9 +190,10 @@ about a hundred builds by itself.
has one 2,514-character run of 263 pipe-separated identifiers, from `On|Off|Explicit` to
`UserDefinedTypeIsAnAlias`, with `DllExport|ComExport` in the middle of it. It mixes
keywords, attributes and object members (`Debug`, `Circle`, `PSet`), so a name in it is only a
candidate. Swept, exactly one name the page does not document is accepted anywhere:
`[PropertyPage]`, at eighteen sites, all of them members of a Class or an Interface. The
other 195 are refused at every site.
candidate. Swept, exactly one name the page did not document was accepted anywhere:
`[PropertyPage]`, at eighteen sites, all of them members of a Class or an Interface, with a
String argument only (any other is TB5163). It now has an entry. The other 195 are refused at
every site.

**Six things the sweep has to get right, each of which gives a wrong report when missed:**

Expand Down Expand Up @@ -233,6 +234,65 @@ they apply nearly everywhere or the compiler tolerates what it does not check. T
cannot say which, and neither can a clean build, which is also why *accepted but not
documented* is a list to read and not a list to copy into the page.

### The accepted sites already judged

Every *accepted but not documented* site of the BETA 1005 sweep was judged by asking what
the attribute does there against a control without it: the type library of an ActiveX DLL
built with `project.dllRegisterAfterBuild: false` and dumped with TLI
(`C:\Windows\SysWOW64\cscript.exe` and `TLI.TypeLibInfoFromFile`, since `tlbinf32.dll` is
32-bit), diagnostics from `tbbuild`, and output from `tbrun` and `tbrun --exe`. Where it did
something, the page now names the site. **The sites below were accepted and did nothing, or
nothing anyone could see, so the page leaves them out; a later sweep that lists them again
is not news.** Read a new sweep's list against this one and judge only what is not here.

- **Nothing reaches a type library from** a Module or its members, a CoClass (even with
`[CoClassId]` or `[COMCreatable]`), a generic or Private class, a Friend or Private member,
`Class_Initialize`, a WithEvents variable, an Enum, a Type, a Type field or a parameter. So a
type-library attribute there is accepted to no effect: `Restricted`, `Hidden` and
`Description` at those sites (the Module-member `Hidden` sites stay on the page, from the
packages' use, for IntelliSense), `DispId` on a Friend Sub, `COMExtensible`, `ComImport`
and `InterfaceId` on a generic or Private class.
- **On an Interface:** `ClassId`, `COMCreatable`, `CustomControl`, `EventInterfaceId`,
`EventsUseDispInterface`, `WindowsControl`, `SetDllDirectory`, `Debuggable`, and the three
run-time checks (they do not reach an implementing class). `AppObject` and `PredeclaredId`
there make the interface's bare name resolve, with no object behind it, and `PredeclaredId`
on a CoClass does the same given a `[Default]` interface line (TB5044 without): left off as
meaningless.
- **On a Class:** `ComImport`, `OleAutomation` (a class's interfaces are dual), and
`SetDllDirectory`, which shows nothing even at its documented sites.
- **On an Event:** `AllowUnpopulatedVtableEntry`, `BindOnlyIfNoArguments`,
`BindOnlyIfStringSuffix`, `CompilerOptions`, `Debuggable`, `DebugOnly`, `Enumerator`,
`IgnoreWarnings`, `EnforceErrors`, `EnforceWarnings`, the run-time checks, `TypeHint`,
`Unimplemented`. `DefaultMember` on an Event gives it DISPID 0 in the event interface,
which no client uses: left off.
- **On a Declare:** the run-time checks, `IgnoreWarnings`, `EnforceErrors`,
`EnforceWarnings`, `Debuggable`, `BindOnlyIfStringSuffix`, `SimplerByVals`, `IdeButton`,
`TestCase`, `ConstantFoldable` on a Declare Sub. `ConstantFoldable` on a Declare Function
crashes the build (the page's WARNING).
- **On a Const:** `BindOnlyIfNoArguments`, `Unimplemented`. **On a module, Private or
WithEvents variable:** `Unimplemented`. **On a public class variable:** `NonBrowsable`
sets no type library flag, though it stays on the page from the packages' use.
- **On a procedure:** `DLLStackCheck` (the exe is the same size), `ConstantFoldable` on a Sub
or a generic Function, `RunAfterBuild` on a generic Sub (never runs: the page's WARNING),
`RunBeforeStartupObject` on a generic Sub (crashes the build: WARNING), `TypeHint` on a Sub
(its argument is not even checked; on a Function or a Property Get it is, and its effect
there is IntelliSense, which no tool here reads), `TestCase` anywhere but a Sub of a
`[TestFixture]` Module (`tbrun --tests` never asks the compiler about it).
- **On a Delegate:** `SimplerByVals`.
- `BindOnlyIfStringSuffix` showed no effect at its documented site either, and
`ConstantFoldableNumericsOnly` folded a call with a String argument: both are on the page
without a claim the probes could make.

**An `Applicable to:` line is read by `parseTargets`** (`scripts/lib/attributes-doc.mjs`),
one target per phrase between commas and `and`s, from a short list of phrasings. Before
committing a changed line, read it back: `targetsOf` from `scripts/lib/attribute-sweep.mjs`
over `parseAttributes` of the page. The phrasings that misread: a leading article before
a word the rules anchor (`a Const` reads as nothing, `Const` as a module Const), a place
named in a later phrase (`procedure, variable in a Class` turns the bare `procedure` into
one in a Module), and `variables and procedures in a Class` anywhere in a line, which
returns those two targets and drops the rest. Pin each new line in `check_attribute_sweep.mjs`'s
`LINES`.

## Compiling a twinBASIC project without the IDE in front of you

Exported sources say what the compiler *accepts today*; they cannot answer a question no
Expand Down
5 changes: 4 additions & 1 deletion WIP.HelpAddin.md
Original file line number Diff line number Diff line change
Expand Up @@ -797,7 +797,10 @@ has the complete list):
its own rule. Giving that page a `/tB/` permalink, with the old one in `redirect_from:`,
would. Deferred.
- **The offline tree has no copy of the index.** The add-in embeds its own, and the help
archive (increment 5) is the offline tree as built.
archive (increment 5) is the offline tree as built, less `HELP_ARCHIVE_SKIP` in
`builder/help-archive-step.mjs` (the Documentation Development section, Videos,
Challenges, every `downloads` folder). `HelpServer` redirects any file the archive
lacks to `SiteUrl()`, because every page's navigation links to those sections.

### Stage 4: the add-in, in increments

Expand Down
2 changes: 1 addition & 1 deletion WIP.IDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ for contributors.
- **`shoot_docs.mjs` and WIP.Screenshots.md** hold the states the screenshots already set up,
which are often the quickest way to reach a dialog. A new picture is a new shot there, taken
in both themes; never make a picture by hand.
- The kit `.claude/tooling-review-scratch/beta995-probes/ide-driving-survey.md` (gitignored)
- The kit `.claude/tooling-review-scratch/ide-docs/ide-driving-survey.md` (gitignored)
maps the debugger, Settings and Export internals.

## Writing a chapter
Expand Down
5 changes: 4 additions & 1 deletion WIP.Screenshots.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ Ten images under `docs/`, each for a stated reason:
- **`packPublishPackage1`**: `packagePublisherPublish` opens the sign-in form when no publisher
is signed in, before the confirmation box, and a package is never actually published, so the
old file stays. **`packPublishComplete1`** is the result of a real publish, so it stays too.
- **`Tutorials/WebView2/Images/tbWebView2InAForm.gif`** is skipped (owner).
- **`Tutorials/WebView2/Images/tbWebView2InAForm.webp`** is skipped (owner). It is the
former GIF as an animated WebP (Pillow, `quality=80, method=4, allow_mixed=True,
minimize_size=True`: 1.08 MB against the GIF's 3.74 MB, the same 12,260 ms); a lossless
WebP of it is larger than the GIF, because the GIF's dithering changes every frame.
- **`favicon.png`** is never changed by this work (owner).

## Decisions (owner)
Expand Down
2 changes: 1 addition & 1 deletion WIP.Typography.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ centre far enough to intersect a neighbour. The link is sized outright
clearance.

Two rules follow. **Run the full sweep after any change that moves type
metrics**: the fourteen-page sample stays clean through such a change and would
metrics**: the sixteen-page sample stays clean through such a change and would
ship the regression. And **treat a target that passes only on the spacing
exception as unfixed**: the result depends on the font, the surrounding layout
and the viewport at once, and any of the three can move.
Expand Down
Loading
Loading