diff --git a/.github/actions/run-gates/action.yml b/.github/actions/run-gates/action.yml index dcd2021f1..28ecb0aec 100644 --- a/.github/actions/run-gates/action.yml +++ b/.github/actions/run-gates/action.yml @@ -103,6 +103,14 @@ runs: - name: Unit-test the harness's port claims (test/ports.test.mjs) shell: bash run: node --test test/ports.test.mjs + # Unit tests for builder/addin-project.mjs, which packs add-in/ into the + # help add-in's project file the build publishes: the project holds exactly + # the files git tracks there, never Resources/HELP/, and the same bytes from + # a CRLF checkout as from an LF one. Needs git. No browser, no built tree, + # about 1 s. + - name: Unit-test the help add-in's project file (test/addin-project.test.mjs) + shell: bash + run: node --test test/addin-project.test.mjs # A regex that backtracks exponentially does not fail a build, it stops # one: the corpus passes until a page happens to contain the trigger, and # then a render worker sits inside String.replace forever. Nothing else diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index ae0256219..121ade6dd 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -98,10 +98,11 @@ jobs: # step fails on the exit code: 1 when the check found a problem, # 2 when the build could not run. # - # --no-help-archive: a local build ends by zipping the offline tree - # for the IDE help add-in. CI has no use for that archive and must - # not spend time on it. - run: node builder/tbdocs.mjs --src docs --no-fetch-assets --no-help-archive --check-audit-index + # The build also zips the offline tree for the IDE help add-in and + # packs the zip into the add-in's project file, the download the site + # offers. Never pass --no-help-archive here: the download would lose + # the archive (check_ci_workflows.mjs refuses the flag). + run: node builder/tbdocs.mjs --src docs --no-fetch-assets --check-audit-index # The gates both workflows run, in one list: see # .github/actions/run-gates/action.yml, which check_ci_workflows.mjs # checks against test.bat and check.bat. Each gate is its own group in diff --git a/.github/workflows/tbdocs-gh-pages.yml b/.github/workflows/tbdocs-gh-pages.yml index 9de489c0a..3135a2d09 100644 --- a/.github/workflows/tbdocs-gh-pages.yml +++ b/.github/workflows/tbdocs-gh-pages.yml @@ -93,10 +93,11 @@ jobs: # online tree gets a base path -- the offline tree's links are all # relative after the rewrite. # - # --no-help-archive: a local build ends by zipping the offline tree - # for the IDE help add-in. CI has no use for that archive and must - # not spend time on it. - run: node builder/tbdocs.mjs --src docs --url '${{ steps.pages.outputs.origin }}' --baseurl '${{ steps.pages.outputs.base_path }}' --no-fetch-assets --no-help-archive --check-audit-index + # The build also zips the offline tree for the IDE help add-in and + # packs the zip into the add-in's project file, the download the site + # offers. Never pass --no-help-archive here: the download would lose + # the archive (check_ci_workflows.mjs refuses the flag). + run: node builder/tbdocs.mjs --src docs --url '${{ steps.pages.outputs.origin }}' --baseurl '${{ steps.pages.outputs.base_path }}' --no-fetch-assets --check-audit-index # The gates both workflows run, in one list: see # .github/actions/run-gates/action.yml, which check_ci_workflows.mjs # checks against test.bat and check.bat. checks.yml runs only on pull diff --git a/WIP.Build.md b/WIP.Build.md index 79dcf1ca5..ba32fab7c 100644 --- a/WIP.Build.md +++ b/WIP.Build.md @@ -129,7 +129,10 @@ keys through .NET, because `reg.exe` mangles names outside the console code page `lib/` — modules that every other tooling folder may import, and that import none of them. `builder/` may not import `scripts/`, so code that both need lives here; [lib/README.md](lib/README.md) states the rule, and `biome.jsonc` refuses an import that -breaks either one. +breaks either one. The rule has one exception, named in `biome.jsonc`: +`scripts/impexp.mjs`, a published standalone tool that must stay one dependency-free +file, which the build packs the help add-in's project file with (see [The help add-in's +project file](#the-help-add-ins-project-file)). `wisdom/` — Discord knowledge-harvesting tool (three-phase: export → process → extract). Plans in `wisdom/PLAN-{1,2,3}.md`; implementation under `wisdom/`. Uses only Node.js built-in APIs. Running it is [WIP.Wisdom.md](WIP.Wisdom.md). @@ -215,7 +218,7 @@ Unlike the link check, **a finding here aborts the build**. A broken link still leaves a tree worth inspecting; a tree with a private key in it is a tree nobody should be one `upload-pages-artifact` away from publishing. -Three details of the policy are essential: +Four details of the policy are essential: - **`SOURCE_EXTENSIONS` and `BUILD_EXTENSIONS` are separate sets, and must stay separate.** The build emits `.xml` and `.json`; a contributor has no business @@ -235,6 +238,14 @@ Three details of the policy are essential: ends spelled out, which is what makes them shippable. The same extension anywhere else still fails; otherwise declaring one entry would bless a whole type. +- **`addin_project` is exempt by path too, and on the tree surface only.** The + help add-in's `.twinproj` is written by the build, not found in `docs/`, so + `publishPolicyFor` puts its `dest` in a set of its own, `generated`, which only + `unpublishableTreePaths` reads. A `.twinproj` found in `docs/`, even at that + path, is refused at source: the build writes the file there, and a copy in the + source tree could only be a stale one. `check_publish_policy.mjs` asserts all + three: the path publishes from a tree, a `.twinproj` elsewhere does not, and + one at that path in `docs/` does not. **A clean build says only that nothing in `docs/` is currently refused, which is also what an allowlist widened until it refuses nothing says.** No build over a @@ -392,7 +403,7 @@ node scripts/check_tree_fresh.mjs --tree docs/_site-pdf --marker book.html **`--marker` is what makes that work on this tree.** The script identifies a tree by its `index.html`, which every output tree has *except* `_site-pdf/` --- that one holds a single `book.html`. Exit codes are the script's: **2** when the tree is -absent, **1** when it is older than `docs/`, `builder/` or `lib/`. The renderer that runs after it +absent, **1** when it is older than one of its sources (`DEFAULT_SOURCES`). The renderer that runs after it has no 1, so `book.bat`'s 1 means a stale tree (or a failed `npm install`) and nothing about the render. > **Batch detail:** `%ERRORLEVEL%` inside a parenthesised `if errorlevel 1 (...)` @@ -401,11 +412,14 @@ has no 1, so `book.bat`'s 1 means a stale tree (or a failed `npm install`) and n > `goto :fail` and captures outside the block, which is the same shape > `test.bat` uses, and for the same reason. -**Known false positive.** `DEFAULT_SOURCES` is `["docs", "builder", "lib"]` and -does not distinguish code from notes, so editing a `builder/PLAN-*.md` or -`REVIEW-*.md` marks every tree stale even though nothing in the build reads those -files. It errs toward refusing, which is the safe direction, and a rebuild is -~4 s --- but a pure note edit blocks a `book.bat` render until you rebuild. +**Known false positive.** `DEFAULT_SOURCES` is `docs`, `builder`, `lib`, `add-in` +and `scripts/impexp.mjs`, and does not distinguish code from notes, so editing a +`builder/PLAN-*.md` or `REVIEW-*.md` marks every tree stale even though nothing in +the build reads those files; so does a file under `add-in/` that git does not track, +which the add-in's project file leaves out. It errs toward refusing, which is the +safe direction, and a rebuild is ~4 s --- but a pure note edit blocks a `book.bat` +render until you rebuild. `add-in/` and `impexp.mjs` are there because they decide +the bytes of the [help add-in's project file](#the-help-add-ins-project-file). **Which folders under `docs/` are outputs comes from one list.** A build given `--dest docs/_site-basepath` writes `_site-basepath-offline` and @@ -421,7 +435,11 @@ prefix list (`_site`, `_serve`, `_pdf`) the markdown walk uses --- and keeps onl `page-baseline.json` and `symbol-baseline.json` (`IGNORED_FILES`) are written after the tree, so counting them would mark every tree that added a page or a heading stale on the next `check.bat`. `package-api.json` is an input: the build -reads it and it decides the bytes of `tB/symbols.json`. +reads it and it decides the bytes of `tB/symbols.json`. Under `add-in/` two paths are +skipped by their place in the repository (`IGNORED_PATHS`): `Resources/HELP/`, where +the build writes the help archive, and `Resources/SYMBOLS/symbols.json`, +the copy of the index the build rewrites. Neither is an input, since the project file is +packed with the index the build wrote rather than with that copy. ### The code-region gate @@ -642,6 +660,86 @@ members are placed before the rest**, so a member's URL is the heading under Properties and not a prose section of the same name above it (`Screen.Fonts` is `#fonts-1`, its `### Fonts`, not a prose `#fonts`). +### The help add-in's project file + +Every build of `docs`, CI's included, writes `tB/IDE/AddIns/downloads/tbDocsHelp.twinproj` +into the online tree: the `addinProject` task +([builder/addin-project.mjs](builder/addin-project.mjs)), declared by `addin_project` in +`_config.yml`, so the test fixtures' configs get none. The Help Add-In page offers it. +The decisions, each with what it rules out: + +- **What goes in is `git ls-files` of `add-in/`, read from the working folder.** A plain + walk of the folder packs whatever lies there: a stale `Resources/HELP/site.zip` + (gitignored, ~26 MB, from some earlier build), a `Build/` folder an IDE wrote, a + scratch module. Reading git's blobs instead would leave a local build's download one + 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 + `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`. + The archive is made **before** the Gantt injection, so it holds `BuildInfo.html` + without the chart: the chart holds the build's timings, and a download that held it + would differ from one build of a tree to the next. `recheckHelpArchive` checks at + the end of `runBuild` that the offline tree still lists the archive's files and + differs from it in `BuildInfo.html` and `assets/images/gantt.svg` alone. + `gantt.svg` is a tracked 105-byte placeholder in `docs/assets/images/`, copied into + both trees as a static file, so it is in the archive's file list; the injection + overwrites it in the trees and the archive keeps the placeholder. +- **The task waits for every writer of the offline tree.** `flushJoin` (the render + lanes write each page's offline HTML in their flush), `scss` (the offline + stylesheet) and `writeOffline` (statics, theme assets, redirect stubs, `nav.js`, + `search-data.js`). Nothing else writes there before the injection; a writer added + later and missed here fails the build in `recheckHelpArchive`, which is why that + check compares every file's content. +- **The offline tree has no copy, and its link goes to the website.** The project + carries the offline tree's archive, so a copy in that tree would be inside its own + archive. `deriveTreeRels` takes the path as an online-only file. The page links to + it relatively, and the offline rewrite (`computeWebsiteUrl` in `offline-rewrite.mjs`) + writes any URL the offline tree lacks and the online tree holds as `site.url` + base + path + path, without counting a miss. The offline link check would flag that URL + (the `forbid` prefix is the site's origin), so its worker env carries `forbidExempt` + (`isWebsiteOnlyLink`), which excuses a website URL only when the online tree holds + the path and the offline tree does not: a live-site link to a page the offline tree + holds is still a missed rewrite. `scripts/check_links.mjs`, which reads trees from + disk, makes the same exemption when `--online-root` names the online tree, and + `check_links_diff.mjs`'s two offline cases pass it. +- **Line endings are git's, not the checkout's.** `.gitattributes` checks + `add-in/Resources/` out `-text`, but not `Settings`, `Sources/*.twin` or the `.tbform`: + under `core.autocrlf` a Windows checkout holds them with CRLF and CI's Linux one with + LF, and `impexp` stores `Settings` and the `.tbform` byte for byte. So CRLF becomes LF + outside `Resources/` before packing, and `impexp` then gives the code files CRLF. Without + it the published file (built on Linux) and a local build differ, and so do + `compare_trees` sides built on different machines. +- **The index is the build's, not the committed copy.** `syncAddinIndex` never writes in + CI, so packing `add-in/Resources/SYMBOLS/symbols.json` would publish the committed copy, + one build behind whenever a page changed. The task packs `symbolIndex`'s `json`, so + the download holds the index of the pages it is published with; that is also why it + runs after `symbolIndex` and why `checkReport` waits for it. +- **The path is in the online tree from `dispatch` on.** It goes into the list + `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 + `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 + `check_tree_fresh.mjs` now watches it and `add-in/`. Not the compiler executable, + which CI does not have, and not `impexp.py`, which CI's Node job need not have. +- **`--serve` writes it, `--dry-run` does not.** The preview has the page that links to + it, and packing takes about 100 ms; the help archive is skipped under `--serve` because + it takes seconds and writes into `add-in/`, which this writes nowhere near. The `--serve` + download holds no archive. +- **Deterministic by construction.** `impexp` writes no time, numbers every revision with + a constant and sorts every folder by code point; `git ls-files` is sorted too. + `test/addin-project.test.mjs` packs the real `add-in/` and fixture repositories: + exactly the tracked files, no `Resources/HELP/` file from the disk, the given archive + byte for byte (and none when none is given), a CRLF and an LF checkout giving the + same bytes, and the file in exactly the trees it is given; and `checkHelpArchive` + against small trees. + ### Build-time counts as named values `{{tbdocs:pages}}` in a page renders as the number of pages the build diff --git a/WIP.HelpAddin.md b/WIP.HelpAddin.md index a24acd445..a75c496a0 100644 --- a/WIP.HelpAddin.md +++ b/WIP.HelpAddin.md @@ -859,6 +859,18 @@ 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 + `#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 + theme's own border for a value box (`--themePropertySheetValueBorderColor`, the search + box's) is about 1.4:1 on Light's white, and `PropertySheetEntryBorder` no better, so + the owner chose the text colour. It is on the page and not the list because the list's + height is its rows (`--helpRow`) and a border of its own would add its width to it, + which the height tests count; the owner chose 2px. The IDE has no such border for its own panels, which are + divided by a header band (`.sectionHeader`); the properties that look like candidates + (`SectionHeaderBorder*`, `PanelBorder*`) are empty or `0` in the Dark theme. - **Open in browser** opens the page the add-in last gave the frame, through `OpenUrl`, so it honours the test switch. A link followed inside the frame is not seen: the live site is on another origin, whose location the IDE's page cannot read. @@ -884,6 +896,20 @@ harness about a second against opening a new IDE. own IDE reads it; the registry tidy puts that one entry back (`restoreTheme`). Fault runs: without the handler only the theme-change case fails; with the site ignoring the parameter every case that shows a page fails. + **In the detached window the frame's storage throws** (`SecurityError`, both storages, + BETA 997): the window's page is a `data:` URL (`NavigateToString`), so the docs frame is a + frame of another site under an opaque top-level origin, with a DevTools target of its own + (type `iframe`). Until 2026-10-09 the head script applied `?theme=` there, and + `theme-toggle.js`, which read the choice back from storage alone, reset it to *system*, so + the page followed the window's WebView2, which is Windows' app mode; a link inside the + frame also lost `data-pane`. So the head script keeps both in the frame's `window.name` + too (`tbdocs::<1>`, written only by a page given either parameter, read only when + storage throws; a navigation inside the frame keeps it), and `theme-toggle.js` mirrors + the attribute the head script set. A pane frame is on the IDE page's own site and its + storage works. The lanes check both places in Light and Dark, the page given and a page + after a link, with the frame's `prefers-color-scheme` emulated to the other scheme + (`Emulation.setEmulatedMedia`, on the frame's own target in the window): before the fix, + all four window cases failed and the pane's passed. - **The page shows without the site's chrome**: `FrameUrl` adds `pane=1` too, which the head script keeps in `sessionStorage` as it keeps the theme and turns into `data-pane` on the root. `custom.scss` then hides the sidebar and the header (search box, @@ -978,9 +1004,11 @@ harness about a second against opening a new IDE. 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 last step of a local build of the documentation + `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` (`builder/help-archive-step.mjs` over `lib/help-archive.mjs`; `--no-help-archive` - skips it, and CI passes that) (about 21 MB, 1,471 files; the DLL builds with it for + 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 relative, so they work on any origin, and its pages hold only their part of the nav @@ -1188,6 +1216,18 @@ harness about a second against opening a new IDE. this project references*. 10. **Later:** how a user gets an add-in with the archive: the owner's plan (2026-10-07) is to build it in CI with everything else, once the compiler runs on Linux there. + **Done meanwhile (2026-10-09): the project file is a download, and it is the offline + add-in.** 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 + of the build that publishes it, and the archive that build wrote as + `Resources/HELP/site.zip` (the project is then about 28 MB), so the add-in built from it 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. What this + increment still adds is the DLLs themselves, built in CI. + Why the pack is made the way it is: [WIP.Build.md](WIP.Build.md#the-help-add-ins-project-file). **Lookup:** @@ -1249,9 +1289,10 @@ The add-in's code skeletons are written in Stage 4 against the compiler, with te - Build both bitnesses, which `buildAddin` does (P7). The documentation page is [docs/IDE/AddIns/Help.md](docs/IDE/AddIns/Help.md); its pictures are taken by - `scripts/shoot_help_addin.mjs` from `test/addin/helpdemo`, so retake them after a change the + `scripts/shoot_docs.mjs` from `test/addin/helpdemo`, so retake them after a change the pictures show, and update the page's *Getting the add-in* once increment 10 settles how users - get it. + get it. Until then the page offers the project file the docs build publishes, then the + source route. - Distribution is upstream's decision: the community add-ins list, or bundled with the IDE. - Take to upstream, with the probe results as evidence: the shortcut bug; a call to open a URL; a way to ask the compiler about the symbol at a position, whose answer hover already diff --git a/WIP.Screenshots.md b/WIP.Screenshots.md new file mode 100644 index 000000000..89421fc9d --- /dev/null +++ b/WIP.Screenshots.md @@ -0,0 +1,324 @@ +# Screenshots — the plan for `shoot_docs.mjs` + +The documentation's IDE screenshots go stale with every beta. `scripts/shoot_docs.mjs` (until +increment 1, `shoot_help_addin.mjs`, which took only the help add-in's eight pictures) retakes +them from a live IDE; this plan grows it to every picture of the IDE the documentation holds, +annotations included. The owner's decisions behind it are dated 2026-10-08. + +## What there is to retake + +About 212 raster images under `docs/` besides the help add-in's eight. By what producing them +needs: + +| class | count | what | +|---|---|---| +| IDE UI, plain | ~150 | every menu, dialog, panel and editor view is HTML in the IDE's page, so all are reachable over CDP | +| IDE UI, annotated | ~43 | arrows, boxes, rings, numbers and labels drawn on top; 8 of them composites of several crops | +| not IDE UI | ~18 | running exe windows, GitHub pages, the splash window, one GIF: out of scope | + +Stale already: About (BETA 953), New Project (BETA 950), `CallStack.png` (compiler v0.15.957), +`fafaloneIDEscreenshot1.png` (BETA 407), several package pictures (BETA 730). + +## Decisions (owner, 2026-10-08) + +- **One tool.** `shoot_help_addin.mjs` is renamed `shoot_docs.mjs`; the help add-in's pictures + become one setup among the others. +- **2x pictures, shown at half size**: every retaken picture gets `{:width="W" height="H"}` (half + its pixel size) on its page, as `docs/IDE/AddIns/Help.md` has. A JPG retaken becomes a PNG. +- **Menus are transparent cut-outs**, as today: the bar item and its drop-downs opaque, the rest + alpha 0. +- **Annotations are redrawn by the tool in one house style**, anchored to what they point at, + including the ones that only repeat the prose. +- **Composites are assembled by the tool** from element clips. +- **`fafaloneIDEscreenshot1.png`** (a community author's own annotated picture) and its downscaled + copy are replaced by a project-made full-IDE feature map with the same labels. + +## The IDE the tool drives + +Measured by the s103 probe (8 IDE runs, BETA 997): + +- **Fixed device scale.** The IDE page otherwise takes the host's scaling (1.5 here: + `body.scale150`, 22.67 px menu rows). `--force-device-scale-factor=1` added to the WebView2 + browser arguments gives `scale100` and integer sizes; `Emulation.setDeviceMetricsOverride` at + `deviceScaleFactor: 2` then gives a clean 2x. Menus and dialogs come out at the old pictures' + CSS sizes to the pixel (File 189x336, Manage Keyboard Shortcuts 1016x736). **The help add-in's + pictures change once when this goes in**: until now they followed the host's scaling. +- **No-project start.** `Lane.open` always opens a project. Started with no argument, the IDE + shows its splash and then New / Open Project, with every menu in its no-project state. Needs a + `Lane.openNoProject()` (`launchOnDesktop` with no argument, then `attachIde`). 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.** The pictures are of the dark theme (`tbTheme_SwitchToDarkMode`, as now). The menu + bar and dialog title bars follow the theme; drop-downs and dialog bodies are light in every + theme. +- **Opening things.** Menus open on a real mouse press on `#rootMenu`; submenus on a real + mouse move onto the item (~500 ms; wait for `#contextMenuSUB > .contextMenuItem`). Dialogs by + `executeIdeCommand` (`tbHelp_ShowAboutWindow`, `tbIde_ShowIDEOptions`, + `tbKeyboardShortcuts_ShowManageKeyboardShortcuts`, `tbPanels_ShowManagePanelLayouts`, + `tbProject_New`, `tbToolbox_ShowMoreComponents`), closed by a real click on their Close / + Cancel / OK button. +- **What the user's machine leaks into a picture**, and the answer for each: + - the recent-projects lists (twinBASIC and VB6) read the user's registry: overridden in the + page (`HostGetRecentsList`, `HostGetVB6RecentProjects`), nothing read or written; + - the IDE's settings (View's ticks, IDE Options' values, the debugger options, the language + tick, user panel layouts) live in `HKCU\...\twinBASIC_IDE\IDESettings`, not in the lane's + private APPDATA — see Machine state in the pictures; + - the licence line on About (`LICENCE: tB Licence: NOT READY` when the licence check has not + finished) — the same section; + - the Windows user name: the existing visible-text check stays and covers every picture. + +## Capture + +- **Opaque clip**: `Page.captureScreenshot` with a clip, snapped outward to whole device pixels + (`floor(v*2)/2`, `ceil((v+w)*2)/2`). Dialogs clip `.modalDialogContainer` (its 40 px shadow + falls outside). +- **Cut-out**: a style sheet sets `html, body, #bodyInner` transparent and `body *` hidden + except the kept elements (`#rootMenu<Title>`, `#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 + `{: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 + between versions a few pixels apart by 1-14 grey levels: dialog shadow edges, the status bar's + scaled Ko-fi bitmap, panel dividers, rounded corners. `captureBeyondViewport` and + `fromSurface: false` are stable but drop the scrollbars and the cut-outs' alpha, so neither is + usable. `capture` waits 1 s before the first capture (without it rounded-corner pixels come out a + grey level off and stay so), then takes up to 8, 150 ms apart: it keeps one equal to the file on + disk, else the first two in a row that agree, and fails if none agree. So a picture is rewritten + when the IDE draws something different, not when the noise flips. + **Left over, under the full parallel load:** a run now and then settles on the other version of + one or two pictures and keeps it through every capture, so a longer wait in `capture` does not + help: Diagnostics (34 pixels along an arrow's edge), 6ad7a172 (4 title-bar corner pixels), + Menu_Window_PanelLayouts (one submenu corner pixel), 1-3 grey levels each; each is unchanged + when its setup runs alone, and which one varies from run to run (`JOB_SECONDS` moves it, but is + not the whole cause). Put such a picture back rather than commit it; diffs in + `.claude/tooling-review-scratch/s103-shots/perf/diffs-final/`. +- **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). + 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 + `body` has its `scale100` class: the IDE picks 48 or 32 px New Project tiles from it. +- **A picture that shows state sets that state itself** rather than relying on the shots before it + (9eeffbcf, PackagePublishing_1, packLicenceFiles), so a picture is the same whichever IDE or + order takes it. + +## Annotations + +An SVG layer the tool adds to the IDE page above everything (`z-index` max, +`pointer-events: none`), drawn in CSS pixels, so it scales with the 2x capture and survives a +cut-out as a kept element. + +**Anchors**, resolved in the page at shot time: +- an element: a CSS selector, optionally narrowed by its text (`{css: ".buttonGroupItem", text: "Samples"}`); +- a span of code in the editor: a text search in the model (`{code: "NormalState", nth: 2}`), placed + with `editor.getScrolledVisiblePosition`, never a line number; +- a point of a rect: `center`, an edge or corner, with an offset (`{of: anchor, at: "top-left", dx, dy}`). + +**Primitives**: `arrow` (from, to; `bend` for a curve; several `to` for a fork), `box` (a row), +`ring` (an icon or a short word; a box with `rx` half its height), `underline`, `label` (text +beside an anchor: `left`/`right`/`above`/`below`), `badge` (a number). + +**House style** (CSS px at 1x): one red, `#E5252A`; 3 px strokes, round caps and joins, with a +1 px white halo so a stroke reads on light and dark UI; one solid triangular head, 5x the stroke +long; boxes and rings 3 px with 4 px padding; labels in the page's font (Segoe UI) 600 at 15 px on a pill (dark red +with white text over dark UI, white with red text over light); badges a filled red circle of +22 px with a white bold numeral. No hollow block arrows, no hand-drawn strokes. + +**Composites** (the five CustomControls code-to-property pictures, `Editor.png`, `DebugConsole.png`): +each part is captured as its own clip; the tool then lays the parts out in a full-window layer +in the same page (`<img>` of each part on a plain background, with a gap), maps each part's +anchors through its placement, draws the overlay across, and captures the layer. +**Native `<select>` lists** (Editor.png's four, a FAQ picture, one tutorial) open in an OS +window no CDP capture holds: the tool draws a replica list from the select's options, styled +as the IDE's own pop-up list, beside the select. + +## The tool + +`scripts/shoot_docs.mjs [--only <regex>] [--port N] [--ide <path>] [--jobs N] [--diffs <dir>]`, +with the current tool's exit codes (0, 1 a picture failed, 2 the tool could not run, 3 not put +back). + +- **Setups**: one IDE each --- `no-project` (menus, dialogs, panels), `help` (the add-in's eight, + `test/addin/helpdemo`), `project`, `sample`, the `settings` setups, `glyphs`, `global-search` + (Sample 15 exported from the install, built into the lane's copy as an add-in, its own project + opened), `sample6` (Sample 6 exported and opened) and `designer` (the sample fixture with + `test/shots/designer/` staged onto it, plus the Global Search add-in for Toolbar_3; it sets the + page's `currentDPI` to the 2x ratio, or the IDE's 5 s DPI check covers every designer with + RESYNC), and `forms` (the same without the add-in). A setup is `{name, start, prepare}`. + Every add-in a setup loads has its `SaveSetting` key snapshotted, emptied and restored + (`tbDocsHelp`, `GlobalSearchAddIn`). +- **Jobs**: `--jobs N` (default 6) runs setups at once, each IDE on its own claimed ports and + private desktop, and splits the big setups into parts, longest first; a full run is about + 110 s (it was ~765 s sequential; the jobs add up to over 600 s, so six IDEs cannot do much + better). `--jobs 1` runs one IDE per setup in table order. Per-run + state lives on the connection (`c.shot`), never in module variables. +- **Diffs**: `--diffs <dir>` (never under `docs/`) writes, for each picture that differs from the + file on disk, the committed picture, the new one and an amplified difference map side by side + (`scripts/lib/shot-diff.mjs`, `composeComparison` with `amplify` in `scripts/lib/png.mjs`). +- **Shots**: a table of `{out, setup, take, annotate?}`, `out` the picture's path under + `docs/`; `--only` matches it. `take(ctx)` brings the IDE to the state and returns the clip + (or the parts of a composite); `annotate` is a list of primitives. +- Shared helpers move from the probe kit (`.claude/tooling-review-scratch/s103-shots/ui.mjs`): + `openMenu`, `hoverItem`, `closeMenus`, `waitModal`, `closeModal`, the cut-out, the snap. + +## Increments + +1. **Rename and the no-project setup**: `shoot_docs.mjs`; the device-scale fix and + `Lane.openNoProject`; cut-out capture; the 19 menu pictures, `Menu.png` and the 13 dialogs + (About, IDE Options, the two Manage dialogs x2, New Project's tabs, Recent x2, Components + message, the FAQ's New Project Options and Samples, `llvmdoc2`); pages get their + `{:width height}`; alt text and prose checked against each new picture (the Tools menu has + two new entries; About's text changed). Retake the help add-in's eight with the scale fix. +2. **Done: the overlay** (`scripts/lib/shot-annotate.mjs`), on the annotated pictures of the + same setup (`7e1cb69c` New tab, `6ad7a172` and `ccSampleProject` Samples tab, + `tbWebView2Sample0`). Labels and badges take the IDE page's own font, Segoe UI, since the page + has no Inter and none is injected; the dark pill is `#8E161A`. The anchors search the main + document only, so increment 3's tool windows need shadow roots added. `label`, `box`, + `underline`, `bend`, forks and `{code}` anchors work in a probe but are in no picture yet. +3. **Project setups**: Project Settings with a filter, the panels, Project Explorer, the + Global Search add-in (Sample 15), and their annotated pictures. +4. **Designers and code**: a form with controls, the CustomControls sample project, Compiler + Constants; the composites and the replica select lists. +5. **The feature map**, replacing `fafaloneIDEscreenshot1.png` and `014a1d28`. + +6. **The rest**: the pictures left out of increments 1-5, by the owner's disposition below. + +## Increment 6 (owner, 2026-10-09) + +**Increment 5 is done**: `Features/Images/IDE-FeatureMap.png` (2880x1800, setup `featuremap`, +fixture `test/shots/featuremap/` FeatureTour) replaces `fafaloneIDEscreenshot1.png` and +`014a1d28`, both deleted. Twelve labels, each anchored to an element or code span; Problems is +labelled Diagnostics, its 997 title. Sticky scroll, advanced hover info, inline hints, folding +always shown and the debugger's memory figures are switched on in the page only; the memory +figures, which change from run to run, are fixed in the page once real ones are shown, as +History's times are. `Services_Operational` was retaken (setup `project`). Two of the three +pictures added to increment 5 were left: **`Services_Operational_Tooltip` is the badge's native +`title`**, which no capture holds; **Fusion's 569099635**: the `settings-fusion` project only ever +writes `[BUILD] failed to build Fusion server file '...'` (on load and after a compiler restart), +never the "successfully built" line -- a possible IDE defect, or the lane's staged layout; not +reproduced elsewhere. + +After increment 5, 53 images under `docs/` are outside the tool; the unreferenced +`Documentation/Images/environment-variables.png` was deleted (`7d5b5633`). The two left from +increment 5 join the groups below: the tooltip the special IDE states, Fusion the running +programs (it needs a project whose Fusion host really builds). The owner's +disposition, group by group: + +- **Special IDE states** (`IDE/Status Bar.md`'s `Services_Limited`, `Services_Unavailable` and + the three badges' tooltips -- native `title` tooltips, which no CDP capture holds, so these need + another route --, `Licence_CommunityEdition`; `IDE/Splash Screen.md`'s `Splash_Screen`): **inspect + the IDE page first to see how each state can be reproduced**, then generate them. A state is + reproduced as the IDE reaches it, never by editing what it draws. +- **Pictures of a running program** (`Anchoring-Docking.md`'s 3fa1cf2b, 0aeb25f6, fddbffa9, + 4829696d, bc9f3756, 599a66ad, 80185a8d; `GUI-Components/New.md`'s 4ad9c774, 5fc60b7b; + `Forms.md`'s 85f25aa2; `Inheritance.md`'s b0724fe2; `CodeLens.md`'s 351d0147; `Debugging.md`'s + 021f6cbf; `Toolbar_4`; `Creating a TWINPACK package.md`'s 8d74d820 and packPublishComplete1; + `Fusion.md`'s 569099635, a successful Fusion host build): + **write the program or programs that illustrate what each picture depicts, and run them while + the pictures are taken.** +- **Package server** (the other Features/Packages pictures: Importing from TWINSERV, Importing from + a TWINPACK file, Linked Packages, Updating a package, packPublishPackage1): **network access is + fine. Never actually publish a package**; everything short of that is allowed. The + byte-identical pair d9f1e4d9 / e749e10f is still the open question below. +- **Not of the IDE**: retake what can be reproduced. A `.vbp` import uses a **synthetic `.vbp` + project**, and the path is **scrubbed** from the picture (16833fae). The others in the group + (`Documentation/Building.md`'s GitHub and Affinity screenshots, `IDE/Webpage.md`'s `Webpage`, + `FAQs.md`'s 94490c87 and ac019c1a, `Package-Server.md`'s 5951dab6) get the same treatment where + they can be reproduced. **`Tutorials/WebView2/Images/tbWebView2InAForm.gif` is skipped**, and + **`favicon.png` is never changed** by this work. + +## Machine state in the pictures (owner, 2026-10-08) + +- **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` + and the like, in memory, never saved — so no picture shows the user's choices and nothing is + written to the registry. +- **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 old picture's 10 was its author's + setting); the LLVM page's prose is checked for any reliance on 10. + +## Increment 3 (owner, 2026-10-08) + +Survey of its 61 pictures and 17 glyph crops: `.claude/tooling-review-scratch/s103-shots/INC3-SURVEY.md`. +Built in three batches, one after another: no-project panels; a Standard EXE fixture with Project +Settings; Sample 6, Sample 15 and Global Search. **All three batches are done** (`b958f18a`, +`079185d0`, `74c6ed4d`, and batch 3 after `f916a99c`). In BETA 997 Sample 6's control images +are in the CustomControls package's Miscellaneous folder, not the project's, and the tutorial +says so. + +- **History's times are fixed in the page**, as the recent lists are, and its project is a + project-made fixture; left out only if the times cannot be overridden. +- **The glyph crops are retaken** (element clips, 2x, shown at half size). +- **A machine or account value becomes a neutral one** (Publisher blank or a project-made name; + the COM list shows this machine's, under the username check), and prose that names the old + value is changed to match. +- **Old-design pictures are retaken in today's look**, with alt text and prose rechecked. + +## Increment 4 (owner, 2026-10-09) + +Survey: `.claude/tooling-review-scratch/s103-shots/INC4-SURVEY.md`, four batches (designers and +Format menu; Properties and Toolbox; replicas and composites; code views). + +- **Composites** lay their parts out on the IDE's dark background (not white), with a gap, code + on the left and the panel on the right, one red arrow across. +- **Replica select lists** take one style: a white list, 1 px grey border, the IDE's font, the + current option on mid-grey `#6E6E6E` with white text. +- **The CustomControls tutorial's pictures** come from a project-made fixture, + `test/shots/customcontrols/`, referencing the CustomControls package, with a class using the + tutorial's names (`MyField`, `MyEnum`); the Toolbox may list two grids. +- **No mouse pointer** is drawn in any picture. +- (Main session) The designer pictures come from a `designer` setup that stages files onto the + sample fixture at run time; controls are put on forms by hand-written `.tbform` files. +- **Batch 1 is done** (designers, the Format menu, Toolbar_3). The report is made in the page by + Sources > Add > Add Windows Report. Toolbox_Report is the *docked* Toolbox: a floating one + lists every tool even with a report active (`reportDesignerMode` is set on `#bodyInner`, which + a floating panel is outside) -- a possible IDE defect, not yet reproduced or queued. After the + Format shots the designer marks frmControls changed with no control moved (cause unknown), so + those shots close it with Discard Changes. +- **Batch 2 is done** (the Anchors pair, the QR code beside the Toolbox, the FAQ's ICON crop, the + WebView2 tutorial's Toolbox and PROPERTIES). Setup `forms` is `designer` without the add-in; + `settings-webview2` also stages `test/shots/designer/webview2/` (frmWeb, a WebView2 `Web1` + with no DocumentURL, so the designer loads nothing), since only that setup references the + package. `MyOwnIcon.ico` is the template's icon copied at run time. 8611d12a's panels are put + in place by `restorePanelLayout` in the page and the default layout put back after; the crop + stops at History's title bar (its entries carry the real time) and left of the IDE's title. + In 997 PROPERTIES is grouped by category only (Anchors in LAYOUT, WebView2's own properties in + GENERAL), and **a property's description is never shown at the panel's foot**: it goes to the + designer's own `.proprtiesBoxFooter` inside the code panel, not the tool window -- a possible + IDE defect, not reproduced or queued. The WebView2 tutorial now points to the class reference + instead. A floating Toolbox for a form lists what the docked one does. +- **Batch 3 is done** (Editor.png, FindReplace_Direction, 2a1c71fd, 4c8b881e and the six + CustomControls property-sheet pictures). **Replicas** are the overlay's `list` primitive: the + select's options, current index and font, drawn as SVG at its bottom-left in the owner's + style, a scroll bar when only a window of rows shows; its rows are anchors. **Composites** are + `scripts/lib/shot-composite.mjs`: each part captured alone (a code part is the real editor + with the other lines hidden and the line, bracket and occurrence highlights off in the page; + a panel part a cut-out of PROPERTIES), anchors resolved while it shows, then laid out in + columns 40 px apart on the IDE background, drawn one to one, one arrow, captured. The fixture + `test/shots/customcontrols/` (setup `customcontrols`, two parts) has controls `MyGrid` and + `MyButton` and classes `GridColumn` and `MyButtonState` (the package has `Column`). 1b adds + ` = 42` in the page and reopens the form: an open designer never takes a new default and + shows no RESYNC (the page's TIP says it does; only a default value was tested). A custom + control's Left/Top/Width/Height read in twips, its `PixelCount` fields in pixels. +- **Batch 4 is done** (the Compiler Constants pair, flags-attribute, the CustomControls code + crops, ClassId insert, the ICustomControl hover, the custom property, the form JSON, + tbWebView2CreateEvent). `codePart` wraps `codeLines` (any open file; `include`, `extraRight`, + `annotate`, `hover`, `view`, `bare`). Setup `code` stages `test/shots/code/` (ConstantsDemo, + FlagsDemo); `buildConfiguration` changes `buildConfigSelector`, waits for the greying to move, + and clears the DEBUG CONSOLE, whose compiler-restart line names the user's folder. Inline + hints need *Always show IDE Inline Code Hints* (`codeHintsVisibility2`, off by default), set + in the page only. The ClassId hint shows only for `[ ClassId () ]` above a class, put in by + `executeEdits`: typed, the IDE reformats it to `[ClassId()]` with no hint. The JSON view shows + the file's text, so `frmCustomJson.tbform` holds `"MyField": 0` (in `frmCustom` it would + override `= 42` in 1b). Setup `sample9` opens Sample 9, whose `WebView_Create` is unchanged. + +## Open questions + +1. **The byte-identical pair** `d9f1e4d9` / `e749e10f` (Features/Packages/Images): merge into one + file when it is retaken (recommendation). diff --git a/WIP.md b/WIP.md index 6d7bbd1cb..9a71551d6 100644 --- a/WIP.md +++ b/WIP.md @@ -19,6 +19,7 @@ one covering what you are about to change. | change the accessibility scan | [WIP.A11y.md](WIP.A11y.md) --- the axe scan, the sample, the fingerprint gate | | work on the sample-compiling harness | [WIP.ExamplesBuild.md](WIP.ExamplesBuild.md) | | run or change Wisdom, the Discord harvester | [WIP.Wisdom.md](WIP.Wisdom.md) --- the three-phase pipeline and the Phase 3 extract flow | +| retake the documentation's IDE screenshots, or change the tool that takes them | [WIP.Screenshots.md](WIP.Screenshots.md) --- `shoot_docs.mjs`: the setups, cut-outs, the annotation overlay and the owner's decisions | | work on the IDE help add-in, or on testing IDE add-ins by machine | [WIP.HelpAddin.md](WIP.HelpAddin.md) --- the plan, the IDE facts it rests on, and the probes still open | | run a use-case evaluation round | [eval/README.md](eval/README.md) --- start every evaluator with `eval/run_case.mjs`, **never as a subagent**: a subagent inherits this session's `CLAUDE.md`, and with it this file, which is the answer key the corpus withholds | @@ -453,18 +454,19 @@ Why the report separates the wedged task from the merely blocked ones, and why ## Build / preview -- `build.bat` — runs `node builder\tbdocs.mjs --src docs --check-audit-index` (which implies `--check`) and produces three trees in one pass: the online copy at `_site/`, a `file://`-browsable copy at `_site-offline/`, and the sparse pagedjs source at `_site-pdf/`. The offline pass adds ~700 ms and the PDF pass adds ~150 ms on top of the ~2 s online build. Toggle `also_build_offline` / `also_build_pdf` in `_config.yml` (or pass `--no-offline` / `--no-pdf`) to skip a sibling output. `--check` adds ~1.7 s and runs the link + integrity check over the HTML while it is still in worker memory; `build.bat --no-check` gets a plain build. A local build also ends by writing the help add-in's archive, `add-in/Resources/HELP/site.zip` (see the help-archive bullet below); `--no-help-archive` skips it. +- `build.bat` — runs `node builder\tbdocs.mjs --src docs --check-audit-index` (which implies `--check`) and produces three trees in one pass: the online copy at `_site/`, a `file://`-browsable copy at `_site-offline/`, and the sparse pagedjs source at `_site-pdf/`. The offline pass adds ~700 ms and the PDF pass adds ~150 ms on top of the ~2 s online build. Toggle `also_build_offline` / `also_build_pdf` in `_config.yml` (or pass `--no-offline` / `--no-pdf`) to skip a sibling output. `--check` adds ~1.7 s and runs the link + integrity check over the HTML while it is still in worker memory; `build.bat --no-check` gets a plain build. A build also writes the help add-in's archive, `add-in/Resources/HELP/site.zip` (see the help-archive bullet below); `--no-help-archive` skips it, and CI never passes it. - `serve.bat` — runs `tbdocs --serve`: initial build, then a long-lived process with watcher, debounced rebuilds, and SSE-driven browser auto-reload. Writes to `docs/_serve/` (disjoint from `build.bat`'s `_site*/`) and skips the offline + PDF passes — so a one-off `build.bat` for the PDF or offline mirror doesn't disturb the live preview. Ctrl+C to stop. - `check.bat` — the gates that read the built site: a freshness check that refuses a stale tree (`scripts/check_tree_fresh.mjs`), the DOT diagram fit check (`scripts/check_dot_fit.mjs`), the a11y sample-coverage check (`scripts/pick_a11y_sample.mjs --check`), then the accessibility check (`scripts/check_a11y.mjs`). The link + integrity check runs in `build.bat`. ~37 s. -- `test.bat` — the tests the *toolchain* has to pass: the publish-allowlist self-test (`scripts/check_publish_policy.mjs`), the gate-list check (`scripts/check_gate_lists.mjs`), the CI-workflow roster check (`scripts/check_ci_workflows.mjs`), the lint gate (`scripts/check_lint.mjs`), the site-search unit tests (`node --test test/search.test.mjs`), the markdown-plugin unit tests (`node --test test/render.test.mjs`), the date-formatter unit tests (`node --test test/strftime.test.mjs`), the bug-reproducer picture tests (`node --test test/png.test.mjs`), `check_examples.mjs`'s probes (`node --test test/example-batches.test.mjs`), the port-claim unit tests (`node --test test/ports.test.mjs`), the regex-safety gate (`scripts/check_regex_safety.mjs`), the code-region gate (`scripts/check_code_regions.mjs`), the page-count drift-guard probes (`scripts/check_page_baseline.mjs`), the book-coverage probes (`scripts/check_book_coverage.mjs`), the symbol-index probes (`scripts/check_symbol_index.mjs`), the twinBASIC-scanner probes (`scripts/check_twin_parsers.mjs`), the attribute-sweep probes (`scripts/check_attribute_sweep.mjs`), the command-line probes and cases (`scripts/check_cli.mjs`), the pdf-lib shim comparison (`scripts/check_pdf_shims_equiv.mjs`), the impexp parity check (`scripts/check_impexp_parity.mjs`), and the axe source-patch verification (`scripts/check_axe_patch_equiv.mjs`). ~23 s, of which the regex-safety gate is ~10 s and the impexp check ~4 s. See [What belongs in test.bat rather than check.bat](WIP.Build.md#what-belongs-in-testbat-rather-than-checkbat). +- `test.bat` — the tests the *toolchain* has to pass: the publish-allowlist self-test (`scripts/check_publish_policy.mjs`), the gate-list check (`scripts/check_gate_lists.mjs`), the CI-workflow roster check (`scripts/check_ci_workflows.mjs`), the lint gate (`scripts/check_lint.mjs`), the site-search unit tests (`node --test test/search.test.mjs`), the markdown-plugin unit tests (`node --test test/render.test.mjs`), the date-formatter unit tests (`node --test test/strftime.test.mjs`), the bug-reproducer picture tests (`node --test test/png.test.mjs`), `check_examples.mjs`'s probes (`node --test test/example-batches.test.mjs`), the port-claim unit tests (`node --test test/ports.test.mjs`), the add-in project-file tests (`node --test test/addin-project.test.mjs`), the regex-safety gate (`scripts/check_regex_safety.mjs`), the code-region gate (`scripts/check_code_regions.mjs`), the page-count drift-guard probes (`scripts/check_page_baseline.mjs`), the book-coverage probes (`scripts/check_book_coverage.mjs`), the symbol-index probes (`scripts/check_symbol_index.mjs`), the twinBASIC-scanner probes (`scripts/check_twin_parsers.mjs`), the attribute-sweep probes (`scripts/check_attribute_sweep.mjs`), the command-line probes and cases (`scripts/check_cli.mjs`), the pdf-lib shim comparison (`scripts/check_pdf_shims_equiv.mjs`), the impexp parity check (`scripts/check_impexp_parity.mjs`), and the axe source-patch verification (`scripts/check_axe_patch_equiv.mjs`). ~23 s, of which the regex-safety gate is ~10 s and the impexp check ~4 s. See [What belongs in test.bat rather than check.bat](WIP.Build.md#what-belongs-in-testbat-rather-than-checkbat). - `book.bat` — renders the PDF from `docs\_site-pdf\book.html` via `node book\render-book.mjs` into `docs\_pdf\twinBASIC Book.pdf`. Run `build.bat` first to populate `_site-pdf/`; `book.bat` refuses a tree older than its sources rather than rendering the previous book (see [The book refuses a stale source tree](WIP.Build.md#the-book-refuses-a-stale-source-tree)). - `examples.bat` — compiles the documentation's own twinBASIC code samples, every `tb` fence marked `check_build`, and reports the ones the compiler refuses against the line in the page they came from; a statement sample also marked `check_run` is built and run, and what it prints is checked against the page. Needs a twinBASIC install and Windows, so it is outside every gate and outside CI; ~110 s over the 1,197 samples marked (33 of them `check_run`). `--build` also builds each project that compiles clean, and `--llvm` builds it with LLVM (see [WIP.ExamplesBuild.md](WIP.ExamplesBuild.md)). Two modes need no compiler at all: `--census` classifies every fence and says how many classifiable ones are still unmarked, and `--report <survey.json>` groups a saved `--propose --json` survey by diagnostic, section and unresolved name. `--propose` itself does compile. See [Compiling the reference's own code samples](#compiling-the-references-own-code-samples) and [WIP.ExamplesBuild.md](WIP.ExamplesBuild.md). - `addin-test.bat` — tests IDE add-ins by operating an IDE: every lane in `test/addin/lanes.mjs` builds the add-ins it tests into a private copy of the install, opens a project and checks what the add-in does. Outside every gate and outside CI for the same reasons as `examples.bat`; about two minutes for the seventeen lanes today (BETA 997): Samples 10 and 15, the thirteen probe lanes behind Stage 2's answers in [WIP.HelpAddin.md](WIP.HelpAddin.md), and the help add-in in `add-in/` twice, built with its committed copy of the symbol index, `add-in/Resources/SYMBOLS/symbols.json`, which a local build rewrites whenever the index changes. The `help` lane serves the built `docs/_site` on localhost to the add-in's pane; `help-offline` builds the add-in with a help archive of `docs/_site-offline` and the pane's pages come from the add-in's own server. So run `build.bat` first. Exit 0 every lane passed and the registry is as it was found, 1 a lane failed, 2 the harness failed, 3 the registry or a work folder was not put back. See [Driving the twinBASIC compiler](#driving-the-twinbasic-compiler) for its rules. - `ide-test.bat` --- the same runner for scenarios that operate the IDE itself rather than an add-in: every lane in `test/ide/lanes.mjs`, base port 9660. Same exit codes, same standing outside every gate and outside CI, same rules. - `try-help-addin.bat` --- opens one IDE on the user's desktop with the help add-in built and loaded, to try it by hand, and puts the registry back once it is closed (see [Driving the twinBASIC compiler](#driving-the-twinbasic-compiler)). Needs `build.bat` first. Exit 0 closed and put back, 1 the add-in or the project did not compile, 2 the tool could not run, 3 not put back. -- `node scripts/shoot_help_addin.mjs` --- writes the help add-in's documentation screenshots, `docs/IDE/AddIns/Images/Help_<Name>.png` (Pane, Hover, Search, Choices, Description, Settings, Window, Toolbar): it builds the add-in as `try-help-addin.bat` does, opens `test/addin/helpdemo` in an IDE on a **private** desktop (dark theme, `TB_DOCS_HELP_REAL_TITLEBAR=0`, the page overridden to 1280x880 at 2x), operates it and captures each picture with `Page.captureScreenshot` and a `clip`. A picture is rewritten only when its bytes differ, and a second run reports every one `unchanged`. The page's animations and carets are switched off through a style sheet put into the document and every shadow root; no picture holds the DEBUG CONSOLE (its lines carry a time). **The Windows user name must never be in a published picture**: the visible text of the page is searched for it before a picture is kept, and the run fails when it is there. The tool finds each name it presses F1 on by searching the demo's source, not by line. The add-in is built on a port of its own (`Lane.addAddin`'s `port`): the building IDE's WebView2 process can hold its port after the IDE ends for longer than `launchIde`'s ten seconds. No wrapper; outside every gate and outside CI, as `try-help-addin.bat` is. Exit 0 every picture written or unchanged, 1 a picture failed, 2 the tool could not run, 3 not put back. Run `build.bat` first. -- **The help archive is written by `build.bat` itself, as its last step.** `builder/help-archive-step.mjs` writes `add-in/Resources/HELP/site.zip` from `docs/_site-offline` through `lib/help-archive.mjs`, after the Gantt injection (which rewrites `BuildInfo.html` in the tree), and reads the file back to verify it. A mismatch fails the build (exit 1). The help add-in embeds the zip as a resource and serves the pages from it with its own server; a build of the add-in without it uses the live site. The file is gitignored (about 21 MB), so a build of the add-in from a clean clone is the online one until `build.bat` has run. **`--no-help-archive` turns the step off, and both CI workflows pass it** (`check_ci_workflows.mjs` requires it, in `ALLOWED.requiredBuildFlags`). It runs only for the guarded source tree (`docs`) built into the default `docs/_site-offline` (not another `--dest`, so `compare_trees` and `check_links_diff` leave it alone), never under `--serve` or `--dry-run`, never without an offline tree, and not keyed on `$CI`. There is no command-line tool for the archive: the `help-offline` lane makes its own with `writeHelpArchive`. The zip's format is fixed by the add-in's reader, which does no inflating ([WIP.HelpAddin.md](WIP.HelpAddin.md), Stage 4, increment 5). +- `node scripts/shoot_docs.mjs` --- writes the documentation's IDE screenshots (plan and owner's decisions: [WIP.Screenshots.md](WIP.Screenshots.md)). Each picture is a shot in a table, `out` its path under `docs/` (`--only` matches it), in a setup (the main three below; the full list is in WIP.Screenshots.md), each one IDE on a **private** desktop started only when a shot in it is selected: `help` (builds the add-in as `try-help-addin.bat` does, opens `test/addin/helpdemo`, `TB_DOCS_HELP_REAL_TITLEBAR=0`: the eight `docs/IDE/AddIns/Images/Help_<Name>.png`), `no-project` (`Lane.openNoProject` with `includeProjects`: the menus, cut out with alpha, the dialogs that need no project, and the empty panels, each floated on its own, after the dialogs) and `project` (the demo without the add-in: About, which needs the compiler's licence answer, and IDE Options, whose font lists need a project). Every IDE gets `--force-device-scale-factor=1` (`Lane`'s `browserArgs`), else the page follows the display's scaling; the page is overridden to 1280x880 at 2x, dark theme, and `PAGE_DEFAULTS` puts the IDE's options, panel layouts and keyboard groups to their defaults and the recent lists to the tool's own **in the page only**, its save calls replaced first so nothing is written. Each picture is a `Page.captureScreenshot` with a `clip`; a page showing it gives it `{:width height}` of half its pixel size. A shot's `annotate` list (arrows, rings, badges...) is drawn by `scripts/lib/shot-annotate.mjs` as one SVG layer over the page before the capture and removed after it, the clip growing to hold it. A picture is rewritten only when its bytes differ, and a second run reports every one `unchanged`; a capture is not byte-stable, so `capture` takes up to eight and keeps one equal to the file on disk. `--jobs N` (default 6) runs the setups at once on their own ports and desktops, a full run in about 110 s (`--jobs 1`: one at a time); `--diffs <dir>` writes the old picture, the new one and an amplified difference map for each that changed. Settle by a condition, never a fixed sleep, and a picture that shows some state sets it itself rather than relying on the shots before it. The page's animations and carets are switched off through a style sheet put into the document and every shadow root; no picture holds the DEBUG CONSOLE (its lines carry a time). **The Windows user name must never be in a published picture**: the visible text of the page is searched for it before a picture is kept, and the run fails when it is there. The tool finds each name it presses F1 on by searching the demo's source, not by line. The add-in is built on a port of its own (`Lane.addAddin`'s `port`): the building IDE's WebView2 process can hold its port after the IDE ends for longer than `launchIde`'s ten seconds. No wrapper; outside every gate and outside CI, as `try-help-addin.bat` is. Exit 0 every picture written or unchanged, 1 a picture failed, 2 the tool could not run, 3 not put back. Run `build.bat` first. +- **The help add-in's project file is written by every build, CI's included.** The `addinProject` task (`builder/addin-project.mjs`) packs `add-in/` into `tB/IDE/AddIns/downloads/tbDocsHelp.twinproj` in the **online tree only**, which the Help Add-In page offers as a download; `_config.yml`'s `addin_project` declares both ends, so a fixture config gets none. It waits for the `helpArchive` task. It packs the files `git ls-files` lists there, read from the working folder, never a `Resources/HELP/` file from the disk, with CRLF made LF outside `Resources/` (so a Windows and a Linux build write the same bytes), the `tB/symbols.json` the same build wrote in place of the committed copy, and **the zip that build's archive task wrote** as `Resources/HELP/site.zip`, passed in memory (a build that wrote none --- `--no-help-archive`, `--serve`, another `--dest`, a fixture --- packs without it, and the summary line says `without the offline archive`). The download is about 28 MB with the archive, 1 MB without. The offline tree holds no copy (the project carries that tree's archive, so a copy there would be circular), and its links to the file are rewritten to the website by the general rule in `offline-rewrite.mjs`: a URL the offline tree lacks but the online tree has becomes `site.url` + path, and is no miss (`computeWebsiteUrl`; the offline link check's `forbidExempt` excuses exactly those links, `isWebsiteOnlyLink`). The pack is `importProject` from `scripts/impexp.mjs`, the one module under `scripts/` that `builder/` may import (`biome.jsonc`); `check_tree_fresh.mjs` watches it and `add-in/` for that reason. The publish allowlist exempts the one path in the trees only (`publishPolicyFor`'s `generated`); a `.twinproj` anywhere else, or in `docs/`, is refused. `--serve` writes it into its one tree; `--dry-run` writes nothing. A failure (no git) fails the build with exit 1 and the index audit names the missing file. **Don't build the DLLs in CI from it**: that needs a twinBASIC install; the download is the project, and the reader builds it. Why each of these: [WIP.Build.md](WIP.Build.md#the-help-add-ins-project-file); `test/addin-project.test.mjs` is the gate. +- **The help archive is the `helpArchive` task of the build's graph.** `builder/help-archive-step.mjs` writes `add-in/Resources/HELP/site.zip` from `docs/_site-offline` through `lib/help-archive.mjs`, once every task that writes into the offline tree has finished (`flushJoin`, `scss`, `writeOffline`; the archive task lists exactly those) and **before** the Gantt injection, so the zip holds `BuildInfo.html` without the chart and `assets/images/gantt.svg` as the 105-byte placeholder the sources hold (it is a tracked static file, so the file list already has it). It reads the file back to verify it. A mismatch fails the build (exit 1). After the Gantt injection `recheckHelpArchive` checks that the offline tree on disk lists the same files as the archive and differs from it only in `BuildInfo.html` and `gantt.svg`; a task that wrote into the offline tree late fails the build there. The help add-in embeds the zip as a resource and serves the pages from it with its own server; a build of the add-in without it uses the live site. The file is gitignored (about 26 MB), so a build of the add-in from a clean clone is the online one until `build.bat` has run. The download carries the same zip (previous bullet). **`--no-help-archive` turns the step off, and neither CI workflow may pass it** (`check_ci_workflows.mjs` refuses it, in `ALLOWED.forbiddenBuildFlags`), because CI's published download would then hold no archive. It runs only for the guarded source tree (`docs`) built into the default `docs/_site-offline` (not another `--dest`, so `compare_trees` and `check_links_diff` leave it alone), never under `--serve` or `--dry-run`, never without an offline tree, and not keyed on `$CI`. There is no command-line tool for the archive: the `help-offline` lane makes its own with `writeHelpArchive`. The zip's format is fixed by the add-in's reader, which does no inflating ([WIP.HelpAddin.md](WIP.HelpAddin.md), Stage 4, increment 5). - `node scripts/bug_repro.mjs` --- the reproducer projects under `bugs/<slug>/` for the entries of [BUGS-TO-REPORT.md](BUGS-TO-REPORT.md): `new`, `pack` (impexp, then the zip, written in Node), `compile`, `build` and `run` through `tbbuild` and `tbrun`, `vb6` (builds the optional `vb6/` VB6 project beside the twinBASIC one in a temp copy, runs its exe on a private desktop, and prints its `out.txt`; a project with a form or a user control is built without Unattended Execution, which VB6 refuses for it; `pack` zips its sources into `<slug>-vb6.zip`; VB6 is started only by `scripts/lib/vb6.mjs`, never from a shell; a graphical defect's `images` key makes `run` and `vb6` keep what the `PngDump` modules of `test/repro-templates/png/` draw as `images/<name>-tb.png` and `-vb6.png`, with a `-compare.png` of both, tested by `test/png.test.mjs`), `file` (moves a filed entry and its reproducer to `bugs/filed/<slug>/`; `file --marked` does every marked entry), and `verify`, which reads each `bugs/*/repro.json` and `bugs/filed/*/repro.json` and says whether the bug still reproduces on the newest beta; a `lane` reproducer names the `ide-test` or `addin-test` lane and tests that assert its bug, which `verify` runs last through that runner, and a `probe` one a script under `scripts/`. **No reproducer should need a person at the IDE** (owner, 2026-10-05): a bug the IDE has to be operated for gets a lane test, and `manual` is left for what cannot be automated at all. It has no wrapper. **`verify` is run by a person only**, never by a gate or CI: it needs a twinBASIC install, like `examples.bat`. Default port 9440, and `--jobs N` takes N ports from there. Its command-line cases are in `scripts/lib/cli-cases.mjs`, and need no IDE. - `node scripts/vb6run.mjs <file | ->` and `node scripts/vb6run.mjs --docs [--only <regex>]` --- build and run VB6 code, so that what a sample prints in twinBASIC can be compared with VB6: a stand-alone file of statements or a `.bas` module, or `--docs`, which builds the documentation's `check_run` fences in VB6 and says for each whether it prints what its page says (`same`, `differs`, `not VB6`, `error`, `refused`; a `projname=` group is built as a project of its own, its `slot=file` fences translated into VB6 `.cls` and `.bas` components). It has no wrapper and is outside every gate and outside CI: it needs VB6 (`--vb6`, `VB6_EXE`, or the standard install folder). **Never start `VB6.EXE` from a shell**: in Git Bash `/make` is rewritten as a path and VB6 answers with a modal box on the desktop; the tool spawns it from Node with an argument array, and a sample that calls `MsgBox` or `InputBox` or contains `End` is refused. Its `Debug.Print` rewrite and its file translation have probes in `test/example-batches.test.mjs`, and its command-line cases are in `scripts/lib/cli-cases.mjs`; both need no VB6. What it relies on is in [WIP.ExamplesBuild.md](WIP.ExamplesBuild.md). @@ -480,7 +482,7 @@ build.bat && check.bat On the dev box that is ~4 s of build against ~37 s of check, of which the axe scan is ~20 s. [builder/PLAN-checks.md](builder/PLAN-checks.md) records how the link checker sits in the build's task graph and its cost; the axe follow-ons are designed there but not implemented. -**If the change touched `builder/`, `scripts/`, `lib/`, `book/`, `eval/`, `wisdom/`, `test/`, the site's scripts in `docs/assets/js/`, a wrapper or a workflow, run `test.bat` as well** --- another ~23 s. Eighteen of its twenty-one gates cannot be affected by an edit under `docs/` at all. **Three can.** `check_lint.mjs` lints the site's two scripts in `docs/assets/js/` along with the tooling. `check_gate_lists.mjs` reads `README.md` and every page under `docs/Documentation/`, so an edit to any developer page that states a gate count can fail it. **`check_code_regions.mjs` has two halves, and a content edit reaches only one.** Its corpus sweep reads `DOCS_DIR`, which is `<repo>/docs`, and tokenises every markdown file in it, so a page that provokes a rewrite into *altering* a code region fails it --- that half is content-dependent. Its fixed probes are not: they run against their own sources whatever the tree holds, and they cover the **mirror** fault, where a rewrite silently stops firing. The sweep cannot see that fault, because text the rewrite skipped is masked and restored unchanged and every region still matches. So run `test.bat` after adding an unusual code construct --- a fence whose contents include a fence marker, a 4-space indented block, an admonition wrapping a fence --- and read the built page as well, because for the mirror fault the gate is asserting that the mask and the admonition scan still work rather than checking your page: +**If the change touched `builder/`, `scripts/`, `lib/`, `book/`, `eval/`, `wisdom/`, `test/`, `add-in/`, the site's scripts in `docs/assets/js/`, a wrapper or a workflow, run `test.bat` as well** --- another ~23 s. Nineteen of its twenty-two gates cannot be affected by an edit under `docs/` at all. **Three can.** `check_lint.mjs` lints the site's two scripts in `docs/assets/js/` along with the tooling. `check_gate_lists.mjs` reads `README.md` and every page under `docs/Documentation/`, so an edit to any developer page that states a gate count can fail it. **`check_code_regions.mjs` has two halves, and a content edit reaches only one.** Its corpus sweep reads `DOCS_DIR`, which is `<repo>/docs`, and tokenises every markdown file in it, so a page that provokes a rewrite into *altering* a code region fails it --- that half is content-dependent. Its fixed probes are not: they run against their own sources whatever the tree holds, and they cover the **mirror** fault, where a rewrite silently stops firing. The sweep cannot see that fault, because text the rewrite skipped is masked and restored unchanged and every region still matches. So run `test.bat` after adding an unusual code construct --- a fence whose contents include a fence marker, a 4-space indented block, an admonition wrapping a fence --- and read the built page as well, because for the mirror fault the gate is asserting that the mask and the admonition scan still work rather than checking your page: ```sh build.bat && check.bat && test.bat @@ -524,6 +526,7 @@ wrapper: | `test.bat` | `test/strftime.test.mjs` | `builder/strftime.mjs`'s `formatDate`, which no build calls because no page sets `last_modified_date`. Run by `node --test` | | `test.bat` | `test/example-batches.test.mjs` | `check_examples.mjs`'s probes, from `scripts/lib/example-batches.mjs`: batching, crash isolation through a fake lane, the canaries, the fence classifier and `check_run`'s refusals, expected output, dispatcher and marker reading, with no IDE. Run by `node --test` | | `test.bat` | `test/ports.test.mjs` | `scripts/lib/tb-ports.mjs` claims ports as the lane runners need: two claims at once are disjoint, a port in use and a live lock are passed over, a stale lock is taken over, a lock goes when its process exits. Run by `node --test` | +| `test.bat` | `test/addin-project.test.mjs` | `builder/addin-project.mjs` packs the help add-in's project file from exactly the files git tracks in `add-in/`, as the working folder holds them, never `Resources/HELP/`, with the index it is given, the same bytes from a CRLF and an LF checkout, into every tree it is given. Needs git. Run by `node --test` | | `test.bat` | `check_pdf_shims_equiv` | the book's pdf-lib shims write what stock pdf-lib writes: one document written by the gate and one built with `PDFDocument.create` are each saved both ways in child processes, and each pair of files is compared object by object with streams inflated and their cross-reference entries checked; every shim must run, and the members of pdf-lib the shims patch must be those its `PATCHES` lists, each run in one document or the other unless marked there as not reached; each of them must also be in the target table its shim passes to `checkTargets`, which `book/lib/shim-targets.mjs` records for the side to ask | | `test.bat` | `check_impexp_parity` | `impexp.mjs` and `impexp.py` pass the same built-in tests, and one sequence of commands through each gives the exit code each command is there for, the same output and the same files. Without Python it reports itself skipped and passes, but fails when `CI=true` | | `test.bat` | `check_publish_policy`, `check_gate_lists`, `check_page_baseline`, `check_book_coverage`, `check_axe_patch_equiv` | the gates on the gates | diff --git a/add-in/Resources/STYLESHEETS/pane.css b/add-in/Resources/STYLESHEETS/pane.css index cb9303653..6d6291180 100644 --- a/add-in/Resources/STYLESHEETS/pane.css +++ b/add-in/Resources/STYLESHEETS/pane.css @@ -175,3 +175,17 @@ width: 100%; border: 0; } + +/* The line between the results list and what is under it, the page or the + summary: the theme's text colour at 40%, so that it shows on every theme's + panel (the theme's own border for a value box is too faint in Light). It is + drawn on the top edge of what follows the list rather than on the list, + because the list's height is its row count times --helpRow and a border of + its own would add its width to it. It is there only while the list is shown: + with no search the bar is above the page, as before. The list is shown or + hidden by its inline display (ShowResults, shell.js), which a rule here + cannot test but by the attribute. */ +#helpResults:not([style*="display: none"]) ~ #helpSummary, +#helpResults:not([style*="display: none"]) ~ #helpPage { + border-top: 2px solid color-mix(in srgb, var(--themeToolWindowBodyForeColor) 40%, transparent); +} diff --git a/biome.jsonc b/biome.jsonc index 999d60752..731f7f8bd 100644 --- a/biome.jsonc +++ b/biome.jsonc @@ -88,7 +88,10 @@ // builder/ must not depend on scripts/. check_tree_fresh.mjs watches // docs/ and builder/ as the inputs that decide the built bytes, and would // not see a change to a module the build imported from scripts/. Biome - // files the rule under style; here it guards a dependency. + // files the rule under style; here it guards a dependency. The one + // exception is scripts/impexp.mjs, which builder/addin-project.mjs packs + // the help add-in's project file with: it imports nothing, the build + // already publishes it, and check_tree_fresh.mjs watches it by name. "includes": ["builder/**/*.mjs"], "linter": { "rules": { @@ -96,7 +99,12 @@ "noRestrictedImports": { "level": "error", "options": { - "patterns": [{ "group": ["**/scripts/**"], "message": "builder/ must not depend on scripts/." }] + "patterns": [ + { + "group": ["**/scripts/**", "!**/scripts/impexp.mjs"], + "message": "builder/ must not depend on scripts/." + } + ] } } } diff --git a/builder/addin-project.mjs b/builder/addin-project.mjs new file mode 100644 index 000000000..d56b49f6f --- /dev/null +++ b/builder/addin-project.mjs @@ -0,0 +1,193 @@ +// The help add-in's project file, published as a download. The add-in's source +// is the folder add-in/, which holds the project's files rather than a project +// file. The `addinProject` task packs it into a .twinproj and writes that into +// the online tree alone, at the path `addin_project.dest` in _config.yml names, +// so the Help Add-In page can offer the file itself. The offline tree holds no +// copy, because the project carries that tree's archive: the offline pages +// link to the website's file (offline-rewrite.mjs, computeWebsiteUrl). +// +// What goes in is the files git tracks under the folder, as the working folder +// holds them: +// +// * git's list, so a file nobody has added -- a scratch module, a Build/ +// folder the IDE wrote -- never reaches the download, and neither does +// whatever site.zip sits in Resources/HELP/ (gitignored; any tracked file +// there is left out by name as well); +// * the working folder's bytes, so a local build carries an edit not yet +// committed, and the add-in tried from the build is the one being changed; +// * Resources/SYMBOLS/symbols.json is replaced by the index this build wrote +// (tB/symbols.json), so the download carries the index of the pages it is +// published with. In CI the add-in's committed copy is never rewritten +// (addin-index.mjs), and the download is still current; +// * Resources/HELP/site.zip is the archive of the offline tree that this +// build wrote (help-archive-step.mjs), handed over by the build and never +// read from the disk, so the add-in built from the download serves the +// pages itself, without the internet. A build that wrote no archive +// (--no-help-archive, --serve, another --dest, a fixture) packs none. +// +// Line endings are packed as git stores them. A Windows checkout under +// core.autocrlf holds Settings, the .twin files and the .tbform with CRLF and a +// Linux one with LF, so each file outside Resources/ has its CRLFs made LF +// first; impexp then gives the code files CRLF, as the IDE stores them. +// .gitattributes checks Resources/ out byte for byte, so those files are +// packed as they are. A Windows build and a Linux build therefore write the +// same bytes, and impexp writes no time and sorts every folder, so two builds +// of one tree do too. +// +// The pack is impexp's own importProject, in process, as the harness packs +// (scripts/lib/tb-project.mjs): never the compiler executable, never Python. +// impexp.mjs is the one module under scripts/ the builder may import +// (biome.jsonc): it depends on nothing, it is a build input already, published +// through bundle_extra, and check_tree_fresh.mjs watches it. + +import { execFile } from "node:child_process"; +import { promises as fs } from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { promisify } from "node:util"; +import { importProject } from "../scripts/impexp.mjs"; +import { posix } from "./paths.mjs"; + +const exec = promisify(execFile); + +// Paths inside the add-in's folder, compared without case: git on Windows +// reports a path in the case it was added with. +const HELP_DIR = "resources/help/"; +const SYMBOLS_REL = "resources/symbols/symbols.json"; +const VERBATIM_DIR = "resources/"; +// Where the archive goes in the project, as the add-in embeds it. +export const ARCHIVE_IN_PROJECT = "Resources/HELP/site.zip"; + +const byCodePoint = (a, b) => (a < b ? -1 : a > b ? 1 : 0); + +/** + * The download _config.yml declares, or null when it declares none (the test + * fixtures' configs do not). + * + * @returns {{src: string, dest: string} | null} `src` resolved against the + * source tree, as bundle_extra's is; `dest` the path in each tree + */ +export function addinProjectOf(config) { + const e = config?.addin_project; + if (!e?.src || !e?.dest) return null; + return { src: String(e.src), dest: posix(String(e.dest)).replace(/^\/+/, "") }; +} + +// The files git lists under `dir`, relative to it, with forward slashes. +async function gitFiles(dir, ...args) { + const { stdout } = await exec("git", ["-C", dir, "ls-files", "-z", ...args, "--", "."], { + maxBuffer: 16 * 1024 * 1024, + }); + return stdout.split("\0").filter(Boolean).sort(byCodePoint); +} + +const isHelp = (rel) => rel.toLowerCase().startsWith(HELP_DIR); + +function lf(bytes) { + return Buffer.from(bytes.toString("latin1").replaceAll("\r\n", "\n"), "latin1"); +} + +/** + * Pack the add-in's folder into a project file, in memory. Writes nothing + * outside a temporary folder, which it removes. + * + * @param {object} o + * @param {string} o.dir the add-in's folder, the one holding Settings + * @param {string} [o.symbols] the symbol index to pack in place of the folder's copy + * @param {Buffer} [o.archive] the offline tree's archive this build wrote, packed as + * Resources/HELP/site.zip; none when absent, whatever site.zip is on the disk + * @returns {Promise<{bytes: Buffer, files: string[], untracked: string[]}>} the + * project file, the files packed, and the files git does not track that were + * left out (Resources/HELP/ aside) + */ +export async function packAddinProject({ dir, symbols, archive = null }) { + const [tracked, untracked] = await Promise.all([gitFiles(dir), gitFiles(dir, "--others", "--exclude-standard")]); + const files = []; + for (const rel of tracked) { + if (isHelp(rel)) continue; + let bytes; + if (symbols != null && rel.toLowerCase() === SYMBOLS_REL) bytes = Buffer.from(symbols, "utf8"); + else { + try { + bytes = await fs.readFile(path.join(dir, ...rel.split("/"))); + } catch (err) { + // Deleted in the working folder and not yet in git: not in the add-in. + if (err.code === "ENOENT") continue; + throw err; + } + } + files.push({ rel, bytes: rel.toLowerCase().startsWith(VERBATIM_DIR) ? bytes : lf(bytes) }); + } + if (!files.some((f) => f.rel === "Settings")) { + throw new Error(`git tracks no Settings file in ${dir}, so it is not the folder of a project`); + } + if (archive) files.push({ rel: ARCHIVE_IN_PROJECT, bytes: archive }); + + const tmp = await fs.mkdtemp(path.join(os.tmpdir(), "addin-project-")); + try { + const stage = path.join(tmp, path.basename(dir)); + for (const f of files) { + const p = path.join(stage, ...f.rel.split("/")); + await fs.mkdir(path.dirname(p), { recursive: true }); + await fs.writeFile(p, f.bytes); + } + const out = path.join(tmp, "project.twinproj"); + importProject(out, stage); + return { + bytes: await fs.readFile(out), + files: files.map((f) => f.rel), + untracked: untracked.filter((rel) => !isHelp(rel)), + }; + } finally { + await fs.rm(tmp, { recursive: true, force: true }); + } +} + +/** + * Pack the add-in's folder and write the project file into each tree. + * + * @param {object} o + * @param {string} o.dir the add-in's folder + * @param {string} [o.symbols] the symbol index this build wrote + * @param {Buffer} [o.archive] the offline archive this build wrote, if it wrote one + * @param {string[]} o.roots the trees to write into + * @param {string} o.rel the file's path in each tree + * @returns {Promise<{rel: string, bytes: number, files: number, archived: boolean, untracked: string[], roots: number, ms: number}>} + */ +export async function writeAddinProject({ dir, symbols, archive = null, roots, rel }) { + const t0 = Date.now(); + const packed = await packAddinProject({ dir, symbols, archive }); + for (const root of roots) { + const out = path.join(root, ...rel.split("/")); + await fs.mkdir(path.dirname(out), { recursive: true }); + await fs.writeFile(out, packed.bytes); + } + return { + rel, + bytes: packed.bytes.length, + files: packed.files.length, + archived: !!archive, + untracked: packed.untracked, + roots: roots.length, + ms: Date.now() - t0, + }; +} + +/** + * What the build prints for the task's result: one line, and one more when + * files git does not track were left out. Each line starts with `indent`. + */ +export function formatAddinProject(r, dirLabel, indent = "") { + if (!r) return ""; + if (r.failed) return `${indent}add-in project: ${r.rel} not written: ${r.error}\n`; + const size = r.bytes >= 1048576 ? `${(r.bytes / 1048576).toFixed(1)} MB` : `${(r.bytes / 1024).toFixed(0)} KB`; + const archive = r.archived ? "with the offline archive" : "without the offline archive"; + const trees = `${r.roots} tree${r.roots === 1 ? "" : "s"}`; + let text = `${indent}add-in project: ${r.rel}, ${r.files} files, ${size}, ${archive}, in ${trees} (${r.ms} ms)\n`; + if (r.untracked.length) { + const shown = r.untracked.slice(0, 5).join(", "); + const more = r.untracked.length > 5 ? `, and ${r.untracked.length - 5} more` : ""; + text += `${indent} left out, as git does not track them: ${r.untracked.length} in ${dirLabel}: ${shown}${more}\n`; + } + return text; +} diff --git a/builder/check-tree.mjs b/builder/check-tree.mjs index 3bbf757ba..a81256ebe 100644 --- a/builder/check-tree.mjs +++ b/builder/check-tree.mjs @@ -24,7 +24,12 @@ const OFFLINE_AUX = ["assets/js/search-data.js", "assets/js/nav.js"]; // Every relative path a tree receives. `which` is "online" or "offline"; // the PDF tree's contents come from writePdf, which knows them exactly. -export function deriveTreeRels(which, { pages, staticFiles, stubs, themeAssetRels, excludePatterns }) { +// `onlineOnlyRels` are files the online tree receives and the offline one +// does not: the help add-in's project file. +export function deriveTreeRels( + which, + { pages, staticFiles, stubs, themeAssetRels, excludePatterns, onlineOnlyRels = [] }, +) { const rels = new Set(); const add = which === "offline" @@ -42,6 +47,7 @@ export function deriveTreeRels(which, { pages, staticFiles, stubs, themeAssetRel for (const s of staticFiles) add(s.destRel); for (const rel of themeAssetRels) add(rel); for (const rel of which === "offline" ? OFFLINE_AUX : ONLINE_AUX) add(rel); + if (which !== "offline") for (const rel of onlineOnlyRels) add(rel); return [...rels]; } diff --git a/builder/check.mjs b/builder/check.mjs index 56e0327f9..81824c969 100644 --- a/builder/check.mjs +++ b/builder/check.mjs @@ -169,7 +169,12 @@ export function checkChunk(docs, env) { ids.push([abs, [...r.ids]]); } if (r.forbidden && r.forbidden.length) { - for (const f of r.forbidden) forbidden.push(doc.destPath, f.url, f.prefix); + for (const f of r.forbidden) { + // The offline tree's links to a file only the website holds are the + // rewrite's own (offline-rewrite.mjs, isWebsiteOnlyLink). + if (env.forbidExempt?.(f.url)) continue; + forbidden.push(doc.destPath, f.url, f.prefix); + } } if (r.htmlErrors?.length || r.a11yErrors?.length || r.dupIds?.length || r.remoteAssets?.length) { integrity.push([ diff --git a/builder/cpu-worker.mjs b/builder/cpu-worker.mjs index 00a1f1a73..489e5b97a 100644 --- a/builder/cpu-worker.mjs +++ b/builder/cpu-worker.mjs @@ -15,7 +15,7 @@ import { renderFullNav, templatePhase, withPartialNav } from "./template.mjs"; import { unpackShared } from "./sab-broadcast.mjs"; import { deriveSearchEntries } from "./search.mjs"; import { computeChunkSeo } from "./seo.mjs"; -import { deriveOfflinePage } from "./offline-rewrite.mjs"; +import { deriveOfflinePage, isWebsiteOnlyLink, websiteOf } from "./offline-rewrite.mjs"; import { normalizeBaseurl } from "./url.mjs"; import { @@ -69,6 +69,7 @@ const handlers = { baseurl, buildInfo, sitePathsArr, + onlineSitePathsArr, skipOffline, svgContentsMap, checkTrees, @@ -100,6 +101,7 @@ const handlers = { if (!skipOffline) { offlineBase = { sitePaths: new Set(sitePathsArr), + website: websiteOf(siteData.config, new Set(onlineSitePathsArr)), baseurl: normalizeBaseurl(baseurl), }; } @@ -118,6 +120,7 @@ const handlers = { tree: TREES[which], basePath: normBase(bu), index: treeIndexFor(root, rels), + ...(which === "offline" && offlineBase ? { forbidExempt: (url) => isWebsiteOnlyLink(url, offlineBase) } : {}), }; } } diff --git a/builder/gantt.mjs b/builder/gantt.mjs index 3fc1c43a9..9e2f6b83f 100644 --- a/builder/gantt.mjs +++ b/builder/gantt.mjs @@ -46,7 +46,8 @@ export const GANTT_SECTION = { deriveRedirects: "Spine", deriveSitemap: "Spine", dispatch: "Render", prepDest: "Render", prepPageDirs: "Render", renderJoin: "Render", flushJoin: "Write", - writeAssets: "Write", searchData: "Write", symbolIndex: "Write", writeAux: "Write", writeOffline: "Write", writePdf: "Write", + writeAssets: "Write", searchData: "Write", symbolIndex: "Write", helpArchive: "Write", addinProject: "Write", writeAux: "Write", + writeOffline: "Write", writePdf: "Write", linkJoin: "Check", checkBook: "Check", checkReport: "Check", }; const GANTT_SECTION_ORDER = ["Seeds", "Spine", "Render", "Write", "Check"]; diff --git a/builder/help-archive-step.mjs b/builder/help-archive-step.mjs index 1a9afbeb2..a6bcc88f1 100644 --- a/builder/help-archive-step.mjs +++ b/builder/help-archive-step.mjs @@ -1,22 +1,33 @@ -// The build's last step: the help add-in's documentation archive. The add-in in -// add-in/ embeds the offline tree as a zip, add-in/Resources/HELP/site.zip, which -// lib/help-archive.mjs writes and reads back; its format and checks are there. -// The archive is gitignored, so a local build is what produces it. +// The help add-in's documentation archive. The add-in in add-in/ embeds the +// offline tree as a zip, add-in/Resources/HELP/site.zip, which lib/help-archive.mjs +// writes and reads back; its format and checks are there. The archive is +// gitignored, so a local build is what produces it. The add-in's project file, +// the download, carries the same bytes (addin-project.mjs). // -// It runs at the very end of runBuild, after injectGanttChart and recheckInjected. -// They rewrite BuildInfo.html in both trees once the task graph is done, so an -// archive written any earlier would hold the page as it was before the Gantt -// chart went in, and differ from the tree on disk. +// `helpArchiveStep` is the build's `helpArchive` task. It waits for every task +// that writes into the offline tree, and runs before the Gantt injection, which +// rewrites BuildInfo.html in both trees after the task graph is done and +// writes assets/images/gantt.svg. So the archive holds BuildInfo.html without +// the chart, and gantt.svg as the placeholder the offline tree copied from the +// sources. Two builds of one tree write the same archive, and the same project +// file with it, which the chart (it holds the build's timings) would prevent. +// +// `recheckHelpArchive` runs at the end of runBuild, after the Gantt injection, +// and checks that the offline tree on disk still lists the archive's files and +// that only the two files the injection writes differ in content. A tree that +// drifted from its archive, because some task wrote into it after the archive +// task ran, fails the build. // // Only a build of the documentation tree into docs/_site-offline writes the // archive. A build of any other source tree (tbdocs also builds test fixtures), // or into another --dest (compare_trees, check_links_diff), leaves the add-in's -// archive alone, as does `--serve`, `--dry-run`, a build without an offline -// tree, and `--no-help-archive`, which CI passes. The step does not look at -// $CI: the flag is how CI turns it off. +// archive alone, as does `--serve`, `--dry-run` and a build without an offline +// tree. `--no-help-archive` skips it too; CI does not pass it, because the +// download published from CI's build holds the archive. The step does not look +// at $CI. import path from "node:path"; -import { writeHelpArchive } from "../lib/help-archive.mjs"; +import { checkHelpArchive, writeHelpArchive } from "../lib/help-archive.mjs"; import { REPO_ROOT } from "../lib/repo-paths.mjs"; import { GUARDED_SRC } from "./baseline.mjs"; @@ -29,6 +40,8 @@ const mb = (n) => (n / 1048576).toFixed(1); const OFFLINE_TREE = path.join(REPO_ROOT, GUARDED_SRC, "_site-offline"); const sameDir = (a, b) => path.relative(a, b) === ""; +const NOT_WRITTEN = { text: "", failed: false, zip: null, out: null }; + /** * Write the add-in's archive of the offline tree this build wrote. * @@ -39,7 +52,8 @@ const sameDir = (a, b) => path.relative(a, b) === ""; * @param {boolean} o.dryRun the build wrote no trees * @param {boolean} o.disabled `--no-help-archive` was given * @param {string} [o.out] the zip; the add-in's by default - * @returns {Promise<{text: string, failed: boolean}>} what to print, "" when the step did not run + * @returns {Promise<{text: string, failed: boolean, zip: Buffer | null, out: string | null}>} what to + * print, "" when the step did not run; the archive's bytes and path, null when this build wrote none */ export async function helpArchiveStep({ src, @@ -49,23 +63,56 @@ export async function helpArchiveStep({ disabled, out = path.join(REPO_ROOT, HELP_ARCHIVE_REL), }) { - if (src !== GUARDED_SRC || serve || dryRun || disabled || !offlineRoot) return { text: "", failed: false }; - if (!sameDir(path.resolve(offlineRoot), OFFLINE_TREE)) return { text: "", failed: false }; + if (src !== GUARDED_SRC || serve || dryRun || disabled || !offlineRoot) return NOT_WRITTEN; + if (!sameDir(path.resolve(offlineRoot), OFFLINE_TREE)) return NOT_WRITTEN; try { const s = await writeHelpArchive({ src: path.resolve(offlineRoot), out }); return { text: `help archive: ${HELP_ARCHIVE_REL}, ${s.entries} entries, ${mb(s.zipBytes)} MB (${(s.ms / 1000).toFixed(1)} s)\n`, failed: false, + zip: s.zip, + out, }; } catch (err) { if (err.verifyFailed) { const shown = err.problems.slice(0, 50).map((p) => ` ${p}\n`); const more = err.problems.length > 50 ? ` ... and ${err.problems.length - 50} more\n` : ""; return { + ...NOT_WRITTEN, text: `help archive: ${HELP_ARCHIVE_REL} does not match the offline tree: ${err.problems.length} problem(s)\n${shown.join("")}${more}`, failed: true, }; } - return { text: `help archive: not written: ${err.message}\n`, failed: true }; + return { ...NOT_WRITTEN, text: `help archive: not written: ${err.message}\n`, failed: true }; + } +} + +/** + * Check, after the Gantt injection, that the offline tree still matches the + * archive this build wrote: the same files, the same bytes in each but the + * `mayDiffer` ones. + * + * @param {object} o + * @param {{zip: Buffer | null, out: string | null}} o.archive the task's result + * @param {string} o.offlineRoot the offline tree + * @param {string[]} o.mayDiffer the files the Gantt injection wrote, relative to the tree + * @returns {Promise<{text: string, failed: boolean}>} "" when the archive was not written or matches + */ +export async function recheckHelpArchive({ archive, offlineRoot, mayDiffer }) { + if (!archive?.zip || !archive.out) return { text: "", failed: false }; + let problems; + try { + problems = await checkHelpArchive({ src: path.resolve(offlineRoot), zip: archive.out, mayDiffer }); + } catch (err) { + return { text: `help archive: the offline tree could not be checked against it: ${err.message}\n`, failed: true }; } + if (problems.length === 0) return { text: "", failed: false }; + const shown = problems.slice(0, 50).map((p) => ` ${p}\n`); + const more = problems.length > 50 ? ` ... and ${problems.length - 50} more\n` : ""; + return { + text: + `help archive: the offline tree on disk differs from the archive, beyond ${mayDiffer.join(" and ")}: ` + + `${problems.length} problem(s)\n${shown.join("")}${more}`, + failed: true, + }; } diff --git a/builder/offline-rewrite.mjs b/builder/offline-rewrite.mjs index 759ad266c..b0024409f 100644 --- a/builder/offline-rewrite.mjs +++ b/builder/offline-rewrite.mjs @@ -8,6 +8,7 @@ // §B Site-paths set (buildSitePathsSync, offlineExcluded, // fnmatchPathname) // §C URL resolution (computeRelative, resolveRaw, computeRelUrl, +// computeWebsiteUrl, isWebsiteOnlyLink, websiteOf, // buildSegs, decode, fileDirSegsFromRel, // posixDirname, getPageCache) // §D HTML rewrite (stripSeo, stripFontPreloads, rewriteHtml, @@ -158,6 +159,61 @@ export function resolveRaw(raw, sitePaths, baseurl) { // §6.4 computeRelUrl -- page-relative URL → page-relative URL with the // .html / /index.html / "" suffix that makes it resolve under file://. export function computeRelUrl(raw, fileSegs, sitePaths) { + const probe = probeRelUrl(raw, fileSegs); + if (probe === null) return null; + const { pathPart, sep, tail, candidates } = probe; + for (const [suffix, full] of candidates) { + if (sitePaths.has(full)) return pathPart + suffix + sep + tail; + } + return null; +} + +// §6.4a computeWebsiteUrl -- a URL the offline tree does not resolve, but the +// online tree does: the absolute URL of the file on the website, which holds +// it. Both forms of URL: a root-absolute one is the website's origin put in +// front of it (it carries the base path already, as every online URL does); a +// page-relative one is resolved against the page's folder first. `website` +// is { sitePaths, url } -- the online tree's path set and the site's origin -- +// and null makes every URL a miss, as an offline tree with no website behind +// it must. +export function computeWebsiteUrl(raw, fileSegs, website, baseurl) { + if (!website) return null; + if (raw.startsWith("/")) { + if (resolveRaw(raw, website.sitePaths, baseurl)[2] === null) return null; + const lead = !baseurl || raw === baseurl || raw.startsWith(baseurl + "/") ? "" : baseurl; + return website.url + lead + raw; + } + const probe = probeRelUrl(raw, fileSegs); + if (probe === null) return null; + const { sep, tail, probePath, candidates } = probe; + if (!candidates.some(([, full]) => website.sitePaths.has(full))) return null; + const [, encoded] = buildSegs(probePath); + return website.url + (baseurl || "") + "/" + encoded.join("/") + sep + tail; +} + +// Whether `url`, found in an offline page, is a link computeWebsiteUrl wrote on +// purpose: it points at the website, at a file the website holds and the +// offline tree does not. `offline` is the rewrite's state ({ sitePaths, website, +// baseurl }). A live-site link to a file the offline tree does hold is a link +// the rewrite missed, and is not one. +export function isWebsiteOnlyLink(url, { sitePaths, website, baseurl }) { + if (!website || !url.startsWith(website.url)) return false; + const rest = url.slice(website.url.length); + if (!rest.startsWith("/")) return false; + return resolveRaw(rest, website.sitePaths, baseurl)[2] !== null && resolveRaw(rest, sitePaths, baseurl)[2] === null; +} + +// The online tree's side of the offline rewrite: its path set and the site's +// origin, or null when the config names no origin to point at. +export function websiteOf(config, onlineSitePaths) { + const url = String(config?.url ?? "").replace(/\/+$/, ""); + return url && onlineSitePaths ? { sitePaths: onlineSitePaths, url } : null; +} + +// Where a page-relative URL points in the site, and the paths that could hold +// it: the shared half of computeRelUrl and computeWebsiteUrl. null for a URL +// with no path (a bare query or fragment). +function probeRelUrl(raw, fileSegs) { const splitIdx = raw.search(/[?#]/); const pathPart = splitIdx === -1 ? raw : raw.slice(0, splitIdx); const sep = splitIdx === -1 ? "" : raw[splitIdx]; @@ -195,10 +251,7 @@ export function computeRelUrl(raw, fileSegs, sitePaths) { ]; } - for (const [suffix, full] of candidates) { - if (sitePaths.has(full)) return pathPart + suffix + sep + tail; - } - return null; + return { pathPart, sep, tail, probePath, candidates }; } // Cached decoded/encoded segments for a site-rooted path. @@ -312,8 +365,11 @@ export const HTML_COMBINED_RE = new RegExp( // pathological page cannot turn the build log into the page. const MISS_SAMPLE = 10; -// §6.6 rewriteHtml -- single regex pass over the HTML. -export function rewriteHtml(html, fileDir, fileSegs, sitePaths, caches, baseurl) { +// §6.6 rewriteHtml -- single regex pass over the HTML. A URL the offline +// tree does not hold but the online tree does (`website`, see websiteOf) is +// rewritten to its absolute URL on the website and is not a miss; a URL in +// neither tree is. +export function rewriteHtml(html, fileDir, fileSegs, sitePaths, caches, baseurl, website = null) { let misses = 0; // The URLs behind the count, not only the count. "6 unresolved" // printed on every green build for as long as anyone can remember, @@ -331,6 +387,7 @@ export function rewriteHtml(html, fileDir, fileSegs, sitePaths, caches, baseurl) rel = rawUrl.startsWith("/") ? computeRelative(rawUrl, fileSegs, sitePaths, caches, baseurl) : computeRelUrl(rawUrl, fileSegs, sitePaths); + if (rel === null) rel = computeWebsiteUrl(rawUrl, fileSegs, website, baseurl); pageCache.set(rawUrl, rel); } if (rel === null) { @@ -369,13 +426,13 @@ export function injectSearchSetup(html, fileSegs) { // per-build reuse; pass a fresh state if cache pollution across pages // is a concern. export function deriveOfflinePage(page, state) { - const { sitePaths, caches, baseurl } = state; + const { sitePaths, caches, baseurl, website } = state; const fileDir = posixDirname(page.destPath); const fileSegs = fileDirSegsFromRel(page.destPath); let html = page.html; html = stripSeo(html); html = stripFontPreloads(html); - const { rewritten, misses, missed } = rewriteHtml(html, fileDir, fileSegs, sitePaths, caches, baseurl); + const { rewritten, misses, missed } = rewriteHtml(html, fileDir, fileSegs, sitePaths, caches, baseurl, website); html = rewritten; html = injectSearchSetup(html, fileSegs); if (misses) warnMisses(page.destPath, misses, missed); diff --git a/builder/offline.mjs b/builder/offline.mjs index 0744d6eea..488467b36 100644 --- a/builder/offline.mjs +++ b/builder/offline.mjs @@ -4,12 +4,13 @@ // and docs/_plugins/offlinify.rb for the canonical Jekyll reference. // // One entry point: writeOffline(staticFiles, site, destRoot, -// { auxStats, sitePaths }). The render workers derive each page's -// offline HTML and write it during their flush, so writeOffline writes +// { auxStats, sitePaths, onlineSitePaths }). The render workers derive each +// page's offline HTML and write it during their flush, so writeOffline writes // the rest: the patched just-the-docs.js, search-data.js, the redirect // stubs, the static files and the theme assets. sitePaths is the set // the caller built with buildSitePathsSync (offline-rewrite.mjs), which -// holds the pure-compute rewrite helpers. +// holds the pure-compute rewrite helpers; onlineSitePaths is the online +// tree's, which a link to a file only the website holds is resolved against. // // Internal sections: // @@ -36,6 +37,7 @@ import { posixDirname, rewriteHtml, warnMisses, + websiteOf, } from "./offline-rewrite.mjs"; import { NAV_SCRIPT_REL, navScript, renderFullNav } from "./template.mjs"; import { normalizeBaseurl } from "./url.mjs"; @@ -72,13 +74,13 @@ export async function writeOffline( staticFiles, site, destRoot, - { auxStats, profileOffline = false, sitePaths, check = false } = {}, + { auxStats, profileOffline = false, sitePaths, onlineSitePaths = null, check = false } = {}, ) { if (!destRoot) { throw new Error("writeOffline requires a destRoot"); } - const state = buildOfflineState(site, destRoot, sitePaths); + const state = buildOfflineState(site, destRoot, sitePaths, onlineSitePaths); // Project-owned theme assets (head-nav.css, print.css, theme-toggle.js) // live under docs/assets/ and ride the static-file copy path -- but the // online pass also copies them into <destRoot>/assets/, so they show up @@ -159,13 +161,15 @@ export async function writeOffline( return { ...deps.counters, jtdPatches, subT, checkStubs: deps.checkStubs }; } -// The state the URL rewrite reads: the site-paths Set, the resolution -// caches, and the base URL and exclusions from the site config. -function buildOfflineState(site, destRoot, sitePaths) { +// The state the URL rewrite reads: the site-paths Set, the online tree's +// (`website`, for the files only the website holds), the resolution caches, +// and the base URL and exclusions from the site config. +function buildOfflineState(site, destRoot, sitePaths, onlineSitePaths) { const excludePatterns = Array.isArray(site.config?.offline_exclude) ? site.config.offline_exclude.map(String) : []; return { destRoot, sitePaths, + website: websiteOf(site.config, onlineSitePaths), caches: { rawResolution: new Map(), seg: new Map(), @@ -533,6 +537,7 @@ async function writeOfflineNavJs(site, deps) { deps.sitePaths, deps.caches, deps.baseurl, + deps.website, ); if (misses) warnMisses(NAV_SCRIPT_REL, misses, missed); deps.counters.unresolved += misses; diff --git a/builder/page-baseline.json b/builder/page-baseline.json index bcc42e4b1..9524197f2 100644 --- a/builder/page-baseline.json +++ b/builder/page-baseline.json @@ -1,5 +1,5 @@ { "src": "docs", "pages": 928, - "staticFiles": 258 + "staticFiles": 253 } diff --git a/builder/publish-policy.mjs b/builder/publish-policy.mjs index f53b76cd6..bde37d4bf 100644 --- a/builder/publish-policy.mjs +++ b/builder/publish-policy.mjs @@ -73,12 +73,21 @@ export const EXTENSIONLESS_FILENAMES = new Set(["CNAME"]); // exempt from the extension rule -- that is what declaring one means. // It is how `Features/Packages/downloads/impexp.py` and `impexp.mjs` ship // while a stray `.py` or `.mjs` anywhere else still fails. +// +// `addin_project` declares one file the build itself writes, the help +// add-in's project file (addin-project.mjs), and is exempt the same way on +// the tree surface only: a .twinproj is not a publishable type, and one found +// in docs/ is refused even at that path, because the build writes the file +// there and a copy in the source tree could only be a stale one. export function publishPolicyFor(config) { const declared = new Set(); for (const entry of config?.bundle_extra ?? []) { if (entry?.dest) declared.add(posix(String(entry.dest))); } - return { declared }; + const generated = new Set(); + const addin = config?.addin_project; + if (addin?.dest) generated.add(posix(String(addin.dest)).replace(/^\/+/, "")); + return { declared, generated }; } // Returns null when `rel` may be published, or a short reason when it may @@ -131,9 +140,10 @@ export function unpublishableSourceFiles(staticFiles, policy) { // vendored theme assets and generated auxiliaries. export function unpublishableTreePaths(rels, policy) { const extensions = new Set([...SOURCE_EXTENSIONS, ...BUILD_EXTENSIONS]); + const declared = new Set([...policy.declared, ...(policy.generated ?? [])]); const out = []; for (const rel of rels) { - const why = refuse(rel, extensions, policy.declared); + const why = refuse(rel, extensions, declared); if (why) out.push({ rel: posix(rel), why }); } return out.sort(byRel); diff --git a/builder/tbdocs.mjs b/builder/tbdocs.mjs index 3e63f7224..13f7b1c58 100644 --- a/builder/tbdocs.mjs +++ b/builder/tbdocs.mjs @@ -22,9 +22,15 @@ // --check-audit-index additionally diffs the derived tree index against // what actually landed on disk; see builder/check.mjs. // -// A build of the documentation tree ends by writing the IDE help add-in's -// archive of the offline tree, add-in/Resources/HELP/site.zip -// (builder/help-archive-step.mjs). --no-help-archive skips it; CI passes that. +// A build of the documentation tree writes the IDE help add-in's archive of the +// offline tree, add-in/Resources/HELP/site.zip, once the offline tree is +// complete and before the Gantt chart is injected (the helpArchive task, +// builder/help-archive-step.mjs). --no-help-archive skips it. +// +// Every build of a tree whose _config.yml declares addin_project also packs the +// IDE help add-in's project file into the online tree, as a download, with that +// archive in it when this build wrote one (builder/addin-project.mjs, the +// addinProject task, which waits for helpArchive). // // Default --src is "docs" relative to the current working directory. // Default --dest is "<src>/_site". --dry-run skips all filesystem writes. @@ -88,7 +94,8 @@ import { writePdf } from "./pdf.mjs"; // build without --check pays nothing. import { deriveTreeRels } from "./check-tree.mjs"; import { syncAddinIndex } from "./addin-index.mjs"; -import { helpArchiveStep } from "./help-archive-step.mjs"; +import { addinProjectOf, formatAddinProject, writeAddinProject } from "./addin-project.mjs"; +import { helpArchiveStep, recheckHelpArchive } from "./help-archive-step.mjs"; import { checkPageBaseline } from "./page-baseline.mjs"; import { checkSymbolBaseline } from "./symbol-baseline.mjs"; import { deriveSymbolIndex, reportableGaps, serializeSymbolIndex, symbolPages, SYMBOL_INDEX_REL } from "./symbols.mjs"; @@ -631,6 +638,12 @@ const TASKS = { const excludePatterns = Array.isArray(state.site.config?.offline_exclude) ? state.site.config.offline_exclude.map(String) : []; + // The help add-in's project file, which the addinProject task writes + // late, is in the online tree from here on, and only there: its index + // and the publish allowlist's sweep need the path now. The offline + // tree holds no copy -- the project carries the offline tree's archive -- + // so a link to the file there goes to the website (computeWebsiteUrl). + const addin = addinProjectOf(state.site.config); const themeAssetRels = [ ...enumerateVendoredThemeAssets(), "assets/css/tb-highlight.css", @@ -654,9 +667,14 @@ const TASKS = { stubs, themeAssetRels, excludePatterns, + onlineOnlyRels: addin ? [addin.dest] : [], }; const treeNames = skipOffline ? ["online"] : ["online", "offline"]; const treeRels = new Map(treeNames.map((w) => [w, deriveTreeRels(w, common)])); + // What the offline tree's links to a file only the website holds are + // resolved against. + const onlineSitePaths = new Set(treeRels.get("online").map((rel) => `/${rel}`)); + state.onlineSitePaths = onlineSitePaths; // Second enforcement point for the publish allowlist, over the // inventory each tree will actually receive. The source sweep in @@ -719,6 +737,7 @@ const TASKS = { staticFilesArr: state.staticFiles.map((f) => f.srcRel), baseurl: String(state.site.config.baseurl || ""), sitePathsArr: [...sitePaths], + onlineSitePathsArr: skipOffline ? [] : [...onlineSitePaths], offlineExcludePatterns: excludePatterns, skipOffline, svgContentsMap, @@ -942,13 +961,68 @@ const TASKS = { // search.mjs), not written anywhere itself -- symbolIndex's own // file output is tB/symbols.json, above. symbols: result.symbols, - // The file's text, for the add-in's copy (addin-index.mjs). + // The file's text, for the add-in's copy (addin-index.mjs) and its + // project file (addinProject). json, }; }, submit() {}, }, + // Write the IDE help add-in's archive of the offline tree -- see + // builder/help-archive-step.mjs. The offline tree must be complete, so it + // waits for every task that writes into it: flushJoin (the pages, written by + // the render lanes' flushes), scss (the offline stylesheet) and writeOffline + // (the rest: statics, theme assets, redirect stubs, nav.js, search-data.js). + // Nothing else writes there before the Gantt injection, which runBuild does + // after the graph; recheckHelpArchive checks that at the end. The Gantt chart + // is injected after this task, so the archive holds BuildInfo.html without it. + helpArchive: { + expected: ["flushJoin", "scss", "writeOffline"], + runOnMain: true, + async execute(_, ctx, state) { + const skipOffline = ctx.opts.skipOffline ?? state.site.config.also_build_offline === false; + return helpArchiveStep({ + src: path.relative(REPO_ROOT, ctx.srcRoot).replaceAll(path.sep, "/"), + offlineRoot: skipOffline ? null : `${ctx.destRoot}-offline`, + serve: !!ctx.opts.serve, + dryRun: !!ctx.opts.dryRun, + disabled: !!ctx.opts.skipHelpArchive, + }); + }, + submit() {}, + }, + + // Write the help add-in's project file, packed from add-in/, into the online + // tree at the path _config.yml's addin_project names -- see + // builder/addin-project.mjs. After symbolIndex because the project carries the + // index this build wrote, and after helpArchive because it carries the archive + // that task wrote (none when it wrote none). dispatch put the path in the + // online tree's index, so checkReport waits for this task. --dry-run writes + // nothing, and a build of a tree whose config declares no download (the test + // fixtures) packs nothing. A failure is reported and fails the build; the + // trees are still written. + addinProject: { + expected: ["symbolIndex", "prepDest", "helpArchive"], + runOnMain: true, + async execute({ symbolIndex, helpArchive }, ctx, state) { + const addin = addinProjectOf(state.site.config); + if (!addin || ctx.opts.dryRun) return null; + try { + return await writeAddinProject({ + dir: path.resolve(ctx.srcRoot, addin.src), + symbols: symbolIndex.json, + archive: helpArchive?.zip ?? null, + roots: [ctx.destRoot], + rel: addin.dest, + }); + } catch (err) { + return { failed: true, rel: addin.dest, error: err.message }; + } + }, + submit() {}, + }, + // Write redirect stubs + sitemap/robots. Waits for writeAssets (theme on // disk), searchData, deriveRedirects, and deriveSitemap. // Passes searchStats through to writeOffline (for search-data.js). @@ -979,6 +1053,7 @@ const TASKS = { return writeOffline(state.staticFiles, state.site, ctx.destRoot, { auxStats, sitePaths: state.sitePaths, + onlineSitePaths: state.onlineSitePaths, profileOffline: ctx.opts.profileOffline, check: !!state.checkTrees, }); @@ -1165,8 +1240,9 @@ const TASKS = { // long before the check; on a three-page fixture it does not, and // the audit failed the build over nothing. // symbolIndex for the same reason: tB/symbols.json is in the online tree's - // index, and the audit must not look for it before it is written. - expected: ["linkJoin", "checkBook", "scss", "symbolIndex"], + // index, and the audit must not look for it before it is written. And + // addinProject: the add-in's project file is in both trees' indexes. + expected: ["linkJoin", "checkBook", "scss", "symbolIndex", "addinProject"], runOnMain: true, async execute({ linkJoin: trees, checkBook: book }, ctx, state) { if (!state.checkTrees) return null; @@ -1231,6 +1307,10 @@ function chunkPages(pages, workers) { // ── Gantt chart ─────────────────────────────────────────────────────────────── +// The chart's file in each tree. The sources hold a placeholder at this path, +// which the trees copy; the injection overwrites it with the chart. +const GANTT_SVG_REL = "assets/images/gantt.svg"; + // The Gantt is rendered from the scheduler's own timings, so it cannot // exist until every task -- including the check -- has finished. That // makes it the one page whose shipped bytes the check never saw. It @@ -1252,7 +1332,7 @@ async function injectGanttChart(pages, destRoot, svgContent) { if (e.code !== "ENOENT") throw e; continue; } - const marker = 'data-svg-src="assets/images/gantt.svg"'; + const marker = `data-svg-src="${GANTT_SVG_REL}"`; const idx = html.indexOf(marker); if (idx < 0) continue; const svgStart = html.indexOf("<svg", idx); @@ -1260,7 +1340,7 @@ async function injectGanttChart(pages, destRoot, svgContent) { if (svgStart < 0 || svgEnd < 0) continue; const patched = html.slice(0, svgStart) + svgContent + html.slice(svgEnd + 6); await fs.writeFile(htmlPath, patched, "utf8"); - await fs.writeFile(path.join(root, "assets", "images", "gantt.svg"), svgContent, "utf8"); + await fs.writeFile(path.join(root, ...GANTT_SVG_REL.split("/")), svgContent, "utf8"); injected.push({ which: root === destRoot ? "online" : "offline", destPath: page.destPath.replaceAll("\\", "/"), @@ -1452,6 +1532,16 @@ export async function runBuild(opts) { ); } } + const archive = results.get("helpArchive"); + if (archive?.text) process.stdout.write(archive.text); + if (archive?.failed) failBuild(); + const addinResult = results.get("addinProject"); + if (addinResult) { + const addinDir = path.resolve(srcRoot, addinProjectOf(site.config).src); + const label = `${path.relative(REPO_ROOT, addinDir).replaceAll(path.sep, "/")}/`; + process.stdout.write(formatAddinProject(addinResult, label, " ")); + if (addinResult.failed) failBuild(); + } if (offlineResult) { console.log(` ${pc.bold("offline:")} -> ${pc.cyan(`${destRoot}-offline`)}`); console.log( @@ -1559,18 +1649,17 @@ export async function runBuild(opts) { process.stdout.write(await syncAddinIndex({ src: guardedSrc, json: symbolStats.json, write: mayWrite })); } - // The help add-in's archive of the offline tree, last of all: the Gantt - // injection above has rewritten BuildInfo.html in it -- see help-archive-step.mjs. - const skipOfflineTree = opts.skipOffline ?? site.config.also_build_offline === false; - const archive = await helpArchiveStep({ - src: guardedSrc, - offlineRoot: skipOfflineTree ? null : `${destRoot}-offline`, - serve: !!opts.serve, - dryRun: !!opts.dryRun, - disabled: !!opts.skipHelpArchive, + // The help add-in's archive was written before the Gantt injection above, so + // the offline tree on disk has to differ from it in the two files the + // injection writes and in nothing else -- see help-archive-step.mjs. + const injectedOffline = injected.filter((i) => i.which === "offline").map((i) => i.destPath); + const stale = await recheckHelpArchive({ + archive, + offlineRoot: `${destRoot}-offline`, + mayDiffer: injectedOffline.length ? [...injectedOffline, GANTT_SVG_REL] : [], }); - if (archive.text) process.stdout.write(archive.text); - if (archive.failed) failBuild(); + if (stale.text) process.stdout.write(stale.text); + if (stale.failed) failBuild(); return { pages, staticFiles, site, destRoot }; } diff --git a/builder/template.mjs b/builder/template.mjs index 29e732562..470a31f93 100644 --- a/builder/template.mjs +++ b/builder/template.mjs @@ -161,16 +161,22 @@ function renderHead(page, site, init) { // It also marks the page for the help add-in's pane: `?pane=1`, kept in // sessionStorage the same way, sets `data-pane`, and custom.scss then hides // the site's navigation and header. + // Where storage throws, as it does in the frame of the add-in's detached + // window (a frame of another site than its `data:` page), both are kept in + // the frame's window.name instead, as `tbdocs:<theme>:<1 for pane>`, which + // a navigation inside the frame keeps; it is written only by a page given + // either parameter, and read only when storage throws. const bu = String(site.config.baseurl ?? ""); return ( `<head>\n` + ` <meta charset="UTF-8">\n` + ` <meta http-equiv="X-UA-Compatible" content="IE=Edge"><script>\n` + - ` (function () { var q = /[?&]theme=(light|dark)(?:&|$)/.exec(location.search), p = /[?&]pane=1(?:&|$)/.test(location.search), t = null;\n` + + ` (function () { var q = /[?&]theme=(light|dark)(?:&|$)/.exec(location.search), a = /[?&]pane=1(?:&|$)/.test(location.search), p = a, t = null, n = /^tbdocs:(light|dark)?:(1)?$/.exec(window.name);\n` + ` try { if (q) sessionStorage.setItem('theme', q[1]); t = sessionStorage.getItem('theme') || localStorage.getItem('theme');\n` + - ` if (p) sessionStorage.setItem('pane', '1'); else p = sessionStorage.getItem('pane') === '1'; } catch (e) {}\n` + + ` if (p) sessionStorage.setItem('pane', '1'); else p = sessionStorage.getItem('pane') === '1'; } catch (e) { if (n) { t = n[1]; p = p || !!n[2]; } }\n` + ` if (q) t = q[1];\n` + ` if (t === 'light' || t === 'dark') document.documentElement.setAttribute('data-theme', t);\n` + + ` if (q || a) window.name = 'tbdocs:' + (t === 'light' || t === 'dark' ? t : '') + ':' + (p ? '1' : '');\n` + ` if (p) document.documentElement.setAttribute('data-pane', ''); })();\n` + ` </script>\n` + ` <script type="text/javascript" src="${escapeMarkupAndQuotes(relativeUrl("/assets/js/theme-toggle.js", bu))}" defer></script>\n` + diff --git a/docs/Documentation/Builder.md b/docs/Documentation/Builder.md index c42a1061f..e1b04885e 100644 --- a/docs/Documentation/Builder.md +++ b/docs/Documentation/Builder.md @@ -130,6 +130,7 @@ Modules grouped by role. Each entry has one line; deep-dive in [Pipeline Stages] | [`page-baseline.mjs`](https://github.com/twinbasic/documentation/blob/main/builder/page-baseline.mjs) | The page-count drift guard, and the committed `page-baseline.json` it compares against. A rise rewrites the file, a fall fails the build, and neither CI nor `--serve` may write. See [Building and Deployment](Building#the-page-count-drift-guard). | | [`symbol-baseline.mjs`](https://github.com/twinbasic/documentation/blob/main/builder/symbol-baseline.mjs) | The symbol index's drift guard, and the committed `symbol-baseline.json`, every URL `tB/symbols.json` has published. A new URL rewrites the file, a lost one fails the build. | | [`addin-index.mjs`](https://github.com/twinbasic/documentation/blob/main/builder/addin-index.mjs) | The help add-in's committed copy of the index, `add-in/Resources/SYMBOLS/symbols.json`: a local build rewrites it when it differs from `tB/symbols.json`, and CI and `--serve` only say so. | +| [`addin-project.mjs`](https://github.com/twinbasic/documentation/blob/main/builder/addin-project.mjs) | The help add-in's project file, a published download: packs the files git tracks in `add-in/`, with this build's symbol index, through `importProject` from `scripts/impexp.mjs`, the one module under `scripts/` the builder may import. See [Tools and Scripts](Tools#the-help-add-ins-project-file). | | [`baseline.mjs`](https://github.com/twinbasic/documentation/blob/main/builder/baseline.mjs) | The read, the write and the comparison the two drift guards share, and `GUARDED_SRC`, the one source tree they have figures for. | | [`publish-policy.mjs`](https://github.com/twinbasic/documentation/blob/main/builder/publish-policy.mjs) | The allowlist of file types that may reach a published tree. Enforced twice, unconditionally: over the static-file inventory in `discover`, and over each tree's `deriveTreeRels` inventory in `dispatch`. A finding aborts the build. See [Drift guards](#drift-guards-and-failure-modes). | @@ -208,12 +209,12 @@ The complete layout, allocation helper, and the `readTaskMeta` / `writeTaskMeta` ## Task DAG by section -The pipeline has 32 named static tasks plus 2N dynamic ones (N render chunks + N flush tasks). The Gantt chart groups them into five sections, and the discussion below follows the same grouping: +The pipeline has 34 named static tasks plus 2N dynamic ones (N render chunks + N flush tasks). The Gantt chart groups them into five sections, and the discussion below follows the same grouping: - **Seeds**: `config`, `buildInfo`, `scssLight`, `scssDark`, `highlighterInit`, `loadData` - **Spine**: `discover`, `vendorAssets`, `nav`, `dot`, `buildInit`, `markdownInit`, `deriveRedirects`, `deriveSitemap`, `resolveBookChapters` - **Render**: `dispatch`, `prepDest`, `prepPageDirs`, `render:i`, `renderJoin` -- **Write**: `scss`, `flush:i`, `flushJoin`, `writeAssets`, `searchData`, `symbolIndex`, `writeAux`, `writeOffline`, `writePdf` +- **Write**: `scss`, `flush:i`, `flushJoin`, `writeAssets`, `searchData`, `symbolIndex`, `helpArchive`, `addinProject`, `writeAux`, `writeOffline`, `writePdf` - **Check**: `linkJoin`, `checkBook`, `checkReport` --- present on every ordinary build, because `build.bat` always passes `--check-audit-index` `warmInit` and `renderEnvInit` are in none of the five. The chart draws them in the worker rows as start-up bars, beside each worker's cold start, and they are described under Render. The chart does not draw the three `Join` barriers. @@ -270,7 +271,7 @@ dispatch ┬→ render:0 ┬→ flush:0 ┐ ↓ ↓ renderJoin flushJoin ↓ - symbolIndex + symbolIndex ─→ addinProject ↓ searchData ``` @@ -291,7 +292,9 @@ dispatch ┬→ render:0 ┬→ flush:0 ┐ - `flushJoin` (main, `on_demand`) --- barrier that aggregates per-chunk write stats and gates `writeAux` + `writePdf`. - `writeAssets` (main) --- writes generated CSS, copies vendored theme JS, copies the project's static files. Page HTML is *not* written here --- the per-chunk `flush:i` tasks already did that. Depends on `prepPageDirs` so the directory tree exists. - `searchData` (main) --- concatenates `state.searchChunks` (already populated by each `render:i`'s `submit()`), joins `symbolIndex`'s symbols onto the matching entries by URL (adding the `names` / `qualified` / `primary` fields; see [Site search](Pipeline-Stages#searchdata-main) and `WIP.Search.md`'s "Design" §2), renumbers the global `i` index, and writes `search-data.json`. The heavy work (heading split, content sanitisation, URL encoding) ran on the workers; this task only joins and consolidates. Depends on `renderJoin`, `prepDest` and `symbolIndex` (for the join). -- `symbolIndex` (main) --- writes `tB/symbols.json`, the [symbol index](Building#the-symbol-index) the IDE help add-in reads, and hands its `symbols` array to `searchData`'s join. Reads the heading ids out of every `/tB/` page's `renderedContent`, joins them with the committed `builder/package-api.json`, and returns the index's URLs for the drift guard that `runBuild` runs once the build is done. Depends on `renderJoin` and `prepDest`; `checkReport` waits for it, because the file is in the online tree's index; `searchData` now waits for it too. +- `symbolIndex` (main) --- writes `tB/symbols.json`, the [symbol index](Building#the-symbol-index) the IDE help add-in reads, and hands its `symbols` array to `searchData`'s join. Reads the heading ids out of every `/tB/` page's `renderedContent`, joins them with the committed `builder/package-api.json`, and returns the index's URLs for the drift guard that `runBuild` runs once the build is done. Depends on `renderJoin` and `prepDest`; `checkReport` waits for it, because the file is in the online tree's index; `searchData` and `addinProject` wait for it too. +- `helpArchive` (main) --- writes `add-in/Resources/HELP/site.zip`, the archive of the offline tree that the IDE help add-in embeds (see [Tools and Scripts](Tools#the-help-archive)). Depends on `flushJoin`, `scss` and `writeOffline`, which between them write everything in the offline tree, and runs before the Gantt chart is injected, so the archive holds `BuildInfo.html` without the chart. Does nothing under `--serve` or `--dry-run`, for a source tree other than `docs`, for a `--dest` other than `docs/_site`, without an offline tree, or with `--no-help-archive`. After the Gantt chart is injected, `runBuild` checks that the offline tree still lists the archive's files and differs from it in `BuildInfo.html` and `assets/images/gantt.svg` alone. +- `addinProject` (main) --- packs the help add-in's source folder, `add-in/`, into `tbDocsHelp.twinproj` and writes it into the online tree at the path `_config.yml`'s `addin_project` names (see [Tools and Scripts](Tools#the-help-add-ins-project-file)). The files are those git tracks there, the symbol index in it is the one `symbolIndex` wrote, and `Resources/HELP/site.zip` is the archive `helpArchive` wrote, if it wrote one. Depends on `symbolIndex`, `prepDest` and `helpArchive`; `dispatch` has already put the file in the online tree's index, so `checkReport` waits for it. Does nothing under `--dry-run`, or for a config that declares no download. - `writeAux` (main) --- writes redirect stubs + sitemap + robots.txt. Depends on `writeAssets`, `searchData`, `flushJoin`, `deriveRedirects`, `deriveSitemap`. - `writeOffline` (main) --- produces `_site-offline/`. The per-page offline HTML was already computed inside `render:i` and written by `flush:i`, so this task only handles the cross-cutting work: CSS url() rewriting, the just-the-docs.js AST patch, the `search-data.js` wrapper, the offline `nav.js`, theme assets, redirect stubs. - `writePdf` (main) --- assembles `_site-pdf/book.html` and copies the images it references. Depends on `flushJoin` (so `renderedContent` is filled), `resolveBookChapters` (so `bookData._chapters` is wired), and `dot` (so diagram SVGs are in `staticFiles`). @@ -317,7 +320,7 @@ For a one-page reference, every task and its execution locus: | Write | `scss` | main | Joins light + dark; writes online + offline CSS. | | Write | `flush:i` | worker (pinned) | Page HTML write, online + offline. | | Write | `flushJoin` | main | Barrier. | -| Write | `writeAssets`, `searchData`, `symbolIndex`, `writeAux`, `writeOffline`, `writePdf` | main | I/O bound; cooperative async concurrency. | +| Write | `writeAssets`, `searchData`, `symbolIndex`, `helpArchive`, `addinProject`, `writeAux`, `writeOffline`, `writePdf` | main | I/O bound; cooperative async concurrency. | | Check | `linkJoin`, `checkBook`, `checkReport` | main | No-ops without `--check`. | Three pieces of work newly distributed to render workers under the current design: @@ -438,7 +441,7 @@ The residual 0.5% is kerning, which a per-character table cannot express. It err Every build emits an inline-SVG Gantt chart of its task timeline. [`gantt.mjs`](https://github.com/twinbasic/documentation/blob/main/builder/gantt.mjs)'s `groupGanttTimings` sorts the scheduler's timings into a `Map<section, taskTiming[]>`, and its `renderGantt(grouped)` renders one SVG row per main-thread task plus one row per worker lane. The main-thread rows are grouped in four bands --- Seeds, Spine, Write and Check --- with Render's main-thread tasks in the Spine band and the worker rows between Spine and Write; the `Join` barriers are not drawn. Each worker's row has one coloured rectangle per task it ran, in the order they started, and the colour encodes the task's section. The start-up bars share that row, labelled `cold`, `warm` and `env`: the worker's cold start, drawn on the first build only, since on a rebuild the workers are already running, then `warmInit` and `renderEnvInit`. -The Gantt chart flows through the same SVG inlining pipeline as other diagrams. The [Build Info](BuildInfo) page contains a standard markdown image reference to a placeholder `gantt.svg`; during the render pass it becomes an inline SVG wrapper with zoom and export controls. After `writeOffline` completes, `tbdocs.mjs:injectGanttChart` locates the wrapper's `data-svg-src` marker in the rendered HTML and swaps the placeholder SVG content for the real Gantt chart. Both the online and offline copies of the page are patched; the on-disk `gantt.svg` file is also updated so the offline mirror's fallback stays current. +The Gantt chart flows through the same SVG inlining pipeline as other diagrams. The [Build Info](BuildInfo) page contains a standard markdown image reference to a placeholder `gantt.svg`; during the render pass it becomes an inline SVG wrapper with zoom and export controls. After `writeOffline` completes, `tbdocs.mjs:injectGanttChart` locates the wrapper's `data-svg-src` marker in the rendered HTML and swaps the placeholder SVG content for the real Gantt chart. Both the online and offline copies of the page are patched; the on-disk `gantt.svg` file is also updated so the offline mirror's fallback stays current. The help archive and the help add-in's project file are made before the injection, and hold the placeholder, because the chart holds the build's own timings and would make every build's download differ. When adding a new task to `TASKS`, give it a section in `GANTT_SECTION` in `gantt.mjs`: `Seeds`, `Spine`, `Render`, `Write` or `Check`. The build fails, naming the task, if a task has no section or one the chart does not draw. Only a `unique_per_worker` task needs none: its timings are drawn in each worker's lane. @@ -500,7 +503,7 @@ The fonts are committed artifacts, like the DOT renders: `scripts/build_fonts.py CSS files in either copy path get a baseurl rewrite (`url("/path")` → `url("<baseurl>/path")`) when the deployment baseurl is non-empty; the same transform applies to generated CSS so the `url("/favicon.png")` the SCSS entry point emits resolves correctly under sub-path deployments. -The project JS is deliberately small. `theme-toggle.js` implements the three-state (system / light / dark) theme switch as a progressive enhancement over the no-JS `prefers-color-scheme` default: the correct palette renders even with scripting disabled, and the script only adds the manual override that persists a `data-theme` choice. A page opened with `?theme=light` or `?theme=dark` takes that theme instead, as do the pages reached from it in the same tab, without changing the reader's stored choice; the IDE's help add-in passes the IDE's theme this way. It also passes `?pane=1`, which the same early script keeps for the tab and which hides the sidebar and the header, so that its narrow pane shows the page alone. `svg-inline.js` powers the click-to-zoom overlay and the download / copy controls on inlined diagrams. +The project JS is deliberately small. `theme-toggle.js` implements the three-state (system / light / dark) theme switch as a progressive enhancement over the no-JS `prefers-color-scheme` default: the correct palette renders even with scripting disabled, and the script only adds the manual override that persists a `data-theme` choice. A page opened with `?theme=light` or `?theme=dark` takes that theme instead, as do the pages reached from it in the same tab, without changing the reader's stored choice; the IDE's help add-in passes the IDE's theme this way. It also passes `?pane=1`, which the same early script keeps for the tab and which hides the sidebar and the header, so that its narrow pane shows the page alone. Where the browser refuses the page its storage, as it does to the frame in the add-in's detached window, the early script keeps both parameters in the frame's `window.name` instead, and `theme-toggle.js` takes the theme from the page rather than from storage. `svg-inline.js` powers the click-to-zoom overlay and the download / copy controls on inlined diagrams. ## Project styling diff --git a/docs/Documentation/Building.md b/docs/Documentation/Building.md index fdde87d62..9c538e3ec 100644 --- a/docs/Documentation/Building.md +++ b/docs/Documentation/Building.md @@ -78,6 +78,7 @@ Each `.bat` opens with `@pushd "%~dp0"`, which is what lets it be invoked from a && node --test test/png.test.mjs \ && node --test test/example-batches.test.mjs \ && node --test test/ports.test.mjs \ + && node --test test/addin-project.test.mjs \ && node scripts/check_regex_safety.mjs \ && node scripts/check_code_regions.mjs \ && node scripts/check_page_baseline.mjs \ @@ -116,7 +117,9 @@ or directly, from the repository root: A single `tbdocs` run produces all three trees. The `also_build_offline` and `also_build_pdf` keys in `_config.yml` toggle the sibling outputs; the `--no-offline` and `--no-pdf` flags do the same from the command line if you only want `_site/`. -A local build also ends by writing `add-in/Resources/HELP/site.zip`, an archive of the offline tree that the IDE help add-in embeds, and prints one line for it. The file is not committed. `--no-help-archive` turns the step off, and both CI workflows pass it. [Tools and Scripts](Tools#the-help-archive) describes the archive. +Every build writes `add-in/Resources/HELP/site.zip`, an archive of the offline tree that the IDE help add-in embeds, and prints one line for it. The archive is made once the offline tree is complete and before the Gantt chart goes into the build-information page. The file is not committed. `--no-help-archive` turns the step off. [Tools and Scripts](Tools#the-help-archive) describes the archive. + +Every build, CI's included, also packs the IDE help add-in's source folder, `add-in/`, into its project file, `tB/IDE/AddIns/downloads/tbDocsHelp.twinproj` in the online tree, which [Help Add-In](../../tB/IDE/AddIns/Help) offers as a download, and prints one line for it. Only the files git tracks there are packed, with the symbol index the same build wrote and the archive that build wrote; a build that wrote no archive packs none and says so. The offline tree holds no copy of the file, and its links to it open the website's copy. [Tools and Scripts](Tools#the-help-add-ins-project-file) describes the file. The full set of `tbdocs` CLI flags --- every flag, what each one does, when to use it --- lives on the [Tools and Scripts](Tools#tbdocs) page. @@ -254,7 +257,7 @@ The link check is part of the build. `build.bat` passes `--check-audit-index`, a build.bat -It covers all three trees --- `_site/` (the online tree), `_site-offline/` (the `file://`-browsable mirror, which also carries `--forbid 'https://docs.twinbasic.com'` so a surviving live-site link is flagged: the offline mirror should never navigate back to the live docs site), and `_site-pdf/book.html` (informational, and listing as `OUT OF BOOK` every link that leaves the book for the website). Every tree is also checked for HTML well-formedness, duplicate `id`s, anchor resolution, accessibility hints and remote `<img src>`; the online tree adds sitemap, search-index and canonical-URL integrity. The same check runs in CI on every pull request and on every push to `staging`. +It covers all three trees --- `_site/` (the online tree), `_site-offline/` (the `file://`-browsable mirror, which also carries `--forbid 'https://docs.twinbasic.com'` so a surviving live-site link is flagged: the offline mirror should never navigate back to the live docs site, except through the links the rewrite writes to the few files only the website holds, such as the help add-in's download), and `_site-pdf/book.html` (informational, and listing as `OUT OF BOOK` every link that leaves the book for the website). Every tree is also checked for HTML well-formedness, duplicate `id`s, anchor resolution, accessibility hints and remote `<img src>`; the online tree adds sitemap, search-index and canonical-URL integrity. The same check runs in CI on every pull request and on every push to `staging`. A failing check does not abort the build --- a broken link still produces a site worth looking at --- so it sets the exit code to 1 instead, whether the check found link failures, integrity failures or both. The summary lines say which. @@ -411,6 +414,8 @@ build rewrites that copy whenever its own index differs and says so: Commit it with the pages that changed it. CI and `--serve` only say that it differs. A copy one build behind is never a failure: the add-in then has an older index, not a broken one. +The add-in's project file that the build publishes never holds that copy: it is packed with +the index the same build wrote, so the download is current even when the copy is not. A page in a package folder that gives no entry at all is reported by name after the summary. Its title names nothing the package declares --- most often @@ -586,7 +591,7 @@ Both workflows add `--no-fetch-assets` to the build, and this is the difference The asymmetry is the whole point. An author who wrote the markdown but forgot to commit the image would otherwise get a green build while the published site went on hotlinking a third party --- which is the failure the vendoring mechanism exists to prevent, so CI cannot be the place that quietly repairs it. Setting `$CI` already selects offline mode; the flag only states it. Build locally once after adding a video or pasting a screenshot, and `git status` names exactly what to add --- see [Authoring Pages](Authoring#committing-downloaded-assets). -Both workflows also add `--no-help-archive`, because CI has no use for the help add-in's archive of the offline tree that a local build writes. +Neither workflow adds `--no-help-archive`, and `check_ci_workflows.mjs` fails when one does: the help add-in's project file, which the site publishes as a download, carries the archive of the offline tree, and without it the add-in built from the download shows the pages from the website only. ### The deployment tells the build where it is diff --git a/docs/Documentation/Extending.md b/docs/Documentation/Extending.md index ecfd2b64d..5dc983925 100644 --- a/docs/Documentation/Extending.md +++ b/docs/Documentation/Extending.md @@ -153,7 +153,7 @@ Find the row for what changed. The middle column is the step that explains the m | A task's `expected` list | [Pick the right flags](#2-pick-the-right-flags) | Nothing. The scheduler derives successor edges from `expected`, so a static edge is declared exactly once. The dynamic edges are the exception: `render:i` and `flush:i` are wired in `dispatch.submit`, not in `TASKS`. | | A task's `execute()` or handler return shape | [Define the task in `TASKS`](#4-define-the-task-in-tasks) | The task's own `submit()`, and every downstream task that reads the field back off `state`. | | Which thread a task runs on | [Decide where the work runs](#1-decide-where-the-work-runs) | Swap `runOnMain: true` + `execute` for `handler:` plus a `cpu-worker.mjs` entry, and add the name to `HANDLERS` in `sab-scheduler.mjs`. Moving a task onto a worker costs it direct access to `state`: everything it reads has to arrive through the payload SAB, and everything it produces has to come back through its return value. | -| Which Gantt section a task charts under | --- | **`GANTT_SECTION` in `gantt.mjs`**, not the task definition. A `ganttSection` on the definition does win where it is set, which is why the walkthroughs below set one --- but only the `render:i` and `flush:i` tasks that `dispatch` registers carry one. Of the 32 static tasks, 30 are listed in the map; the other two, `warmInit` and `renderEnvInit`, are `unique_per_worker` tasks, whose per-lane timings chart as `Boot`. Any other task with no section, or with a section the chart does not draw, fails the build. | +| Which Gantt section a task charts under | --- | **`GANTT_SECTION` in `gantt.mjs`**, not the task definition. A `ganttSection` on the definition does win where it is set, which is why the walkthroughs below set one --- but only the `render:i` and `flush:i` tasks that `dispatch` registers carry one. Of the 33 static tasks, 31 are listed in the map; the other two, `warmInit` and `renderEnvInit`, are `unique_per_worker` tasks, whose per-lane timings chart as `Boot`. Any other task with no section, or with a section the chart does not draw, fails the build. | | A field on the per-page render delta | [Worked example B](#6-worked-example-b-distributed-compute) | The `pages.map(…)` projection at the end of the render handler **and** the merge in `dispatch.submit`'s `render:i` callback. Both, or the field never reaches main at all. | | What a markdown-it rule emits | [Write the plugin](#1-write-the-plugin) | Nothing in the chain, if the rule is self-contained. But changing the emitted HTML changes what the accessibility scan can see --- read the construct-family note at the end of [Verify](#3-verify). | diff --git a/docs/Documentation/Images/environment-variables.png b/docs/Documentation/Images/environment-variables.png deleted file mode 100644 index 6ea9cdd32..000000000 Binary files a/docs/Documentation/Images/environment-variables.png and /dev/null differ diff --git a/docs/Documentation/Pipeline-Stages.md b/docs/Documentation/Pipeline-Stages.md index 8b663404e..8ca7af73f 100644 --- a/docs/Documentation/Pipeline-Stages.md +++ b/docs/Documentation/Pipeline-Stages.md @@ -274,7 +274,7 @@ dispatch.execute() → { chunks, sharedSAB } The fan-out point. `execute`: 1. Slices `state.pages` into `workerCount × SLICES_PER_WORKER` chunks (capped at one chunk per worker for small page counts). -2. Computes the `sitePaths` set via `buildSitePathsSync` from `offline-rewrite.mjs`, using the vendored theme asset list from `enumerateVendoredThemeAssets()` rather than traversing `_site/assets/`. +2. Computes the `sitePaths` set via `buildSitePathsSync` from `offline-rewrite.mjs`, using the vendored theme asset list from `enumerateVendoredThemeAssets()` rather than traversing `_site/assets/`. The same list carries the path of the help add-in's project file when `_config.yml` declares one (`addinProjectOf`), since [`addinProject`](#addinproject-main) writes it only after the render. 3. Derives each output tree's inventory with `deriveTreeRels` and runs `unpublishableTreePaths` from `publish-policy.mjs` over it, **throwing** on anything that is not a publishable type. This is the sweep that sees what `discover`'s cannot: redirect stubs, vendored theme assets, and the generated `sitemap.xml` and `search-data.json`. The inventory is derived unconditionally now and shared with `checkTrees` below, which previously computed it only under `--check`. 4. Builds the shared payload and packs it into one SAB via `packShared` from `sab-broadcast.mjs`: config, site-level SEO, pre-rendered chrome + sidebar, serialized link tables, static-file relative-path set, baseurl, site-paths array, offline-exclude patterns, skip-offline flag, build info, the inlined `svgContents`, the `vendoredVideos` / `vendoredImages` maps from `vendorAssets`, and `checkTrees` (per-tree `rels` + `baseurl`, or absent when `--check` is off). @@ -424,7 +424,16 @@ symbolIndex.execute() → { entries, urls, gaps, unplaced, symbols } Writes `tB/symbols.json`, the [symbol index](Building#the-symbol-index). Reads `builder/package-api.json` --- the build fails, naming `scripts/build_package_api.mjs`, if it is missing --- then calls `symbolPages(state.pages)`, `deriveSymbolIndex({ pages, api })` and `serializeSymbolIndex(...)` from `symbols.mjs`. `renderJoin` is the dependency that matters: an entry's anchor is the id the render gave its heading, read out of `renderedContent`, never computed a second time. ~50 ms over the reference's pages. With `--symbol-gaps <path>` it also writes `reportableGaps(...)` there as JSON. -`urls` is every distinct URL in the index. `runBuild` hands it to `checkSymbolBaseline` from `symbol-baseline.mjs` after the check report, beside the page-count guard; `unplaced` names the package pages that gave no entry, which the summary prints. `checkReport` waits for this task, since `tB/symbols.json` is in the online tree's index and `--check-audit-index` must not look for it early. `symbols` -- the same array `deriveSymbolIndex` returned, each `{ name, container, url, ... }` -- passes through to `searchData`, which is the only other consumer; it is not written anywhere itself (that is `tB/symbols.json`, above). `json` is that file's text, which `runBuild` hands to `syncAddinIndex` from `addin-index.mjs` for the help add-in's copy. +`urls` is every distinct URL in the index. `runBuild` hands it to `checkSymbolBaseline` from `symbol-baseline.mjs` after the check report, beside the page-count guard; `unplaced` names the package pages that gave no entry, which the summary prints. `checkReport` waits for this task, since `tB/symbols.json` is in the online tree's index and `--check-audit-index` must not look for it early. `symbols` -- the same array `deriveSymbolIndex` returned, each `{ name, container, url, ... }` -- passes through to `searchData`, which is the only other consumer; it is not written anywhere itself (that is `tB/symbols.json`, above). `json` is that file's text, which `runBuild` hands to `syncAddinIndex` from `addin-index.mjs` for the help add-in's copy, and which `addinProject` packs into the add-in's project file. + +### `addinProject` (main) + +```js +addinProject.expected = ["symbolIndex", "prepDest"] +addinProject.execute({ symbolIndex }) → { rel, bytes, files, untracked, roots, ms } | { failed, rel, error } | null +``` + +Writes the help add-in's project file, the download [Help Add-In](../../tB/IDE/AddIns/Help) offers (see [Tools and Scripts](Tools#the-help-add-ins-project-file)). Reads `addin_project` from the config with `addinProjectOf`, and returns `null` when the config declares none or under `--dry-run`. Otherwise calls `writeAddinProject` from `addin-project.mjs` with the add-in's folder (`src`, resolved against the source root), `symbolIndex.json` and the trees to write: the online tree, and the offline tree unless the build has none or `offline_exclude` leaves the file out of it. `dispatch` has already put `dest` in both trees' indexes and in `sitePaths`, so the links to the file resolve in both trees, and `checkReport` waits for this task. A failure --- `git` missing, an unreadable file --- is caught and returned rather than thrown, so the trees are still written; `runBuild` prints the result and fails the build on `failed`. ~100 ms. ### `writeAux` (main) @@ -493,7 +502,7 @@ Checks `_site-pdf/book.html` as a single one-document chunk, against a tree inde ### `checkReport` (main, terminal) ```js -checkReport.expected = ["linkJoin", "checkBook", "scss", "symbolIndex"] +checkReport.expected = ["linkJoin", "checkBook", "scss", "symbolIndex", "addinProject"] checkReport.execute({ linkJoin, checkBook }) → void ``` @@ -503,7 +512,7 @@ Formats every tree's result, decides the exit code, and optionally writes the ma - **`--check-findings <path>`** writes the findings as JSON for [`check_links_diff.mjs`](Tools#check-links-diff) to diff against the standalone script's. Written *before* the exit code is decided, so a failing check still produces the file that says what it found. - **`--check-audit-index`** additionally diffs the tree index the build derived from its own records against what actually landed on disk. This is the one failure mode the two-checker findings comparison structurally cannot see: a *missing* index entry turns a working link into a reported break, which is loud, but a *spurious* one makes the oracle answer "exists" for a path that 404s in production, and on a clean site nothing links to a path that does not exist, so nothing would ever notice. Cost is one `readdir` per tree. -`scss` is in `expected` for a reason worth keeping: `--check-audit-index` reads the tree off disk, and the combined stylesheet is in the index from the moment `dispatch` builds it. Without that edge the audit can run first and report the file as "indexed but not on disk" --- which it was, for another few milliseconds. On the real site `scss` finishes long before the check; on a three-page fixture it does not, and the audit failed the build over nothing. `symbolIndex` is there for the same reason: `tB/symbols.json` is in the online tree's index too. +`scss` is in `expected` for a reason worth keeping: `--check-audit-index` reads the tree off disk, and the combined stylesheet is in the index from the moment `dispatch` builds it. Without that edge the audit can run first and report the file as "indexed but not on disk" --- which it was, for another few milliseconds. On the real site `scss` finishes long before the check; on a three-page fixture it does not, and the audit failed the build over nothing. `symbolIndex` is there for the same reason: `tB/symbols.json` is in the online tree's index too. So is `addinProject`, whose project file is in both trees' indexes. --- @@ -585,7 +594,7 @@ The allowlist of file types that may reach a published tree. Every non-page unde | `SOURCE_EXTENSIONS` | `Set<string>` | Extensions a file discovered under `docs/` may carry. | | `BUILD_EXTENSIONS` | `Set<string>` | Extensions the build itself emits, additionally allowed in a tree inventory. | | `EXTENSIONLESS_FILENAMES` | `Set<string>` | Exact basenames allowed with no extension (`CNAME`). | -| `publishPolicyFor` | `(config) → { declared }` | Reads `bundle_extra` into the set of individually declared published paths, which are exempt by path rather than by extension. | +| `publishPolicyFor` | `(config) → { declared, generated }` | Reads `bundle_extra` into `declared`, the set of individually declared published paths, which are exempt by path rather than by extension on both surfaces; and `addin_project`'s `dest` into `generated`, a path the build writes itself, exempt on the tree surface only. | | `unpublishableSourceFiles` | `(staticFiles, policy) → findings[]` | Source sweep. Each finding has `rel`, `from` (the path on disk) and `why`. | | `unpublishableTreePaths` | `(rels, policy) → findings[]` | Tree sweep over a `deriveTreeRels` inventory. | | `formatPublishRefusal` | `(findings, { surface, label }) → string` | The abort message: every finding named, plus the fix appropriate to the surface. | @@ -865,6 +874,15 @@ For **renderer rules**, order inverts. Both image plugins capture the current `m | `syncAddinIndex` | `({ src, json, write, file }) → Promise<string>` | Compares `json`, the `tB/symbols.json` this build wrote, with the help add-in's committed copy byte for byte, and rewrites the copy when they differ and `write` is set. Does nothing for a source root other than `GUARDED_SRC`, or when `json` is empty (`--dry-run`). Returns the line to print, `""` when the copy is current; a difference never fails the build. Called by `runBuild` after the symbol index's drift guard. | | `ADDIN_INDEX_REL` | `string` | `"add-in/Resources/SYMBOLS/symbols.json"`, the default `file` under the repository root. | +### `addin-project.mjs` + +| Symbol | Signature | Description | +|---|---|---| +| `addinProjectOf` | `(config) → { src, dest } \| null` | The download `_config.yml`'s `addin_project` declares, `dest` without a leading slash; `null` unless both ends are given. | +| `packAddinProject` | `({ dir, symbols }) → Promise<{ bytes, files, untracked }>` | Packs the files `git ls-files` lists in `dir`, as the working folder holds them, less `Resources/HELP/`, with CRLF made LF outside `Resources/`, and `symbols` in place of `Resources/SYMBOLS/symbols.json` when given. Stages them in a temporary folder and packs it with `importProject` from `scripts/impexp.mjs`. `untracked` lists the files git does not track that were left out. Throws when git fails or tracks no `Settings`. | +| `writeAddinProject` | `({ dir, symbols, roots, rel }) → Promise<{ rel, bytes, files, untracked, roots, ms }>` | Packs once and writes the file at `rel` in each of `roots`. | +| `formatAddinProject` | `(result, dirLabel, indent) → string` | The line `runBuild` prints, and a second naming any untracked file left out. | + ### `page-baseline.mjs` | Symbol | Signature | Description | diff --git a/docs/Documentation/Tools.md b/docs/Documentation/Tools.md index 9da8d2d2d..a3f2a6de3 100644 --- a/docs/Documentation/Tools.md +++ b/docs/Documentation/Tools.md @@ -71,7 +71,7 @@ One of the four does not mean the same thing locally as it does in CI, on any pl test.bat -The tests the toolchain has to pass. Twenty-one steps, each stopping the run if it fails: +The tests the toolchain has to pass. Twenty-two steps, each stopping the run if it fails: 1. [`scripts/check_publish_policy.mjs`](#check-publish-policy) --- verifies the publish allowlist still refuses the types it is meant to. Needs neither a browser nor a built tree, so it goes first. 2. [`scripts/check_gate_lists.mjs`](#check-gate-lists) --- verifies the two gate lists on this page still match the wrappers that run them. @@ -83,17 +83,18 @@ The tests the toolchain has to pass. Twenty-one steps, each stopping the run if 8. [`test/png.test.mjs`](#png-test) --- unit tests for the pictures of a bug reproducer: PNG decoding, comparing and the side-by-side image, the files behind `images` and `expect.imagesDiffer`, and the refusals of `bug_repro.mjs` over fixtures. 9. [`test/example-batches.test.mjs`](#example-batches-test) --- runs `check_examples.mjs`'s probes, which test how samples are batched and how a crashed batch is cut down, without an IDE. 10. [`test/ports.test.mjs`](#ports-test) --- unit tests for how the harness claims the DevTools ports its IDEs use, without an IDE. -11. [`scripts/check_regex_safety.mjs`](#check-regex-safety) --- refuses a regex that can backtrack exponentially, written as a literal or built from constants. -12. [`scripts/check_code_regions.mjs`](#check-code-regions) --- verifies no pre-render rewrite alters the contents of a code fence or code span, that the rewrites over rendered HTML leave a raw `<pre>` or `<code>` alone, and that `lib/markdown.mjs` and `lib/frontmatter.mjs` pass their probes. -13. [`scripts/check_page_baseline.mjs`](#check-page-baseline) --- verifies the page-count drift guard still refuses a fall. -14. [`scripts/check_book_coverage.mjs`](#check-book-coverage) --- verifies the build still warns about a page `docs/_book.yml` does not mention. -15. [`scripts/check_symbol_index.mjs`](#check-symbol-index) --- verifies the symbol index still places each kind of symbol, and its drift guard still refuses a lost URL. -16. [`scripts/check_twin_parsers.mjs`](#check-twin-parsers) --- verifies the scanners of twinBASIC source and of the attribute reference still read the shapes each once misread. -17. [`scripts/check_attribute_sweep.mjs`](#check-attribute-sweep) --- verifies the logic of the attribute sweep: its site skeletons, how it reads a probe's diagnostics, how it batches probes, and how it compares the answers with `Attributes.md`. -18. [`scripts/check_cli.mjs`](#check-cli) --- verifies `lib/cli.mjs`, the command-line parser, and each tool's recorded command-line errors. -19. [`scripts/check_pdf_shims_equiv.mjs`](#check-pdf-shims-equiv) --- verifies the book's pdf-lib shims write what stock pdf-lib writes, patch the members of pdf-lib it lists, have each of them in their shim's table of targets, and run. -20. [`scripts/check_impexp_parity.mjs`](#check-impexp-parity) --- verifies the two editions of the impexp tool pass the same built-in tests, and exit, print and write the same for one sequence of commands. Without Python it reports itself skipped and passes, except in CI. -21. [`scripts/check_axe_patch_equiv.mjs`](#check-axe-patch-equiv) --- verifies the vendored axe source patch still produces identical colour values. +11. [`test/addin-project.test.mjs`](#addin-project-test) --- unit tests for how the build packs the help add-in's project file: exactly the files git tracks in `add-in/`, never `Resources/HELP/`, the same bytes from any checkout. +12. [`scripts/check_regex_safety.mjs`](#check-regex-safety) --- refuses a regex that can backtrack exponentially, written as a literal or built from constants. +13. [`scripts/check_code_regions.mjs`](#check-code-regions) --- verifies no pre-render rewrite alters the contents of a code fence or code span, that the rewrites over rendered HTML leave a raw `<pre>` or `<code>` alone, and that `lib/markdown.mjs` and `lib/frontmatter.mjs` pass their probes. +14. [`scripts/check_page_baseline.mjs`](#check-page-baseline) --- verifies the page-count drift guard still refuses a fall. +15. [`scripts/check_book_coverage.mjs`](#check-book-coverage) --- verifies the build still warns about a page `docs/_book.yml` does not mention. +16. [`scripts/check_symbol_index.mjs`](#check-symbol-index) --- verifies the symbol index still places each kind of symbol, and its drift guard still refuses a lost URL. +17. [`scripts/check_twin_parsers.mjs`](#check-twin-parsers) --- verifies the scanners of twinBASIC source and of the attribute reference still read the shapes each once misread. +18. [`scripts/check_attribute_sweep.mjs`](#check-attribute-sweep) --- verifies the logic of the attribute sweep: its site skeletons, how it reads a probe's diagnostics, how it batches probes, and how it compares the answers with `Attributes.md`. +19. [`scripts/check_cli.mjs`](#check-cli) --- verifies `lib/cli.mjs`, the command-line parser, and each tool's recorded command-line errors. +20. [`scripts/check_pdf_shims_equiv.mjs`](#check-pdf-shims-equiv) --- verifies the book's pdf-lib shims write what stock pdf-lib writes, patch the members of pdf-lib it lists, have each of them in their shim's table of targets, and run. +21. [`scripts/check_impexp_parity.mjs`](#check-impexp-parity) --- verifies the two editions of the impexp tool pass the same built-in tests, and exit, print and write the same for one sequence of commands. Without Python it reports itself skipped and passes, except in CI. +22. [`scripts/check_axe_patch_equiv.mjs`](#check-axe-patch-equiv) --- verifies the vendored axe source patch still produces identical colour values. POSIX: @@ -107,6 +108,7 @@ POSIX: && node --test test/png.test.mjs \ && node --test test/example-batches.test.mjs \ && node --test test/ports.test.mjs \ + && node --test test/addin-project.test.mjs \ && node scripts/check_regex_safety.mjs \ && node scripts/check_code_regions.mjs \ && node scripts/check_page_baseline.mjs \ @@ -121,7 +123,7 @@ POSIX: Exit codes: **0** every step passed; otherwise the code of the step that stopped the run, as that step's entry gives it. -**Eighteen of the twenty-one cannot be affected by an edit confined to `docs/`**, which is why they are separate from `check.bat`. Run this one when the change touches `builder/`, `scripts/`, `lib/`, `book/`, `eval/`, `wisdom/` or `test/`, the site's scripts in `docs/assets/js/`, a wrapper, or a workflow. Both CI workflows run all twenty-one unconditionally, so skipping it locally cannot let a tooling regression reach `staging`. +**Nineteen of the twenty-two cannot be affected by an edit confined to `docs/`**, which is why they are separate from `check.bat`. Run this one when the change touches `builder/`, `scripts/`, `lib/`, `book/`, `eval/`, `wisdom/` or `test/`, the site's scripts in `docs/assets/js/`, the help add-in's source in `add-in/`, a wrapper, or a workflow. Both CI workflows run all twenty-two unconditionally, so skipping it locally cannot let a tooling regression reach `staging`. The three exceptions are [`check_code_regions.mjs`](#check-code-regions), [`check_gate_lists.mjs`](#check-gate-lists), which reads this page, and [`check_lint.mjs`](#check-lint), which lints the site's scripts in `docs/assets/js/`. The first is worth knowing in detail. Its corpus sweep tokenises every markdown file under `docs/`, so a page that provokes a rewrite into altering a code region fails it. Its fixed probes are a different matter: they run against their own sources whatever the tree holds, and they cover the *mirror* fault, where a rewrite silently stops firing. The sweep cannot see that one --- text the rewrite skipped is stashed and restored unchanged, so every region still matches. Add a page with an unusual code construct and run `test.bat`, but read the built page too. @@ -245,7 +247,7 @@ Entry point for the static site generator. Every caller adds flags to the bare ` | Caller | Invocation | |---|---| | `build.bat` | `--src docs --check-audit-index` (plus anything passed through) | -| `checks.yml` (PR checks) | `--src docs --no-fetch-assets --no-help-archive --check-audit-index` | +| `checks.yml` (PR checks) | `--src docs --no-fetch-assets --check-audit-index` | | `tbdocs-gh-pages.yml` (deploy) | the same, plus `--url` and `--baseurl` from the Pages environment | Full invocation: @@ -275,7 +277,7 @@ Full invocation: | `--dry-run` | Skip every filesystem write. Useful for benchmarking or validating discovery / compute / render. | | `--no-offline` | Skip the offline tree pass. | | `--no-pdf` | Skip the PDF tree pass. | -| `--no-help-archive` | Do not write the [help archive](#the-help-archive). CI passes it. | +| `--no-help-archive` | Do not write the [help archive](#the-help-archive). The project file the build publishes then holds no archive, so CI never passes it. | | `--tolerate-missing-images` | Downgrade Phase 8's missing-image error to a warning. Use when the source tree is mid-edit and may temporarily reference an image that does not yet exist. | | `--fetch-assets` / `--no-fetch-assets` | Force remote-asset vendoring on or off. Without either flag, the build downloads missing YouTube thumbnails and GitHub user-attachment images on a dev machine, and refuses to download anything when `$CI` is set --- a referenced but uncommitted asset is a hard build error there. See [Authoring Pages](Authoring#committing-downloaded-assets). | | `--profile-offline` | Print per-substep timing for the offline tree pass. | @@ -294,14 +296,35 @@ A command-line error --- an unknown flag, an unexpected argument, a flag without Exit codes: **0** nothing to report (with `--serve`, the server was stopped with Ctrl+C); **1** the build or its check found a problem: a link or integrity failure, a failed build step, a fall in the page count, or a symbol-index URL lost; **2** a refused command line (a `--dest` the build refuses included), a build stopped by the stall watchdog, with `--serve` a failed first build or a port in use, or a crash. +#### The help add-in's project file +{: #the-help-add-ins-project-file } + +Every build of the documentation, CI's included, packs the IDE help add-in's source folder, `add-in/`, into a project file and publishes it, so that [Help Add-In](../../tB/IDE/AddIns/Help) can offer it as a download. `_config.yml` declares it: + +```yaml +addin_project: + src: ../add-in + dest: tB/IDE/AddIns/downloads/tbDocsHelp.twinproj +``` + +`src` is resolved against `docs/`, as a `bundle_extra` source is, and `dest` is the file's path in the online tree, a `downloads` folder beside the page, as the [Import/Export Tool](../../Features/Packages/Import-Export-Tool)'s downloads are beside its page. The offline tree holds no copy: the project carries the offline tree's archive, which the offline tree could not hold as well. A link in an offline page to a file only the online tree holds, this one included, is written as the website's absolute URL, and is not counted as an unresolved link. The `addinProject` task (`builder/addin-project.mjs`) writes the file once the symbol index and the help archive are written, and prints one line: + + add-in project: tB/IDE/AddIns/downloads/tbDocsHelp.twinproj, 21 files, 26.9 MB, with the offline archive, in 1 tree (143 ms) + +What goes in is the files git tracks in `add-in/`, as they are in the working folder, so an edit not yet committed is in a local build's file and a file nobody has added never is. A second line names any file git does not track there that was left out. A `site.zip` in `Resources/HELP/` on the disk is never packed. The file holds the [help archive](#the-help-archive) this build wrote as `Resources/HELP/site.zip`, byte for byte, so the add-in built from it serves the pages itself, without the internet. A build that wrote no archive (`--no-help-archive`, `--serve`, a `--dest` other than `docs/_site`, a fixture) packs none, and its line says `without the offline archive`; the add-in built from that file shows the pages from the website. `Resources/SYMBOLS/symbols.json` is replaced by the `tB/symbols.json` the same build wrote, so the download always holds the index of the pages it is published with. Line endings are packed as git stores them: CRLF becomes LF in every file outside `Resources/`, which `.gitattributes` checks out byte for byte, and the code files then get CRLF, as the IDE stores them. A Windows build and a Linux build therefore write the same bytes, and two builds of one tree write the same file. The pack is `importProject` from `scripts/impexp.mjs`, run in the build's own process; it needs Node and git, and neither twinBASIC nor Python. + +A `.twinproj` is not a publishable type: the [publish allowlist](#check-publish-policy) exempts this one path in the output trees, and still refuses a `.twinproj` anywhere else, and one found in `docs/`. `--dry-run` writes nothing, `--serve` writes the file into its one tree so that the preview's link works, and a build of a source tree whose `_config.yml` declares no `addin_project` (the test fixtures) packs nothing. A pack that fails --- `git` missing, say --- is reported and fails the build with exit code 1; the trees are still written, and the index audit then reports the missing file too. [`test/addin-project.test.mjs`](#addin-project-test) tests what the file holds. + #### The help archive {: #the-help-archive } -A local build ends by writing the zip the IDE help add-in serves the documentation from. The add-in embeds `add-in/Resources/HELP/site.zip` in its DLL as a resource, and the build writes that file from the offline tree it has just written. It writes the archive last, after the build-time chart has been added to `BuildInfo.html` in both trees, so the archive holds the pages as they are on disk. The step prints one line: +Every build writes the zip the IDE help add-in serves the documentation from. The add-in embeds `add-in/Resources/HELP/site.zip` in its DLL as a resource, and the build writes that file from the offline tree. The `helpArchive` task runs once every task that writes into the offline tree has finished, and before the build-time chart is added to `BuildInfo.html` in both trees. So the archive holds `BuildInfo.html` without the chart and `assets/images/gantt.svg` as the placeholder the sources hold, and two builds of one tree write the same archive. The step prints one line: + + help archive: add-in/Resources/HELP/site.zip, 1475 entries, 26.0 MB (0.3 s) - help archive: add-in/Resources/HELP/site.zip, 1471 entries, 21.2 MB (1.2 s) +The zip is not committed, because it is too large; `add-in/Resources/HELP/` is listed in `.gitignore`. The [help add-in's project file](#the-help-add-ins-project-file) carries the zip this build wrote. Only a build of the documentation tree into `docs/_site-offline` writes it. A build of another source tree (the test fixtures, for one) or into another `--dest`, `--serve`, `--dry-run` and a build without an offline tree leave it alone, and so does `--no-help-archive`. Neither CI workflow passes that flag, because the project file CI publishes would then hold no archive; `check_ci_workflows.mjs` fails when one does. A file the build cannot write, or an archive that does not match the tree, fails the build with exit code 1. -The zip is not committed, because it is too large; `add-in/Resources/HELP/` is listed in `.gitignore`. Only a build of the documentation tree into `docs/_site-offline` writes it. A build of another source tree (the test fixtures, for one) or into another `--dest`, `--serve`, `--dry-run` and a build without an offline tree leave it alone, and so does `--no-help-archive`, which both CI workflows pass. A file the build cannot write, or an archive that does not match the tree, fails the build with exit code 1. +The build ends by checking the offline tree on disk against the archive again, now that the chart is in. It must list the same files, and only `BuildInfo.html` and `assets/images/gantt.svg` may differ in content. A file written into the offline tree after the archive was made fails the build, and so does a missing one. The reader on the twinBASIC side does no inflating of its own, so the format is fixed. There is one entry per file and no directory entries. A name is relative to the tree root, uses forward slashes and is UTF-8, with general-purpose flag bit 11 set. Entries are sorted by name in code-unit order, so the same tree gives the same bytes. The DOS date and time are always 1980-01-01 00:00, the version made by and needed is 20, and there are no extra fields, no comments and no data descriptors: the CRC-32 and both sizes are in the local header and in the central directory. There is no zip64, so a tree of more than 65,535 files, or an archive of 4 GB, is refused. Files that are compressed already (`.png`, `.jpg`, `.jpeg`, `.gif`, `.webp`, `.ico`, `.woff2`, `.woff`, `.mp4`, `.zip`, `.pdf`) are stored. Every other file is deflated at level 9, and stored instead when the deflated data is not smaller than the file. @@ -322,7 +345,8 @@ Offline (filesystem-only) link checker plus optional integrity checks. Multiple | `--index-files <list>` | Comma-separated list of filenames to try when a URL resolves to a directory. Use `'index.html,.'` to also accept the directory itself as a valid target. | | `--base-path <prefix>` | Strip this prefix from root-absolute URLs before resolving. Used in CI when `--baseurl` is set. | | `--include-fragments` | Resolve `#fragment` anchors against the target page's IDs. | -| `--forbid <prefix>` | Repeatable. Fail the run if any extracted link starts with `prefix`. Used by the offline pass to catch live-site links the offlinify rewrite missed (the bare prefix and `prefix/` are exempt). | +| `--forbid <prefix>` | Repeatable. Fail the run if any extracted link starts with `prefix`. Used by the offline pass to catch live-site links the offlinify rewrite missed (the bare prefix and `prefix/` are exempt). On a built offline tree, pass `--online-root` as well, or the links the rewrite writes on purpose to files only the website holds are reported. | +| `--online-root <dir>` | The online tree the `--forbid` prefixes serve. A forbidden link to a file that tree holds and the checked tree does not is the offline rewrite's link to the website, and is not reported, as the build's own offline check does not report it. A link to a page both trees hold is still a rewrite that was missed. | | `--check-html` | Assert HTML well-formedness. | | `--check-a11y` | Report accessibility hints (missing `alt`, etc.). | | `--check-ids` | Flag duplicate `id` attributes within a page. | @@ -456,7 +480,7 @@ Not a command --- `scripts/lib/axe-scan.mjs` is the shared module that **defines The gate on the publish allowlist. Everything under `docs/` that is not a page is copied into the published site verbatim, so the source tree's shape is the site's shape --- [`builder/publish-policy.mjs`](Builder#module-map) is the list of types that may be published, enforced inside the build at the source inventory and again at each output tree's inventory. A finding there aborts the build rather than setting an exit code: a broken link still leaves a tree worth inspecting, a tree with a private key in it does not. -A clean build only says **nothing in `docs/` is currently refused**, which is also what an allowlist widened until it refuses nothing would say, and no build over a clean tree can distinguish the two. So this asserts the other half: thirteen named probes that must stay refused (a `.bak`, a `.pem`, a `.docx`, a `.twin`, a `secrets.json`, a `Thumbs.db`, a frontmatter-less `.md`, an extensionless `LICENSE`, a dotfile), six that must keep publishing (`.png`, `.PNG`, `.woff2`, `.txt`, `.html`, `CNAME`), that `bundle_extra` exemptions stay scoped to the exact declared path rather than blessing the extension everywhere, and that `SOURCE_EXTENSIONS` and `BUILD_EXTENSIONS` stay disjoint --- folding the two together would pass every other assertion here while quietly making a stray `docs/secrets.json` publishable. +A clean build only says **nothing in `docs/` is currently refused**, which is also what an allowlist widened until it refuses nothing would say, and no build over a clean tree can distinguish the two. So this asserts the other half: thirteen named probes that must stay refused (a `.bak`, a `.pem`, a `.docx`, a `.twin`, a `secrets.json`, a `Thumbs.db`, a frontmatter-less `.md`, an extensionless `LICENSE`, a dotfile), six that must keep publishing (`.png`, `.PNG`, `.woff2`, `.txt`, `.html`, `CNAME`), that `bundle_extra` exemptions stay scoped to the exact declared path rather than blessing the extension everywhere, that the [help add-in's project file](#the-help-add-ins-project-file) publishes from the output trees at its declared path and a `.twinproj` nowhere else, and that `SOURCE_EXTENSIONS` and `BUILD_EXTENSIONS` stay disjoint --- folding the two together would pass every other assertion here while quietly making a stray `docs/secrets.json` publishable. It also checks that the refusal *message* for a `.md` still names a fault that can happen, which is a narrower thing than it sounds. The message tells the reader the opening `---` must be the first line, and that is the right advice only because the two causes that come to mind first are handled elsewhere: a UTF-8 BOM is stripped before parsing, and malformed YAML aborts with its own error. An earlier draft named the BOM and would have sent every reader hunting for something that cannot occur, so all three behaviours are now asserted against real files --- nothing else in the repository covers them. @@ -469,7 +493,7 @@ Exit codes: **0** every assertion held, and the source tree holds no file the al node scripts/check_tree_fresh.mjs [--tree DIR] [--source DIR ...] -`check.bat`'s first gate. Refuses a built tree older than the sources that produced it, by comparing the newest mtime under the source tree against the built tree's `index.html`. The build's own output trees under `docs/` are not sources, and which folders those are comes from `lib/markdown-files.mjs`, the list [`check_code_regions.mjs`](#check-code-regions) walks by. Without it, editing a page and running `check.bat` without rebuilding audits the *previous* build and passes --- a green run that says nothing about the change just made. CI never hits this because it builds in the same job; a development box hits it whenever the two commands run out of order. The message for a stale tree names `build.bat`. +`check.bat`'s first gate. Refuses a built tree older than the sources that produced it, by comparing the newest mtime under the sources against the built tree's `index.html`. The sources are `docs/`, `builder/` and `lib/`, and the two inputs of the [help add-in's project file](#the-help-add-ins-project-file): `add-in/`, less the help archive and the add-in's copy of the symbol index, which the build writes, and `scripts/impexp.mjs`. The build's own output trees under `docs/` are not sources, and which folders those are comes from `lib/markdown-files.mjs`, the list [`check_code_regions.mjs`](#check-code-regions) walks by. Without it, editing a page and running `check.bat` without rebuilding audits the *previous* build and passes --- a green run that says nothing about the change just made. CI never hits this because it builds in the same job; a development box hits it whenever the two commands run out of order. The message for a stale tree names `build.bat`. Exit codes: **0** the tree is at least as new as its inputs; **1** the tree is stale (run `build.bat`); **2** the check could not run: a refused command line, no built tree or marker file, or a crash. @@ -556,7 +580,7 @@ Exit codes: **0** the wrappers match the gate lists, every stated count agrees, node scripts/check_ci_workflows.mjs -The same question as [`check_gate_lists.mjs`](#check-gate-lists), asked of the two CI workflows, which nothing else reads. It requires that `checks.yml` and `tbdocs-gh-pages.yml` each run every gate [`test.bat`](#testbat) and [`check.bat`](#checkbat) run, with the same arguments and in each wrapper's own order; that the two workflows run the same gate steps in the same order; and that each workflow's build passes every argument [`build.bat`](#buildbat) passes, plus `--no-fetch-assets`. A step dropped from a workflow, a gate added to a wrapper and never to CI, or a lost `--check-audit-index` would otherwise leave CI green over a check it had stopped making. +The same question as [`check_gate_lists.mjs`](#check-gate-lists), asked of the two CI workflows, which nothing else reads. It requires that `checks.yml` and `tbdocs-gh-pages.yml` each run every gate [`test.bat`](#testbat) and [`check.bat`](#checkbat) run, with the same arguments and in each wrapper's own order; that the two workflows run the same gate steps in the same order; and that each workflow's build passes every argument [`build.bat`](#buildbat) passes, plus `--no-fetch-assets`, and never `--no-help-archive`, because the help add-in's project file that CI publishes would then hold no [help archive](#the-help-archive). A step dropped from a workflow, a gate added to a wrapper and never to CI, or a lost `--check-audit-index` would otherwise leave CI green over a check it had stopped making. The gates both workflows share are one composite action, `.github/actions/run-gates/action.yml`, and the gate reads a workflow step that uses a local action as that action's own steps. A local action it cannot read is a finding, so a renamed action cannot take its gates out of CI unnoticed. @@ -602,7 +626,7 @@ Exit codes: **0** every test passed, **1** a test failed. node --test test/render.test.mjs -Unit tests for the markdown-it plugins in `builder/render.mjs`, run by Node's own test runner through the site's own `createMarkdownIt`. The build compares whole pages, so a plugin that is wrong only on input no page holds passes it; these tests give each plugin such input. They cover the ellipsis plugin, which keeps the dots past the third in a run such as `....`, next to code spans, dashes, guillemets, quotes and autolinks. No browser, no built tree, well under a second. +Unit tests for the markdown-it plugins in `builder/render.mjs`, run by Node's own test runner through the site's own `createMarkdownIt`. The build compares whole pages, so a plugin that is wrong only on input no page holds passes it; these tests give each plugin such input. They cover the ellipsis plugin, which keeps the dots past the third in a run such as `....`, next to code spans, dashes, guillemets, quotes and autolinks. They also cover the offline tree's URL rewrite in `builder/offline-rewrite.mjs` for a link to a file only the website holds: a link the offline tree holds becomes page-relative, one that only the online tree holds becomes the website's absolute URL (with the base path, and encoded) and is not counted as unresolved, and one in neither tree stays as written and is. No browser, no built tree, well under a second. Exit codes: **0** every test passed, **1** a test failed. @@ -642,6 +666,15 @@ Unit tests for `scripts/lib/tb-ports.mjs`, which claims the DevTools ports that Exit codes: **0** every test passed, **1** a test failed. +### addin-project.test.mjs +{: #addin-project-test } + + node --test test/addin-project.test.mjs + +Unit tests for `builder/addin-project.mjs`, which packs the help add-in's folder, `add-in/`, into the project file the build publishes beside [Help Add-In](../../tB/IDE/AddIns/Help) (see [The help add-in's project file](#the-help-add-ins-project-file)). The build's index audit says only that the file is in each tree; nothing in the build reads what it holds. The first test packs the real `add-in/` and compares the project, file by file, with the files git tracks there: a file lost or gained, a changed file or a `Resources/HELP/` file in the project fails it. The others pack fixture folders, each a git repository of its own, for what the real folder cannot show on every machine: a file git does not track and a `Resources/HELP/site.zip` on the disk, tracked or not, are both left out, the archive the build hands over is packed as `Resources/HELP/site.zip` byte for byte, the index the build passes replaces the folder's copy, a CRLF checkout packs to the same bytes as an LF one, two packs are identical, and the file is written into exactly the trees it is given. A second group writes small archives with `lib/help-archive.mjs` and checks them against their tree, as the build does at its end: a tree as it was archived has no differences, only the files named may differ in content, and any other changed, added or removed file is named. Needs git; no browser, no built tree, no twinBASIC, about 1 s. + +Exit codes: **0** every test passed, **1** a test failed. + ### check_page_baseline.mjs {: #check-page-baseline } @@ -1354,34 +1387,74 @@ the copy. Exit codes: **0** the IDE was closed, and the registry is as it was found; **1** the add-in did not build, or the project does not compile; **2** the tool could not run: a refused command line, no IDE, no built site, a registry it could not record, or a crash; **3** the registry or the work folder was not put back (see the lines above). -### shoot_help_addin.mjs -{: #shoot-help-addin } - - node scripts/shoot_help_addin.mjs [--only <regex>] [--out <dir>] [--port N] [--ide <path>] - -Writes the screenshots of the help add-in's documentation page, `Help_<Name>.png`, into -`docs/IDE/AddIns/Images/`: `Pane`, `Hover`, `Search`, `Choices`, `Description`, `Settings`, `Window` -and `Toolbar`. It builds the add-in in `add-in/` into a private copy of the twinBASIC install, -opens the small demo project `test/addin/helpdemo` in an IDE on a private desktop, operates it -and captures each picture, so that one run regenerates every image after a new BETA. The IDE is -set up as a lane of [`addin_test.mjs`](#addin-test) is, with a private `APPDATA` and -`TB_ADDIN_TEST` set to `1`, and it never appears on your desktop. The pane's pages come from the -built site, `docs/_site`, served on `localhost`, so run `build.bat` first. - -The pictures are repeatable: the IDE's page is given a fixed size of 1280 by 880 CSS pixels at -2x, animations and the text caret are switched off, the theme is dark, and no picture shows the -Debug Console, whose lines carry the time they were written. A picture is written only when its -bytes differ from the file already there, and each is reported as `new`, `updated` or -`unchanged`; a second run with nothing changed reports every picture `unchanged`. The Windows user -name must never be in a published picture, so before each one is kept the visible text of the page -it was taken from is searched for the name, and a picture whose page holds it is refused. - -`--only` takes the pictures whose name matches a regular expression. `--out` is the folder the -pictures go to. `--port` is where the search for the IDE's DevTools port starts, 9620 by default; -the tool claims three, as `addin_test.mjs` does: the IDE's, the detached window's, and one for the -IDE that builds the add-in, whose browser process can hold its port for a while after that IDE -ends. The add-in's saved settings (`tbDocsHelp`) are emptied for the run -and, with the IDE's registry entries, put back at the end. +### shoot_docs.mjs +{: #shoot-docs } + + node scripts/shoot_docs.mjs [--only <regex>] [--out <dir>] [--diffs <dir>] [--jobs N] [--port N] [--ide <path>] + +Takes the documentation's screenshots of the IDE, so that one run regenerates them after a new +BETA. Each picture belongs to a *setup*, one IDE brought to a known state, and a setup's IDE is +started only when one of its pictures is selected: + +- **help** builds the help add-in in `add-in/` into a private copy of the twinBASIC install and + opens the small demo project `test/addin/helpdemo`: the eight pictures of the [Help + Add-In](../../tB/IDE/AddIns/Help) page, `docs/IDE/AddIns/Images/Help_<Name>.png`. The pane's pages + come from the built site, `docs/_site`, served on `localhost`, so run `build.bat` first. +- **no-project** starts the IDE with no project open: the menus under `docs/IDE/Menu/Images/`, + and the dialogs that need no project --- New / Open Project and its tabs, the Manage Keyboard + Shortcuts and Manage Panel Layouts dialogs, and the ones the FAQ shows. +- **project** opens the demo project without the add-in, for the dialogs that show something + only with a project open: About, whose licence line needs the compiler, and IDE Options, + whose font lists do. +- **sample**, **settings** and **glyphs** open `test/shots/sample`, a small Standard EXE made + for the pictures: the panels and the editor with a project, Project Settings (with the + settings files of `test/shots/settings`), and the icons the pages show inline. + +Every IDE is set up as a lane of [`addin_test.mjs`](#addin-test) is, with a private `APPDATA` +and `TB_ADDIN_TEST` set to `1`, and it never appears on your desktop. + +The pictures are repeatable. The IDE lays its page out at 100% whatever the display's scaling, +the page is given a fixed size of 1280 by 880 CSS pixels at 2x, animations and the text caret +are switched off, and the theme is dark. The IDE's settings --- its options, panel layouts and +keyboard shortcut groups --- are set to their defaults in the page only, and the recent-project +lists are replaced there, so no picture shows your own choices or projects; nothing is saved. +No picture shows the Debug Console, whose lines carry the time they were written. Menus are +cut out, transparent around the menu; dialogs are taken whole. A picture that needs arrows, rings +or numbered badges has them drawn by the tool in one style, anchored to the element they point at +(`scripts/lib/shot-annotate.mjs`), so that a retake after a new BETA puts them where the element +now is. A picture is written only when +its bytes differ from the file already there, and each is reported as `new`, `updated` or +`unchanged`; a second run with nothing changed reports every picture `unchanged`. The Windows +user name must never be in a published picture, so before each one is kept the visible text of +the page it was taken from is searched for the name, and a picture whose page holds it is +refused. + +Every picture is twice the size it is shown at. A page that shows one gives it a `{:width +height}` of half its pixel size, so that it is sharp on a high-resolution display and in the +book. + +`--only` takes the pictures whose path under the output folder matches a regular expression, +such as `Menu_File` or `IDE/Menu/`. `--out` is the folder those paths are under, `docs` by +default. `--diffs` names a folder, outside `docs`, for a difference picture of each picture +that is updated: the file on disk, the new picture and their difference side by side, with +the region that differs magnified underneath. In the difference, a pixel that matches is a +dark grey copy, and one that differs is yellow for a difference of one grey level, shading +to red for large ones, so that a change too small to see stands out. The tool also writes +one, named `.capture-<n>`, for each two captures of one state that disagree, which shows +where the IDE's drawing varies from one frame to the next. + +`--jobs` is how many IDEs run at once, 6 by default and at most 16. Each runs on a +private desktop with a port, a work folder and a copy of the install of its own, and the +setups wait in a queue, the longest first. The three long setups, no-project, sample +and customcontrols, are cut into three parts each, every part in an IDE of its own; each +picture brings its IDE to the state it shows, so it comes out the same in a part as in the whole setup. Every line of +output starts with the setup or part it is from. `--jobs 1` runs each setup whole in one IDE, +one after another, in the order of the tool's table. `--port` is where the search for +DevTools ports starts, 9700 by default; each IDE claims the first free one, and the help setup +claims three, as `addin_test.mjs` does: the IDE's, the detached window's, and one for the IDE +that builds the add-in, whose browser process can hold its port for a while after that IDE +ends. The add-in's saved settings (`tbDocsHelp`) are emptied for the run and, with the IDE's +registry entries, put back at the end. Exit codes: **0** every picture was written or was unchanged; **1** a picture failed (an element was not found, or the page showed the user name), the add-in did not build, or the demo project does not compile; **2** the tool could not run: a refused command line, no IDE, no built site, a registry it could not record, or a crash; **3** the registry or the work folder was not put back (see the lines above). @@ -1653,7 +1726,7 @@ Exit codes: **0** the report was produced and its self-checks held; **1** a self Standalone `.twinproj` / `.twinpack` unpacker and packer, with the compiler executable's own command line: the same six commands, the project file first, and `--overwrite` required to replace anything. `scripts/impexp.py` is the same tool, run as `python scripts/impexp.py ...`; the two editions print the same output and write byte-identical project files, which [`check_impexp_parity.mjs`](#check-impexp-parity) checks. Neither has dependencies; the Node edition needs Node 18+, the Python edition Python 3.6+. The exit code says what happened, so a caller need not read the output. `--self-test` needs nothing but the script, and adds a round trip of `indexer/sample.twinpack` when run from this repository. -**Neither is run by the site build.** They are published downloads: `_config.yml`'s `bundle_extra` copies both into `Features/Packages/downloads/`, and [Import/Export Tool](../../Features/Packages/Import-Export-Tool) offers them to readers as the two editions of one tool. That is why `impexp.py` is one of only two `.py` files in a repository whose tooling is otherwise all Node --- porting it would delete a deliberate offering rather than tidy anything up. The `bundle_extra` exemption is by exact path, so moving either file breaks the download; see [`check_publish_policy.mjs`](#check-publish-policy). The Node edition is also a library: `scripts/lib/tb-project.mjs` imports its `exportProject` and `importProject`, and the tools that unpack or pack a twinBASIC project call them directly, among them [`tbbuild.mjs`](#tbbuild), [`tbrun.mjs`](#tbrun), [`check_examples.mjs`](#check-examples), [`census_attributes.mjs`](#census-attributes) and [`bug_repro.mjs`](#bug-repro). The Python edition is only a download. +**Neither is run as a command by the site build.** They are published downloads: `_config.yml`'s `bundle_extra` copies both into `Features/Packages/downloads/`, and [Import/Export Tool](../../Features/Packages/Import-Export-Tool) offers them to readers as the two editions of one tool. That is why `impexp.py` is one of only two `.py` files in a repository whose tooling is otherwise all Node --- porting it would delete a deliberate offering rather than tidy anything up. The `bundle_extra` exemption is by exact path, so moving either file breaks the download; see [`check_publish_policy.mjs`](#check-publish-policy). The Node edition is also a library: `scripts/lib/tb-project.mjs` imports its `exportProject` and `importProject`, and the tools that unpack or pack a twinBASIC project call them directly, among them [`tbbuild.mjs`](#tbbuild), [`tbrun.mjs`](#tbrun), [`check_examples.mjs`](#check-examples), [`census_attributes.mjs`](#census-attributes) and [`bug_repro.mjs`](#bug-repro). The site build imports `importProject` too, to pack the [help add-in's project file](#the-help-add-ins-project-file): `impexp.mjs` is the one module under `scripts/` that `builder/` may import (`biome.jsonc` refuses any other), which is why [`check_tree_fresh.mjs`](#check-tree-fresh) watches it. The Python edition is only a download. Exit codes: the table in [Import/Export Tool](../../Features/Packages/Import-Export-Tool#checking-the-result) gives every code. This tool keeps its own codes, which the two editions share and which are not those of the other tools here. diff --git a/docs/Features/Compiler-IDE/Compiler-Warnings.md b/docs/Features/Compiler-IDE/Compiler-Warnings.md index 04665aeb5..efb1d20ff 100644 --- a/docs/Features/Compiler-IDE/Compiler-Warnings.md +++ b/docs/Features/Compiler-IDE/Compiler-Warnings.md @@ -27,7 +27,7 @@ This feature is discouraged for it making code difficult to read and prone to di The full list can be found in your project's Settings page: -![The project Settings page listing warnings TB0001 to TB0016, each with a dropdown set to WARNING](../Images/017bd6f8-4b35-43a9-b6be-84cba69daf64.png) +![The Compiler Warnings list of the Project Settings dialog, showing warnings TB0001 to TB0016, each with a dropdown set to WARNING except TB0015, which is set to HINT](../Images/017bd6f8-4b35-43a9-b6be-84cba69daf64.png){:width="846" height="344"} ## Adjusting Warnings diff --git a/docs/Features/Compiler-IDE/Debugging.md b/docs/Features/Compiler-IDE/Debugging.md index 6c78c26cd..1889048b7 100644 --- a/docs/Features/Compiler-IDE/Debugging.md +++ b/docs/Features/Compiler-IDE/Debugging.md @@ -13,7 +13,7 @@ twinBASIC includes several features to help with debugging. Breakpoints, steppin New to the debugging experience is a trace logging feature that automatically creates detailed logs to either the debug console or a file. Messages can be output with [**Debug.TracePrint**](../../tB/Modules/Debug#traceprint). The logger works both when running from the IDE and in compiled executables. -![The Project Settings page showing the Compilation: Trace Flags and Compilation: Trace Output options](../Images/4fc2bf99-2bec-4943-837d-21038d791574.png) +![The Project Settings dialog scrolled to the Trace Flags row, a column of eleven unticked boxes from Trace Procedure Entry and Exit points to Trace RaiseEvent calls and arguments, and to the Trace Output row, an empty text box above a description of the ${SESSIONID}, ${DATE}, ${TIME} and ${DEBUG} placeholders.](../Images/4fc2bf99-2bec-4943-837d-21038d791574.png){:width="760" height="531"} ```tb check_build Public Sub ProcessOrder(ByVal orderId As Long) diff --git a/docs/Features/Compiler-IDE/IDE-Features.md b/docs/Features/Compiler-IDE/IDE-Features.md index 6082e2450..1541558e8 100644 --- a/docs/Features/Compiler-IDE/IDE-Features.md +++ b/docs/Features/Compiler-IDE/IDE-Features.md @@ -39,29 +39,29 @@ Fully theme-able, with Dark (default), Light, and Classic (Light) built in, and - **A History panel** containing a list of recently modified methods. - **An Outline panel** with selectable categories. -- **Problems panel**, provides a list of all current errors and warnings (you can filter to show only one or the other). +- **A Diagnostics panel** (the Problems panel), which lists all current errors and warnings (you can filter to show only one or the other). ## Form Designer Enhancements On the Form Designer, control with `Visible = False` are faded to visually indicate this. Also, pressing and holding Control shows the tab index of each tab stop. -![An annotated twinBASIC IDE screenshot labelling sticky scroll, colour matching, indent guides, the advanced info popup, inline code hints, Unicode in the editor, folding controls, the mini-map, the Outline, History and Problems panels, and memory usage and object counts](../Images/014a1d28-30af-4a4d-8b9b-83ab6084f00a.png) -[Full size](../Images/fafaloneIDEscreenshot1.png) +![The whole twinBASIC IDE window with a class open in the editor and twelve labels, each with an arrow: Sticky scroll at the class and function lines held at the top of the editor, Colour matching at a line of nested brackets, Unicode in the editor at a string holding an emoji and Japanese text, Indent guides at the guide line through an empty line inside an If block, Inline code hints at the hint after Next, Advanced info popup at the hover over a user-defined type that lists its members' offsets, its Len and LenB and its alignment, Mini-map at the code overview at the editor's right, Folding controls at a fold arrow beside a function, Memory usage and object counts at the status bar, and Outline, History and Diagnostics at those panels down the left of the window](../Images/IDE-FeatureMap.png){:width="1440" height="900"} +[Full size](../Images/IDE-FeatureMap.png) ### New Code-Based Project Explorer A new code structure based Project Explorer: -![The Project Explorer in code-based view, grouping the project into Modules, Forms, Interfaces, UserDefinedTypes and Enumerations](../Images/9a5c50d5-a9f8-44a7-96f7-ae84548bd7ef.png) +![The Project Explorer in code-based view, grouping the project into Modules, Forms, Interfaces and User Defined Types, each item beside the file it comes from, above the collapsed VBA, VBRUN and VB packages](../Images/9a5c50d5-a9f8-44a7-96f7-ae84548bd7ef.png){:width="423" height="526"} The classic file-based view is still used by default, you can activate the new view with a toggle button: -![The Project Explorer title bar with the file view toggle button highlighted](../Images/b000d3aa-3689-4d94-88e3-bca44f8b7de6.png) +![The Project Explorer title bar with the file view toggle button highlighted](../Images/b000d3aa-3689-4d94-88e3-bca44f8b7de6.png){:width="296" height="30"} ## View Forms and Packages as JSON -Project forms and packages are stored as JSON format data, and you can view this by right-click in Project Explorer and selecting 'View as JSON'. This is particularly interesting for packages as it exposes the entire code in a more parseable format. +Project forms and packages are stored as JSON format data, and you can view this by right-click in Project Explorer and selecting **View As JSON**. This is particularly interesting for packages as it exposes the entire code in a more parseable format. -![The Project Explorer right-click menu with View As JSON highlighted](../Images/22660f54-ff5d-4b21-93d3-39715f1f35ed.png) +![The Project Explorer with the Packages folder open, and the right-click menu of CustomControlsPackage with View As JSON highlighted](../Images/22660f54-ff5d-4b21-93d3-39715f1f35ed.png){:width="485" height="409"} -![A package shown as JSON in the editor, listing its components, interfaces and procedures](../Images/a6525b1d-ac22-4303-ae27-7984c20eba0c.png) +![The CustomControls package shown as JSON in the editor, listing its components with their events, fields and procedures](../Images/a6525b1d-ac22-4303-ae27-7984c20eba0c.png){:width="756" height="198"} diff --git a/docs/Features/Fusion.md b/docs/Features/Fusion.md index fa2c0f604..6baeda2c5 100644 --- a/docs/Features/Fusion.md +++ b/docs/Features/Fusion.md @@ -76,7 +76,7 @@ A project-level setting allows you to control where the Fusion host EXE is gener - **ActiveX Fusion Host EXE Output Path** -![tbFusionProjectSettings](Images/569150839-9ffc87ac-250d-40a4-bb47-669b607ad76f.png){:width="800" height="400"} +![The Project Settings dialog with the Build Output Path row, which holds the standard build path, above the ActiveX Fusion Host EXE Output Path row, whose empty text box and label are outlined in red.](Images/569150839-9ffc87ac-250d-40a4-bb47-669b607ad76f.png){:width="1102" height="460"} If left blank (default), the standard build path set in the project settings is used. Unless overriden, the standard build path is: `${SourcePath}\Build\${ProjectName}_${Architecture}.${FileExtension}` @@ -91,7 +91,7 @@ This allows Fusion host EXEs to be clearly distinguished from normal build outpu Each COM reference (type library) exposes Fusion-specific options. -![tbFusionPerLibraryOptions](Images/569100769-f1f2790a-0094-4843-809f-a8a9e928fd41.png){:width="737" height="323"} +![The Library References list of Project Settings on its Enabled Libraries tab, scrolled to the right. Beside each library's identifier are the Locale, Licence and Date columns, a Use ActiveX Controls tick box, an ActiveX Fusion Mode drop-down set to auto or fusionAllTo32, and a Fusion: Async Events tick box, ticked for one library.](Images/569100769-f1f2790a-0094-4843-809f-a8a9e928fd41.png){:width="1102" height="439"} ### ActiveX Fusion Mode diff --git a/docs/Features/GUI-Components/Anchoring-Docking.md b/docs/Features/GUI-Components/Anchoring-Docking.md index 47c99c567..420437003 100644 --- a/docs/Features/GUI-Components/Anchoring-Docking.md +++ b/docs/Features/GUI-Components/Anchoring-Docking.md @@ -9,13 +9,13 @@ redirect_from: # Anchoring -One of the new form designer features you see in twinBASIC is the 'Anchors' property: +One of the new form designer features you see in twinBASIC is the 'Anchors' property, in the **LAYOUT** group of the PROPERTIES panel: -![The PROPERTIES pane for a TextBox with the Anchors property highlighted](../Images/b26da59b-4e98-40b7-b97b-bb3cef4ca1d0.png) +![The PROPERTIES panel for the TextBox Text1, showing its LAYOUT group, with the Anchors row underlined](../Images/b26da59b-4e98-40b7-b97b-bb3cef4ca1d0.png){:width="401" height="191"} Clicking the arrow on the left expands it to provide 4 options: -![The Anchors property expanded into Left, Top, Right and Bottom rows, with Left and Top set to True](../Images/d5dff8f5-c5fa-4620-ba11-430d06276b27.png) +![The Anchors property expanded into Left, Top, Right and Bottom rows, with Left and Top set to True](../Images/d5dff8f5-c5fa-4620-ba11-430d06276b27.png){:width="400" height="275"} These control whether the position of each point relative to the borders of their parent form or control container are maintained when the form is resized. By default it behaves in the expected manner; the top and left stay the same, and the control is not resized or moved with the form unless you do this manually with code, typically in the `Form_Resize` event. These provide an alternative to handle sizing and moving automatically. @@ -57,7 +57,7 @@ Using these 4 points you can automatically maintain a relative size, position, o Similar to anchoring but slightly different, tB also offers a 'Dock' property: -![The Dock property dropdown open, listing vbDockNone, vbDockLeft, vbDockTop, vbDockRight, vbDockBottom and vbDockFill](../Images/4c8b881e-1216-4819-a558-d2ce20f47fcd.png) +![The PROPERTIES panel for the TextBox Text1, on its LAYOUT group, with the list of the Dock dropdown open below it: 0 - vbDockNone, marked as the current value, then 1 - vbDockLeft, 2 - vbDockTop, 3 - vbDockRight, 4 - vbDockBottom and 5 - vbDockFill.](../Images/4c8b881e-1216-4819-a558-d2ce20f47fcd.png){:width="360" height="320"} You might already be familiar with how a StatusBar control locks itself to the bottom of a form; that's the kind of positioning this property controls. A control can be docked on any side, and it will stay sized to the full width or height, and move with, that side of the Form or parent container. For example, a CommandButton with `vbDockBottom`: diff --git a/docs/Features/GUI-Components/New.md b/docs/Features/GUI-Components/New.md index 3da9bb1ac..49c11c7a9 100644 --- a/docs/Features/GUI-Components/New.md +++ b/docs/Features/GUI-Components/New.md @@ -11,9 +11,9 @@ twinBASIC introduces several new controls to enhance your applications. ## QR Code Control -![A QR code displayed on a form in the designer, with the toolbox alongside](../Images/54ed49d8-b434-45e3-9e63-a1fe75cdf814.png) +![A QR code control on a form in the designer, with the Toolbox alongside](../Images/54ed49d8-b434-45e3-9e63-a1fe75cdf814.png){:width="302" height="355"} -Easily display custom QR codes with a native control. +Display custom QR codes with a native control. ## Multiframe Control diff --git a/docs/Features/Images/014a1d28-30af-4a4d-8b9b-83ab6084f00a.png b/docs/Features/Images/014a1d28-30af-4a4d-8b9b-83ab6084f00a.png deleted file mode 100644 index 6b7433c3a..000000000 Binary files a/docs/Features/Images/014a1d28-30af-4a4d-8b9b-83ab6084f00a.png and /dev/null differ diff --git a/docs/Features/Images/017bd6f8-4b35-43a9-b6be-84cba69daf64.png b/docs/Features/Images/017bd6f8-4b35-43a9-b6be-84cba69daf64.png index 67577afbf..db822b4ea 100644 Binary files a/docs/Features/Images/017bd6f8-4b35-43a9-b6be-84cba69daf64.png and b/docs/Features/Images/017bd6f8-4b35-43a9-b6be-84cba69daf64.png differ diff --git a/docs/Features/Images/22660f54-ff5d-4b21-93d3-39715f1f35ed.png b/docs/Features/Images/22660f54-ff5d-4b21-93d3-39715f1f35ed.png index d3c4c95d0..b1e68868f 100644 Binary files a/docs/Features/Images/22660f54-ff5d-4b21-93d3-39715f1f35ed.png and b/docs/Features/Images/22660f54-ff5d-4b21-93d3-39715f1f35ed.png differ diff --git a/docs/Features/Images/4c8b881e-1216-4819-a558-d2ce20f47fcd.png b/docs/Features/Images/4c8b881e-1216-4819-a558-d2ce20f47fcd.png index f755920bb..76c6f4a9c 100644 Binary files a/docs/Features/Images/4c8b881e-1216-4819-a558-d2ce20f47fcd.png and b/docs/Features/Images/4c8b881e-1216-4819-a558-d2ce20f47fcd.png differ diff --git a/docs/Features/Images/4fc2bf99-2bec-4943-837d-21038d791574.png b/docs/Features/Images/4fc2bf99-2bec-4943-837d-21038d791574.png index c3fc0ec37..b514c6348 100644 Binary files a/docs/Features/Images/4fc2bf99-2bec-4943-837d-21038d791574.png and b/docs/Features/Images/4fc2bf99-2bec-4943-837d-21038d791574.png differ diff --git a/docs/Features/Images/54ed49d8-b434-45e3-9e63-a1fe75cdf814.png b/docs/Features/Images/54ed49d8-b434-45e3-9e63-a1fe75cdf814.png index 51e43e095..7043e9a7e 100644 Binary files a/docs/Features/Images/54ed49d8-b434-45e3-9e63-a1fe75cdf814.png and b/docs/Features/Images/54ed49d8-b434-45e3-9e63-a1fe75cdf814.png differ diff --git a/docs/Features/Images/569100769-f1f2790a-0094-4843-809f-a8a9e928fd41.png b/docs/Features/Images/569100769-f1f2790a-0094-4843-809f-a8a9e928fd41.png index 8482c23cb..18170ab43 100644 Binary files a/docs/Features/Images/569100769-f1f2790a-0094-4843-809f-a8a9e928fd41.png and b/docs/Features/Images/569100769-f1f2790a-0094-4843-809f-a8a9e928fd41.png differ diff --git a/docs/Features/Images/569150839-9ffc87ac-250d-40a4-bb47-669b607ad76f.png b/docs/Features/Images/569150839-9ffc87ac-250d-40a4-bb47-669b607ad76f.png index 81937c736..4f2bd7e31 100644 Binary files a/docs/Features/Images/569150839-9ffc87ac-250d-40a4-bb47-669b607ad76f.png and b/docs/Features/Images/569150839-9ffc87ac-250d-40a4-bb47-669b607ad76f.png differ diff --git a/docs/Features/Images/9a5c50d5-a9f8-44a7-96f7-ae84548bd7ef.png b/docs/Features/Images/9a5c50d5-a9f8-44a7-96f7-ae84548bd7ef.png index d8377044c..e4722b4d9 100644 Binary files a/docs/Features/Images/9a5c50d5-a9f8-44a7-96f7-ae84548bd7ef.png and b/docs/Features/Images/9a5c50d5-a9f8-44a7-96f7-ae84548bd7ef.png differ diff --git a/docs/Features/Images/IDE-FeatureMap.png b/docs/Features/Images/IDE-FeatureMap.png new file mode 100644 index 000000000..be38f5330 Binary files /dev/null and b/docs/Features/Images/IDE-FeatureMap.png differ diff --git a/docs/Features/Images/a6525b1d-ac22-4303-ae27-7984c20eba0c.png b/docs/Features/Images/a6525b1d-ac22-4303-ae27-7984c20eba0c.png index 226ce6a78..f0493b737 100644 Binary files a/docs/Features/Images/a6525b1d-ac22-4303-ae27-7984c20eba0c.png and b/docs/Features/Images/a6525b1d-ac22-4303-ae27-7984c20eba0c.png differ diff --git a/docs/Features/Images/b000d3aa-3689-4d94-88e3-bca44f8b7de6.png b/docs/Features/Images/b000d3aa-3689-4d94-88e3-bca44f8b7de6.png index 60fb9e4ef..6c9afc6bb 100644 Binary files a/docs/Features/Images/b000d3aa-3689-4d94-88e3-bca44f8b7de6.png and b/docs/Features/Images/b000d3aa-3689-4d94-88e3-bca44f8b7de6.png differ diff --git a/docs/Features/Images/b26da59b-4e98-40b7-b97b-bb3cef4ca1d0.png b/docs/Features/Images/b26da59b-4e98-40b7-b97b-bb3cef4ca1d0.png index fda9b7fe2..aba08df6d 100644 Binary files a/docs/Features/Images/b26da59b-4e98-40b7-b97b-bb3cef4ca1d0.png and b/docs/Features/Images/b26da59b-4e98-40b7-b97b-bb3cef4ca1d0.png differ diff --git a/docs/Features/Images/d5dff8f5-c5fa-4620-ba11-430d06276b27.png b/docs/Features/Images/d5dff8f5-c5fa-4620-ba11-430d06276b27.png index 7bd6d88c6..d09323758 100644 Binary files a/docs/Features/Images/d5dff8f5-c5fa-4620-ba11-430d06276b27.png and b/docs/Features/Images/d5dff8f5-c5fa-4620-ba11-430d06276b27.png differ diff --git a/docs/Features/Images/fafaloneIDEscreenshot1.png b/docs/Features/Images/fafaloneIDEscreenshot1.png deleted file mode 100644 index 91ccc8897..000000000 Binary files a/docs/Features/Images/fafaloneIDEscreenshot1.png and /dev/null differ diff --git a/docs/Features/Packages/Creating a TWINPACK package.md b/docs/Features/Packages/Creating a TWINPACK package.md index 4a36c5c60..e0a1974e3 100644 --- a/docs/Features/Packages/Creating a TWINPACK package.md +++ b/docs/Features/Packages/Creating a TWINPACK package.md @@ -13,13 +13,13 @@ redirect_from: To create a new TWINPACK package, navigate to the twinBASIC New Project dialog, and under the 'Samples' tab, choose the option labelled 'Package': -![The New / Open Project dialog on the Samples tab with Sample 7. Package circled](Images/6ad7a172-0e1b-4276-ac89-042681552507.png) +![The New / Open Project dialog on the Samples tab, with a red ring round the title of Sample 7. Package](Images/6ad7a172-0e1b-4276-ac89-042681552507.png){:width="522" height="405"} <br> <br> Once you've created the project, you should find the extra 'PACKAGE PUBLISHING' panel as a popup: -![The PACKAGE PUBLISHING panel with EDIT links for Publisher, Namespace, Description, Licence, Visibility and Version, above the PUBLISH THIS PACKAGE button](Images/9eeffbcf-d73e-4a92-bce5-811ed60aba98.png) +![The PACKAGE PUBLISHING panel, floating over the editor, with EDIT links for Namespace, Description, Licence, Visibility and Version and a SIGN IN link for Publisher, above the PUBLISH THIS PACKAGE button](Images/9eeffbcf-d73e-4a92-bce5-811ed60aba98.png){:width="348" height="378"} <br> <br> @@ -41,7 +41,7 @@ You can now create components (Class, Module, Interface) in your project as norm Use this option if you want to just create a local TWINPACK file that you can use in other projects. For this, the build process is the same as any ordinary twinBASIC build... just hit the Build button in the TWINBASIC toolbar: -![The twinBASIC toolbar with the Build button circled](Images/4d90f313-35d5-426d-8fc3-852ca03382fa.png) +![The twinBASIC toolbar with a red ring round the Build button, the last of the three icons after the win32 list](Images/4d90f313-35d5-426d-8fc3-852ca03382fa.png){:width="323" height="83"} <br> <br> ![The DEBUG CONSOLE reporting the TWINPACK file being created and the build succeeding](Images/8d74d820-9907-4e76-ac42-71d0233187f1.png) @@ -56,7 +56,7 @@ Job done. See [Importing a package from a TWINPACK file](Importing-TWINPACK) fo If you're publishing your package onto TWINSERV, you don't need to create the TWINPACK file manually. Just use the 'PUBLISH THIS PACKAGE' button: -![The TWINBASIC PACKAGE MANAGER panel with arrows pointing to the PUBLISH THIS PACKAGE button](Images/packPublishButton.png){:style="width:45%; height:auto;"} +![The PACKAGE PUBLISHING panel with two arrows rising from below it to the lower corners of the PUBLISH THIS PACKAGE button](Images/packPublishButton.png){:width="316" height="361"} <br> <br> @@ -86,7 +86,7 @@ See [Importing a package from TWINSERV](Importing-TWINSERV) for referencing and When you create a new package project, you'll see two additional files created for you in the project filesystem: -![The project file tree with arrows pointing to the CHANGELOG.md and LICENCE.md files](Images/packLicenceFiles.png){:style="width:55%; height:auto;"} +![The project file tree of a package, with the folders ImportedTypeLibraries to Sources above the files CHANGELOG.md and LICENCE.md, and an arrow from the right pointing at each of the two files](Images/packLicenceFiles.png){:width="416" height="252"} <br> <br> diff --git a/docs/Features/Packages/Images/4d90f313-35d5-426d-8fc3-852ca03382fa.png b/docs/Features/Packages/Images/4d90f313-35d5-426d-8fc3-852ca03382fa.png index 3d1e5d1e1..1c380ef1b 100644 Binary files a/docs/Features/Packages/Images/4d90f313-35d5-426d-8fc3-852ca03382fa.png and b/docs/Features/Packages/Images/4d90f313-35d5-426d-8fc3-852ca03382fa.png differ diff --git a/docs/Features/Packages/Images/6ad7a172-0e1b-4276-ac89-042681552507.png b/docs/Features/Packages/Images/6ad7a172-0e1b-4276-ac89-042681552507.png index 2710a91c1..b46c7b502 100644 Binary files a/docs/Features/Packages/Images/6ad7a172-0e1b-4276-ac89-042681552507.png and b/docs/Features/Packages/Images/6ad7a172-0e1b-4276-ac89-042681552507.png differ diff --git a/docs/Features/Packages/Images/9eeffbcf-d73e-4a92-bce5-811ed60aba98.png b/docs/Features/Packages/Images/9eeffbcf-d73e-4a92-bce5-811ed60aba98.png index 72ce0af62..8a45dc6e2 100644 Binary files a/docs/Features/Packages/Images/9eeffbcf-d73e-4a92-bce5-811ed60aba98.png and b/docs/Features/Packages/Images/9eeffbcf-d73e-4a92-bce5-811ed60aba98.png differ diff --git a/docs/Features/Packages/Images/LibrarySymbols.png b/docs/Features/Packages/Images/LibrarySymbols.png index c3d76bfb8..843e799d4 100644 Binary files a/docs/Features/Packages/Images/LibrarySymbols.png and b/docs/Features/Packages/Images/LibrarySymbols.png differ diff --git a/docs/Features/Packages/Images/packLicenceFiles.png b/docs/Features/Packages/Images/packLicenceFiles.png index 2c40fdcd5..6243b1499 100644 Binary files a/docs/Features/Packages/Images/packLicenceFiles.png and b/docs/Features/Packages/Images/packLicenceFiles.png differ diff --git a/docs/Features/Packages/Images/packPublishButton.png b/docs/Features/Packages/Images/packPublishButton.png index 7a9166ef3..bbaa2afbc 100644 Binary files a/docs/Features/Packages/Images/packPublishButton.png and b/docs/Features/Packages/Images/packPublishButton.png differ diff --git a/docs/Features/Packages/Library symbols.md b/docs/Features/Packages/Library symbols.md index 340106652..841ea2ae7 100644 --- a/docs/Features/Packages/Library symbols.md +++ b/docs/Features/Packages/Library symbols.md @@ -16,9 +16,9 @@ The symbol is a property of the *reference*, not of the library, so a project ca Open *Project Settings*, find **Library References**, and select the **Enabled Libraries** tab. The **Library Symbol** column shows the symbol each library currently contributes under, and a pencil icon beside each one opens it for editing. -![The Project Settings dialog, Library References, Enabled Libraries tab. A Name column lists the VBA, VBRUN, OLE Automation and VB libraries, each with a tick box; a Library Symbol column shows VBA, VBRUN, stdole and VB, each followed by a small pencil icon; a Version column follows. The VB row's symbol reads VB struck through with *MyVB on the line beneath it.](Images/LibrarySymbols.png) +![The Project Settings dialog, Library References, Enabled Libraries tab. A Name column lists the VBA, VBRUN, OLE Automation and VB libraries, each with a tick box; a Library Symbol column shows VBA, VBRUN, stdole and VB, each followed by a small pencil icon; a Version column follows. The VB row's symbol reads VB struck through with MyVB on the line beneath it.](Images/LibrarySymbols.png){:width="1102" height="274"} -A symbol that has been changed is shown as the original struck through, with the replacement beneath it --- the `VB` / `*MyVB` pair in the picture above. That is display only: in code the library is `MyVB`, and `VB` no longer names anything. +A symbol that has been changed is shown as the original struck through, with the replacement beneath it --- the `VB` / `MyVB` pair in the picture above. That is display only: in code the library is `MyVB`, and `VB` no longer names anything. ## Exposing a library's private symbols diff --git a/docs/IDE/AddIns/GlobalSearch.md b/docs/IDE/AddIns/GlobalSearch.md index ba8265e2d..2370ee488 100644 --- a/docs/IDE/AddIns/GlobalSearch.md +++ b/docs/IDE/AddIns/GlobalSearch.md @@ -17,9 +17,9 @@ Developer The global search add in will contain a [Toolbar](../Project/Toolbar) item. -![A single toolbar button labelled Global Search.](Images/Toolbar_GlobalSearch.png) +![A single toolbar button labelled Global Search.](Images/Toolbar_GlobalSearch.png){:width="101" height="35"} -![The Global Search tool window before a search: an empty search box with a magnifier icon, and four clear checkboxes labelled In packages, Match case, Whole word and Exclude comments.](Images/GlobalSearch.png) +![The Global Search tool window before a search: an empty search box with a magnifier icon, and four clear checkboxes labelled In packages, Match case, Whole word and Exclude comments.](Images/GlobalSearch.png){:width="352" height="124"} Options @@ -28,9 +28,9 @@ Options - Whole word - Exclude comments -Type a search term i.e. Button1 into the text field and a list of matches will be returned. +Type a search term, such as Button1, into the text box, and the add-in lists the matches. -![The Global Search window after searching for Button1 with In packages ticked. Each result is a card giving the file name, its size in bytes, its path, a match count and the matching line of code with Button1 highlighted, followed by its line and column position. Constants.twin reports one match and MainModule.twin seventeen.](Images/GlobalSearch_2.png) +![The Global Search window after searching for Button1 with In packages ticked, in the add-in's own project. Each result is a card giving the file name, its size in bytes, its path, a match count and the matching line of code with Button1 highlighted, followed by its line and column position. Two cards are listed, both for Constants.twin in the packages, each with one match, vbDefaultButton1 at line 128, column 18.](Images/GlobalSearch_2.png){:width="352" height="389"} ## Download diff --git a/docs/IDE/AddIns/Help.md b/docs/IDE/AddIns/Help.md index 50bf09944..650a4ed28 100644 --- a/docs/IDE/AddIns/Help.md +++ b/docs/IDE/AddIns/Help.md @@ -1,11 +1,11 @@ --- -title: Documentation Help +title: Help Add-In parent: Add Ins nav_order: 2 permalink: /tB/IDE/AddIns/Help --- -# Documentation Help +# Help Add-In Shows this documentation inside the IDE: press F1 on a name in the code editor, and its reference page opens in a tool window beside the code. @@ -15,7 +15,7 @@ The add-in is named *twinBASIC Documentation Help* in the IDE. It works from an Put the cursor on a name and press F1. The page that documents the name opens in the **TWINBASIC HELP** tool window. The cursor can be anywhere in the name, or just after it, as it is after typing the name. -![The code editor showing a module named Inventory, with the cursor on MsgBox in line 19. Beside it, the TWINBASIC HELP tool window shows the MsgBox page: breadcrumbs, the heading, its summary, the syntax and the first parameter.](Images/Help_Pane.png){:width="1143" height="550"} +![The code editor showing a module named Inventory, with the cursor on MsgBox in line 19. Beside it, the TWINBASIC HELP tool window shows the MsgBox page: breadcrumbs, the heading, its summary, the syntax and the first parameter.](Images/Help_Pane.png){:width="1143" height="546"} The help pane is an ordinary IDE tool window: it can float, or be docked like the other tool windows. The page in it follows the IDE's theme, dark or light, and its links work as they do on the website. Above the page are a search box and three buttons: @@ -51,7 +51,7 @@ A link names the symbol, its kind and where it is declared: *Help: MsgBox functi Type in the search box to find a page by its symbol's name. The list shows the names that start with the text first, then those that contain it, with each one's kind. Text with a dot in it is matched against qualified names such as `Collection.Add`. Click a name to show its page, or press Enter for the first one. The clear button at the right of the box empties it and hides the list. -![The help pane with format typed in its search box. The list below shows Strings.Format, Strings.Format$, Strings.FormatCurrency, Strings.FormatDateTime, Strings.FormatNumber and Strings.FormatPercent, all functions, then OLE.Format and DataObjectFormat.FormatType, properties, and Constants.VbDateTimeFormat, an enum.](Images/Help_Search.png){:width="480" height="262"} +![The help pane with format typed in its search box. The list below shows Strings.Format, Strings.Format$, Strings.FormatCurrency, Strings.FormatDateTime, Strings.FormatNumber and Strings.FormatPercent, all functions, then OLE.Format and DataObjectFormat.FormatType, properties, and Constants.VbDateTimeFormat, an enum.](Images/Help_Search.png){:width="480" height="264"} The search finds symbols, not words in the pages: to search the text of the documentation, use the search on the website. @@ -59,13 +59,13 @@ The search finds symbols, not words in the pages: to search the text of the docu Sometimes the name alone cannot say which page is meant. A member called through a variable declared **As Object** is the common case: the object's type is known only when the program runs. F1 then puts the name in the search box and lists every page that documents it, and the page in the pane stays as it was. Click the one that applies. -![The help pane after F1 on prices.Add, where prices is declared As Object. The search box holds prices.Add, and the list shows six methods: Collection.Add, DataMembers.Add, DataObjectFiles.Add, HtmlElements.Add, KeyboardShortcuts.Add and ToolWindows.Add.](Images/Help_Choices.png){:width="480" height="196"} +![The help pane after F1 on prices.Add, where prices is declared As Object. The search box holds prices.Add, and the list shows three methods: Collection.Add, DataMembers.Add and DataObjectFiles.Add.](Images/Help_Choices.png){:width="480" height="132"} ## Your own procedures F1 on a name declared in the project itself, or in a package or type library this documentation does not cover, shows a summary in place of a page. The summary gives the declaration, the text of the procedure's [**Description**](../../Core/Attributes#description) attribute, and where the name is declared. -![The help pane after F1 on TotalPrice. In place of a page it shows the declaration Function TotalPrice ( ByVal prices As Object ) As Double, the description Adds up the prices held in a Dictionary., and the line Declared in Inventory.Inventory.](Images/Help_Description.png){:width="480" height="169"} +![The help pane after F1 on TotalPrice. In place of a page it shows the declaration Function TotalPrice ( ByVal prices As Object ) As Double, the description Adds up the prices held in a Dictionary., and the line Declared in Inventory.Inventory.](Images/Help_Description.png){:width="480" height="170"} The summary for the procedure above comes from this code: @@ -80,7 +80,7 @@ Without a **Description** attribute, the summary says *No description*. **Open i The gear opens the settings. A click anywhere else, or Esc, closes them. -![The top of the help pane with the settings open under the gear: a ticked Hover help box and an unticked All packages box.](Images/Help_Settings.png){:width="480" height="149"} +![The top of the help pane with the settings open under the gear: the Hover help and All packages boxes, both unticked.](Images/Help_Settings.png){:width="480" height="150"} **Hover help** : Adds the links described under [Hover help](#hover-help). Off by default. @@ -111,20 +111,25 @@ The window is open again the next time the IDE starts, if it was open when the I ## Getting the add-in -The add-in is built from its source code, which is in the `add-in` folder of the [documentation's repository](https://github.com/twinbasic/documentation). The folder holds the project's files, not a project file, so the first step makes one. - -1. Pack the folder into a project file with the [Import/Export Tool](../../../Features/Packages/Import-Export-Tool), from the repository's root folder: - - ```batch - python scripts/impexp.py import tbDocsHelp.twinproj add-in - ``` +The add-in is a twinBASIC project, which the IDE builds into two DLLs, one for each bitness. +1. Download the project file, <a href="downloads/tbDocsHelp.twinproj" download>tbDocsHelp.twinproj</a>, about 28 MB. It is made from the add-in's source code each time this documentation is published. It holds the index of the symbols these pages document and a copy of the whole documentation. 2. Open `tbDocsHelp.twinproj` in the twinBASIC IDE and build it. Then set the build target to **win64** and build it again. The two DLLs, `tbDocsHelp_win32.dll` and `tbDocsHelp_win64.dll`, are written to a `Build` folder next to the project file. 3. Copy each DLL to the matching `win32` or `win64` add-ins folder, as [Add Ins](../AddIns/) describes, and restart the IDE. -An add-in built this way shows the pages from [docs.twinbasic.com](https://docs.twinbasic.com), so the pane needs an internet connection. +An add-in built this way serves the pages itself, from the copy of the documentation in its DLL, so the pane needs no internet connection. **Open in browser** still opens the page on the website, [docs.twinbasic.com](https://docs.twinbasic.com). + +### From the source code + +The add-in's source code is in the `add-in` folder of the [documentation's repository](https://github.com/twinbasic/documentation). Build from it to change the add-in. The folder holds the project's files, not a project file, so pack it into one first with the [Import/Export Tool](../../../Features/Packages/Import-Export-Tool), from the repository's root folder: + +```batch +python scripts/impexp.py import tbDocsHelp.twinproj add-in +``` + +Then build `tbDocsHelp.twinproj` and copy the DLLs as in steps 2 and 3 above. -**For help that works offline**, build the documentation before step 1, as [Building and Deployment](../../../Documentation/Development/Building) describes. The build writes a copy of the whole site into the add-in's source folder, `add-in\Resources\HELP\site.zip`, about 24 MB. A DLL built with that file in place serves the pages itself, without the internet. **Open in browser** still opens the website. +**For help that works offline**, build the documentation before packing the folder, as [Building and Deployment](../../../Documentation/Development/Building) describes. The build writes a copy of the whole site into the add-in's source folder, `add-in\Resources\HELP\site.zip`, about 27 MB. A DLL built with that file in place serves the pages itself, without the internet. Without the file, the add-in shows the pages from the website. ## See Also diff --git a/docs/IDE/AddIns/Images/GlobalSearch.png b/docs/IDE/AddIns/Images/GlobalSearch.png index d35d18c1f..2bc0edebf 100644 Binary files a/docs/IDE/AddIns/Images/GlobalSearch.png and b/docs/IDE/AddIns/Images/GlobalSearch.png differ diff --git a/docs/IDE/AddIns/Images/GlobalSearch_2.png b/docs/IDE/AddIns/Images/GlobalSearch_2.png index 3bc5df2e0..da59ad903 100644 Binary files a/docs/IDE/AddIns/Images/GlobalSearch_2.png and b/docs/IDE/AddIns/Images/GlobalSearch_2.png differ diff --git a/docs/IDE/AddIns/Images/Help_Choices.png b/docs/IDE/AddIns/Images/Help_Choices.png index 8f3373abf..ad5f314e4 100644 Binary files a/docs/IDE/AddIns/Images/Help_Choices.png and b/docs/IDE/AddIns/Images/Help_Choices.png differ diff --git a/docs/IDE/AddIns/Images/Help_Description.png b/docs/IDE/AddIns/Images/Help_Description.png index 70f6bc7f0..e242db8ee 100644 Binary files a/docs/IDE/AddIns/Images/Help_Description.png and b/docs/IDE/AddIns/Images/Help_Description.png differ diff --git a/docs/IDE/AddIns/Images/Help_Hover.png b/docs/IDE/AddIns/Images/Help_Hover.png index a9097d8e4..badc0fd48 100644 Binary files a/docs/IDE/AddIns/Images/Help_Hover.png and b/docs/IDE/AddIns/Images/Help_Hover.png differ diff --git a/docs/IDE/AddIns/Images/Help_Pane.png b/docs/IDE/AddIns/Images/Help_Pane.png index ee09767ab..092a13e43 100644 Binary files a/docs/IDE/AddIns/Images/Help_Pane.png and b/docs/IDE/AddIns/Images/Help_Pane.png differ diff --git a/docs/IDE/AddIns/Images/Help_Search.png b/docs/IDE/AddIns/Images/Help_Search.png index 4bd124576..355dbe9e6 100644 Binary files a/docs/IDE/AddIns/Images/Help_Search.png and b/docs/IDE/AddIns/Images/Help_Search.png differ diff --git a/docs/IDE/AddIns/Images/Help_Settings.png b/docs/IDE/AddIns/Images/Help_Settings.png index 1f84bde8d..2df14da6e 100644 Binary files a/docs/IDE/AddIns/Images/Help_Settings.png and b/docs/IDE/AddIns/Images/Help_Settings.png differ diff --git a/docs/IDE/AddIns/Images/Help_Toolbar.png b/docs/IDE/AddIns/Images/Help_Toolbar.png index 42dd513ed..3ff4b00fd 100644 Binary files a/docs/IDE/AddIns/Images/Help_Toolbar.png and b/docs/IDE/AddIns/Images/Help_Toolbar.png differ diff --git a/docs/IDE/AddIns/Images/Toolbar_GlobalSearch.png b/docs/IDE/AddIns/Images/Toolbar_GlobalSearch.png index 279f57110..557678f30 100644 Binary files a/docs/IDE/AddIns/Images/Toolbar_GlobalSearch.png and b/docs/IDE/AddIns/Images/Toolbar_GlobalSearch.png differ diff --git a/docs/IDE/Call Stack.md b/docs/IDE/Call Stack.md index d6c1a2031..f6c673ddf 100644 --- a/docs/IDE/Call Stack.md +++ b/docs/IDE/Call Stack.md @@ -8,7 +8,7 @@ index: call stack window # Call Stack -![The Call Stack pane listing two active threads, each collapsed to a single row: a Win32 compiler thread on version 0.15.957 LAA, and MAIN_THREAD, marked idle. A large annotation arrow points at both rows and labels them as the active threads.](Images/CallStack.png) +![The Call Stack pane listing two active threads, each collapsed to a single row: a Win32 compiler thread with its version number, and MAIN_THREAD, marked idle. A label reading Active Threads sits below the rows, with an arrow rising from it to each row.](Images/CallStack.png){:width="376" height="206"} The Call Stack pane lists the active chain of procedure calls at the current execution point during a debugging session, with the most recent call at the top. Clicking an entry navigates the editor to that call site. diff --git a/docs/IDE/Debug Console.md b/docs/IDE/Debug Console.md index f201d5a7f..e5f51bc0e 100644 --- a/docs/IDE/Debug Console.md +++ b/docs/IDE/Debug Console.md @@ -8,11 +8,11 @@ index: immediate window # Debug Console -![The Debug Console pane with its three header buttons outlined at the top right and annotation arrows naming them Auto Scroll, Clear Debug Console and Options. The Options menu is open, offering Invert Output Direction and a ticked Show Timestamps, and a fourth arrow points at the input row outlined along the bottom edge.](Images/DebugConsole.png) +![The Debug Console pane with its three header buttons outlined at the top right. Labels above the pane, with arrows, name the first two buttons Auto Scroll and Clear Debug Console. The Options menu of the third button is open below it, offering Invert Output Direction and a ticked Show Timestamps, and a label at its right, also with an arrow, names it Options. The input row is outlined along the bottom edge, and a label with an arrow names it Input.](Images/DebugConsole.png){:width="914" height="326"} -The Debug Console captures output from [**Debug.Print**](../../Modules/Debug#print) statements and other debug-layer messages written at runtime, displaying them in a scrollable log. [**Debug.TracePrint**](../../Modules/Debug#traceprint) writes here too when *Compilation: Trace Output* is set to the debug console rather than a file. +The Debug Console captures output from [**Debug.Print**](../../Modules/Debug#print) statements and other debug-layer messages written at runtime, displaying them in a scrollable log. [**Debug.TracePrint**](../../Modules/Debug#traceprint) writes here too when the *Trace Output* project setting is `${DEBUG}` rather than the path of a log file. -## ![](Images/DebugConsole_AutoScroll.png) Auto Scroll +## ![](Images/DebugConsole_AutoScroll.png){:width="16" height="16"} Auto Scroll Keeps the newest output in view. While Auto Scroll is on, the console scrolls to each new line as it arrives: to the bottom, or to the top when **Invert Output Direction** is ticked. It is on each time the IDE starts, and the button is highlighted while it is on. @@ -20,16 +20,16 @@ Scrolling away from the newest line turns Auto Scroll off, so earlier output sta Clicking the button turns Auto Scroll off until it is clicked again. Scrolling back to the newest line then leaves it off. -## ![](Images/DebugConsole_Clear.png) Clear Debug Console +## ![](Images/DebugConsole_Clear.png){:width="16" height="16"} Clear Debug Console Empties the console. [**Debug.Cls**](../../Modules/Debug#cls) does the same thing from code. -## ![](Images/DebugConsole_Options.png) Options +## ![](Images/DebugConsole_Options.png){:width="16" height="16"} Options - Invert Output Direction - Show Timestamps -## ![](Images/DebugConsole_Input.png) Input +## ![](Images/DebugConsole_Input.png){:width="20" height="15"} Input The row along the bottom of the console runs one line of code when <kbd>ENTER</kbd> is pressed. <kbd>CTRL</kbd> + <kbd>G</kbd> moves the keyboard focus to it, and the up arrow key (<kbd>ARROWUP</kbd>) brings back earlier lines, the most recent first. diff --git a/docs/IDE/Diagnostics.md b/docs/IDE/Diagnostics.md index f979a56c3..c5f401f5a 100644 --- a/docs/IDE/Diagnostics.md +++ b/docs/IDE/Diagnostics.md @@ -8,11 +8,11 @@ index: diagnostics window # Diagnostics -![Diagnostics](Images/Diagnostics.png) +![The Diagnostics pane with no project open. Four counts, each 0, sit at the right of its title bar: Errors, Warnings, Hints and Information. A label at the left of the pane names each one, with an arrow that curves up to its count from below.](Images/Diagnostics.png){:width="588" height="266"} -![Diagnostics](Images/Diagnostics_Toggles.png) +![The title bar of the Diagnostics pane with two errors, no warnings, no hints and three information messages, each count in a red box. A label reading Click to toggle ON/OFF sits below the bar, with an arrow rising from it to each of the four counts.](Images/Diagnostics_Toggles.png){:width="446" height="121"} -![Diagnostics](Images/Diagnostics_Totals.png) +![The same title bar with only the number of each count in a red box. A label reading Total counts for each category sits below the bar, with an arrow rising from it to each of the four numbers.](Images/Diagnostics_Totals.png){:width="446" height="118"} diff --git a/docs/IDE/Editor.md b/docs/IDE/Editor.md index bbcb99097..16c9a7cba 100644 --- a/docs/IDE/Editor.md +++ b/docs/IDE/Editor.md @@ -7,7 +7,7 @@ permalink: /tB/IDE/Project/Editor # Editor -![The Editor pane with its two header buttons outlined and annotated Options and Tabs List. The Options menu is open with all four of its dropdowns expanded at once: Show Code Folding set to when hovering, Render Whitespace to none, Font Size to 13px and Show Navigation Bar to above. Below them sit the toggles, of which Show Indent Guides, Show Line Numbers, Show MiniMap, Auto Prettify Code and Show CodeLens Run Procedure are ticked.](Images/Editor.png) +![The EDITOR 1 pane with its two header buttons boxed and labelled Options and Tabs List. The Options menu is open, and the lists of its four dropdowns are drawn open beside it, each with an arrow from its dropdown: Show Code Folding set to when hovering, Render Whitespace to none, Font Size to 13px and Show Navigation Bar to above. Below the dropdowns sit the toggles, of which Show Indent Guides, Show Line Numbers, Show MiniMap, Auto Prettify Code and Show CodeLens Run Procedure are ticked.](Images/Editor.png){:width="760" height="440"} ## Options @@ -47,12 +47,12 @@ permalink: /tB/IDE/Project/Editor When a file is open in the _Editor_ it will be listed in the _Tabs List_ and you can jump between them here. -![The open Tabs List menu, holding a Recently Closed submenu entry above a separator and one open file beneath it, Sources/MyForm.tbform.](Images/Editor_TabsList_Example.png) +![The open Tabs List menu, holding a Recently Closed submenu entry above a separator and one open file beneath it, Sources/frmMain.tbform.](Images/Editor_TabsList_Example.png){:width="187" height="66"} _Recently Closed_. -![The Tabs List button with its menu open directly beneath it, holding a single entry, Recently Closed, with a submenu arrow and no files listed under it.](Images/Editor_TabsList_RecentlyClosed.png) +![The Tabs List button with its menu open directly beneath it, holding a single entry, Recently Closed, with a submenu arrow and no files listed under it.](Images/Editor_TabsList_RecentlyClosed.png){:width="152" height="54"} _Recently Closed - List_ -![The Recently Closed entry highlighted with its submenu expanded to the right, listing three closed files: Resources/MANIFEST/#1.xml, Resources/STRING/Strings.json and Resources/MESSAGETABLE/Strings.json.](Images/Editor_TabsList_RecentlyClosed_Example.png) +![The Recently Closed entry highlighted with its submenu expanded to the right, listing three closed files: Resources/MANIFEST/#1.xml, Resources/STRING/Strings.json and Resources/MESSAGETABLE/Strings.json.](Images/Editor_TabsList_RecentlyClosed_Example.png){:width="404" height="104"} diff --git a/docs/IDE/FindReplace.md b/docs/IDE/FindReplace.md index ce300ca4b..be002ce2a 100644 --- a/docs/IDE/FindReplace.md +++ b/docs/IDE/FindReplace.md @@ -10,7 +10,7 @@ index: # Find / Replace -![The Find / Replace pane. Find What and Replace With are both empty. The scope radio group has Current File selected, with Current Module and Selected Text greyed out. Direction is set to All, and the checkboxes Whole Word Only, Match Case, Pattern Matching, Match Regular Expressions and Inside Packages are all clear. Find Next, Cancel, Replace and Replace All run down the right edge.](Images/FindReplace.png) +![The Find / Replace pane. Find What and Replace With are both empty. The scope radio group has Current File selected, with Current Procedure, Current Module and Selected Text greyed out. Direction is set to All, and the checkboxes Whole Word Only, Match Case, Pattern Matching and Match Regular Expressions are all clear, while Inside Packages is greyed out. Find Next, Cancel, Replace and Replace All run down the right edge.](Images/FindReplace.png){:width="600" height="209"} Find What @@ -24,7 +24,7 @@ Replace With Direction: All / Down / Up -![The Direction dropdown opened, offering All, highlighted as the current choice, then Down and Up.](Images/FindReplace_Direction.png) +![The Direction dropdown with its list open below it, offering All, marked as the current choice, then Down and Up.](Images/FindReplace_Direction.png){:width="232" height="99"} - Whole Word Only - Match Case diff --git a/docs/IDE/History.md b/docs/IDE/History.md index a57922e94..a839f9f41 100644 --- a/docs/IDE/History.md +++ b/docs/IDE/History.md @@ -12,18 +12,18 @@ index: When a project isn't open this will be empty. -![The HISTORY panel with no project open: a title bar with the drag grip and close button, above an empty body.](Images/History.png) +![The HISTORY panel with no project open: a title bar with the drag grip and close button, above an empty body.](Images/History.png){:width="300" height="96"} -Once you open a project +Once a project is open, editing a file adds an entry. -![The same panel after a project is opened, listing two entries both named frmMain, timestamped 18:02 and 16:41 at the right edge.](Images/History_1.png) +![The same panel after the form and the code of a project have been edited, listing two entries both named frmMain, timestamped 18:02 and 16:41 at the right edge.](Images/History_1.png){:width="300" height="96"} If you hover on an item it will show you the path of the file. -![The pointer resting on the 18:02 frmMain entry, which raises a tooltip reading /twinBASICMonthlyChallenge1/Sources/frmMain.tbform [line: undefined] above a second line giving Fri Jan 09 2026 18:02:51 GMT+0000 (Greenwich Mean Time).](Images/History_2.png) +![The pointer resting on the 18:02 frmMain entry, which raises a tooltip reading /SampleProject/Sources/frmMain.tbform [line: undefined] above a second line giving Fri Jan 09 2026 18:02:51 GMT+0000 (GMT+00:00).](Images/History_2.png){:width="360" height="120"} If it's a code file (i.e. `.twin`) it will show the "line: #". -![The pointer on the 16:41 entry instead, its tooltip reading /twinBASICMonthlyChallenge1/Sources/frmMain.twin [line: 4] above Fri Jan 09 2026 16:41:58 GMT+0000 (Greenwich Mean Time).](Images/History_3.png) +![The pointer on the 16:41 entry instead, its tooltip reading /SampleProject/Sources/frmMain.twin [line: 4] above Fri Jan 09 2026 16:41:58 GMT+0000 (GMT+00:00).](Images/History_3.png){:width="360" height="120"} You can also click on an item to open it. diff --git a/docs/IDE/Images/Add.png b/docs/IDE/Images/Add.png index 4f44f702e..f2b08d033 100644 Binary files a/docs/IDE/Images/Add.png and b/docs/IDE/Images/Add.png differ diff --git a/docs/IDE/Images/CallStack.png b/docs/IDE/Images/CallStack.png index 1a6957a3e..ebc7fae41 100644 Binary files a/docs/IDE/Images/CallStack.png and b/docs/IDE/Images/CallStack.png differ diff --git a/docs/IDE/Images/Clear.png b/docs/IDE/Images/Clear.png index 72d5ff082..13c69e446 100644 Binary files a/docs/IDE/Images/Clear.png and b/docs/IDE/Images/Clear.png differ diff --git a/docs/IDE/Images/Components_Message.png b/docs/IDE/Images/Components_Message.png index ecf6b7456..76bc2dde7 100644 Binary files a/docs/IDE/Images/Components_Message.png and b/docs/IDE/Images/Components_Message.png differ diff --git a/docs/IDE/Images/DebugConsole.png b/docs/IDE/Images/DebugConsole.png index 7c80d7113..b3e89e855 100644 Binary files a/docs/IDE/Images/DebugConsole.png and b/docs/IDE/Images/DebugConsole.png differ diff --git a/docs/IDE/Images/DebugConsole_AutoScroll.png b/docs/IDE/Images/DebugConsole_AutoScroll.png index a53f2eb00..89ea1b518 100644 Binary files a/docs/IDE/Images/DebugConsole_AutoScroll.png and b/docs/IDE/Images/DebugConsole_AutoScroll.png differ diff --git a/docs/IDE/Images/DebugConsole_Clear.png b/docs/IDE/Images/DebugConsole_Clear.png index acac543f1..07d20f87d 100644 Binary files a/docs/IDE/Images/DebugConsole_Clear.png and b/docs/IDE/Images/DebugConsole_Clear.png differ diff --git a/docs/IDE/Images/DebugConsole_Input.png b/docs/IDE/Images/DebugConsole_Input.png index a870f751a..91baf7c32 100644 Binary files a/docs/IDE/Images/DebugConsole_Input.png and b/docs/IDE/Images/DebugConsole_Input.png differ diff --git a/docs/IDE/Images/DebugConsole_Options.png b/docs/IDE/Images/DebugConsole_Options.png index ae1f1c817..efdba15ed 100644 Binary files a/docs/IDE/Images/DebugConsole_Options.png and b/docs/IDE/Images/DebugConsole_Options.png differ diff --git a/docs/IDE/Images/Diagnostics.png b/docs/IDE/Images/Diagnostics.png index c1e4ee185..b0798cddd 100644 Binary files a/docs/IDE/Images/Diagnostics.png and b/docs/IDE/Images/Diagnostics.png differ diff --git a/docs/IDE/Images/Diagnostics_Toggles.png b/docs/IDE/Images/Diagnostics_Toggles.png index 437e98dbd..b7cafaa3c 100644 Binary files a/docs/IDE/Images/Diagnostics_Toggles.png and b/docs/IDE/Images/Diagnostics_Toggles.png differ diff --git a/docs/IDE/Images/Diagnostics_Totals.png b/docs/IDE/Images/Diagnostics_Totals.png index 7a349c135..f10aee9a4 100644 Binary files a/docs/IDE/Images/Diagnostics_Totals.png and b/docs/IDE/Images/Diagnostics_Totals.png differ diff --git a/docs/IDE/Images/Editor.png b/docs/IDE/Images/Editor.png index cba20f961..6e23111cb 100644 Binary files a/docs/IDE/Images/Editor.png and b/docs/IDE/Images/Editor.png differ diff --git a/docs/IDE/Images/Editor_TabsList.png b/docs/IDE/Images/Editor_TabsList.png deleted file mode 100644 index d4dede929..000000000 Binary files a/docs/IDE/Images/Editor_TabsList.png and /dev/null differ diff --git a/docs/IDE/Images/Editor_TabsList_Example.png b/docs/IDE/Images/Editor_TabsList_Example.png index c5d247e6c..7cd2fe7ad 100644 Binary files a/docs/IDE/Images/Editor_TabsList_Example.png and b/docs/IDE/Images/Editor_TabsList_Example.png differ diff --git a/docs/IDE/Images/Editor_TabsList_RecentlyClosed.png b/docs/IDE/Images/Editor_TabsList_RecentlyClosed.png index d5ac32e0f..425aac84d 100644 Binary files a/docs/IDE/Images/Editor_TabsList_RecentlyClosed.png and b/docs/IDE/Images/Editor_TabsList_RecentlyClosed.png differ diff --git a/docs/IDE/Images/Editor_TabsList_RecentlyClosed_Example.png b/docs/IDE/Images/Editor_TabsList_RecentlyClosed_Example.png index 93f1bea54..fd93eb3e0 100644 Binary files a/docs/IDE/Images/Editor_TabsList_RecentlyClosed_Example.png and b/docs/IDE/Images/Editor_TabsList_RecentlyClosed_Example.png differ diff --git a/docs/IDE/Images/File-Green.png b/docs/IDE/Images/File-Green.png index 7d7bff2e9..2485cf8b7 100644 Binary files a/docs/IDE/Images/File-Green.png and b/docs/IDE/Images/File-Green.png differ diff --git a/docs/IDE/Images/FindReplace.png b/docs/IDE/Images/FindReplace.png index 51b40a83d..7b525c68b 100644 Binary files a/docs/IDE/Images/FindReplace.png and b/docs/IDE/Images/FindReplace.png differ diff --git a/docs/IDE/Images/FindReplace_Direction.png b/docs/IDE/Images/FindReplace_Direction.png index f2bccd901..affdd520f 100644 Binary files a/docs/IDE/Images/FindReplace_Direction.png and b/docs/IDE/Images/FindReplace_Direction.png differ diff --git a/docs/IDE/Images/Folder.png b/docs/IDE/Images/Folder.png index f26c79ca8..97e547dc5 100644 Binary files a/docs/IDE/Images/Folder.png and b/docs/IDE/Images/Folder.png differ diff --git a/docs/IDE/Images/History.png b/docs/IDE/Images/History.png index cd92ba7b8..029c5b2e9 100644 Binary files a/docs/IDE/Images/History.png and b/docs/IDE/Images/History.png differ diff --git a/docs/IDE/Images/History_1.png b/docs/IDE/Images/History_1.png index ec758fa88..9700d0b36 100644 Binary files a/docs/IDE/Images/History_1.png and b/docs/IDE/Images/History_1.png differ diff --git a/docs/IDE/Images/History_2.png b/docs/IDE/Images/History_2.png index 85c629f09..6d1216502 100644 Binary files a/docs/IDE/Images/History_2.png and b/docs/IDE/Images/History_2.png differ diff --git a/docs/IDE/Images/History_3.png b/docs/IDE/Images/History_3.png index 2a4d8a8d7..cba4ead90 100644 Binary files a/docs/IDE/Images/History_3.png and b/docs/IDE/Images/History_3.png differ diff --git a/docs/IDE/Images/IDE.png b/docs/IDE/Images/IDE.png index d9d20666c..44095f49c 100644 Binary files a/docs/IDE/Images/IDE.png and b/docs/IDE/Images/IDE.png differ diff --git a/docs/IDE/Images/Links.png b/docs/IDE/Images/Links.png index f5695839a..1f31d7606 100644 Binary files a/docs/IDE/Images/Links.png and b/docs/IDE/Images/Links.png differ diff --git a/docs/IDE/Images/Memory.png b/docs/IDE/Images/Memory.png index b07669f8a..b79404c77 100644 Binary files a/docs/IDE/Images/Memory.png and b/docs/IDE/Images/Memory.png differ diff --git a/docs/IDE/Images/New_Project.png b/docs/IDE/Images/New_Project.png index 6ec80a849..eae8bb49b 100644 Binary files a/docs/IDE/Images/New_Project.png and b/docs/IDE/Images/New_Project.png differ diff --git a/docs/IDE/Images/New_Project_Recent_1.png b/docs/IDE/Images/New_Project_Recent_1.png index 60cf35523..ab62075dc 100644 Binary files a/docs/IDE/Images/New_Project_Recent_1.png and b/docs/IDE/Images/New_Project_Recent_1.png differ diff --git a/docs/IDE/Images/New_Project_Recent_2.png b/docs/IDE/Images/New_Project_Recent_2.png index 58e08ccf6..6ad3eb394 100644 Binary files a/docs/IDE/Images/New_Project_Recent_2.png and b/docs/IDE/Images/New_Project_Recent_2.png differ diff --git a/docs/IDE/Images/New_Project_Samples.png b/docs/IDE/Images/New_Project_Samples.png index 48341febd..b52c1f7e3 100644 Binary files a/docs/IDE/Images/New_Project_Samples.png and b/docs/IDE/Images/New_Project_Samples.png differ diff --git a/docs/IDE/Images/OpenEditors.png b/docs/IDE/Images/OpenEditors.png index 64267946a..d7648dae0 100644 Binary files a/docs/IDE/Images/OpenEditors.png and b/docs/IDE/Images/OpenEditors.png differ diff --git a/docs/IDE/Images/OpenEditors_1.png b/docs/IDE/Images/OpenEditors_1.png index 50160c7a5..3aef500e2 100644 Binary files a/docs/IDE/Images/OpenEditors_1.png and b/docs/IDE/Images/OpenEditors_1.png differ diff --git a/docs/IDE/Images/Options.png b/docs/IDE/Images/Options.png deleted file mode 100644 index 05749c3be..000000000 Binary files a/docs/IDE/Images/Options.png and /dev/null differ diff --git a/docs/IDE/Images/Outline.png b/docs/IDE/Images/Outline.png index b857caf50..2dba11fed 100644 Binary files a/docs/IDE/Images/Outline.png and b/docs/IDE/Images/Outline.png differ diff --git a/docs/IDE/Images/Outline_1.png b/docs/IDE/Images/Outline_1.png index 8ae5da3e4..45d877597 100644 Binary files a/docs/IDE/Images/Outline_1.png and b/docs/IDE/Images/Outline_1.png differ diff --git a/docs/IDE/Images/PackagePublishing.png b/docs/IDE/Images/PackagePublishing.png index e57491bff..7b5655c73 100644 Binary files a/docs/IDE/Images/PackagePublishing.png and b/docs/IDE/Images/PackagePublishing.png differ diff --git a/docs/IDE/Images/PackagePublishing_1.png b/docs/IDE/Images/PackagePublishing_1.png index 025b008ea..f55befd90 100644 Binary files a/docs/IDE/Images/PackagePublishing_1.png and b/docs/IDE/Images/PackagePublishing_1.png differ diff --git a/docs/IDE/Images/ProjectExplorer.png b/docs/IDE/Images/ProjectExplorer.png index fe0943045..f7d6c5b8f 100644 Binary files a/docs/IDE/Images/ProjectExplorer.png and b/docs/IDE/Images/ProjectExplorer.png differ diff --git a/docs/IDE/Images/ProjectExplorer_Header.png b/docs/IDE/Images/ProjectExplorer_Header.png index 6267f0952..d51b78bcf 100644 Binary files a/docs/IDE/Images/ProjectExplorer_Header.png and b/docs/IDE/Images/ProjectExplorer_Header.png differ diff --git a/docs/IDE/Images/ProjectExplorer_Sample.png b/docs/IDE/Images/ProjectExplorer_Sample.png index 705947e49..4db92679e 100644 Binary files a/docs/IDE/Images/ProjectExplorer_Sample.png and b/docs/IDE/Images/ProjectExplorer_Sample.png differ diff --git a/docs/IDE/Images/ProjectSettings_AvailableCOMReferences.png b/docs/IDE/Images/ProjectSettings_AvailableCOMReferences.png index 46d6e0d3f..94255aeec 100644 Binary files a/docs/IDE/Images/ProjectSettings_AvailableCOMReferences.png and b/docs/IDE/Images/ProjectSettings_AvailableCOMReferences.png differ diff --git a/docs/IDE/Images/ProjectSettings_LibraryReferences.png b/docs/IDE/Images/ProjectSettings_LibraryReferences.png index eb46efa45..22c6cbcd6 100644 Binary files a/docs/IDE/Images/ProjectSettings_LibraryReferences.png and b/docs/IDE/Images/ProjectSettings_LibraryReferences.png differ diff --git a/docs/IDE/Images/Properties.png b/docs/IDE/Images/Properties.png index 79db17658..536110865 100644 Binary files a/docs/IDE/Images/Properties.png and b/docs/IDE/Images/Properties.png differ diff --git a/docs/IDE/Images/RightClick-Add-CustomControlsForm-Popup.png b/docs/IDE/Images/RightClick-Add-CustomControlsForm-Popup.png index e404ae244..dc8a711e7 100644 Binary files a/docs/IDE/Images/RightClick-Add-CustomControlsForm-Popup.png and b/docs/IDE/Images/RightClick-Add-CustomControlsForm-Popup.png differ diff --git a/docs/IDE/Images/RightClick-Add.png b/docs/IDE/Images/RightClick-Add.png index bf7633148..dcea2523f 100644 Binary files a/docs/IDE/Images/RightClick-Add.png and b/docs/IDE/Images/RightClick-Add.png differ diff --git a/docs/IDE/Images/Services_Operational.png b/docs/IDE/Images/Services_Operational.png index 1e127881c..8bffbafb7 100644 Binary files a/docs/IDE/Images/Services_Operational.png and b/docs/IDE/Images/Services_Operational.png differ diff --git a/docs/IDE/Images/Settings.png b/docs/IDE/Images/Settings.png index 5da71b835..a414f0659 100644 Binary files a/docs/IDE/Images/Settings.png and b/docs/IDE/Images/Settings.png differ diff --git a/docs/IDE/Images/StatusBar.png b/docs/IDE/Images/StatusBar.png index e279d488e..debe4f067 100644 Binary files a/docs/IDE/Images/StatusBar.png and b/docs/IDE/Images/StatusBar.png differ diff --git a/docs/IDE/Images/Toggle.png b/docs/IDE/Images/Toggle.png index cfdc46777..c06922067 100644 Binary files a/docs/IDE/Images/Toggle.png and b/docs/IDE/Images/Toggle.png differ diff --git a/docs/IDE/Images/Toolbar_1.png b/docs/IDE/Images/Toolbar_1.png index 31896391f..fb5519460 100644 Binary files a/docs/IDE/Images/Toolbar_1.png and b/docs/IDE/Images/Toolbar_1.png differ diff --git a/docs/IDE/Images/Toolbar_2.png b/docs/IDE/Images/Toolbar_2.png index 3a39a9585..ebf4ee6b1 100644 Binary files a/docs/IDE/Images/Toolbar_2.png and b/docs/IDE/Images/Toolbar_2.png differ diff --git a/docs/IDE/Images/Toolbar_3.png b/docs/IDE/Images/Toolbar_3.png index 633a8f8c4..d0172f2c1 100644 Binary files a/docs/IDE/Images/Toolbar_3.png and b/docs/IDE/Images/Toolbar_3.png differ diff --git a/docs/IDE/Images/Toolbox_MoreComponents.png b/docs/IDE/Images/Toolbox_MoreComponents.png index 0c3a57763..6a8c9efd3 100644 Binary files a/docs/IDE/Images/Toolbox_MoreComponents.png and b/docs/IDE/Images/Toolbox_MoreComponents.png differ diff --git a/docs/IDE/Images/Toolbox_Report.png b/docs/IDE/Images/Toolbox_Report.png index b93e3892b..46981b8d7 100644 Binary files a/docs/IDE/Images/Toolbox_Report.png and b/docs/IDE/Images/Toolbox_Report.png differ diff --git a/docs/IDE/Images/UserControl.png b/docs/IDE/Images/UserControl.png deleted file mode 100644 index 4e27b55fa..000000000 Binary files a/docs/IDE/Images/UserControl.png and /dev/null differ diff --git a/docs/IDE/Images/Variables.png b/docs/IDE/Images/Variables.png index 496f99982..aeee34613 100644 Binary files a/docs/IDE/Images/Variables.png and b/docs/IDE/Images/Variables.png differ diff --git a/docs/IDE/Images/Watches.png b/docs/IDE/Images/Watches.png index e37d3e3a9..ddf961aee 100644 Binary files a/docs/IDE/Images/Watches.png and b/docs/IDE/Images/Watches.png differ diff --git a/docs/IDE/Images/Watches_1.png b/docs/IDE/Images/Watches_1.png index f921bb465..dbc9ba55d 100644 Binary files a/docs/IDE/Images/Watches_1.png and b/docs/IDE/Images/Watches_1.png differ diff --git a/docs/IDE/Images/Watches_2.png b/docs/IDE/Images/Watches_2.png index 1dab827c1..96ec4a341 100644 Binary files a/docs/IDE/Images/Watches_2.png and b/docs/IDE/Images/Watches_2.png differ diff --git a/docs/IDE/Images/project settings description text.png b/docs/IDE/Images/project settings description text.png index fd552b9e9..9e0c22ef5 100644 Binary files a/docs/IDE/Images/project settings description text.png and b/docs/IDE/Images/project settings description text.png differ diff --git a/docs/IDE/Images/tB-Blue.png b/docs/IDE/Images/tB-Blue.png index 433a5344b..0d4e102f9 100644 Binary files a/docs/IDE/Images/tB-Blue.png and b/docs/IDE/Images/tB-Blue.png differ diff --git a/docs/IDE/Images/tB-Green.png b/docs/IDE/Images/tB-Green.png index 63cb7c375..9ec317d5d 100644 Binary files a/docs/IDE/Images/tB-Green.png and b/docs/IDE/Images/tB-Green.png differ diff --git a/docs/IDE/Images/tB-Orange.png b/docs/IDE/Images/tB-Orange.png index 65b4d4d24..cae282013 100644 Binary files a/docs/IDE/Images/tB-Orange.png and b/docs/IDE/Images/tB-Orange.png differ diff --git a/docs/IDE/Images/tB-Red.png b/docs/IDE/Images/tB-Red.png index 60026d639..f264e3b7b 100644 Binary files a/docs/IDE/Images/tB-Red.png and b/docs/IDE/Images/tB-Red.png differ diff --git a/docs/IDE/Images/tbForm.png b/docs/IDE/Images/tbForm.png index e23759afa..1dfddae01 100644 Binary files a/docs/IDE/Images/tbForm.png and b/docs/IDE/Images/tbForm.png differ diff --git a/docs/IDE/Images/tbReport.png b/docs/IDE/Images/tbReport.png index 4bcb357d1..f7223dbcb 100644 Binary files a/docs/IDE/Images/tbReport.png and b/docs/IDE/Images/tbReport.png differ diff --git a/docs/IDE/Memory.md b/docs/IDE/Memory.md index e6fd87e1b..f9b899266 100644 --- a/docs/IDE/Memory.md +++ b/docs/IDE/Memory.md @@ -10,7 +10,7 @@ index: # Memory -![The Memory 1 pane showing only its toolbar row: a refresh button, an empty box prompting for an expression to watch, a display format dropdown set to Byte (Hex), and a column count dropdown set to Cols 16. No memory contents are listed.](Images/Memory.png) +![The Memory 1 pane showing only its toolbar row: a refresh button, an empty box prompting for an expression to watch, a display format dropdown set to Byte (Hex), and a column count dropdown set to Cols 16. No memory contents are listed.](Images/Memory.png){:width="696" height="82"} The Memory pane displays the raw contents of process memory during a paused debugging session, with addresses in the left column and byte values on the right. It is useful for inspecting data structures at the byte level. diff --git a/docs/IDE/Menu/Add-Ins.md b/docs/IDE/Menu/Add-Ins.md index 2a22c849f..0637c2a32 100644 --- a/docs/IDE/Menu/Add-Ins.md +++ b/docs/IDE/Menu/Add-Ins.md @@ -8,16 +8,16 @@ permalink: /tB/IDE/Project/Menu/AddIns # Add-Ins Menu -![The Add-Ins menu open on a greyed-out placeholder line reading no addins loaded, and nothing else.](Images/Menu_Add-Ins.png) +![The Add-Ins menu open on a greyed-out placeholder line reading no addins loaded, and nothing else.](Images/Menu_Add-Ins.png){:width="157" height="57"} {no addins loaded} Once you open a project: -![The Add-Ins menu open on a single available entry, GlobalSearchAddIn AddIn, with a tick beside it.](Images/Menu_Add-Ins_GlobalSearch.png) +![The Add-Ins menu open on a single available entry, GlobalSearchAddIn AddIn, with a tick beside it.](Images/Menu_Add-Ins_GlobalSearch.png){:width="219" height="57"} Clicking on this menu option shows > 🛈 Sorry, this menu option has not been implemented yet -![A notification strip with an information icon at the left and a close cross at the right, reading Sorry, this menu option has not been implemented yet.](Images/GlobalSearch-Popup.png) +![A notification strip with an information icon at the left and a close cross at the right, reading Sorry, this menu option has not been implemented yet.](Images/GlobalSearch-Popup.png){:width="502" height="52"} diff --git a/docs/IDE/Menu/Debug.md b/docs/IDE/Menu/Debug.md index 7c16c0928..a88334953 100644 --- a/docs/IDE/Menu/Debug.md +++ b/docs/IDE/Menu/Debug.md @@ -8,7 +8,7 @@ permalink: /tB/IDE/Project/Menu/Debug # Debug Menu -![The Debug menu open with every stepping, watch and breakpoint command greyed out --- Step Into, Step Over, Add Watch, Clear Watches, Toggle Breakpoint, Clear All Breakpoints and Set Next Statement --- leaving Debugger Options at the foot of the menu as the only available entry, marked with a submenu arrow.](Images/Menu_Debug.png) +![The Debug menu open with every stepping, watch and breakpoint command greyed out --- Step Into, Step Over, Add Watch, Clear Watches, Toggle Breakpoint, Clear All Breakpoints and Set Next Statement --- leaving Debugger Options at the foot of the menu as the only available entry, marked with a submenu arrow.](Images/Menu_Debug.png){:width="291" height="258"} - Step Into <kbd>F8</kbd> / <kbd>F11</kbd> - Step Over <kbd>SHIFT</kbd> + <kbd>F8</kbd> / <kbd>F10</kbd> @@ -102,11 +102,11 @@ The last two are defects, as of BETA 997. ## Debugger Options -![The same Debug menu with Debugger Options highlighted and its submenu open to the right, holding two entries: Break On All Errors, unticked, and Allow Breakpoints (Debuggable), ticked.](Images/Menu_Debug_DebuggerOptions.png) +![The same Debug menu with Debugger Options highlighted and its submenu open to the right, holding two entries: Break On All Errors, unticked, and Allow Breakpoints (Debuggable), ticked.](Images/Menu_Debug_DebuggerOptions.png){:width="534" height="285"} - Break On All Errors - ✓ Allow Breakpoints (Debuggable) -![A close-up of that submenu on its own: a tick against Allow Breakpoints (Debuggable) and none against Break On All Errors.](Images/Menu_Debug_DebuggerOptions_2.png) +![A close-up of that submenu on its own: a tick against Allow Breakpoints (Debuggable) and none against Break On All Errors.](Images/Menu_Debug_DebuggerOptions_2.png){:width="252" height="56"} **Break On All Errors** turns the project setting of the [same name](../Settings#break-on-all-errors) on and off. It is off by default, and then an error raised while `On Error Resume Next` or `On Error GoTo` is in effect goes to that handler, as usual. When it is on, the program stops at the failing line even there, with the same error panel as [an error that nothing handles](#when-a-run-time-error-stops-the-program). **Ignore (Resume Next)** then skips the line. Under `On Error Resume Next`, `Err.Number` still holds the error afterwards, so code that checks it sees the error. Under `On Error GoTo`, the handler never runs. diff --git a/docs/IDE/Menu/Edit.md b/docs/IDE/Menu/Edit.md index 97fac40fc..7758721d7 100644 --- a/docs/IDE/Menu/Edit.md +++ b/docs/IDE/Menu/Edit.md @@ -8,7 +8,7 @@ permalink: /tB/IDE/Project/Menu/Edit # Edit Menu -![The Edit menu open with every command greyed out. Its groups run from Undo and Redo through the clipboard commands, Find, Replace and Find In Project, the indent and format commands, the Quick Find group, the fold and unfold commands, and finally Go To Line/Column and four Transform To case commands. Most entries show a keyboard shortcut at the right.](Images/Menu_Edit.png) +![The Edit menu open with every command greyed out. Its groups run from Undo and Redo through the clipboard commands, Find, Replace and Find In Project, the indent and format commands, the Quick Find group, the fold and unfold commands, and finally Go To Line/Column and four Transform To case commands. Most entries show a keyboard shortcut at the right.](Images/Menu_Edit.png){:width="297" height="748"} - Undo <kbd>CTRL</kbd> + <kbd>Z</kbd> - Redo <kbd>CTRL</kbd> + <kbd>Y</kbd> diff --git a/docs/IDE/Menu/File.md b/docs/IDE/Menu/File.md index bed8b4c0f..9ac64c5c8 100644 --- a/docs/IDE/Menu/File.md +++ b/docs/IDE/Menu/File.md @@ -8,7 +8,7 @@ permalink: /tB/IDE/Project/Menu/File # File Menu -![The File menu open below the menu bar. New Project with CTRL+N, Open Project with CTRL+O, Open Recent, and Exit with ALT+F4 are available; Close Project, Save Project, Save Project As, Export Project with CTRL+E, Save Current Document, Build and Clean are all greyed out.](Images/Menu_File.png) +![The File menu open below the menu bar. New Project with CTRL+N, Open Project with CTRL+O, Open Recent, and Exit with ALT+F4 are available; Close Project, Save Project, Save Project As, Export Project with CTRL+E, Save Current Document, Build and Clean are all greyed out.](Images/Menu_File.png){:width="190" height="337"} - New Project... <kbd>CTRL</kbd> + <kbd>N</kbd> - Open Project... <kbd>CTRL</kbd> + <kbd>O</kbd> diff --git a/docs/IDE/Menu/Format.md b/docs/IDE/Menu/Format.md index 304d53039..57fff8fc5 100644 --- a/docs/IDE/Menu/Format.md +++ b/docs/IDE/Menu/Format.md @@ -8,11 +8,11 @@ permalink: /tB/IDE/Project/Menu/Format # Format Menu -![The Format menu open with every one of its commands greyed out: Align and Make Same Size, Horizontal Spacing and Vertical Spacing, the two Center In Container commands, Bring To Front and Send To Back, and Lock Controls. The first four carry submenu arrows.](Images/Menu_Format.png) +![The Format menu open with every one of its commands greyed out: Align and Make Same Size, Horizontal Spacing and Vertical Spacing, the two Center In Container commands, Bring To Front and Send To Back, and Lock Controls. The first four carry submenu arrows.](Images/Menu_Format.png){:width="232" height="281"} Every command on this menu acts on the controls selected in the form designer, so the whole menu is unavailable until a designer is open with a selection. The screenshot above shows that state; the one below shows the same menu with the commands available. -![The same Format menu with all of its commands available rather than greyed out.](Images/Menu_Format_1.png) +![The same Format menu with two controls selected in the form designer: all of its commands are available rather than greyed out.](Images/Menu_Format_1.png){:width="232" height="281"} - Align - Make Same Size @@ -32,7 +32,7 @@ Every command on this menu acts on the controls selected in the form designer, s --- - Lock Controls -![The Format menu with Align highlighted and its submenu open to the right. Left, Center (Horizontal) and Right sit above Top, Center (Vertical) and Bottom, with To Grid on its own below; the four edge commands carry ALT plus arrow-key shortcuts. All are available.](Images/Menu_Format_Align.png) +![The Format menu with Align highlighted and its submenu open to the right. Left, Center (Horizontal) and Right sit above Top, Center (Vertical) and Bottom, with To Grid on its own below; the four edge commands carry ALT plus arrow-key shortcuts. All are available.](Images/Menu_Format_Align.png){:width="436" height="281"} - Left <kbd>ALT</kbd> + <kbd>ARROWLEFT</kbd> - Center (Horizontal) @@ -46,21 +46,21 @@ Every command on this menu acts on the controls selected in the form designer, s --- - To Grid -![The Format menu with Make Same Size highlighted and its submenu open to the right, offering Width (Widest), Width (Narrowest), Height (Tallest) and Height (Shortest), each with a CTRL plus SHIFT plus arrow-key shortcut.](Images/Menu_Format_MakeSameSize.png) +![The Format menu with Make Same Size highlighted and its submenu open to the right, offering Width (Widest), Width (Narrowest), Height (Tallest) and Height (Shortest), each with a CTRL plus SHIFT plus arrow-key shortcut.](Images/Menu_Format_MakeSameSize.png){:width="523" height="281"} - Width (Widest) <kbd>CTRL</kbd> + <kbd>SHIFT</kbd> + <kbd>ARROWRIGHT</kbd> - Width (Narrowest) <kbd>CTRL</kbd> + <kbd>SHIFT</kbd> + <kbd>ARROWLEFT</kbd> - Height (Tallest) <kbd>CTRL</kbd> + <kbd>SHIFT</kbd> + <kbd>ARROWDOWN</kbd> - Height (Shortest) <kbd>CTRL</kbd> + <kbd>SHIFT</kbd> + <kbd>ARROWUP</kbd> -![The Format menu with Horizontal Spacing highlighted and its submenu open to the right. Make Equal is greyed out; Increase, Decrease and Remove are available.](Images/Menu_Format_HorizontalSpacing.png) +![The Format menu with Horizontal Spacing highlighted and its submenu open to the right. Make Equal is greyed out; Increase, Decrease and Remove are available.](Images/Menu_Format_HorizontalSpacing.png){:width="345" height="281"} - Make Equal - Increase - Decrease - Remove -![The Format menu with Vertical Spacing highlighted and its submenu open to the right. Make Equal is greyed out; Increase, Decrease and Remove are available.](Images/Menu_Format_VerticalSpacing.png) +![The Format menu with Vertical Spacing highlighted and its submenu open to the right. Make Equal is greyed out; Increase, Decrease and Remove are available.](Images/Menu_Format_VerticalSpacing.png){:width="345" height="281"} - Make Equal - Increase diff --git a/docs/IDE/Menu/Help.md b/docs/IDE/Menu/Help.md index e96f8ec1a..d2e656edb 100644 --- a/docs/IDE/Menu/Help.md +++ b/docs/IDE/Menu/Help.md @@ -8,7 +8,7 @@ permalink: /tB/IDE/Project/Menu/Help # Help Menu -![The Help menu open with every entry available, each beside its own icon: About twinBASIC, Licence Agreement and Automatic IDE Error Reporting; the Discord, GitHub and Twitter support links; Purchase A Licence, Enter Licence Key and Buy us a Coffee; and a closing status line reading Compiler services TRACE mode: Disabled.](Images/Menu_Help.png) +![The Help menu open with every entry available, each beside its own icon: About twinBASIC, Licence Agreement and Automatic IDE Error Reporting; the Discord, GitHub and Twitter support links; Purchase A Licence, Enter Licence Key and Buy us a Coffee; and a closing status line reading Compiler services TRACE mode: Disabled.](Images/Menu_Help.png){:width="268" height="294"} - About twinBASIC... - Licence Agreement... @@ -29,7 +29,7 @@ permalink: /tB/IDE/Project/Menu/Help ## About twinBASIC... -![The About twinBASIC dialog. Beneath the twinBASIC logo it gives the version as twinBASIC IDE BETA 953 and the copyright as 2026 twinBASIC Ltd, followed by an italic paragraph thanking the community and a SPECIAL THANKS line marked coming soon. A VIP Gold Supporters band heads a grid of four sponsor logos and two empty See your logo here placeholders. Below that, nine third-party open-source libraries are listed against the licence file each one ships --- Monaco, Codicon, LLVM, LodePNG, Spectrum, Grapick, ChartJS, jQuery and VbQRCodegen --- above a green band reading LICENCE: tB Licence: NOT READY and a Close button.](Images/Menu_Help_About.png) +![The About twinBASIC dialog. Beneath the twinBASIC logo it gives the version of the IDE and the copyright as 2026 twinBASIC Ltd, followed by an italic paragraph thanking the community and a SPECIAL THANKS line marked coming soon. A VIP Gold Supporters band heads a grid of four sponsor logos and two empty See your logo here placeholders. Below that, ten third-party open-source libraries are listed against the licence file each one ships --- Monaco, Codicon, LLVM, LodePNG, Spectrum, Grapick, ChartJS, jQuery, VbQRCodegen and LZ4 --- above a green band naming the licence edition, and a Close button.](Images/Menu_Help_About.png){:width="506" height="880"} ## Licence Agreement... diff --git a/docs/IDE/Menu/Images/GlobalSearch-Popup.png b/docs/IDE/Menu/Images/GlobalSearch-Popup.png index 6325ab91a..a6d4be732 100644 Binary files a/docs/IDE/Menu/Images/GlobalSearch-Popup.png and b/docs/IDE/Menu/Images/GlobalSearch-Popup.png differ diff --git a/docs/IDE/Menu/Images/Menu.png b/docs/IDE/Menu/Images/Menu.png index 8e43d66b9..bda7645a2 100644 Binary files a/docs/IDE/Menu/Images/Menu.png and b/docs/IDE/Menu/Images/Menu.png differ diff --git a/docs/IDE/Menu/Images/Menu_Add-Ins.png b/docs/IDE/Menu/Images/Menu_Add-Ins.png index db56366fd..a3bc8104c 100644 Binary files a/docs/IDE/Menu/Images/Menu_Add-Ins.png and b/docs/IDE/Menu/Images/Menu_Add-Ins.png differ diff --git a/docs/IDE/Menu/Images/Menu_Add-Ins_GlobalSearch.png b/docs/IDE/Menu/Images/Menu_Add-Ins_GlobalSearch.png index 42aad172c..bc2da2b29 100644 Binary files a/docs/IDE/Menu/Images/Menu_Add-Ins_GlobalSearch.png and b/docs/IDE/Menu/Images/Menu_Add-Ins_GlobalSearch.png differ diff --git a/docs/IDE/Menu/Images/Menu_Debug.png b/docs/IDE/Menu/Images/Menu_Debug.png index ab8f301da..b2844a2d6 100644 Binary files a/docs/IDE/Menu/Images/Menu_Debug.png and b/docs/IDE/Menu/Images/Menu_Debug.png differ diff --git a/docs/IDE/Menu/Images/Menu_Debug_DebuggerOptions.png b/docs/IDE/Menu/Images/Menu_Debug_DebuggerOptions.png index 783a14650..5bc2c230b 100644 Binary files a/docs/IDE/Menu/Images/Menu_Debug_DebuggerOptions.png and b/docs/IDE/Menu/Images/Menu_Debug_DebuggerOptions.png differ diff --git a/docs/IDE/Menu/Images/Menu_Debug_DebuggerOptions_2.png b/docs/IDE/Menu/Images/Menu_Debug_DebuggerOptions_2.png index fd9f9f853..af7d418d1 100644 Binary files a/docs/IDE/Menu/Images/Menu_Debug_DebuggerOptions_2.png and b/docs/IDE/Menu/Images/Menu_Debug_DebuggerOptions_2.png differ diff --git a/docs/IDE/Menu/Images/Menu_Edit.png b/docs/IDE/Menu/Images/Menu_Edit.png index c64d495d9..38b63d623 100644 Binary files a/docs/IDE/Menu/Images/Menu_Edit.png and b/docs/IDE/Menu/Images/Menu_Edit.png differ diff --git a/docs/IDE/Menu/Images/Menu_File.png b/docs/IDE/Menu/Images/Menu_File.png index ab8e170d7..4e00e668a 100644 Binary files a/docs/IDE/Menu/Images/Menu_File.png and b/docs/IDE/Menu/Images/Menu_File.png differ diff --git a/docs/IDE/Menu/Images/Menu_Format.png b/docs/IDE/Menu/Images/Menu_Format.png index 93852bc9d..b1f747cc6 100644 Binary files a/docs/IDE/Menu/Images/Menu_Format.png and b/docs/IDE/Menu/Images/Menu_Format.png differ diff --git a/docs/IDE/Menu/Images/Menu_Format_1.png b/docs/IDE/Menu/Images/Menu_Format_1.png index 6b70168b1..b87dea25f 100644 Binary files a/docs/IDE/Menu/Images/Menu_Format_1.png and b/docs/IDE/Menu/Images/Menu_Format_1.png differ diff --git a/docs/IDE/Menu/Images/Menu_Format_Align.png b/docs/IDE/Menu/Images/Menu_Format_Align.png index 4bf5855a8..0cb691816 100644 Binary files a/docs/IDE/Menu/Images/Menu_Format_Align.png and b/docs/IDE/Menu/Images/Menu_Format_Align.png differ diff --git a/docs/IDE/Menu/Images/Menu_Format_HorizontalSpacing.png b/docs/IDE/Menu/Images/Menu_Format_HorizontalSpacing.png index cd93d2d61..00a3d3aeb 100644 Binary files a/docs/IDE/Menu/Images/Menu_Format_HorizontalSpacing.png and b/docs/IDE/Menu/Images/Menu_Format_HorizontalSpacing.png differ diff --git a/docs/IDE/Menu/Images/Menu_Format_MakeSameSize.png b/docs/IDE/Menu/Images/Menu_Format_MakeSameSize.png index f56ede156..c9d11a442 100644 Binary files a/docs/IDE/Menu/Images/Menu_Format_MakeSameSize.png and b/docs/IDE/Menu/Images/Menu_Format_MakeSameSize.png differ diff --git a/docs/IDE/Menu/Images/Menu_Format_VerticalSpacing.png b/docs/IDE/Menu/Images/Menu_Format_VerticalSpacing.png index d00309165..bea317e44 100644 Binary files a/docs/IDE/Menu/Images/Menu_Format_VerticalSpacing.png and b/docs/IDE/Menu/Images/Menu_Format_VerticalSpacing.png differ diff --git a/docs/IDE/Menu/Images/Menu_Help.png b/docs/IDE/Menu/Images/Menu_Help.png index 9f74fa99c..4901a038d 100644 Binary files a/docs/IDE/Menu/Images/Menu_Help.png and b/docs/IDE/Menu/Images/Menu_Help.png differ diff --git a/docs/IDE/Menu/Images/Menu_Help_About.png b/docs/IDE/Menu/Images/Menu_Help_About.png index 9f00ce230..c52d197c7 100644 Binary files a/docs/IDE/Menu/Images/Menu_Help_About.png and b/docs/IDE/Menu/Images/Menu_Help_About.png differ diff --git a/docs/IDE/Menu/Images/Menu_Project.png b/docs/IDE/Menu/Images/Menu_Project.png index 07efeece4..539cb6183 100644 Binary files a/docs/IDE/Menu/Images/Menu_Project.png and b/docs/IDE/Menu/Images/Menu_Project.png differ diff --git a/docs/IDE/Menu/Images/Menu_Run.png b/docs/IDE/Menu/Images/Menu_Run.png index 3fa7a6a70..f77d4b866 100644 Binary files a/docs/IDE/Menu/Images/Menu_Run.png and b/docs/IDE/Menu/Images/Menu_Run.png differ diff --git a/docs/IDE/Menu/Images/Menu_Tools.png b/docs/IDE/Menu/Images/Menu_Tools.png index c3413dc7c..65e552b66 100644 Binary files a/docs/IDE/Menu/Images/Menu_Tools.png and b/docs/IDE/Menu/Images/Menu_Tools.png differ diff --git a/docs/IDE/Menu/Images/Menu_Tools_IDEOptions.png b/docs/IDE/Menu/Images/Menu_Tools_IDEOptions.png index e045df9fc..61f0ef333 100644 Binary files a/docs/IDE/Menu/Images/Menu_Tools_IDEOptions.png and b/docs/IDE/Menu/Images/Menu_Tools_IDEOptions.png differ diff --git a/docs/IDE/Menu/Images/Menu_View.png b/docs/IDE/Menu/Images/Menu_View.png index a749fa401..329bd1376 100644 Binary files a/docs/IDE/Menu/Images/Menu_View.png and b/docs/IDE/Menu/Images/Menu_View.png differ diff --git a/docs/IDE/Menu/Images/Menu_Window.png b/docs/IDE/Menu/Images/Menu_Window.png index e57b20fd9..a5dcffcaf 100644 Binary files a/docs/IDE/Menu/Images/Menu_Window.png and b/docs/IDE/Menu/Images/Menu_Window.png differ diff --git a/docs/IDE/Menu/Images/Menu_Window_KeyboardShortcuts.png b/docs/IDE/Menu/Images/Menu_Window_KeyboardShortcuts.png index cf9ebf966..f8fa4df82 100644 Binary files a/docs/IDE/Menu/Images/Menu_Window_KeyboardShortcuts.png and b/docs/IDE/Menu/Images/Menu_Window_KeyboardShortcuts.png differ diff --git a/docs/IDE/Menu/Images/Menu_Window_KeyboardShortcuts_ManageKeyboardShortcuts.png b/docs/IDE/Menu/Images/Menu_Window_KeyboardShortcuts_ManageKeyboardShortcuts.png index 81e0840a1..de18b4095 100644 Binary files a/docs/IDE/Menu/Images/Menu_Window_KeyboardShortcuts_ManageKeyboardShortcuts.png and b/docs/IDE/Menu/Images/Menu_Window_KeyboardShortcuts_ManageKeyboardShortcuts.png differ diff --git a/docs/IDE/Menu/Images/Menu_Window_KeyboardShortcuts_ManageKeyboardShortcuts_1.png b/docs/IDE/Menu/Images/Menu_Window_KeyboardShortcuts_ManageKeyboardShortcuts_1.png index 900a36450..5789dd82a 100644 Binary files a/docs/IDE/Menu/Images/Menu_Window_KeyboardShortcuts_ManageKeyboardShortcuts_1.png and b/docs/IDE/Menu/Images/Menu_Window_KeyboardShortcuts_ManageKeyboardShortcuts_1.png differ diff --git a/docs/IDE/Menu/Images/Menu_Window_Language.png b/docs/IDE/Menu/Images/Menu_Window_Language.png index 088e707ee..2e3a91eb4 100644 Binary files a/docs/IDE/Menu/Images/Menu_Window_Language.png and b/docs/IDE/Menu/Images/Menu_Window_Language.png differ diff --git a/docs/IDE/Menu/Images/Menu_Window_PanelFeatures.png b/docs/IDE/Menu/Images/Menu_Window_PanelFeatures.png index c32a93f29..2fba6b74f 100644 Binary files a/docs/IDE/Menu/Images/Menu_Window_PanelFeatures.png and b/docs/IDE/Menu/Images/Menu_Window_PanelFeatures.png differ diff --git a/docs/IDE/Menu/Images/Menu_Window_PanelLayouts.png b/docs/IDE/Menu/Images/Menu_Window_PanelLayouts.png index 95db45311..597feea0c 100644 Binary files a/docs/IDE/Menu/Images/Menu_Window_PanelLayouts.png and b/docs/IDE/Menu/Images/Menu_Window_PanelLayouts.png differ diff --git a/docs/IDE/Menu/Images/Menu_Window_PanelLayouts_ManagePanelLayouts_Default.png b/docs/IDE/Menu/Images/Menu_Window_PanelLayouts_ManagePanelLayouts_Default.png index 077238d86..e1e456379 100644 Binary files a/docs/IDE/Menu/Images/Menu_Window_PanelLayouts_ManagePanelLayouts_Default.png and b/docs/IDE/Menu/Images/Menu_Window_PanelLayouts_ManagePanelLayouts_Default.png differ diff --git a/docs/IDE/Menu/Images/Menu_Window_PanelLayouts_ManagePanelLayouts_Fullscreen.png b/docs/IDE/Menu/Images/Menu_Window_PanelLayouts_ManagePanelLayouts_Fullscreen.png index 37f0aca72..3bbd6804e 100644 Binary files a/docs/IDE/Menu/Images/Menu_Window_PanelLayouts_ManagePanelLayouts_Fullscreen.png and b/docs/IDE/Menu/Images/Menu_Window_PanelLayouts_ManagePanelLayouts_Fullscreen.png differ diff --git a/docs/IDE/Menu/Images/Menu_Window_Theme.png b/docs/IDE/Menu/Images/Menu_Window_Theme.png index 3feff6ecf..427b84c2d 100644 Binary files a/docs/IDE/Menu/Images/Menu_Window_Theme.png and b/docs/IDE/Menu/Images/Menu_Window_Theme.png differ diff --git a/docs/IDE/Menu/Project.md b/docs/IDE/Menu/Project.md index a9dae552b..c3b38dc25 100644 --- a/docs/IDE/Menu/Project.md +++ b/docs/IDE/Menu/Project.md @@ -8,7 +8,7 @@ permalink: /tB/IDE/Project/Menu/Project # Project Menu -![The Project menu open with every command greyed out: Add, which carries a submenu arrow, References with CTRL+T, Project Settings, Open Project Folder and Open Build Output Folder.](Images/Menu_Project.png) +![The Project menu open with every command greyed out: Add, which carries a submenu arrow, References with CTRL+T, Project Settings, Open Project Folder and Open Build Output Folder.](Images/Menu_Project.png){:width="200" height="169"} - Add @@ -24,14 +24,14 @@ permalink: /tB/IDE/Project/Menu/Project **Add** is the same as Right Click Add on the [Project Explorer](../Explorer) -![The Project Explorer context menu with Add highlighted and its submenu opened out to the left. The submenu lists Add Folder; five Windows item types from Form through MDI Form, UserControl and PropertyPage to Report; Add CustomControls Form; the Unicode-capable .TWIN module and class; the .BAS module and .CLS class; Add Other File; Import; and three Add Resource entries for a Visual Styles Manifest, a String Table and a MESSAGETABLE. On the parent menu, Cut, Copy, Paste, View As JSON and View As Markdown Preview are greyed out.](../Images/RightClick-Add.png) +![The Project Explorer context menu with Add highlighted and its submenu opened out to the left. The submenu lists Add Folder; five Windows item types from Form through MDI Form, UserControl and PropertyPage to Report; Add CustomControls Form; the Unicode-capable .TWIN module and class; the .BAS module and .CLS class; Add Other File; Import; and three Add Resource entries for a Visual Styles Manifest, a String Table and a MESSAGETABLE. On the parent menu, Cut, Copy, Paste, View As JSON and View As Markdown Preview are greyed out.](../Images/RightClick-Add.png){:width="510" height="492"} ## References {: index_also="add reference" } See [Project Settings](../Settings) filtered by "project.references". -![The Project Settings dialog with project.references in its filter box, showing the Library References section on its Enabled Libraries tab. Four ticked rows are listed in priority order against Library Symbol and Version columns: the twinBASIC VBA and VBRUN compatibility packages, OLE Automation as stdole, and the IDE Extensibility package as tbIDE.](../Images/ProjectSettings_LibraryReferences.png) +![The Project Settings dialog with project.references in its filter box, showing the Library References section on its Enabled Libraries tab. Four ticked rows are listed in priority order against Library Symbol and Version columns: the twinBASIC VBA and VBRUN compatibility packages, OLE Automation as stdole, and the IDE Extensibility package as tbIDE.](../Images/ProjectSettings_LibraryReferences.png){:width="1102" height="774"} ## Project Settings diff --git a/docs/IDE/Menu/Run.md b/docs/IDE/Menu/Run.md index a2f9e4112..e3a6d0844 100644 --- a/docs/IDE/Menu/Run.md +++ b/docs/IDE/Menu/Run.md @@ -8,7 +8,7 @@ permalink: /tB/IDE/Project/Menu/Run # Run Menu -![The Run menu open with all three commands greyed out: Start with F5 beside a play triangle, Break with CTRL+BREAK beside a pause icon, and End beside a stop icon.](Images/Menu_Run.png) +![The Run menu open with all three commands greyed out: Start with F5 beside a play triangle, Break with CTRL+BREAK beside a pause icon, and End beside a stop icon.](Images/Menu_Run.png){:width="168" height="103"} - Start <kbd>F5</kbd> - Break <kbd>CTRL</kbd> + <kbd>BREAK</kbd> diff --git a/docs/IDE/Menu/Tools.md b/docs/IDE/Menu/Tools.md index 5a86bfe1d..fd5e58108 100644 --- a/docs/IDE/Menu/Tools.md +++ b/docs/IDE/Menu/Tools.md @@ -8,12 +8,13 @@ permalink: /tB/IDE/Project/Menu/Tools # Tools Menu -![The Tools menu open, holding a single available entry, IDE Options, beside a sliders icon.](Images/Menu_Tools.png) +![The Tools menu open with no project loaded: IDE Options beside a sliders icon, then, below a separator, the greyed-out Restart the compiler and Flush the LLVM compiler cache.](Images/Menu_Tools.png){:width="217" height="113"} - IDE Options... +- Restart the compiler --- ends the compiler and starts a new one, which compiles the project again and loads the add-ins afresh. - Flush the LLVM compiler cache --- clears the cache of code compiled with [LLVM](../../../../LLVM/Getting-Started), so that the next build compiles everything. -![The twinBASIC IDE Options dialog: a scrolling list of right-aligned dark red labels against their controls. Tab Size reads 4, IDE Font Size and Code Editor Font Size both read 13px, IDE Font reads Segoe UI and Code Editor Font is empty, and the two drop-downs below them read above and none. A run of tick boxes follows, of which only Restore Editors State When Opening Project and File Explorer: Ask Before Delete Files are ticked. Change Licence sits at the bottom left, with a greyed-out Save Changes and a Close button at the right.](Images/Menu_Tools_IDEOptions.png) +![The twinBASIC IDE Options dialog: a scrolling list of right-aligned dark red labels against their controls. Tab Size reads 4, IDE Font Size and Code Editor Font Size both read 13px, IDE Font reads Segoe UI and Code Editor Font is empty, and the two drop-downs below them read above and none. Three LLVM Compiler settings follow: a maximum of 1 thread, a complex procedure reporting threshold of 10000 milliseconds, and a ticked box to keep the cache process alive after exiting the IDE. Then comes a run of tick boxes, of which only Restore Editors State When Opening Project and File Explorer: Ask Before Delete Files are ticked. Change Licence sits at the bottom left, with a greyed-out Save Changes and a Close button at the right.](Images/Menu_Tools_IDEOptions.png){:width="628" height="594"} > [!NOTE] > diff --git a/docs/IDE/Menu/View.md b/docs/IDE/Menu/View.md index ecf76827c..f88e61e13 100644 --- a/docs/IDE/Menu/View.md +++ b/docs/IDE/Menu/View.md @@ -8,7 +8,7 @@ permalink: /tB/IDE/Project/Menu/View # View Menu -![The View menu open. The upper commands --- Code Editor, Object Designer, Definition, Last Position, Object Browser, Zoom In and Zoom Out --- are greyed out. Below them the seventeen panel names from EDITOR down to MEMORY 1 are all available, each beside its own icon, with CTRL+L against CALL STACK and CTRL+G against DEBUG CONSOLE.](Images/Menu_View.png) +![The View menu open. The upper commands --- Code Editor, Object Designer, Definition, Last Position, Object Browser, Zoom In and Zoom Out --- are greyed out. Below them the seventeen panel names from EDITOR down to MEMORY 1 are all available, each beside its own icon, with CTRL+L against CALL STACK and CTRL+G against DEBUG CONSOLE.](Images/Menu_View.png){:width="218" height="626"} - Code Editor - Object Designer <kbd>SHIFT</kbd> + <kbd>F7</kbd> diff --git a/docs/IDE/Menu/Window.md b/docs/IDE/Menu/Window.md index 75cae8e1e..8f18af149 100644 --- a/docs/IDE/Menu/Window.md +++ b/docs/IDE/Menu/Window.md @@ -8,7 +8,7 @@ permalink: /tB/IDE/Project/Menu/Window # Window Menu -![The Window menu open with five available entries, each carrying a submenu arrow: Panel Layouts, Panel Features and Keyboard Shortcuts, then Theme beside a palette icon and Language beside a globe icon.](Images/Menu_Window.png) +![The Window menu open with five available entries, each carrying a submenu arrow: Panel Layouts, Panel Features and Keyboard Shortcuts, then Theme beside a palette icon and Language beside a globe icon.](Images/Menu_Window.png){:width="172" height="159"} - Panel Layouts - Panel Features @@ -20,13 +20,14 @@ permalink: /tB/IDE/Project/Menu/Window ## Panel Layouts -![The Window menu with Panel Layouts highlighted and its submenu open to the right: Default Built-in Layout with CTRL+#, Full Screen Editor Layout, a ticked Custom Layout (Unsaved), then Save Current Panel Layout As and Manage Panel Layouts.](Images/Menu_Window_PanelLayouts.png) +![The Window menu with Panel Layouts highlighted and its submenu open to the right: a ticked Default Built-in Layout with CTRL+#, Full Screen Editor Layout, then Save Current Panel Layout As and Manage Panel Layouts.](Images/Menu_Window_PanelLayouts.png){:width="389" height="159"} - Default Built-in Layout <kbd>CTRL</kbd> + <kbd>#</kbd> - Full Screen Editor Layout --- -- ✓ Custom Layout (Unsaved) +- ✓ Custom Layout (Unsaved) --- shown, and ticked, while none of the named layouts is the active one +- Custom Layout *n*. *name* --- one for each saved layout --- - Save Current Panel Layout As... @@ -35,7 +36,7 @@ permalink: /tB/IDE/Project/Menu/Window ### Manage Panel Layouts... -![The Manage Panel Layouts dialog. Its left pane lists two built-in layouts, DEFAULT and FULLSCREEN, with DEFAULT selected; the right pane shows that layout JSON --- a docked tree of TOOLBAR, TOOLBOX and EDITOR entries with percentage sizes. Add New sits at the bottom left, with a greyed-out Save Changes and a Cancel button at the right.](Images/Menu_Window_PanelLayouts_ManagePanelLayouts_Default.png) +![The Manage Panel Layouts dialog. Its left pane lists two built-in layouts, DEFAULT and FULLSCREEN, with DEFAULT selected; the right pane shows that layout JSON --- a docked tree of TOOLBAR, TOOLBOX and EDITOR entries with percentage sizes. Add New sits at the bottom left, with a greyed-out Save Changes and a Cancel button at the right.](Images/Menu_Window_PanelLayouts_ManagePanelLayouts_Default.png){:width="1016" height="638"} <details> <summary markdown=span><DEFAULT> (built-in)</summary> @@ -149,7 +150,7 @@ permalink: /tB/IDE/Project/Menu/Window </details> -![The same dialog with FULLSCREEN selected instead, its JSON showing a TOOLBAR sized to fit its content, a TOOLBOX at about 8.5 per cent, and an EDITOR and PROPERTIES pair filling the rest.](Images/Menu_Window_PanelLayouts_ManagePanelLayouts_Fullscreen.png) +![The same dialog with FULLSCREEN selected instead, its JSON showing a TOOLBAR sized to fit its content, a TOOLBOX at about 8.5 per cent, and an EDITOR and PROPERTIES pair filling the rest.](Images/Menu_Window_PanelLayouts_ManagePanelLayouts_Fullscreen.png){:width="1016" height="638"} <details> <summary markdown=span><FULLSCREEN> (built-in)</summary> @@ -205,7 +206,7 @@ permalink: /tB/IDE/Project/Menu/Window ## Panel Features -![The Window menu with Panel Features highlighted and its submenu open to the right, holding five options that are all ticked: resizing, rearrangement and tear-out of docked panels, then resizing and movement of floating panels.](Images/Menu_Window_PanelFeatures.png) +![The Window menu with Panel Features highlighted and its submenu open to the right, holding five options that are all ticked: resizing, rearrangement and tear-out of docked panels, then resizing and movement of floating panels.](Images/Menu_Window_PanelFeatures.png){:width="421" height="186"} - ✓ Allow resizing of docked panels - ✓ Allow rearrangement of docked panels @@ -217,7 +218,7 @@ permalink: /tB/IDE/Project/Menu/Window ## Keyboard Shortcuts -![The Window menu with Keyboard Shortcuts highlighted and its submenu open to the right: a ticked Default Built-in Keyboard Shortcuts above Manage Keyboard Shortcuts.](Images/Menu_Window_KeyboardShortcuts.png) +![The Window menu with Keyboard Shortcuts highlighted and its submenu open to the right: a ticked Default Built-in Keyboard Shortcuts above Manage Keyboard Shortcuts.](Images/Menu_Window_KeyboardShortcuts.png){:width="404" height="159"} - ✓ Default Built-in Keyboard Shortcuts @@ -226,9 +227,9 @@ permalink: /tB/IDE/Project/Menu/Window ### Manage Keyboard Shortcuts -![The Manage Keyboard Shortcuts dialog. A paragraph across the top explains that groups are merged in the order listed, and how to override an existing shortcut or add one alongside it. The left pane holds the single built-in group, DEFAULT; the right pane shows its JSON with the Edit as JSON box ticked, mapping names such as tbEditor_SelectAll and tbEditor_ClipboardCopy to KEYDOWN sequences. Save Changes is greyed out.](Images/Menu_Window_KeyboardShortcuts_ManageKeyboardShortcuts.png) +![The Manage Keyboard Shortcuts dialog. A paragraph across the top explains that groups are merged in the order listed, and how to override an existing shortcut or add one alongside it. The left pane holds the single built-in group, DEFAULT; the right pane shows its JSON with the Edit as JSON box ticked, mapping names such as tbEditor_SelectAll and tbEditor_ClipboardCopy to KEYDOWN sequences. Save Changes is greyed out.](Images/Menu_Window_KeyboardShortcuts_ManageKeyboardShortcuts.png){:width="1016" height="736"} -![The same dialog with the Edit as JSON box cleared: the editing pane is empty apart from the line Keyboard editor coming soon, and the Add New button carries the focus outline.](Images/Menu_Window_KeyboardShortcuts_ManageKeyboardShortcuts_1.png) +![The same dialog with the Edit as JSON box cleared: the editing pane is empty apart from the line Keyboard editor coming soon.](Images/Menu_Window_KeyboardShortcuts_ManageKeyboardShortcuts_1.png){:width="1016" height="736"} <details> <summary markdown=span>Options</summary> @@ -960,7 +961,7 @@ permalink: /tB/IDE/Project/Menu/Window ## Theme {: index_also="dark mode" } -![The Window menu with Theme highlighted and its submenu open to the right, listing Classic (Light), a ticked Dark, and Light, with Reload from disk below a separator.](Images/Menu_Window_Theme.png) +![The Window menu with Theme highlighted and its submenu open to the right, listing Classic (Light), a ticked Dark, and Light, with Reload from disk below a separator.](Images/Menu_Window_Theme.png){:width="312" height="219"} - Classic (Light) - ✓ Dark @@ -971,7 +972,7 @@ permalink: /tB/IDE/Project/Menu/Window ## Language -![The Window menu with Language highlighted and its submenu open to the right, listing nineteen languages in one tall column from Catalan and German through to Chinese Simplified and Chinese Traditional. Each is named in English with its own native spelling beside it, and English, British English, carries the tick.](Images/Menu_Window_Language.png) +![The Window menu with Language highlighted and its submenu open to the right, listing nineteen languages in one tall column from Catalan and German through to Chinese Simplified and Chinese Traditional. Each is named in English with its own native spelling beside it, and English, British English, carries the tick.](Images/Menu_Window_Language.png){:width="390" height="577"} Each language is listed by its English name, followed by its native name. British English is the base language; a string that another language pack does not translate is shown in British English. diff --git a/docs/IDE/Menu/index.md b/docs/IDE/Menu/index.md index db258f902..133b1c0bf 100644 --- a/docs/IDE/Menu/index.md +++ b/docs/IDE/Menu/index.md @@ -7,4 +7,4 @@ permalink: /tB/IDE/Project/Menu # Menu -![The twinBASIC menu bar along the top of the IDE window, in white on a dark title bar: File, Edit, View, Project, Format, Debug, Run, Tools, Add-Ins, Window and Help, with the minimise, restore and close buttons at the right-hand end.](Images/Menu.png) +![The twinBASIC menu bar along the top of the IDE window, in white on a dark title bar: File, Edit, View, Project, Format, Debug, Run, Tools, Add-Ins, Window and Help, with the minimise, maximise and close buttons at the right-hand end.](Images/Menu.png){:width="611" height="25"} diff --git a/docs/IDE/New Project.md b/docs/IDE/New Project.md index d339ee148..0f3ed453f 100644 --- a/docs/IDE/New Project.md +++ b/docs/IDE/New Project.md @@ -8,9 +8,9 @@ index: new project dialog # New Project -![The New / Open Project dialog on its New tab, headed twinBASIC IDE BETA 950. Eight project templates sit in two rows: Standard EXE, which is selected, ActiveX Control, ActiveX DLL, Standard DLL marked non-ActiveX, Standard EXE (Console App), Standard EXE plus VBCCR v1.8 marked experimental, Import from VBP and Import from folder. Browse, Open and Cancel run along the bottom.](Images/New_Project.png) +![The New / Open Project dialog on its New tab, headed with the version of the IDE. Eight project templates sit in two rows: Standard EXE, which is selected, ActiveX Control, ActiveX DLL, Standard DLL marked non-ActiveX, Standard EXE (Console App), Standard EXE plus VBCCR v1.8 marked experimental, Import from VBP and Import from folder. Browse, Open and Cancel run along the bottom.](Images/New_Project.png){:width="522" height="405"} -![The IDE File menu open with no project loaded. New Project, Open Project, Open Recent and Exit are available; Close Project, Save Project, Save Project As, Export Project, Save Current Document, Build and Clean are all greyed out.](Menu/Images/Menu_File.png) +![The IDE File menu open with no project loaded. New Project, Open Project, Open Recent and Exit are available; Close Project, Save Project, Save Project As, Export Project, Save Current Document, Build and Clean are all greyed out.](Menu/Images/Menu_File.png){:width="190" height="337"} Shortcut: <kbd>CTRL</kbd> + <kbd>N</kbd> @@ -38,13 +38,14 @@ If the folder holds the compiler packages under `Packages`, as an export by the ## Samples -![The same dialog on its Samples tab, a scrolling column of sample projects headed by Sample 0. Reports (Experimental), which is selected, then Sample 1. HelloWorld, Sample 1a. WebView2 Examples, Sample 2. GetIPAddresses and Sample 3. MyCodeLibrary, with the list running on past the bottom of the panel.](Images/New_Project_Samples.png) +![The same dialog on its Samples tab, a scrolling column of sample projects headed by Sample 0. Reports (Experimental), which is selected, then Sample 1. HelloWorld, Sample 1a. WebView2 Examples, Sample 1b. Chromium Embedded Framework Examples and Sample 2. GetIPAddresses, with the list running on past the bottom of the panel.](Images/New_Project_Samples.png){:width="522" height="405"} -Each row is labelled **Sample** followed by its number. The numbering is the dialog's own: **1a** sits between **1** and **2**. +Each row is labelled **Sample** followed by its number. The numbering is the dialog's own: **1a** and **1b** sit between **1** and **2**. - **0.** Reports (Experimental) - **1.** HelloWorld - **1a.** WebView2 Examples +- **1b.** Chromium Embedded Framework Examples - **2.** GetIPAddresses - **3.** MyCodeLibrary - **4.** MyVBEAddin (NEW with ToolWindow) @@ -72,11 +73,11 @@ Each row is labelled **Sample** followed by its number. The numbering is the dia If you haven't opened any projects, or removed all then this tab will be blank. -![The same dialog on its Recent tab with nothing listed, an empty grey panel between the tab strip and the Browse, Open and Cancel buttons.](Images/New_Project_Recent_1.png) +![The same dialog on its Recent tab with nothing listed, an empty grey panel between the tab strip and the Browse, Open and Cancel buttons.](Images/New_Project_Recent_1.png){:width="522" height="405"} A list of recent projects will be displayed, resizing depending on how many are in the list. -![The Recent tab holding two entries, the first highlighted white and the second grey, each with a small X button at its right edge. Both rows read blank because the project paths have been erased from the screenshot.](Images/New_Project_Recent_2.png) +![The Recent tab holding two entries, the first highlighted white and the second grey, each with a small X button at its right edge. Both rows read blank because the project paths have been erased from the screenshot.](Images/New_Project_Recent_2.png){:width="522" height="405"} _Removed paths_ diff --git a/docs/IDE/Open Editors.md b/docs/IDE/Open Editors.md index 8f9b14805..e7c90124b 100644 --- a/docs/IDE/Open Editors.md +++ b/docs/IDE/Open Editors.md @@ -9,10 +9,10 @@ permalink: /tB/IDE/Project/OpenEditors When a project isn't open this will be empty. -![The Open Editors pane with nothing listed beneath its title bar.](Images/OpenEditors.png) +![The Open Editors pane with nothing listed beneath its title bar.](Images/OpenEditors.png){:width="360" height="92"} When a project is open it will list the files that are currently open in your [Editor](Editor) -![The Open Editors pane listing two open files from the twinBASICMonthlyChallenge1 project, each with its own file type icon: Sources/frmMain.tbform and Sources/frmMain.twin.](Images/OpenEditors_1.png) +![The Open Editors pane listing two open files of SampleProject, each with its own file type icon and its full path: /SampleProject/Sources/frmMain.tbform and /SampleProject/Sources/frmMain.twin.](Images/OpenEditors_1.png){:width="360" height="92"} Clicking a file in the list brings it into focus in the editor. diff --git a/docs/IDE/Outline.md b/docs/IDE/Outline.md index 755dd3a65..551abc28f 100644 --- a/docs/IDE/Outline.md +++ b/docs/IDE/Outline.md @@ -15,10 +15,10 @@ The Outline pane shows a structural overview of the declarations in the active s When a project isn't open this will be empty. -![The Outline pane with nothing listed beneath its title bar.](Images/Outline.png) +![The Outline pane with nothing listed beneath its title bar.](Images/Outline.png){:width="300" height="96"} -Once you open a project it will list the `Modules`/`Classes` etc. +Once you open a source file, it lists the `Modules`/`Classes` etc. that the file declares. -![The Outline pane listing the two top level items of the open project, MainModule and WaynesWorldAddin.](Images/Outline_1.png) +![The Outline pane listing the two top level items of the open file, the module MainModule and the class GlobalSearchAddIn.](Images/Outline_1.png){:width="300" height="96"} You can click on an item to navigate to that point in the code file. diff --git a/docs/IDE/Package Publishing.md b/docs/IDE/Package Publishing.md index fb7827bc6..982c542c0 100644 --- a/docs/IDE/Package Publishing.md +++ b/docs/IDE/Package Publishing.md @@ -11,10 +11,10 @@ The Package Publishing pane manages the metadata for the current project when it When a project isn't open this will be empty. -![The Package Publishing pane with every value blank. Six labelled rows read Publisher, Namespace, Description, Licence, Visibility and Version, each with an Edit link on the right except Publisher, which offers Sign In. A note says changes are not effective until published, above a greyed out Publish This Package button.](Images/PackagePublishing.png) +![The Package Publishing pane with every value blank. Six labelled rows read Publisher, Namespace, Description, Licence, Visibility and Version, each with an Edit link on the right except Publisher, which offers Sign In. A note says changes are not effective until published, above a greyed out Publish This Package button.](Images/PackagePublishing.png){:width="300" height="268"} -Once you open a project you will be able to edit the properties. +Once you open a project whose build type is Package TWINPACK, the pane shows its properties and you can edit them. For any other build type the values stay blank. -![The Package Publishing pane with a project open and its values filled in: Namespace VBFLXGRD18, Description VB FlexGrid Control 1.8 (Experimental, 32-bit and 64-bit), Licence MIT, Visibility PUBLIC and Version 1.8.0.30. Publisher still offers Sign In, and the Publish This Package button is now enabled.](Images/PackagePublishing_1.png) +![The Package Publishing pane with a package project open and its values filled in: Namespace SampleProject, Description SampleProject description, Licence MIT, Visibility PRIVATE and Version 1.0.0.0. Publisher still offers Sign In, and the Publish This Package button is now enabled.](Images/PackagePublishing_1.png){:width="300" height="330"} Click the "EDIT" and this will open the [Project Settings](Settings) diff --git a/docs/IDE/Project Explorer.md b/docs/IDE/Project Explorer.md index 8b2eeb5f5..52d3174d3 100644 --- a/docs/IDE/Project Explorer.md +++ b/docs/IDE/Project Explorer.md @@ -7,63 +7,63 @@ permalink: /tB/IDE/Project/Explorer # Project Explorer -![The PROJECT EXPLORER panel with no project open: its title bar carries only a drag grip and a close button, and the body below it is empty.](Images/ProjectExplorer.png) -![The same panel with SampleProject loaded, its root node expanded over six yellow folders, each with a plus box for expanding it: ImportedTypeLibraries, Miscellaneous, Packages, References, Resources and Sources, the last of them selected.](Images/ProjectExplorer_Sample.png) +![The PROJECT EXPLORER panel with no project open: its title bar carries only a drag grip and a close button, and the body below it is empty.](Images/ProjectExplorer.png){:width="400" height="116"} +![The same panel with SampleProject loaded, its root node expanded over six yellow folders, each with a plus box for expanding it: ImportedTypeLibraries, Miscellaneous, Packages, References, Resources and Sources, the last of them selected.](Images/ProjectExplorer_Sample.png){:width="400" height="200"} -![A small yellow folder icon](Images/Folder.png) ImportedTypeLibraries -![A small yellow folder icon](Images/Folder.png) Miscellaneous -![A small yellow folder icon](Images/Folder.png) Packages -![A small yellow folder icon](Images/Folder.png) References -![A small yellow folder icon](Images/Folder.png) Resources -![A small yellow folder icon](Images/Folder.png) Sources +![A small yellow folder icon](Images/Folder.png){:width="16" height="11"} ImportedTypeLibraries +![A small yellow folder icon](Images/Folder.png){:width="16" height="11"} Miscellaneous +![A small yellow folder icon](Images/Folder.png){:width="16" height="11"} Packages +![A small yellow folder icon](Images/Folder.png){:width="16" height="11"} References +![A small yellow folder icon](Images/Folder.png){:width="16" height="11"} Resources +![A small yellow folder icon](Images/Folder.png){:width="16" height="11"} Sources When a Project is open contextual icons will appear. -![The PROJECT EXPLORER title bar once a project is open, with four buttons at its right: a settings gear, a toggle file view icon of two overlapping documents, a plus for adding items, and the panel close button.](Images/ProjectExplorer_Header.png) +![The PROJECT EXPLORER title bar once a project is open, with four buttons at its right: a settings gear, a toggle file view icon of two overlapping documents, a plus for adding items, and the panel close button.](Images/ProjectExplorer_Header.png){:width="400" height="30"} -## ![](Images/Settings.png) Project Settings +## ![](Images/Settings.png){:width="16" height="16"} Project Settings - [Info](Settings) -## ![](Images/Toggle.png) Toggle file view (<kbd>CTRL</kbd> + <kbd>R</kbd>) +## ![](Images/Toggle.png){:width="16" height="16"} Toggle file view (<kbd>CTRL</kbd> + <kbd>R</kbd>) -## ![](Images/Add.png) Add... +## ![](Images/Add.png){:width="16" height="16"} Add... Same as Right-Click ## Right-Click - Add -![The Project Explorer context menu with its Add submenu open alongside it. The submenu runs from Add Folder through the Windows Form, MDI Form, UserControl, PropertyPage and Report entries, the CustomControls form, the .TWIN module and class, the .BAS module and .CLS class, Add Other File, Import, and three Add Resource lines. The menu behind it lists Cut, Copy, Paste, Copy Path, View As JSON, View As Markdown Preview, Rename, Export and Delete Permanently, with Cut, Copy, Paste and the two View As entries greyed out.](Images/RightClick-Add.png) +![The Project Explorer context menu with its Add submenu open alongside it. The submenu runs from Add Folder through the Windows Form, MDI Form, UserControl, PropertyPage and Report entries, the CustomControls form, the .TWIN module and class, the .BAS module and .CLS class, Add Other File, Import, and three Add Resource lines. The menu beside it lists Add, Cut, Copy, Paste, Copy Name, Copy Path, View As JSON, View As Markdown Preview, Rename, Export and Delete Permanently, with Cut, Copy, Paste and the two View As entries greyed out.](Images/RightClick-Add.png){:width="510" height="492"} -- ![](Images/Folder.png) Add Folder -- ![](Images/tB-Green.png) Add Windows Form -- ![](Images/tB-Green.png) Add Windows MDI Form -- ![](Images/tB-Green.png) Add Windows UserControl -- ![](Images/tB-Green.png) Add Windows PropertyPage -- ![](Images/tB-Green.png) Add Windows Report +- ![](Images/Folder.png){:width="16" height="11"} Add Folder +- ![](Images/tB-Green.png){:width="16" height="16"} Add Windows Form +- ![](Images/tB-Green.png){:width="16" height="16"} Add Windows MDI Form +- ![](Images/tB-Green.png){:width="16" height="16"} Add Windows UserControl +- ![](Images/tB-Green.png){:width="16" height="16"} Add Windows PropertyPage +- ![](Images/tB-Green.png){:width="16" height="16"} Add Windows Report --- -- ![](Images/tB-Green.png) Add CustomControls Form +- ![](Images/tB-Green.png){:width="16" height="16"} Add CustomControls Form --- -- ![](Images/tB-Red.png) Add Module (.TWIN supporting Unicode) -- ![](Images/tB-Red.png) Add Class (.TWIN supporting Unicode) +- ![](Images/tB-Red.png){:width="16" height="16"} Add Module (.TWIN supporting Unicode) +- ![](Images/tB-Red.png){:width="16" height="16"} Add Class (.TWIN supporting Unicode) --- -- ![](Images/tB-Blue.png) Add Module (.BAS) -- ![](Images/tB-Orange.png) Add Class (.CLS) +- ![](Images/tB-Blue.png){:width="16" height="16"} Add Module (.BAS) +- ![](Images/tB-Orange.png){:width="16" height="16"} Add Class (.CLS) --- -- ![](Images/File-Green.png) Add Other File +- ![](Images/File-Green.png){:width="16" height="16"} Add Other File --- -- ![](Images/File-Green.png) Import +- ![](Images/File-Green.png){:width="16" height="16"} Import --- @@ -71,43 +71,43 @@ Same as Right-Click - Add Resource: String Table - Add Resource: MESSAGETABLE -## ![](Images/Folder.png) Folder +## ![](Images/Folder.png){:width="16" height="11"} Folder {: #folder } -## ![](Images/tB-Green.png) Windows Form +## ![](Images/tB-Green.png){:width="16" height="16"} Windows Form {: #windows-form } [tbForm](Editor/Form) -## ![](Images/tB-Green.png) Windows MDI Form +## ![](Images/tB-Green.png){:width="16" height="16"} Windows MDI Form {: #windows-mdi-form } -## ![](Images/UserControl.png) Windows UserControl +## ![](Images/tB-Green.png){:width="16" height="16"} Windows UserControl {: #windows-usercontrol } -## ![](Images/tB-Green.png) Windows PropertyPage +## ![](Images/tB-Green.png){:width="16" height="16"} Windows PropertyPage {: #windows-propertypage } -## ![](Images/tB-Green.png) Windows Report +## ![](Images/tB-Green.png){:width="16" height="16"} Windows Report {: #windows-report } [tbReport](Editor/Report) -## ![](Images/tB-Green.png) CustomControls Forms +## ![](Images/tB-Green.png){:width="16" height="16"} CustomControls Forms {: #customcontrols-forms } -![A twinBASIC message box headed Package needed: CustomControls, saying that a reference to the CustomControls package must be added to the project first and that the compiler will be restarted once it is, above an Add CustomControls package button and a Cancel button.](Images/RightClick-Add-CustomControlsForm-Popup.png) +![A twinBASIC message box headed Package needed: CustomControls, saying that a reference to the CustomControls package must be added to the project first and that the compiler will be restarted once it is, above an Add CustomControls package button and a Cancel button.](Images/RightClick-Add-CustomControlsForm-Popup.png){:width="539" height="194"} -## ![](Images/tB-Red.png) Module +## ![](Images/tB-Red.png){:width="16" height="16"} Module {: #module } -## ![](Images/tB-Red.png) Class +## ![](Images/tB-Red.png){:width="16" height="16"} Class {: #class } -## ![](Images/File-Green.png) Other File +## ![](Images/File-Green.png){:width="16" height="16"} Other File {: #other-file } -## ![](Images/File-Green.png) Import +## ![](Images/File-Green.png){:width="16" height="16"} Import {: #import } ## Resource: Visual Styles Manifest @@ -117,7 +117,7 @@ An application manifest is an XML file that Windows reads when it starts the pro A manifest can also declare the program's DPI awareness. A program whose manifest does should set [Force DPI Awareness At Startup](Settings#dpi-awareness) to **NONE**. -See ![A small yellow folder icon](Images/Folder.png) `/.../Resources/MANIFEST/#1.xml` +See ![A small yellow folder icon](Images/Folder.png){:width="16" height="11"} `/.../Resources/MANIFEST/#1.xml` ```xml <?xml version="1.0" encoding="UTF-8" standalone="yes"?> @@ -146,7 +146,7 @@ See ![A small yellow folder icon](Images/Folder.png) `/.../Resources/MANIFEST/#1 ## Resource: String Table -See ![A small yellow folder icon](Images/Folder.png) `/.../Resources/STRING/Strings.json` +See ![A small yellow folder icon](Images/Folder.png){:width="16" height="11"} `/.../Resources/STRING/Strings.json` ```json [ @@ -171,7 +171,7 @@ See ![A small yellow folder icon](Images/Folder.png) `/.../Resources/STRING/Stri ## Resource: MESSAGETABLE -See ![A small yellow folder icon](Images/Folder.png) `/.../Resources/MESSAGETABLE/Strings.json` +See ![A small yellow folder icon](Images/Folder.png){:width="16" height="11"} `/.../Resources/MESSAGETABLE/Strings.json` ```json { diff --git a/docs/IDE/Project Settings.md b/docs/IDE/Project Settings.md index 4dd41b5df..152ba2974 100644 --- a/docs/IDE/Project Settings.md +++ b/docs/IDE/Project Settings.md @@ -9,7 +9,7 @@ permalink: /tB/IDE/Project/Settings The Project Settings dialog, **Project → Project Settings...**, holds the settings of the open project. They are listed below in the order the dialog shows them. The dialog shows a description under each setting, and each entry below restates it: -![The Option Explicit On setting in the Project Settings dialog, set to Yes, with an arrow pointing at the description under it](Images/project settings description text.png) +![The Option Explicit On setting in the Project Settings dialog, set to Yes, with an arrow pointing at the description under it](Images/project settings description text.png){:width="900" height="67"} The drop-down list of a setting ends with a **COMPILER DEFAULT** entry, which names the value used when the project does not set one. Choosing it removes the setting from the project's `Settings` file, and so does emptying a box. [**File → Export Project**](Menu/File#export-project) writes that file beside the project's `Sources` folder, and the entries below give the settings' keys in it. @@ -46,9 +46,9 @@ The type libraries and packages that the compiler uses for this project. In the To add a reference, tick it on the **Available COM References** tab, which lists the type libraries registered on the machine, or on the **Available Packages** tab. The **Enabled Libraries** tab lists the references the project uses, in priority order. -![The Project Settings dialog on its Enabled Libraries tab, listing four ticked references in priority order with Library Symbol and Version columns: the twinBASIC VBA and VBRUN compatibility packages, OLE Automation as stdole, and the IDE Extensibility package as tbIDE.](Images/ProjectSettings_LibraryReferences.png) +![The Project Settings dialog on its Enabled Libraries tab, listing four ticked references in priority order with Library Symbol and Version columns: the twinBASIC VBA and VBRUN compatibility packages, OLE Automation as stdole, and the IDE Extensibility package as tbIDE.](Images/ProjectSettings_LibraryReferences.png){:width="1102" height="774"} -![The same dialog on its Available COM References tab. A search box sits above an alphabetical list of unticked type libraries registered on the machine --- AccessibilityCplAdmin, Active DS, ActiveMovie, AgentWmiLib and so on --- against Library Symbol, Version and Publisher columns.](Images/ProjectSettings_AvailableCOMReferences.png) +![The same dialog on its Available COM References tab. A search box sits above an alphabetical list of unticked type libraries registered on the machine --- on this machine ABBYY FineReader 16 SharePoint Integration Component, AccessibilityCplAdmin, Acrobat, Acrobat Access and so on --- against Library Symbol, Version and Publisher columns.](Images/ProjectSettings_AvailableCOMReferences.png){:width="1102" height="774"} See [Packages](../../../Features/Packages/) diff --git a/docs/IDE/Properties.md b/docs/IDE/Properties.md index 5be917038..1c03debe9 100644 --- a/docs/IDE/Properties.md +++ b/docs/IDE/Properties.md @@ -7,7 +7,7 @@ permalink: /tB/IDE/Project/Properties # Properties -![The Properties pane, empty apart from its title bar, with no property rows listed.](Images/Properties.png) +![The Properties pane, empty apart from its title bar, with no property rows listed.](Images/Properties.png){:width="400" height="376"} The Properties pane shows the properties of the control or object currently selected in the form designer. Property names appear in the left column with their current values on the right; clicking a value edits it in place. diff --git a/docs/IDE/Status Bar.md b/docs/IDE/Status Bar.md index 190cd78c8..6047239d7 100644 --- a/docs/IDE/Status Bar.md +++ b/docs/IDE/Status Bar.md @@ -7,7 +7,9 @@ permalink: /tB/IDE/Project/StatusBar # Status Bar -![The full width of the status bar: a red tB Services: UNAVAILABLE badge and an olive COMMUNITY EDITION badge at the left, each with a warning triangle, then the Ko-fi, Discord, Twitter and GitHub icons, and dimmed text reading tbProject_Close at the far right.](Images/StatusBar.png) +![The status bar with no project open: a red tB Services: UNAVAILABLE badge and a red tB Licence: NOT READY badge at the left, each with a warning triangle, then the Ko-fi, Discord, Twitter and GitHub icons, and dimmed text reading tbProject_Close at the far right.](Images/StatusBar.png){:width="661" height="27"} + +With no project open, no compiler is connected: the services badge reads **UNAVAILABLE** and the licence badge reads **tB Licence: NOT READY**. Both change once a project has loaded and the compiler has answered. The Status Bar runs along the bottom of the IDE window. It has four regions, left to right: the health of the backend services, the active licence tier, links to community resources, and the name of the command currently under the mouse cursor. @@ -19,7 +21,7 @@ The Status Bar runs along the bottom of the IDE window. It has four regions, lef ![An olive status bar badge with a warning triangle, reading tB Services: LIMITED.](Images/Services_Limited.png) -![A green status bar badge with a tick, reading tB Services: OPERATIONAL.](Images/Services_Operational.png) +![A green status bar badge with a tick, reading tB Services: OPERATIONAL.](Images/Services_Operational.png){:width="175" height="24"} ![The tooltip that badge shows on hover, four lines giving COMPILER, FS, LSP and DEBUGGER each as OPERATIONAL.](Images/Services_Operational_Tooltip.png) @@ -45,7 +47,7 @@ DEBUGGER: Disconnected / OPERATIONAL ## Links -![Four white icons at the right of the status bar: a Ko-fi coffee cup holding a heart, the Discord face, the Twitter bird and the GitHub cat.](Images/Links.png) +![Four white icons in the status bar, after its badges: a Ko-fi coffee cup holding a heart, the Discord face, the Twitter bird and the GitHub cat.](Images/Links.png){:width="127" height="27"} - https://ko-fi.com/twinbasic - https://discord.com/invite/UaW9GgKKuE diff --git a/docs/IDE/Toolbar.md b/docs/IDE/Toolbar.md index f3443ef97..061c7f640 100644 --- a/docs/IDE/Toolbar.md +++ b/docs/IDE/Toolbar.md @@ -7,9 +7,9 @@ permalink: /tB/IDE/Project/Toolbar # Toolbar -![The IDE toolbar with no project open. Every button is greyed out, from save and find through the start, break, stop and stepping controls to the build, alignment, resize and z-order groups; the zoom box beside the PREVIEW button is empty, and there is no Global Search box at the right. Only the build configuration box, reading win32, is active.](Images/Toolbar_1.png) -![The same toolbar with a project open but not running. Start is a green triangle, Restart the compiler a blue circular arrow, and Clean and Build are white, while Break and Stop stay grey. A Global Search box now sits at the right, and the zoom box is still empty.](Images/Toolbar_2.png) -![The same toolbar with a form open in the designer. Switch Between Form And Code has become a red circle holding a white lightning bolt and the zoom box reads 100%; Start is still green and the debugging buttons are still grey.](Images/Toolbar_3.png) +![The IDE toolbar with no project open. Every button is greyed out, from save and find through the start, break, stop and stepping controls to the build, alignment, resize and z-order groups; the zoom box beside the PREVIEW button reads 100% in grey, and there is no Global Search button at the right. Only the build configuration box, reading win32, is active.](Images/Toolbar_1.png){:width="1128" height="35"} +![The same toolbar with a project open but not running, and no file open. Save and find are white, Start is a green triangle, Restart the compiler a blue circular arrow, and Clean and Build are white, while Break, Stop and the stepping controls stay grey. A Global Search button now sits at the right, and the zoom box still reads 100% in grey.](Images/Toolbar_2.png){:width="1228" height="35"} +![The same toolbar with a form open in the designer. Switch Between Form And Code has become a red circle holding a white lightning bolt and the zoom box reads 100%; Start is still green, the debugging buttons are still grey, and the Global Search button is still at the right.](Images/Toolbar_3.png){:width="1228" height="35"} ![The same toolbar while the project runs. Start has gone grey, Break is blue and Stop is a red square; the red form and code button and the 100% zoom box are unchanged.](Images/Toolbar_4.png) - Save All Changes (<kbd>CTRL</kbd> + <kbd>S</kbd>) diff --git a/docs/IDE/Toolbox.md b/docs/IDE/Toolbox.md index 423cf1e2f..fe76bdb7d 100644 --- a/docs/IDE/Toolbox.md +++ b/docs/IDE/Toolbox.md @@ -13,16 +13,16 @@ See [Controls](../../Controls) <!-- ![Toolbox](../Controls/Images/toolbox.png "Toolbox") --> -![The Toolbox button labelled More components, a grey plus sign beside the text inside a dashed outline.](Images/Toolbox_MoreComponents.png) +![The Toolbox button labelled More components, a grey plus sign beside the text inside a dashed outline.](Images/Toolbox_MoreComponents.png){:width="208" height="44"} -![A twinBASIC information box reading Components list coming soon, explaining that for now ordinary COM references to the appropriate ActiveX type library must be added and the components will then appear in the form designer automatically, above a GoTo COM References button and an OK button.](Images/Components_Message.png) +![A twinBASIC information box reading Components list coming soon, explaining that for now ordinary COM references to the appropriate ActiveX type library must be added and the components will then appear in the form designer automatically, above a GoTo COM References button and an OK button.](Images/Components_Message.png){:width="654" height="169"} The "GoTo COM References" button takes you to **Project Settings** and filters by "project.references". -![The Project Settings dialog filtered to project.references, on its Enabled Libraries tab. Four ticked entries are listed in priority order against Library Symbol and Version columns: the twinBASIC VBA and VBRUN compatibility packages, OLE Automation as stdole, and the IDE Extensibility package as tbIDE.](Images/ProjectSettings_LibraryReferences.png) +![The Project Settings dialog filtered to project.references, on its Enabled Libraries tab. Four ticked entries are listed in priority order against Library Symbol and Version columns: the twinBASIC VBA and VBRUN compatibility packages, OLE Automation as stdole, and the IDE Extensibility package as tbIDE.](Images/ProjectSettings_LibraryReferences.png){:width="1102" height="774"} Click on the _Available COM References_ tab. -![The same dialog on its Available COM References tab, where a search box sits above an alphabetical list of unticked type libraries registered on the machine, AccessibilityCplAdmin, Active DS, ActiveMovie and AgentWmiLib among them, against Library Symbol, Version and Publisher columns.](Images/ProjectSettings_AvailableCOMReferences.png) +![The same dialog on its Available COM References tab, where a search box sits above an alphabetical list of unticked type libraries registered on the machine, ABBYY FineReader 16 SharePoint Integration Component, AccessibilityCplAdmin, Acrobat and Acrobat Access among them on this machine, against Library Symbol, Version and Publisher columns.](Images/ProjectSettings_AvailableCOMReferences.png){:width="1102" height="774"} See [Project Settings](Settings) for more info. diff --git a/docs/IDE/Variables.md b/docs/IDE/Variables.md index 557036e18..8784c3c50 100644 --- a/docs/IDE/Variables.md +++ b/docs/IDE/Variables.md @@ -10,7 +10,7 @@ index: # Variables -![The Variables pane, empty apart from its title bar, with no variables listed.](Images/Variables.png) +![The Variables pane, empty apart from its title bar, with no variables listed.](Images/Variables.png){:width="240" height="248"} The Variables pane lists the variables in scope at the current execution point during debugging, showing each variable's name, type, and current value. Structured types---classes and user-defined types---can be expanded to inspect their individual members. It takes the place of the Locals window in VB6. diff --git a/docs/IDE/Watches.md b/docs/IDE/Watches.md index 2df2006d4..f238933f5 100644 --- a/docs/IDE/Watches.md +++ b/docs/IDE/Watches.md @@ -10,14 +10,14 @@ index: watch window When a project isn't open this will be empty. -![The WATCHES panel with no project open: a title bar holding only the drag grip and a close button, above an empty body.](Images/Watches.png) +![The WATCHES panel with no project open: a title bar holding only the drag grip and a close button, above an empty body.](Images/Watches.png){:width="300" height="76"} Once you open a project it will other buttons available. -![The same panel with a project open, two more buttons now in its title bar: a plus for adding a watch, and a stack of lines with a small x for clearing the list.](Images/Watches_1.png) +![The same panel with a project open, two more buttons now in its title bar: a plus for adding a watch, and a stack of lines with a small x for clearing the list.](Images/Watches_1.png){:width="300" height="76"} -If you wish to add a new watch click ![a thin grey plus sign](Images/Add.png) and type in your "Expression to watch" +If you wish to add a new watch click ![a thin grey plus sign](Images/Add.png){:width="16" height="16"} and type in your "Expression to watch" -![The panel after the plus is clicked, a white text box across the top of its body holding the grey placeholder text Expression to watch, with the caret at the start of it.](Images/Watches_2.png) +![The panel after the plus is clicked, a white text box across the top of its body holding the grey placeholder text Expression to watch.](Images/Watches_2.png){:width="300" height="76"} -You can remove any with the Clear button ![four stacked lines with a small x at their lower right](Images/Clear.png) +You can remove any with the Clear button ![four stacked lines with a small x at their lower right](Images/Clear.png){:width="16" height="16"} diff --git a/docs/IDE/index.md b/docs/IDE/index.md index e3a8edc36..261c46389 100644 --- a/docs/IDE/index.md +++ b/docs/IDE/index.md @@ -6,7 +6,7 @@ permalink: /tB/IDE # The twinBASIC IDE -![The twinBASIC IDE with no project loaded. A menu bar and toolbar run across the top, the Toolbox is docked on the left, an empty Editor 1 fills the centre, and Project Explorer sits above Properties on the right. Debug Console, Diagnostics, Call Stack and Variables share a row along the bottom. Every pane is empty, and the status bar reports tB Services unavailable and Community Edition.](Images/IDE.png) +![The twinBASIC IDE with no project loaded. A menu bar and toolbar run across the top, the Toolbox is docked on the left, an empty Editor 1 fills the centre, and Project Explorer sits above Properties on the right. Debug Console, Diagnostics, Call Stack and Variables share a row along the bottom. Every pane is empty, and the status bar reports tB Services unavailable and tB Licence not ready.](Images/IDE.png){:width="1920" height="1032"} The IDE consists of several fixed panes and tool windows. The [**Project Explorer**](IDE/Project/Explorer) shows the file tree of the open project; the [**Editor**](IDE/Project/Editor) is the main code and designer surface; the [**Properties**](IDE/Project/Properties) pane shows and edits properties for the selected item; the [**Toolbox**](IDE/Project/Toolbox) lists the controls available to drop onto a form. diff --git a/docs/IDE/tbForm.md b/docs/IDE/tbForm.md index e7021c601..d7df7c97f 100644 --- a/docs/IDE/tbForm.md +++ b/docs/IDE/tbForm.md @@ -7,6 +7,6 @@ permalink: /tB/IDE/Project/Editor/Form # .tbForm -![The form designer open on a tab named MyForm.tbform in Editor 1. An empty form occupies the top left corner as a white dotted grid, with a plus menu button above it, sizing handles on its right and bottom edges, and a 300 x 300 size readout at its lower corner.](Images/tbForm.png) +![The form designer open on a tab named MyForm.tbform in Editor 1. An empty form occupies the top left corner as a white dotted grid, with a plus menu button above it, sizing handles on its right and bottom edges, and a 300 x 300 size readout at its lower corner.](Images/tbForm.png){:width="1319" height="698"} See [Toolbox](../Toolbox) for [Controls](../../../Controls) available to use. diff --git a/docs/IDE/tbReport.md b/docs/IDE/tbReport.md index c74b74f56..5c2c5d660 100644 --- a/docs/IDE/tbReport.md +++ b/docs/IDE/tbReport.md @@ -15,7 +15,7 @@ Contains - Page Footer - Report Footer -![The report designer open on a tab named MyReport.tbreport, with a horizontal ruler across the top and five bands stacked down the page: Report Header, Page Header, Report Detail, Page Footer and Report Footer. Each band has a collapsible header and an empty dotted design surface beneath it, and Page Header is the tallest of the five.](Images/tbReport.png) +![The report designer open on a tab named MyReport.tbreport, with a horizontal ruler across the top and five bands stacked down the page: Report Header, Page Header, Report Detail, Page Footer and Report Footer. Each band has a collapsible header and an empty dotted design surface beneath it, and Page Header is the tallest of the five.](Images/tbReport.png){:width="1319" height="698"} Only certain [Controls](../../../Controls) are available to a Report. @@ -27,4 +27,4 @@ Only certain [Controls](../../../Controls) are available to a Report. - CheckMark - QRCode -![The Toolbox pane as it appears for a report, holding seven tool icons in a single column: the pointer arrow, a letter A for Label, a shape, a diagonal line, a picture, a ticked box and a QR code.](Images/Toolbox_Report.png) +![The Toolbox pane as it appears for a report, holding seven tool icons in a single column: the pointer arrow, a letter A for Label, a shape, a diagonal line, a picture, a ticked box and a QR code.](Images/Toolbox_Report.png){:width="198" height="269"} diff --git a/docs/LLVM/Getting-Started.md b/docs/LLVM/Getting-Started.md index 2bef7e988..b9f401438 100644 --- a/docs/LLVM/Getting-Started.md +++ b/docs/LLVM/Getting-Started.md @@ -22,7 +22,7 @@ During compilation, LLVM translates your code into an *intermediate representati You turn LLVM on in [Project Settings](../tB/IDE/Project/Settings). It is off by default while it is experimental. Project Settings has these options for it: -![The Project Settings dialog scrolled to its two compiler option sections, Compiler Options (BUILD) and Compiler Options (DEBUG). Each has the same unticked boxes in two columns: Enable LLVM Compilation, LLVM: Generate optimized code, LLVM: Optimize for smaller filesize, and one LLVM: Target CPUs with box for each of AES, AVX, AVX2, BMI2, FMA, FXSR, LZCNT, POPCNT, RDSEED, SHA, SSE, SSE2, SSE3, SSE4.1, SSE4.2, SSSE3, XSAVE, XSAVEC, XSAVEOPT and XSAVES. Under the BUILD section a note says that software built for a CPU feature will not run on a CPU without it. Under the DEBUG section a bold note says that LLVM compilation in the IDE debug environment is not recommended, because LLVM-compiled code cannot be debugged.](Images/llvmdoc1.jpg) +![The Project Settings dialog scrolled to its two compiler option sections, Compiler Options (BUILD) and Compiler Options (DEBUG). Each has the same unticked boxes in two columns: Enable LLVM Compilation, LLVM: Generate optimized code, LLVM: Optimize for smaller filesize, and one LLVM: Target CPUs with box for each of AES, AVX, AVX2, BMI2, FMA, FXSR, LZCNT, POPCNT, RDSEED, SHA, SSE, SSE2, SSE3, SSE4.1, SSE4.2, SSSE3, XSAVE, XSAVEC, XSAVEOPT and XSAVES. Under the BUILD section a note says that software built for a CPU feature will not run on a CPU without it. Under the DEBUG section a bold note says that LLVM compilation in the IDE debug environment is not recommended, because LLVM-compiled code cannot be debugged.](Images/llvmdoc1.png){:width="1102" height="774"} The same options appear in two sections: @@ -74,7 +74,7 @@ All other language features should work, in both 32-bit and 64-bit builds. Pleas Besides the project settings, three settings in [IDE Options](../tB/IDE/Project/Menu/Tools) control how LLVM works when it is on: -![Part of the twinBASIC IDE Options dialog, showing three LLVM settings: LLVM Compiler: Maximum number of threads, set to 10; LLVM Compiler: Complex procedure reporting threshold (milliseconds), set to 10000; and LLVM Compiler: Keep cache process alive after exiting the IDE, ticked.](Images/llvmdoc2.jpg) +![Part of the twinBASIC IDE Options dialog, showing three LLVM settings: LLVM Compiler: Maximum number of threads, set to its default of 1; LLVM Compiler: Complex procedure reporting threshold (milliseconds), set to 10000; and LLVM Compiler: Keep cache process alive after exiting the IDE, ticked.](Images/llvmdoc2.png){:width="628" height="187"} **LLVM Compiler: Maximum number of threads** : The number of threads the LLVM compiler can create. More threads can make LLVM compilation faster, but use more memory. If LLVM compilation starts to crash, this value is probably too high, and lowering it should stop the crashes. The default is 1, but newer computers can probably handle 10 or more. diff --git a/docs/LLVM/Images/llvmdoc1.jpg b/docs/LLVM/Images/llvmdoc1.jpg deleted file mode 100644 index 1bdcc24d7..000000000 Binary files a/docs/LLVM/Images/llvmdoc1.jpg and /dev/null differ diff --git a/docs/LLVM/Images/llvmdoc1.png b/docs/LLVM/Images/llvmdoc1.png new file mode 100644 index 000000000..ceb566e3d Binary files /dev/null and b/docs/LLVM/Images/llvmdoc1.png differ diff --git a/docs/LLVM/Images/llvmdoc2.jpg b/docs/LLVM/Images/llvmdoc2.jpg deleted file mode 100644 index b595d718f..000000000 Binary files a/docs/LLVM/Images/llvmdoc2.jpg and /dev/null differ diff --git a/docs/LLVM/Images/llvmdoc2.png b/docs/LLVM/Images/llvmdoc2.png new file mode 100644 index 000000000..8bc69b093 Binary files /dev/null and b/docs/LLVM/Images/llvmdoc2.png differ diff --git a/docs/Miscellaneous/FAQs.md b/docs/Miscellaneous/FAQs.md index 4f86491ac..2bc241900 100644 --- a/docs/Miscellaneous/FAQs.md +++ b/docs/Miscellaneous/FAQs.md @@ -238,11 +238,11 @@ Anyone who has ever tested their own programs against a wide variety of AV engin The easiest way is through the import wizard. When you first start the twinBASIC IDE, you're presented with the New Project dialog- this contains an 'Import from VBP' option: -![The New / Open Project dialog on its New tab, showing a grid of project templates from Standard EXE to Import from folder, with a red arrow pointing up at the Import from VBP tile.](Images/7e1cb69c-6db3-4f3f-aea1-c1fae25938a2.png) +![The New / Open Project dialog on its New tab, showing a grid of project templates from Standard EXE to Import from folder, with a red arrow pointing up at the Import from VBP tile.](Images/7e1cb69c-6db3-4f3f-aea1-c1fae25938a2.png){:width="522" height="405"} You can import individual files, from VB projects or any type, through the Import option on the Add menu, under Project or by right clicking the desired folder in the Project Explorer pane: -![The Project Explorer context menu opened on a project's Sources folder, with the Add submenu expanded beside it. Orange arrows trace the route from Add into the submenu, where Import is ringed among the Add Folder, Add Windows Form and Add Resource entries.](Images/2b32ab8c-fabc-4f42-9e6b-06e85574eaf4.png){:style="width:80%; height:auto;"} +![The Project Explorer context menu opened on a project's Sources folder, with the Add submenu expanded beside it. A red arrow points at Add, and a second arrow points at Import, which is ringed among the Add Folder, Add Windows Form and Add Resource entries.](Images/2b32ab8c-fabc-4f42-9e6b-06e85574eaf4.png){:width="753" height="626"} > [!NOTE] > You can select .bas/.cls files individually, but to import Forms, UserControls, Property Pages, and Resource Files you must currently select the .vbp file they're associated with. You'll then be shown a list of files you can import (with their new twinBASIC extensions .tbform/.twin etc-- make sure to import both, e.g. for Form1.frm you'll see Form1.frm.tbform and Form1.frm.twin: @@ -255,19 +255,19 @@ Importing a .vbp also carries over its [startup object](tB/IDE/Project/Settings# <details> <summary markdown=span id="unrecognized-variables"><b>Why do I see a lot of errors saying my variables are unrecognized?</b></summary> -![The DIAGNOSTICS panel reporting five errors under Form1.frm.twin, each reading TB5079 Unrecognized symbol for hr or hMenu with its line and column. The first is expanded to show its Jump to issue and Help links.](Images/e409ea37-96ad-44c5-8017-3699ef04b53d.png) +![The DIAGNOSTICS panel reporting five errors under frmMain.twin, each reading TB5079 Unrecognized symbol for hr or hMenu with its line and column. The first is expanded to show its Jump to issue and Help links.](Images/e409ea37-96ad-44c5-8017-3699ef04b53d.png){:width="347" height="193"} While use is strongly recommended and considered a best practice, twinBASIC **does not** require `Option Explicit`. If you're seeing these errors, you may have overlooked a new feature of twinBASIC: automatically enabling `Option Explicit` project-wide. When you import a VB6 project, or create a new one, a small dialog pops up: -![The twinBASIC New Project Options dialog: a Project Name box above ticked boxes for Option Explicit ON at project scope, Visual Styles and Include Common Controls, and a DPI Awareness dropdown set to NONE.](Images/05306a72-4ff6-427d-8970-969ef0c582e6.png) +![The twinBASIC New Project Options dialog: a Project Name box above ticked boxes for Option Explicit ON at project scope, Visual Styles and Include Common Controls, and a DPI Awareness dropdown set to SYSTEM_DPI_AWARE.](Images/05306a72-4ff6-427d-8970-969ef0c582e6.png){:width="514" height="275"} If you leave "Option Explicit ON" checked, that means it will be enforced project-wide, regardless of whether `Option Explicit` is used in the form/module/etc itself. If you uncheck it, you won't get any errors for it, just a warning: "This variable has been auto-declared by the compiler due to Option Explicit being OFF". If you want, you can disable that warning in Project Settings: -![The Compiler Warnings section of Project Settings, listing warnings TB0001 upwards with a severity dropdown against each. TB0002 and TB0003 are ringed in green, and one dropdown is open on its WARNING, HINT, INFO, IGNORE and ERROR choices.](Images/2a1c71fd-f81c-4bd3-b61a-0f2979e8961f.png) +![The Compiler Warnings section of Project Settings, listing warnings TB0001 to TB0015 with a severity dropdown against each. TB0002 and TB0003 are ringed in red, and the dropdown of TB0002 is open below it on its WARNING, HINT, INFO, IGNORE and ERROR choices, WARNING marked as the current one.](Images/2a1c71fd-f81c-4bd3-b61a-0f2979e8961f.png){:width="899" height="374"} -For an existing project, the Project Scope Option Explicit can be turned on or off from Project Settings, under "Project: Option Explicit On": +For an existing project, the Project Scope Option Explicit can be turned on or off from Project Settings, under "Option Explicit On": -![The Project Settings pane below its filter box, showing the Project: Option Explicit On entry with a ticked box and a dropdown set to No, above the note that this takes effect only when the source file does not specify Option Explicit itself.](Images/01009879-fdbc-4a8e-8683-353aab6193df.png) +![The Project Settings dialog below its filter box, showing the Option Explicit On row with a dropdown set to Yes, above the note that this takes effect only when the source file does not specify Option Explicit itself.](Images/01009879-fdbc-4a8e-8683-353aab6193df.png){:width="740" height="140"} </details> @@ -276,7 +276,7 @@ For an existing project, the Project Scope Option Explicit can be turned on or o Addins for VB6 and VBA are not supported by the twinBASIC IDE. However, tB has its own addin infrastructure based on modern web technologies. See Samples 10 through 16 in the 'Samples' tab of the New Project dialog: -![The New / Open Project dialog on its Samples tab, scrolled to the addin samples: Sample 10 twinBASIC IDE Addin followed by its Chart, Monaco, ListView and VirtualListView variants, with the list continuing below.](Images/0e24eb5c-c9af-49a9-a908-03968b211554.png) +![The New / Open Project dialog on its Samples tab, scrolled to the addin samples: Sample 10 twinBASIC IDE Addin followed by its Chart, Monaco, ListView and VirtualListView variants, with the list continuing below.](Images/0e24eb5c-c9af-49a9-a908-03968b211554.png){:width="522" height="405"} twinBASIC supports **creating** addins for VBA. It's currently the only tool that supports creating these addins for 64bit Office using a language with 100% compatible syntax. See Sample 4 and Sample 5. @@ -292,9 +292,9 @@ The IDE loads the addins in the folder that matches the project's build target: <details> <summary markdown=span id="resources"><b>How do I use resources in twinBASIC?</b></summary> -Currently tB does not have a dedicated resource editor; instead, resources are managed through the Project Explorer. In the tree, you'll see a Resources folder; by default, it will include ICON in a Standard EXE, and MANIFEST, if you've chosen to enable Visual Styles: +Currently tB does not have a dedicated resource editor; instead, resources are managed through the Project Explorer. In the tree, you'll see a Resources folder; by default, it will include ICON in a Standard EXE, and MANIFEST, if you've chosen to enable Visual Styles. The project in the picture below also has a STRING and a MESSAGETABLE folder: -![The Project Explorer tree of a new project with the Resources branch expanded over an ICON folder holding twinBASIC.ico and a MANIFEST folder holding #1.xml, above the Sources branch and the project Settings file.](Images/71ddde83-a091-47e3-b5b8-681954b0639d.png) +![The right of the IDE window: its title bar, the toolbar, the editor and the Project Explorer. In the tree the Resources branch is expanded over an ICON folder holding twinBASIC.ico, a MANIFEST folder holding #1.xml and the folders MESSAGETABLE and STRING, above the Sources branch.](Images/71ddde83-a091-47e3-b5b8-681954b0639d.png){:width="940" height="426"} You can create additional folders here, using their standard names. For example a BITMAP group could be added, then used with `LoadResImage`. Unlike its predecessor, tB does not restrict the type of resources: you can create any type of folder you want, and import binary data into it. For example, some community projects have inserted `UIFILE` resources for Ribbon controls and `DIALOG` resources for property sheets. Resources can be imported by right-clicking the folder you want them in, and selecting Add->Import file... from the menu. @@ -304,7 +304,7 @@ If you're importing a project, the resources in a linked .res file will be impor String table resources are currently treated specially; they're edited in the IDE as JSON. If you import from VBP with a .res, string resources will be automatically converted. If you right click the 'Resources' folder, and go to the 'Add' submenu, at the bottom, you'll find "Add resource: String table" that adds one populated with example strings: -![An editor tab holding Strings.json, a JSON array of two entries, each carrying an id, a name and one line per locale, keyed LCID_0000 for the neutral text through LCID_0809. The Project Explorer beside it shows the file inside a STRING folder under Resources.](Images/97cc8655-7a8b-47f3-b52c-eb1ddfce662f.png) +![An editor tab holding Strings.json, a JSON array of two entries, each carrying an id, a name and one line per locale, keyed LCID_0000 for the neutral text through LCID_0809. The Project Explorer beside it shows the file inside a STRING folder under Resources.](Images/97cc8655-7a8b-47f3-b52c-eb1ddfce662f.png){:width="1040" height="417"} ### Group names @@ -335,7 +335,7 @@ Imported projects use the icon of the Form chosen in Settings. This can be modif If you don't set that option, or your project contains no Forms, the icon can be managed manually through the Resources folder. If you're not already familiar with using resources in twinBASIC, see the FAQ entry right above this one. In this scenario, the icon used for your application in Explorer is the one in the Resources\ICON folder that comes first alphabetically. If you do not have an ICON folder in your project, you can create one by right-clicking the Resources folder and selecting Add->Add folder. -![The Project Explorer with the ICON folder under Resources expanded to two entries, MyOwnIcon.ico above twinBASIC.ico, beside the Toolbox and a form open in the designer.](Images/8611d12a-d7a6-48cc-9544-cb27c5299aa5.png) +![The Project Explorer with the ICON folder under Resources expanded to two entries, MyOwnIcon.ico above twinBASIC.ico, beside the Toolbox and a form open in the designer.](Images/8611d12a-d7a6-48cc-9544-cb27c5299aa5.png){:width="642" height="461"} In the above picture, MyOwnIcon.ico would be used by Explorer and other apps to represent your .exe, as it comes before twinBASIC.ico alphabetically. diff --git a/docs/Miscellaneous/Images/01009879-fdbc-4a8e-8683-353aab6193df.png b/docs/Miscellaneous/Images/01009879-fdbc-4a8e-8683-353aab6193df.png index 4786a543c..3da2e2173 100644 Binary files a/docs/Miscellaneous/Images/01009879-fdbc-4a8e-8683-353aab6193df.png and b/docs/Miscellaneous/Images/01009879-fdbc-4a8e-8683-353aab6193df.png differ diff --git a/docs/Miscellaneous/Images/05306a72-4ff6-427d-8970-969ef0c582e6.png b/docs/Miscellaneous/Images/05306a72-4ff6-427d-8970-969ef0c582e6.png index 5600d3c59..8368d0088 100644 Binary files a/docs/Miscellaneous/Images/05306a72-4ff6-427d-8970-969ef0c582e6.png and b/docs/Miscellaneous/Images/05306a72-4ff6-427d-8970-969ef0c582e6.png differ diff --git a/docs/Miscellaneous/Images/0e24eb5c-c9af-49a9-a908-03968b211554.png b/docs/Miscellaneous/Images/0e24eb5c-c9af-49a9-a908-03968b211554.png index 9f4880da5..9a8b8389a 100644 Binary files a/docs/Miscellaneous/Images/0e24eb5c-c9af-49a9-a908-03968b211554.png and b/docs/Miscellaneous/Images/0e24eb5c-c9af-49a9-a908-03968b211554.png differ diff --git a/docs/Miscellaneous/Images/2a1c71fd-f81c-4bd3-b61a-0f2979e8961f.png b/docs/Miscellaneous/Images/2a1c71fd-f81c-4bd3-b61a-0f2979e8961f.png index f90f74dfa..6ed00b0d9 100644 Binary files a/docs/Miscellaneous/Images/2a1c71fd-f81c-4bd3-b61a-0f2979e8961f.png and b/docs/Miscellaneous/Images/2a1c71fd-f81c-4bd3-b61a-0f2979e8961f.png differ diff --git a/docs/Miscellaneous/Images/2b32ab8c-fabc-4f42-9e6b-06e85574eaf4.png b/docs/Miscellaneous/Images/2b32ab8c-fabc-4f42-9e6b-06e85574eaf4.png index a2d04478e..c65359439 100644 Binary files a/docs/Miscellaneous/Images/2b32ab8c-fabc-4f42-9e6b-06e85574eaf4.png and b/docs/Miscellaneous/Images/2b32ab8c-fabc-4f42-9e6b-06e85574eaf4.png differ diff --git a/docs/Miscellaneous/Images/71ddde83-a091-47e3-b5b8-681954b0639d.png b/docs/Miscellaneous/Images/71ddde83-a091-47e3-b5b8-681954b0639d.png index 9eb7f20f2..744b02b03 100644 Binary files a/docs/Miscellaneous/Images/71ddde83-a091-47e3-b5b8-681954b0639d.png and b/docs/Miscellaneous/Images/71ddde83-a091-47e3-b5b8-681954b0639d.png differ diff --git a/docs/Miscellaneous/Images/7e1cb69c-6db3-4f3f-aea1-c1fae25938a2.png b/docs/Miscellaneous/Images/7e1cb69c-6db3-4f3f-aea1-c1fae25938a2.png index 5f009eef9..f82e40321 100644 Binary files a/docs/Miscellaneous/Images/7e1cb69c-6db3-4f3f-aea1-c1fae25938a2.png and b/docs/Miscellaneous/Images/7e1cb69c-6db3-4f3f-aea1-c1fae25938a2.png differ diff --git a/docs/Miscellaneous/Images/8611d12a-d7a6-48cc-9544-cb27c5299aa5.png b/docs/Miscellaneous/Images/8611d12a-d7a6-48cc-9544-cb27c5299aa5.png index 51885b0c6..01bad04ea 100644 Binary files a/docs/Miscellaneous/Images/8611d12a-d7a6-48cc-9544-cb27c5299aa5.png and b/docs/Miscellaneous/Images/8611d12a-d7a6-48cc-9544-cb27c5299aa5.png differ diff --git a/docs/Miscellaneous/Images/97cc8655-7a8b-47f3-b52c-eb1ddfce662f.png b/docs/Miscellaneous/Images/97cc8655-7a8b-47f3-b52c-eb1ddfce662f.png index 0397fcd91..1aeea8687 100644 Binary files a/docs/Miscellaneous/Images/97cc8655-7a8b-47f3-b52c-eb1ddfce662f.png and b/docs/Miscellaneous/Images/97cc8655-7a8b-47f3-b52c-eb1ddfce662f.png differ diff --git a/docs/Miscellaneous/Images/e409ea37-96ad-44c5-8017-3699ef04b53d.png b/docs/Miscellaneous/Images/e409ea37-96ad-44c5-8017-3699ef04b53d.png index 826a9723d..6f91d06c2 100644 Binary files a/docs/Miscellaneous/Images/e409ea37-96ad-44c5-8017-3699ef04b53d.png and b/docs/Miscellaneous/Images/e409ea37-96ad-44c5-8017-3699ef04b53d.png differ diff --git a/docs/Reference/Attributes.md b/docs/Reference/Attributes.md index 61deadc95..03f0dbefb 100644 --- a/docs/Reference/Attributes.md +++ b/docs/Reference/Attributes.md @@ -571,7 +571,9 @@ Calculate implicit enum values as a flag set (powers of 2). > [!NOTE] > To prevent confusion, once an explicit value is used, all remaining values after it must also be explicit) -![An Enum marked with the Flags attribute, with inline hints showing each member value as a shifted power of two](Images/flags-attribute.png) +With the IDE option **Always show IDE Inline Code Hints** on, the editor shows the value of each member: + +![An Enum named MyFlags marked with the Flags attribute, with four members flag1 to flag4. The editor's inline hints after the members read 1 << 0 to 1 << 3, each with the value in hexadecimal, from &H00000001& to &H00000008&.](Images/flags-attribute.png){:width="357" height="142"} ## FloatingPointErrorChecks (optional Bool) {: #floatingpointerrorchecks } diff --git a/docs/Reference/Compiler Constants.md b/docs/Reference/Compiler Constants.md index 476ca4243..aaaa81c44 100644 --- a/docs/Reference/Compiler Constants.md +++ b/docs/Reference/Compiler Constants.md @@ -114,10 +114,10 @@ Or more simply, to decide whether to use `PtrSafe`, and then `DeclareWide` or ot The twinBASIC editor shows in real time which compiler constants are active. Code in an `#If` block that will not run under the current settings is inactive, and appears greyed out. Unlike VBx, twinBASIC does not check inactive code for errors. For example, in 32-bit mode:\ -![The editor in win32 mode, with the declares in the Win64 branch greyed out and those in the Else branch active](Images/oHpCiV1.png) +![The toolbar's build list set to win32, above the editor with a module open. The two PtrSafe declares in the Win64 branch of the #If block are greyed out, and the two plain declares in the Else branch are active.](Images/oHpCiV1.png){:width="700" height="329"} -Then after switching to 64-bit mode:\ -![The same code in win64 mode, with the Win64 branch now active and the Else branch greyed out](Images/TYizrRW.png) +Then after switching to 64-bit mode with the build list on the toolbar:\ +![The same module with the build list set to win64. The two PtrSafe declares in the Win64 branch are now active, and the two plain declares in the Else branch are greyed out.](Images/TYizrRW.png){:width="700" height="329"} --- diff --git a/docs/Reference/Images/TYizrRW.png b/docs/Reference/Images/TYizrRW.png index 4d345709e..f951d834b 100644 Binary files a/docs/Reference/Images/TYizrRW.png and b/docs/Reference/Images/TYizrRW.png differ diff --git a/docs/Reference/Images/flags-attribute.png b/docs/Reference/Images/flags-attribute.png index c74115a5a..65e161a4e 100644 Binary files a/docs/Reference/Images/flags-attribute.png and b/docs/Reference/Images/flags-attribute.png differ diff --git a/docs/Reference/Images/oHpCiV1.png b/docs/Reference/Images/oHpCiV1.png index fe607960c..0c68ba981 100644 Binary files a/docs/Reference/Images/oHpCiV1.png and b/docs/Reference/Images/oHpCiV1.png differ diff --git a/docs/Tutorials/CustomControls/Defining a CustomControl.md b/docs/Tutorials/CustomControls/Defining a CustomControl.md index 93aabcdc8..85bcab2f5 100644 --- a/docs/Tutorials/CustomControls/Defining a CustomControl.md +++ b/docs/Tutorials/CustomControls/Defining a CustomControl.md @@ -12,36 +12,38 @@ A CustomControl is simply an ordinary twinBASIC class, with a few extra attribut > [!TIP] > It is highly advisable to look at and experiment with the sample project provided with twinBASIC before trying to implement your own CustomControl. -![Custom Control Sample Project](Images/ccSampleProject.png) +![The New / Open Project dialog on the Samples tab. A red ring and the number 1 mark the Samples tab; a red ring and the number 2 mark the title of Sample 6. CustomControls.](Images/ccSampleProject.png){:width="522" height="405"} *** ## CustomControl() attribute -![CustomControl attribute](Images/ccCustomControlAttribute.png) +![The CustomControl attribute of the class MyGrid in the editor, naming the image /Miscellaneous/MyGrid.png.](Images/ccCustomControlAttribute.png){:width="347" height="34"} This is a required attribute for all CustomControls. You must provide the relative path to an image file within your project that can be used to identify your control in the form designer toolbox. We recommend that you put the image file in the Miscellaneous folder in your project. -![CustomControl GridImage Folder](Images/ccGridButtonImage.png) +In the CustomControls sample, the controls are in the CustomControls package, so their images are in the package's own Miscellaneous folder. **WaynesGrid**, for example, names `/miscellaneous/frmGrid.png`: + +![The Project Explorer of the CustomControls sample, with its Packages folder open on CustomControlsPackage and that package's Miscellaneous folder open. A red arrow points at frmGrid.png, among the control images frmButton.png to frmTextbox.png.](Images/ccGridButtonImage.png){:width="416" height="346"} *** ## ClassId() attribute -![CustomControl ClassId Attribute](Images/ccClassIdAttribute.png) +![The ClassId attribute of the class MyGrid in the editor, with a GUID, under its CustomControl attribute.](Images/ccClassIdAttribute.png){:width="382" height="52"} This is a required attribute for all CustomControls. You must provide a unique CLSID (GUID) in order for the form engine to work with your control. > [!TIP] > If you enter `[ ClassId () ]` twinBASIC helps you out - just press the 'insert a randomly generated GUID' text: -![CustomControl ClassId auto-generate](Images/ccClassIdInsert.png) +![A ClassId attribute with empty parentheses in the editor, with a red wavy underline under ClassId and, above the line, the editor's text: insert a randomly generated GUID.](Images/ccClassIdInsert.png){:width="215" height="58"} *** ## COMCreatable() attribute -![CustomControl COMCreatable attribute](Images/ccCOMCreatable.png) +![The three attributes of the class MyGrid in the editor, CustomControl, ClassId and COMCreatable(False), above the line Class MyGrid. A red arrow points at the COMCreatable attribute.](Images/ccCOMCreatable.png){:width="382" height="88"} This is an optional attribute, but it is usually advisable to set this attribute to False, as you don't need to instantiate CustomControls from external COM environments. *** ## Must implement ICustomControl -![CustomControl ICustomControl interface](Images/ccICustomControl.png) +![The editor's hover over ICustomControl in the line Implements CustomControls.ICustomControl of the class MyGrid. It names the interface, in package CustomControls, and lists its members Initialize, Destroy and Paint.](Images/ccICustomControl.png){:width="637" height="277"} All CustomControls *must* implement [`CustomControls.ICustomControl`](../../tB/Packages/CustomControls/Framework/ICustomControl). This interface currently has 3 methods that you must implement: diff --git a/docs/Tutorials/CustomControls/Images/ccCOMCreatable.png b/docs/Tutorials/CustomControls/Images/ccCOMCreatable.png index 778b168e2..39c746830 100644 Binary files a/docs/Tutorials/CustomControls/Images/ccCOMCreatable.png and b/docs/Tutorials/CustomControls/Images/ccCOMCreatable.png differ diff --git a/docs/Tutorials/CustomControls/Images/ccClassIdAttribute.png b/docs/Tutorials/CustomControls/Images/ccClassIdAttribute.png index 0182a5280..3528832c4 100644 Binary files a/docs/Tutorials/CustomControls/Images/ccClassIdAttribute.png and b/docs/Tutorials/CustomControls/Images/ccClassIdAttribute.png differ diff --git a/docs/Tutorials/CustomControls/Images/ccClassIdInsert.png b/docs/Tutorials/CustomControls/Images/ccClassIdInsert.png index 3d7868abf..29b308060 100644 Binary files a/docs/Tutorials/CustomControls/Images/ccClassIdInsert.png and b/docs/Tutorials/CustomControls/Images/ccClassIdInsert.png differ diff --git a/docs/Tutorials/CustomControls/Images/ccCustomControlAttribute.png b/docs/Tutorials/CustomControls/Images/ccCustomControlAttribute.png index bacbd7cbc..28ac235be 100644 Binary files a/docs/Tutorials/CustomControls/Images/ccCustomControlAttribute.png and b/docs/Tutorials/CustomControls/Images/ccCustomControlAttribute.png differ diff --git a/docs/Tutorials/CustomControls/Images/ccEvents.png b/docs/Tutorials/CustomControls/Images/ccEvents.png index 8722b9e0c..b5fdf1e26 100644 Binary files a/docs/Tutorials/CustomControls/Images/ccEvents.png and b/docs/Tutorials/CustomControls/Images/ccEvents.png differ diff --git a/docs/Tutorials/CustomControls/Images/ccGridButtonImage.png b/docs/Tutorials/CustomControls/Images/ccGridButtonImage.png index 1fc234236..792f0d1eb 100644 Binary files a/docs/Tutorials/CustomControls/Images/ccGridButtonImage.png and b/docs/Tutorials/CustomControls/Images/ccGridButtonImage.png differ diff --git a/docs/Tutorials/CustomControls/Images/ccICustomControl.png b/docs/Tutorials/CustomControls/Images/ccICustomControl.png index 6c5dbc520..426697349 100644 Binary files a/docs/Tutorials/CustomControls/Images/ccICustomControl.png and b/docs/Tutorials/CustomControls/Images/ccICustomControl.png differ diff --git a/docs/Tutorials/CustomControls/Images/ccMyEnumFieldPropertySheet.png b/docs/Tutorials/CustomControls/Images/ccMyEnumFieldPropertySheet.png index eae21a36a..479d26190 100644 Binary files a/docs/Tutorials/CustomControls/Images/ccMyEnumFieldPropertySheet.png and b/docs/Tutorials/CustomControls/Images/ccMyEnumFieldPropertySheet.png differ diff --git a/docs/Tutorials/CustomControls/Images/ccMyFieldArray.png b/docs/Tutorials/CustomControls/Images/ccMyFieldArray.png index 2fb608087..51bf1ce3a 100644 Binary files a/docs/Tutorials/CustomControls/Images/ccMyFieldArray.png and b/docs/Tutorials/CustomControls/Images/ccMyFieldArray.png differ diff --git a/docs/Tutorials/CustomControls/Images/ccMyFieldClass.png b/docs/Tutorials/CustomControls/Images/ccMyFieldClass.png index 759bb1fd6..f2b1ebec5 100644 Binary files a/docs/Tutorials/CustomControls/Images/ccMyFieldClass.png and b/docs/Tutorials/CustomControls/Images/ccMyFieldClass.png differ diff --git a/docs/Tutorials/CustomControls/Images/ccMyFieldCustomProperty.png b/docs/Tutorials/CustomControls/Images/ccMyFieldCustomProperty.png index 6ce8624c2..f06ae0e67 100644 Binary files a/docs/Tutorials/CustomControls/Images/ccMyFieldCustomProperty.png and b/docs/Tutorials/CustomControls/Images/ccMyFieldCustomProperty.png differ diff --git a/docs/Tutorials/CustomControls/Images/ccMyFieldJson1a.png b/docs/Tutorials/CustomControls/Images/ccMyFieldJson1a.png index 4c7ead145..082429e2a 100644 Binary files a/docs/Tutorials/CustomControls/Images/ccMyFieldJson1a.png and b/docs/Tutorials/CustomControls/Images/ccMyFieldJson1a.png differ diff --git a/docs/Tutorials/CustomControls/Images/ccMyFieldPropertySheet1a.png b/docs/Tutorials/CustomControls/Images/ccMyFieldPropertySheet1a.png index e8cfdb12b..a996bb0ba 100644 Binary files a/docs/Tutorials/CustomControls/Images/ccMyFieldPropertySheet1a.png and b/docs/Tutorials/CustomControls/Images/ccMyFieldPropertySheet1a.png differ diff --git a/docs/Tutorials/CustomControls/Images/ccMyFieldPropertySheet1b.png b/docs/Tutorials/CustomControls/Images/ccMyFieldPropertySheet1b.png index 13bd73276..0c9141ac0 100644 Binary files a/docs/Tutorials/CustomControls/Images/ccMyFieldPropertySheet1b.png and b/docs/Tutorials/CustomControls/Images/ccMyFieldPropertySheet1b.png differ diff --git a/docs/Tutorials/CustomControls/Images/ccSampleProject.png b/docs/Tutorials/CustomControls/Images/ccSampleProject.png index cb73b2203..65e13e7e2 100644 Binary files a/docs/Tutorials/CustomControls/Images/ccSampleProject.png and b/docs/Tutorials/CustomControls/Images/ccSampleProject.png differ diff --git a/docs/Tutorials/CustomControls/Property sheet and object serialization.md b/docs/Tutorials/CustomControls/Property sheet and object serialization.md index cdb7b799a..8b6bed006 100644 --- a/docs/Tutorials/CustomControls/Property sheet and object serialization.md +++ b/docs/Tutorials/CustomControls/Property sheet and object serialization.md @@ -9,11 +9,11 @@ redirect_from: # Property Sheet and Object Serialization The form designer property sheet will pickup any **_public_** custom properties (fields) that you expose via your CustomControl class. For example, adding a field `Public MyField As Long` will then automatically show up in the control property sheet in the form designer: -![CustomControl MyField propertySheet](Images/ccMyFieldPropertySheet1a.png) +![The declaration Public MyField As Long in the code of a custom control, MyGrid, with an arrow to the MyField row of the PROPERTIES panel for MyGrid1, a MyGrid on a form. The panel lists the control's properties by group, from Name down to MyEnumField and MyField, and MyField is 0.](Images/ccMyFieldPropertySheet1a.png){:width="622" height="436"} This is then persisted to your project as properties inside your form JSON structure: -![CustomControl MyField JSON](Images/ccMyFieldJson1a.png) +![Two lines of a form's JSON: the MyField property of the control MyGrid1 with the value 0, and the Name property with the value MyGrid1.](Images/ccMyFieldJson1a.png){:width="161" height="52"} The key to making this work is your control's [**Initialize**](../../tB/Packages/CustomControls/Framework/ICustomControl#initialize) method, which loads the saved values through the serializer that its Context object provides. It might look something like this: @@ -54,7 +54,7 @@ If `RuntimeUISrzDeserialize(Me, False)` returns `True`, then your class properti ## Default Values An alternative method for setting up default values is to inline them into the class field definition: -![CustomControl MyField = 42](Images/ccMyFieldPropertySheet1b.png) +![The declaration Public MyField As Long = 42, with an arrow to the MyField row of the PROPERTIES panel for MyGrid1, which now shows 42.](Images/ccMyFieldPropertySheet1b.png){:width="658" height="436"} The `RuntimeUISrzDeserialize(Me, False)` call inside your **Initialize** method will overwrite the property value if the control is being synchronized from the persisted property sheet data. @@ -62,7 +62,7 @@ The `RuntimeUISrzDeserialize(Me, False)` call inside your **Initialize** method ## Enumerations Enumerations that you define in your twinBASIC project are supported. Simply expose a class field with the enumeration: -![CustomControl enumeration property sheet example](Images/ccMyEnumFieldPropertySheet.png) +![An enumeration, MyEnum, of Value1, Value2 and Value3, and below it the declaration Public MyEnumField As MyEnum, with an arrow to the MyEnumField row of the PROPERTIES panel for MyGrid1. The row's dropdown is open on 0 - Value1, 1 - Value2 and 2 - Value3, the first marked as the current value.](Images/ccMyEnumFieldPropertySheet.png){:width="665" height="326"} Note: Enumerations are persisted to the form JSON structure as strings, so bare this in mind when making changes/updates to a CustomControl so that you don't introduce breaking changes by renaming an enumeration value. @@ -70,19 +70,19 @@ Note: Enumerations are persisted to the form JSON structure as strings, so bare ## Objects Class objects that you define in your twinBASIC project are supported. You ***must*** supply a ClassId attribute for any exposed object, so that the serialization can identify it. -![CustomControl class property sheet example](Images/ccMyFieldClass.png) +![A class, MyButtonState, with its ClassId attribute and four public fields of the CustomControls package's Corners, Fill, Borders and TextRendering classes, which its Sub New creates. Below it the declaration Public NormalState As MyButtonState = New MyButtonState, with an arrow to the NormalState row of the PROPERTIES panel for MyButton1. NormalState is open on Corners, BackgroundFill, Borders and TextRendering, Corners on TopLeft, TopRight, BottomLeft and BottomRight, and TopLeft on its Radius and Shape.](Images/ccMyFieldClass.png){:width="944" height="599"} *** ## Arrays Arrays are supported. The form designer allows for adding new elements, removing elements, and re-ordering of elements (via drag/drop). -![CustomControl array property sheet example](Images/ccMyFieldArray.png) +![A class, GridColumn, with its ClassId attribute and a Caption and a Width field, and the declaration Public Columns() As GridColumn, with an arrow to the Columns row of the PROPERTIES panel for MyGrid1. Columns is open on its three elements, Columns(0) to Columns(2), each with a button that removes it, and an (add) row below them; Columns(0) is open on its Caption and Width.](Images/ccMyFieldArray.png){:width="830" height="362"} *** ## Property Get / Let Custom property procedures are supported. You will find that using Property Get / Let procedures is required if you want property changes to trigger repainting of your control. -![CustomControl custom property example](Images/ccMyFieldCustomProperty.png) +![A private field _MyField, and a Property Get and a Property Let for MyField. The Property Get returns _MyField. The Property Let stores the value in _MyField and calls ControlContext.Repaint.](Images/ccMyFieldCustomProperty.png){:width="389" height="196"} Note that _**private**_ fields and properties do not form part of the serialization, and so will not appear on the property sheet. @@ -92,9 +92,9 @@ The serialization does not support Variants or generic Objects. Always use stro *** ## Events -Events that you define in your class will be exposed in the Events property sheet: +Events that you define in your class are listed on the **EVENTS** tab of the PROPERTIES panel: -![CustomControl attribute](Images/ccEvents.png) +![The class MyButton with its events Click, GotFocus and LostFocus, and an arrow to the EVENTS tab of the PROPERTIES panel for MyButton1, which lists the same three events.](Images/ccEvents.png){:width="780" height="197"} At the moment, the form-designer doesn't yet support code-behind-forms, so this feature is not yet complete. diff --git a/docs/Tutorials/CustomControls/_Images/ccSampleProject.af b/docs/Tutorials/CustomControls/_Images/ccSampleProject.af deleted file mode 100644 index 4f83d89e8..000000000 Binary files a/docs/Tutorials/CustomControls/_Images/ccSampleProject.af and /dev/null differ diff --git a/docs/Tutorials/WebView2/Customize the UserDataFolder.md b/docs/Tutorials/WebView2/Customize the UserDataFolder.md index 3e6509b59..38628e840 100644 --- a/docs/Tutorials/WebView2/Customize the UserDataFolder.md +++ b/docs/Tutorials/WebView2/Customize the UserDataFolder.md @@ -16,7 +16,7 @@ This default behaviour is not always appropriate.  For example, if you're c It is HIGHLY recommended that you override the default behaviour, and instead provide a path that is considered to be safe to use for storing such data. To override the UserDataFolder path at runtime, handle the Create event of the WebView2 control.  See the example in `Sample 9. ActiveX Control WebView2 + Monaco` here, where we use the `%APPDATA%\Local` system path: -![A WebView_Create event handler setting EnvironmentOptions.UserDataFolder to a path under the local AppData folder](Images/tbWebView2CreateEvent.png){:style="width:80%; height:auto;"} +![A WebView_Create event handler that sets dataPath to the local AppData folder with a folder named tbMonacoActiveXDemo added, and sets EnvironmentOptions.UserDataFolder to dataPath.](Images/tbWebView2CreateEvent.png){:width="782" height="124"} <br> <br> diff --git a/docs/Tutorials/WebView2/Getting started.md b/docs/Tutorials/WebView2/Getting started.md index 759ef0c9c..dfa53e745 100644 --- a/docs/Tutorials/WebView2/Getting started.md +++ b/docs/Tutorials/WebView2/Getting started.md @@ -14,15 +14,15 @@ redirect_from: To create projects that use WebView2, your projects must include both the `WinNativeForms` package and the `WebView2` package in your projects. -Both of these packages can be added through the `Project` > `References` menu option, and selecting the `TWINPACK PACKAGES` button. Ensure both packages are ticked, and then close and save the Settings file and restart the compiler. +Both of these packages can be added through the `Project` > `References` menu option, which opens Project Settings at its Library References, and the **Available Packages** tab. Ensure both packages are ticked, and then apply the changes and restart the compiler. -![The COM Type Library / ActiveX References list with the WebView2 and WinNativeForms packages ticked](Images/tbWebView2References.png){:style="width:45%; height:auto;"} +![The Library References list of Project Settings on its Enabled Libraries tab, with the WebView2 and WinNativeForms packages (the latter listed as the VB Compatibility Package) ticked among the VBA, VBRUN and OLE Automation libraries](Images/tbWebView2References.png){:width="1102" height="226"} <br> <br> Once you've added the package references, you should find that the WebView2 control is now available to you in the form designer: -![The form designer TOOLBOX with an arrow pointing to the WEBVIEW control](Images/tbWebView2Toolbox.png){:style="width:15%; height:auto;"} +![The TOOLBOX with a form open, and a red arrow pointing at the WebView2 control's tile under the WebView2Package heading](Images/tbWebView2Toolbox.png){:width="196" height="366"} <br> <br> @@ -36,15 +36,15 @@ We use the WebView2 control just like any ordinary control: ## WebView2 control properties -There are lots of WebView2 properties and events to experiment with. +There are lots of WebView2 properties and events to experiment with. The control's own properties are in the **GENERAL** group of the PROPERTIES panel: -![The PROPERTIES pane for a WebView2 control, with an explanation of the selected property shown at the bottom](Images/tbWebView2Properties.png){:style="width:45%; height:auto;"} +![The PROPERTIES panel for a WebView2 control named Web1, showing its GENERAL group, from AdditionalAllowedFrameAncestors to ZoomFactor, most of them ticked True](Images/tbWebView2Properties.png){:width="500" height="525"} <br> <br> -Note that toggling any property will show extra information at the bottom of the properties list to give you a little bit more information. For the full reference, see the [WebView2 control class](../../tB/Packages/WebView2/WebView2/); for the underlying browser feature, try searching the official <a href="https://docs.microsoft.com/en-us/microsoft-edge/webview2/">WebView2 documentation</a> +For what each property does, see the [WebView2 control class](../../tB/Packages/WebView2/WebView2/); for the underlying browser feature, try searching the official <a href="https://docs.microsoft.com/en-us/microsoft-edge/webview2/">WebView2 documentation</a> ## Samples -If you prefer to start with a sample, have a look at `Sample 0. WebView2 Examples`, available in the new-project dialog: +If you prefer to start with a sample, have a look at `Sample 1a. WebView2 Examples`, available in the new-project dialog: -![The new-project dialog on the Samples tab with an arrow pointing to Sample 0. WebView2 Examples](Images/tbWebView2Sample0.png){:style="width:45%; height:auto;"} +![The New / Open Project dialog on the Samples tab, with a red arrow pointing at the title of Sample 1a. WebView2 Examples](Images/tbWebView2Sample0.png){:width="522" height="405"} diff --git a/docs/Tutorials/WebView2/Images/tbWebView2CreateEvent.png b/docs/Tutorials/WebView2/Images/tbWebView2CreateEvent.png index 481fb5bbd..4bd52e9c1 100644 Binary files a/docs/Tutorials/WebView2/Images/tbWebView2CreateEvent.png and b/docs/Tutorials/WebView2/Images/tbWebView2CreateEvent.png differ diff --git a/docs/Tutorials/WebView2/Images/tbWebView2Properties.png b/docs/Tutorials/WebView2/Images/tbWebView2Properties.png index f37a9c475..5260dde25 100644 Binary files a/docs/Tutorials/WebView2/Images/tbWebView2Properties.png and b/docs/Tutorials/WebView2/Images/tbWebView2Properties.png differ diff --git a/docs/Tutorials/WebView2/Images/tbWebView2References.png b/docs/Tutorials/WebView2/Images/tbWebView2References.png index 2dc091a92..e3e0280f4 100644 Binary files a/docs/Tutorials/WebView2/Images/tbWebView2References.png and b/docs/Tutorials/WebView2/Images/tbWebView2References.png differ diff --git a/docs/Tutorials/WebView2/Images/tbWebView2Sample0.png b/docs/Tutorials/WebView2/Images/tbWebView2Sample0.png index 85d722238..3b36fc706 100644 Binary files a/docs/Tutorials/WebView2/Images/tbWebView2Sample0.png and b/docs/Tutorials/WebView2/Images/tbWebView2Sample0.png differ diff --git a/docs/Tutorials/WebView2/Images/tbWebView2Toolbox.png b/docs/Tutorials/WebView2/Images/tbWebView2Toolbox.png index b4d3f2841..6fc6e9211 100644 Binary files a/docs/Tutorials/WebView2/Images/tbWebView2Toolbox.png and b/docs/Tutorials/WebView2/Images/tbWebView2Toolbox.png differ diff --git a/docs/_book.yml b/docs/_book.yml index d3b9869ff..8ddeb54c0 100644 --- a/docs/_book.yml +++ b/docs/_book.yml @@ -300,7 +300,7 @@ parts: no_outline_entry: true no_heading_shift: true outline_closed: true - - title: Documentation Help + - title: Help Add-In page: /tB/IDE/AddIns/Help no_outline_entry: true no_heading_shift: true diff --git a/docs/_config.yml b/docs/_config.yml index e7051576c..7ea1b4dfa 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -103,6 +103,16 @@ bundle_extra: - src: ../scripts/impexp.py dest: Features/Packages/downloads/impexp.py +# The IDE help add-in's project file, which the build packs from the add-in's +# source folder (src, relative to docs/) and writes into the online and offline +# trees at dest, beside the Help Add-In page that offers it. Only the files git +# tracks there are packed, with the symbol index this build wrote. The publish +# allowlist exempts dest by path, as it does a bundle_extra entry; a .twinproj +# anywhere else is still refused. See builder/addin-project.mjs. +addin_project: + src: ../add-in + dest: tB/IDE/AddIns/downloads/tbDocsHelp.twinproj + # Search index granularity and generic-section folding. See WIP.Search.md's # "Design" §1 for why: splitting at h3 instead of h2 finds API members # (`### PaintPicture`) as their own entries, and folding boilerplate diff --git a/docs/assets/images/dot/scheduler-dag.dot b/docs/assets/images/dot/scheduler-dag.dot index 81dbb11d7..eb7786284 100644 --- a/docs/assets/images/dot/scheduler-dag.dot +++ b/docs/assets/images/dot/scheduler-dag.dot @@ -61,7 +61,8 @@ digraph scheduler { WA [label=<writeAssets<BR/><FONT POINT-SIZE="10">[M]</FONT>>, fillcolor="#fef7e0", color="#f9ab00"] SDt [label=<searchData<BR/><FONT POINT-SIZE="10">[M]</FONT>>, fillcolor="#fef7e0", color="#f9ab00"] SYM [label=<symbolIndex<BR/><FONT POINT-SIZE="10">[M]</FONT>>, fillcolor="#fef7e0", color="#f9ab00"] - WX [label=<writeAux<BR/><FONT POINT-SIZE="10">[M]</FONT>>, fillcolor="#fef7e0", color="#f9ab00"] + AP [label=<addinProject<BR/><FONT POINT-SIZE="10">[M]</FONT>>, fillcolor="#fef7e0", color="#f9ab00"] + WX [label=<writeAux<BR/><FONT POINT-SIZE="10">[M]</FONT>>, fillcolor="#fef7e0", color="#f9ab00"] WO [label=<writeOffline<BR/><FONT POINT-SIZE="10">[M]</FONT>>, fillcolor="#fef7e0", color="#f9ab00"] WP [label=<writePdf<BR/><FONT POINT-SIZE="10">[M]</FONT>>, fillcolor="#fef7e0", color="#f9ab00"] @@ -121,6 +122,9 @@ digraph scheduler { PD -> SYM SYM -> SDt + SYM -> AP + PD -> AP + WA -> WX SDt -> WX FJ -> WX @@ -146,4 +150,5 @@ digraph scheduler { CB -> CR SCSS -> CR SYM -> CR + AP -> CR } diff --git a/docs/assets/images/dot/scheduler-dag.svg b/docs/assets/images/dot/scheduler-dag.svg index 785bcea81..a2f9d5978 100644 --- a/docs/assets/images/dot/scheduler-dag.svg +++ b/docs/assets/images/dot/scheduler-dag.svg @@ -1,8 +1,8 @@ <!-- Generated by graphviz version 16.1.0 (0) --> <!-- Title: scheduler Pages: 1 --> -<svg width="881pt" height="933pt" - viewBox="0.00 0.00 881.00 933.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> +<svg width="916pt" height="933pt" + viewBox="0.00 0.00 916.00 933.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 928.64)"> <title>scheduler @@ -15,48 +15,48 @@ HI - -highlighterInit -[M] + +highlighterInit +[M] CF->HI - - + + DI - -discover -[M] + +discover +[M] CF->DI - - + + BI - -buildInfo -[W] + +buildInfo +[W] DP - -dispatch -[M] + +dispatch +[M] BI->DP - - + + @@ -68,15 +68,15 @@ SCSS - -scss -[M] + +scss +[M] SL->SCSS - - + + @@ -88,521 +88,546 @@ SD->SCSS - - + + LD - -loadData -[M] + +loadData +[M] HI->LD - - + + WA - -writeAssets -[M] + +writeAssets +[M] HI->WA - - + + - + WP - -writePdf -[M] + +writePdf +[M] - + HI->WP - - + + RBC - -resolveBookChapters -[M] + +resolveBookChapters +[M] LD->RBC - - + + WI - -warmInit -[W] per-lane -on_demand, idle + +warmInit +[W] per-lane +on_demand, idle REI - -renderEnvInit -[W] per-lane + +renderEnvInit +[W] per-lane WI->REI - - + + VA - -vendorAssets -[M] + +vendorAssets +[M] DI->VA - - + + NV - -nav -[M] + +nav +[M] DI->NV - - + + BU - -buildInit -[M] + +buildInit +[M] DI->BU - - + + MI - -markdownInit -[M] + +markdownInit +[M] DI->MI - - + + DR - -deriveRedirects -[M] + +deriveRedirects +[M] DI->DR - - + + VA->MI - - + + VA->WA - - + + NV->DP - - + + BU->DP - - + + MI->DP - - + + DR->DP - - + + - + WX - -writeAux -[M] + +writeAux +[M] - + DR->WX - - + + DT - -dot -[W] + +dot +[W] DT->DP - - + + DT->WA - - + + - + DT->WP - - + + DSi - -deriveSitemap -[M] + +deriveSitemap +[M] DSi->RBC - - + + - + DSi->WX - - + + - + RBC->WP - - + + DP->DSi - - + + PD - -prepDest -[M] + +prepDest +[M] DP->PD - - + + DP->REI - - + + Ri - -render:i -[W] + +render:i +[W] DP->Ri - - + + PPD - -prepPageDirs -[M] + +prepPageDirs +[M] PD->PPD - - + + PD->SCSS - - + + SDt - -searchData -[M] + +searchData +[M] PD->SDt - - + + SYM - -symbolIndex -[M] + +symbolIndex +[M] PD->SYM - - + + + + + +AP + +addinProject +[M] + + + +PD->AP + + Fi - -flush:i -[W] pinned + +flush:i +[W] pinned PPD->Fi - - + + PPD->WA - - + + REI->Ri - - + + RJ - -renderJoin -[M] + +renderJoin +[M] Ri->RJ - - + + Ri->Fi - - + + RJ->SDt - - + + RJ->SYM - - + + - + RJ->WP - - + + - + CR - -checkReport -[M] + +checkReport +[M] - + SCSS->CR - - + + FJ - -flushJoin -[M] + +flushJoin +[M] Fi->FJ - - + + - + FJ->WX - - + + - + FJ->WP - - + + - + LJ - -linkJoin -[M] + +linkJoin +[M] - + FJ->LJ - - + + - + WA->WX - - + + - + WO - -writeOffline -[M] + +writeOffline +[M] - + WA->WO - - + + - + SDt->WX - - + + SYM->SDt - - + + + + + +SYM->AP + + - + SYM->CR - - + + + + + +AP->CR + + - + WX->WO - - + + - + WX->LJ - - + + - + WO->LJ - - + + - + CB - -checkBook -[M] + +checkBook +[M] - + WP->CB - - + + - + LJ->CR - - + + - + CB->CR - - + + diff --git a/docs/assets/js/theme-toggle.js b/docs/assets/js/theme-toggle.js index a1e82a71d..7d0712a31 100644 --- a/docs/assets/js/theme-toggle.js +++ b/docs/assets/js/theme-toggle.js @@ -17,6 +17,10 @@ // IDE's theme so). It wins over localStorage and is never written there; // a click on the button replaces it with the reader's own choice. // +// - Where storage throws, the snippet keeps the parameter in window.name +// instead (see renderHead in builder/template.mjs), so the button mirrors +// the attribute the snippet set, never storage alone. +// // A matching inline no-flash snippet in applies the override before // first paint; both MUST agree on the 'theme' storage key. (function () { @@ -40,18 +44,27 @@ // -- discoverable on focus at any time, not only via the live-region announce. var baseLabel = button.getAttribute("aria-label") || "Theme"; + // What the no-flash snippet applied: the ?theme= parameter or the stored + // choice, or, where storage throws, what the frame's window.name kept. + function shownChoice() { + var shown = root.getAttribute("data-theme"); + return shown === "light" || shown === "dark" ? shown : "system"; + } + function currentChoice() { try { var stored = sessionStorage.getItem(KEY) || localStorage.getItem(KEY); if (stored === "light" || stored === "dark") return stored; } catch (_e) { - // localStorage unavailable (private mode) -- fall through to system. + // Storage unavailable (private mode, or a frame of another site whose + // storage is blocked): the page's own theme is the choice. + return shownChoice(); } return "system"; } // `persist` stores the choice as the reader's own, in place of any ?theme= - // override; the first sync after load only mirrors what is stored. + // override; the first sync after load only mirrors what the page shows. function apply(choice, announce, persist) { if (choice === "system") { root.removeAttribute("data-theme"); @@ -73,8 +86,10 @@ if (status && announce) status.textContent = name; } - // Sync the button to whatever the no-flash snippet already applied, then reveal. - apply(currentChoice(), false, false); + // Sync the button to whatever the no-flash snippet already applied, then + // reveal. Read from the page rather than from storage, which may throw where + // the snippet applied a ?theme= parameter all the same. + apply(shownChoice(), false, false); button.hidden = false; button.addEventListener("click", function () { diff --git a/lib/README.md b/lib/README.md index 62d858b17..bcc2fe9a2 100644 --- a/lib/README.md +++ b/lib/README.md @@ -7,6 +7,9 @@ modules in `lib/` -- and `biome.jsonc` refuses an import that breaks the rule. The folder exists because `builder/` may not import `scripts/`, which `biome.jsonc` enforces too, and some code is needed on both sides of that line. +The one exception is `scripts/impexp.mjs`, named in `biome.jsonc`: it is a +published standalone tool that has to stay a single file, and the build packs +the help add-in's project file with it. [markdown-files.mjs](markdown-files.mjs) is the first: it walks `docs/` for its pages and decides which folders there are the build's output trees. diff --git a/lib/help-archive.mjs b/lib/help-archive.mjs index 4c1c30745..5756840ef 100644 --- a/lib/help-archive.mjs +++ b/lib/help-archive.mjs @@ -1,6 +1,10 @@ // The help add-in's documentation archive: the built offline tree as a zip. // -// const stats = await writeHelpArchive({ src, out }); +// const stats = await writeHelpArchive({ src, out }); // stats.zip is the archive's bytes +// const problems = await checkHelpArchive({ src, zip, mayDiffer }); +// +// checkHelpArchive reads an archive written earlier against the tree as it is now: +// the same files, the same bytes, except in the files named in `mayDiffer`. // // The IDE help add-in serves the documentation offline from this zip, embedded in // its DLL as the resource HELP/site.zip (a twinBASIC project embeds @@ -192,7 +196,7 @@ function assembleArchive(files, prepared) { * Reads the file back and returns the differences found, each naming its entry, * and the size of the file. */ -async function verifyArchive(zipFile, files, limit) { +async function verifyArchive(zipFile, files, limit, mayDiffer = new Set()) { const zip = await readFile(zipFile); const head = []; const tail = []; @@ -289,6 +293,7 @@ async function verifyArchive(zipFile, files, limit) { // And with the file in the tree; this part reads and inflates, so it is deferred. const stored = zip.subarray(dataAt, dataAt + csize); + const differs = mayDiffer.has(name); contentChecks.push(async () => { const source = await readFile(listed.file); let content = null; @@ -300,13 +305,16 @@ async function verifyArchive(zipFile, files, limit) { bad(mine, name, `the data does not inflate: ${err.message}`); return; } - if (csize >= source.length) bad(mine, name, "the entry is deflated and not smaller than the file"); + if (csize >= source.length && !differs) { + bad(mine, name, "the entry is deflated and not smaller than the file"); + } } if (content === null) return; if (content.length !== usize) bad(mine, name, `the data is ${content.length} bytes, the record says ${usize}`); + if (zlib.crc32(content) !== crc) bad(mine, name, "the CRC-32 differs from the data's"); + if (differs) return; if (!content.equals(source)) bad(mine, name, "the content differs from the file in the tree"); if (zlib.crc32(source) !== crc) bad(mine, name, "the CRC-32 differs from the file's"); - if (zlib.crc32(content) !== crc) bad(mine, name, "the CRC-32 differs from the data's"); }); } await mapLimit(contentChecks, limit, (check) => check()); @@ -368,5 +376,45 @@ export async function writeHelpArchive({ src, out }) { stats, }); } - return stats; + return { ...stats, zip }; +} + +/** + * Checks an archive written earlier against the tree as it is now: the same + * file names, and the same bytes in each file, except in the named files, which + * may differ in content (the archive's copy of each must still be intact). It + * reads the archive and the tree and writes nothing. + * + * @param {object} o + * @param {string} o.src the tree, an absolute path + * @param {string} o.zip the archive, an absolute path + * @param {string[]} [o.mayDiffer] names (relative, forward slashes) allowed to differ in content + * @returns {Promise} the differences found, each naming its entry; empty when none + */ +export async function checkHelpArchive({ src, zip, mayDiffer = [] }) { + const files = await listFiles(src); + const inTree = new Set(files.map((f) => f.name)); + const entries = archiveNames(await readFile(zip)); + if (entries === null) return ["(archive): the end-of-central-directory record is not where it should be"]; + const inZip = new Set(entries); + const problems = []; + for (const name of inTree) if (!inZip.has(name)) problems.push(`${name}: in the tree, not in the archive`); + for (const name of inZip) if (!inTree.has(name)) problems.push(`${name}: in the archive, not in the tree`); + if (problems.length > 0) return problems; + return (await verifyArchive(zip, files, os.availableParallelism(), new Set(mayDiffer))).problems; +} + +/** The entry names in an archive's central directory, or null when it has no end record. */ +function archiveNames(zip) { + if (zip.length < 22 || zip.readUInt32LE(zip.length - 22) !== SIG_END) return null; + const endAt = zip.length - 22; + const total = zip.readUInt16LE(endAt + 10); + const names = []; + let at = zip.readUInt32LE(endAt + 16); + for (let i = 0; i < total && at + 46 <= endAt && zip.readUInt32LE(at) === SIG_CENTRAL; i++) { + const nameLen = zip.readUInt16LE(at + 28); + names.push(zip.toString("utf8", at + 46, at + 46 + nameLen)); + at += 46 + nameLen + zip.readUInt16LE(at + 30) + zip.readUInt16LE(at + 32); + } + return names; } diff --git a/scripts/check_ci_workflows.mjs b/scripts/check_ci_workflows.mjs index 813282c4f..c9c21907b 100644 --- a/scripts/check_ci_workflows.mjs +++ b/scripts/check_ci_workflows.mjs @@ -12,8 +12,9 @@ // among check.bat's -- and that is allowed; // 2. the two workflows run the same gate steps, in the same order; // 3. each workflow's build passes every argument build.bat passes, and -// --no-fetch-assets and --no-help-archive, and nothing else unless -// ALLOWED records it. +// --no-fetch-assets, and nothing else unless ALLOWED records it; and it +// never passes --no-help-archive, because the project file CI publishes as +// a download carries the help archive the flag would leave out. // // ALLOWED lists the recorded differences, each with where it is recorded. A // difference not on it is a finding, and so is an allowance that matches @@ -88,8 +89,12 @@ const ALLOWED = { }, requiredBuildFlags: { "--no-fetch-assets": "CI must never download an asset (vendor-assets.mjs)", + }, + forbiddenBuildFlags: { "--no-help-archive": - "CI has no use for the IDE help add-in's archive and must not spend time on it (help-archive-step.mjs)", + "the help add-in's project file, which the site publishes as a download, carries the archive of the offline " + + "tree, and without it the add-in built from the download shows the pages from the website only " + + "(help-archive-step.mjs, addin-project.mjs)", }, }; @@ -177,8 +182,17 @@ function findings({ testBat, checkBat, buildBat, workflows, actions = {}, allowe if (!build.includes(flag)) add("build", wf, `the build does not pass \`${flag}\` (${allowed.requiredBuildFlags[flag]})`); } + // A forbidden flag is reported once, here, and not again as an unknown one. + const forbidden = Object.keys(allowed.forbiddenBuildFlags ?? {}); + for (const flag of forbidden) { + if (build.includes(flag)) { + add("build", wf, `the build passes \`${flag}\`, which CI must not: ${allowed.forbiddenBuildFlags[flag]}`); + } + } const flagsHere = allowed.buildFlags[wf] ?? {}; - const expected = removeEach(build, [...wantBuild, ...Object.keys(allowed.requiredBuildFlags)]).rest; + const expected = removeEach(build, [...wantBuild, ...Object.keys(allowed.requiredBuildFlags)]).rest.filter( + (t) => !forbidden.includes(t), + ); const used = new Set(); for (let i = 0; i < expected.length; i++) { const t = expected[i]; @@ -225,6 +239,7 @@ const P_ALLOWED = { ciOnly: [{ script: "ci.mjs", args: "", workflows: ["one.yml"], why: "probe" }], buildFlags: { "two.yml": { "--url": "probe" } }, requiredBuildFlags: { "--no-fetch-assets": "probe" }, + forbiddenBuildFlags: { "--no-help-archive": "probe" }, }; const BUILD_ONE = "node builder/tbdocs.mjs --src docs --no-fetch-assets --check-audit-index"; const BUILD_TWO = @@ -301,6 +316,9 @@ const PROBES = [ ["a build without --no-fetch-assets", { workflows: pair(wf([...GOOD, "ci.mjs"], BUILD_ONE.replace(" --no-fetch-assets", "")), wf(GOOD, BUILD_TWO)) }, ["build"]], + ["a build that skips the help archive", + { workflows: pair(wf([...GOOD, "ci.mjs"], `${BUILD_ONE} --no-help-archive`), wf(GOOD, BUILD_TWO)) }, + ["build"]], ["a build argument nothing allows", { workflows: pair(wf([...GOOD, "ci.mjs"], `${BUILD_ONE} --no-offline`), wf(GOOD, BUILD_TWO)) }, ["build"]], diff --git a/scripts/check_links.mjs b/scripts/check_links.mjs index da8ff6fc5..2b5961735 100644 --- a/scripts/check_links.mjs +++ b/scripts/check_links.mjs @@ -45,7 +45,11 @@ // * The --forbid PREFIX flag (repeatable) fails the run if any // extracted link starts with one of the given URL prefixes // (bare prefix and 'prefix/' exempt), used by the offline pass -// to catch live-site links the offlinify rewrite missed. +// to catch live-site links the offlinify rewrite missed. With +// --online-root, a link the rewrite sent to the website on purpose -- +// to a file the online tree holds and this tree does not -- is not +// reported (offline-rewrite.mjs's isWebsiteOnlyLink, as the build's +// own offline check excuses it). // // Output limitation: no per-link line numbers in error messages -- // htmlparser2 SAX doesn't expose source positions. @@ -78,6 +82,7 @@ import { resolve, OUTSIDE_BASEPATH_MARKER, } from "../builder/link-check.mjs"; +import { isWebsiteOnlyLink } from "../builder/offline-rewrite.mjs"; import { CliError, choiceOption, exitOnCrash, parseCli } from "../lib/cli.mjs"; // Tree-relative POSIX path, the space check.mjs works and reports in, so @@ -133,6 +138,11 @@ Options: URL prefix. The bare prefix and 'prefix/' are exempt (intentional "go to live site" links). Repeatable. + --online-root DIR The online tree the --forbid prefixes serve. + A forbidden link to a file it holds and the + checked tree does not is the offline + rewrite's link to the website, and is not + reported. --no-fail Exit 0 when the check finds errors. Errors are still printed. A command-line error or a crash still exits 2. Useful for @@ -213,6 +223,7 @@ const LINK_OPTIONS = { "root-dir": { type: "string", default: null }, "base-path": { type: "string", default: "" }, forbid: { type: "string", multiple: true }, + "online-root": { type: "string", default: null }, "no-fail": { type: "boolean", default: false }, verbose: { type: "boolean", short: "v", default: false }, help: { type: "boolean", short: "h", default: false }, @@ -245,6 +256,7 @@ function parseArgs(argv) { rootDir: values.rootDir, basePath: values.basePath, forbid: values.forbid, + onlineRoot: values.onlineRoot, noFail: values.noFail, verbose: values.verbose, checkHtml: values.checkHtml, @@ -348,6 +360,9 @@ export function runCheck(argv, { structured = false } = {}) { if (!inputs.length) { return commandLineError("error: at least one input file or directory is required"); } + if (opts.onlineRoot !== null && !fs.statSync(opts.onlineRoot, { throwIfNoEntry: false })?.isDirectory()) { + return commandLineError(`error: --online-root is not a directory: ${opts.onlineRoot}`); + } // --root-dir is used in the shape it was given. checkChunk joins it to // each page's tree-relative path, so the paths it resolves links @@ -416,8 +431,17 @@ export function runCheck(argv, { structured = false } = {}) { }); const env = { root: rootStr, basePath, tree }; - if (opts.oracle === "index") env.index = treeIndexFor(rootStr, collectAllRelFiles(rootStr)); + const treeRels = collectAllRelFiles(rootStr); + if (opts.oracle === "index") env.index = treeIndexFor(rootStr, treeRels); else env.oracle = FsOracle(); + if (opts.onlineRoot !== null && tree.forbid) { + const pathSet = (rels) => new Set(rels.map((rel) => `/${rel.split(path.sep).join("/")}`)); + const sitePaths = pathSet(treeRels); + const onlinePaths = pathSet(collectAllRelFiles(opts.onlineRoot)); + const websites = tree.forbid.map((prefix) => ({ sitePaths: onlinePaths, url: prefix.replace(/\/+$/, "") })); + env.forbidExempt = (url) => + websites.some((website) => isWebsiteOnlyLink(url, { sitePaths, website, baseurl: basePath })); + } const chunk = checkChunk(docs, env); const tDone = performance.now(); diff --git a/scripts/check_links_diff.mjs b/scripts/check_links_diff.mjs index 247fab83e..c443d037b 100644 --- a/scripts/check_links_diff.mjs +++ b/scripts/check_links_diff.mjs @@ -204,6 +204,9 @@ const CASES = { "--check-remote-assets", "--forbid", "https://docs.twinbasic.com", + // The offline tree's links to files only the website holds. + "--online-root", + root.replace(/-offline$/, ""), "--fallback-extensions", "html", "--index-files", diff --git a/scripts/check_publish_policy.mjs b/scripts/check_publish_policy.mjs index 1dbfe508d..6fb72c2ea 100644 --- a/scripts/check_publish_policy.mjs +++ b/scripts/check_publish_policy.mjs @@ -138,6 +138,27 @@ for (const entry of config.bundle_extra ?? []) { } if (!failures) console.log(` ok bundle_extra exemptions are path-scoped`); +// ── 3b. The help add-in's project file, the same way ───────────────── +// docs/_config.yml's addin_project names the one .twinproj the build writes +// itself (builder/addin-project.mjs). It must publish from the tree at that +// path; a .twinproj anywhere else in a tree must not, and neither may one +// found in docs/, even at that path, since the build writes the file there. +{ + const before = failures; + const dest = policy.generated.size === 1 ? [...policy.generated][0] : null; + if (!dest) fail(`docs/_config.yml should declare one addin_project, and declares ${policy.generated.size}`); + else { + if (unpublishableTreePaths([dest], policy).length) fail(`the declared add-in project ${dest} is refused in a tree`); + if (!unpublishableTreePaths(["Reference/stray.twinproj"], policy).length) { + fail(".twinproj publishes from a tree anywhere, not just at the declared add-in project's path"); + } + if (!unpublishableSourceFiles(asSource([dest]), policy).length) { + fail(`a .twinproj in docs/ at ${dest} publishes, though the build writes that file`); + } + } + if (failures === before) console.log(` ok the add-in project's exemption is path-scoped and tree-only`); +} + // ── 4. The two surfaces stay distinct ─────────────────────────────── // BUILD_EXTENSIONS exists so the build can emit sitemap.xml and // search-data.json without blessing a stray docs/secrets.json. Folding diff --git a/scripts/check_tree_fresh.mjs b/scripts/check_tree_fresh.mjs index 815fbf1d4..347c9ef25 100644 --- a/scripts/check_tree_fresh.mjs +++ b/scripts/check_tree_fresh.mjs @@ -56,12 +56,22 @@ const IGNORED_DIRS = new Set([".git", "node_modules"]); // build reads it, and it decides the bytes of tB/symbols.json. const IGNORED_FILES = new Set(["page-baseline.json", "symbol-baseline.json"]); +// The same for two paths under add-in/, by their place in the repository: the +// build writes the add-in's archive of the offline tree into Resources/HELP/ at +// its very end, and rewrites the add-in's copy of the symbol index whenever it +// differs. Neither is an input: the add-in's project file is packed with the +// index the build wrote, never with that copy. +const IGNORED_PATHS = new Set(["add-in/Resources/HELP", "add-in/Resources/SYMBOLS/symbols.json"]); + // The inputs that decide the built bytes. The source tree is the obvious // one; the builder and the theme sources matter just as much, and are // what a maintainer is most likely to be editing when they run these two // commands in the wrong order. lib/ holds modules the builder shares with -// the other tools, so an edit there can change the build too. -const DEFAULT_SOURCES = ["docs", "builder", "lib"]; +// the other tools, so an edit there can change the build too. add-in/ is +// packed into the help add-in's project file, which the build publishes, and +// scripts/impexp.mjs is both the module that packs it and a download the +// build copies. +const DEFAULT_SOURCES = ["docs", "builder", "lib", "add-in", "scripts/impexp.mjs"]; const DEFAULT_TREE = "docs/_site-offline"; const DEFAULT_MARKER = "index.html"; @@ -103,10 +113,17 @@ if (!existsSync(marker)) { } const builtAt = statSync(marker).mtimeMs; -// Newest mtime under `dir`, with the build's own outputs skipped. +// Newest mtime under `dir`, with the build's own outputs skipped. A source +// that is a file stands for itself. // Returns { path, mtimeMs } or null for an absent directory. function newestUnder(dir) { let best = null; + try { + const st = statSync(dir); + if (st.isFile()) return { path: dir, mtimeMs: st.mtimeMs }; + } catch { + return null; + } const walk = (d, top) => { let entries; try { @@ -117,6 +134,7 @@ function newestUnder(dir) { for (const e of entries) { if (IGNORED_DIRS.has(e.name) || (top && isOutputTree(e.name))) continue; const p = join(d, e.name); + if (IGNORED_PATHS.has(relative(REPO_ROOT, p).replaceAll(sep, "/"))) continue; if (e.isDirectory()) { walk(p, false); continue; diff --git a/scripts/lib/cli-cases.mjs b/scripts/lib/cli-cases.mjs index f8ed12d25..3b1ce3a4f 100644 --- a/scripts/lib/cli-cases.mjs +++ b/scripts/lib/cli-cases.mjs @@ -27,6 +27,7 @@ const CASES = [ { tool: "builder/tbdocs.mjs", args: ["--bogus"], exit: 2, stderr: "unknown option: --bogus\n" }, { tool: "scripts/check_links.mjs", args: ["no-such-tree", "--root-dir"], exit: 2, stderr: "error: --root-dir needs a value\n" }, { tool: "scripts/check_links.mjs", args: ["no-such-tree", "--forbid"], exit: 2, stderr: "error: --forbid needs a value\n" }, + { tool: "scripts/check_links.mjs", args: ["--offline", "--online-root", "no-such-tree", "no-such-tree"], exit: 2, stderr: "error: --online-root is not a directory: no-such-tree\n" }, // In the four harness tools that check, a value flag with no value, at the end or before another flag, // exits 2. tbbuild follows the message with its usage line. @@ -130,10 +131,10 @@ const CASES = [ { tool: "scripts/try_help_addin.mjs", args: ["--ide"], exit: 2, stderr: "--ide needs a value\n" }, { tool: "scripts/try_help_addin.mjs", args: ["--ide", ""], exit: 2, stderr: "--ide needs a non-empty value\n" }, { tool: "scripts/try_help_addin.mjs", args: ["--ide", "no-such.exe"], exit: 2, stderr: /^no twinBASIC IDE found: pass --ide / }, - { tool: "scripts/shoot_help_addin.mjs", args: ["--help"], exit: 0, stdout: /^usage: node scripts\/shoot_help_addin\.mjs / }, - { tool: "scripts/shoot_help_addin.mjs", args: ["--ide"], exit: 2, stderr: "--ide needs a value\n" }, - { tool: "scripts/shoot_help_addin.mjs", args: ["--ide", ""], exit: 2, stderr: "--ide needs a non-empty value\n" }, - { tool: "scripts/shoot_help_addin.mjs", args: ["--ide", "no-such.exe"], exit: 2, stderr: /^no twinBASIC IDE found: pass --ide / }, + { tool: "scripts/shoot_docs.mjs", args: ["--help"], exit: 0, stdout: /^usage: node scripts\/shoot_docs\.mjs / }, + { tool: "scripts/shoot_docs.mjs", args: ["--ide"], exit: 2, stderr: "--ide needs a value\n" }, + { tool: "scripts/shoot_docs.mjs", args: ["--ide", ""], exit: 2, stderr: "--ide needs a non-empty value\n" }, + { tool: "scripts/shoot_docs.mjs", args: ["--ide", "no-such.exe"], exit: 2, stderr: /^no twinBASIC IDE found: pass --ide / }, { tool: "scripts/check_examples.mjs", args: ["--help"], exit: 0, stdout: /^usage: node scripts\/check_examples\.mjs \[options\]\n/ }, { tool: "scripts/check_examples.mjs", args: ["--jobs", "0"], exit: 2, stderr: "check_examples: --jobs expects a whole number of at least 1, got: 0\n" }, { tool: "scripts/check_examples.mjs", args: ["--batch", "1.5"], exit: 2, stderr: "check_examples: --batch expects a whole number of at least 1, got: 1.5\n" }, @@ -348,7 +349,7 @@ const HELP_TOOLS = { "scripts/addin_test.mjs": null, "scripts/ide_test.mjs": null, "scripts/try_help_addin.mjs": null, - "scripts/shoot_help_addin.mjs": null, + "scripts/shoot_docs.mjs": null, "scripts/bug_repro.mjs": null, "scripts/build_dot_metrics.mjs": null, "scripts/build_package_api.mjs": null, @@ -425,7 +426,7 @@ const REFUSALS = { "scripts/addin_test.mjs": ["ide"], "scripts/ide_test.mjs": ["ide"], "scripts/try_help_addin.mjs": ["ide"], - "scripts/shoot_help_addin.mjs": ["ide"], + "scripts/shoot_docs.mjs": ["ide"], "scripts/bug_repro.mjs": ["ide"], "scripts/build_dot_metrics.mjs": [null], "scripts/build_package_api.mjs": ["out"], @@ -733,9 +734,17 @@ bad("scripts/ide_test.mjs", ["--show", "--hide"], "--show and --hide cannot be g bad("scripts/try_help_addin.mjs", ["--port", "0"], NOT_PORT(0) + "\n"); bad("scripts/try_help_addin.mjs", ["--port=1.5"], NOT_PORT(1.5) + "\n"); -bad("scripts/shoot_help_addin.mjs", ["--port", "0"], NOT_PORT(0) + "\n"); -bad("scripts/shoot_help_addin.mjs", ["--port=1.5"], NOT_PORT(1.5) + "\n"); -bad("scripts/shoot_help_addin.mjs", ["--only", "("], REGEX_REASON("--only", "(")); +bad("scripts/shoot_docs.mjs", ["--port", "0"], NOT_PORT(0) + "\n"); +bad("scripts/shoot_docs.mjs", ["--port=1.5"], NOT_PORT(1.5) + "\n"); +bad("scripts/shoot_docs.mjs", ["--only", "("], REGEX_REASON("--only", "(")); +bad("scripts/shoot_docs.mjs", ["--jobs", "0"], "--jobs expects a whole number from 1 to 16, got: 0\n"); +bad("scripts/shoot_docs.mjs", ["--jobs=17"], "--jobs expects a whole number from 1 to 16, got: 17\n"); +// --diffs is resolved against the working folder, which is an empty scratch folder here, so the case +// names a folder under the repository's docs by its absolute path. +{ + const underDocs = path.join(REPO_ROOT, "docs", "x"); + bad("scripts/shoot_docs.mjs", ["--diffs", underDocs], `--diffs must not be under docs: ${underDocs}\n`); +} // check_examples prints "check_examples: " before the message. { diff --git a/scripts/lib/png.mjs b/scripts/lib/png.mjs index 0e5866a73..5ffafe14b 100644 --- a/scripts/lib/png.mjs +++ b/scripts/lib/png.mjs @@ -355,11 +355,14 @@ const INK = [32, 32, 32]; * @param {{width: number, height: number, rgba: Uint8Array} | Buffer} vb6 the VB6 picture, or its PNG file * @param {{tb?: string, vb6?: string, diff?: string}} [labels] default TWINBASIC, VB6, and the count of * differing pixels (IDENTICAL, or SIZES DIFFER with both sizes when they do) - * @param {{scale?: number}} [options] a whole number: each pixel is drawn that many pixels square. The - * default is 2 when both pictures are under 200 pixels in both dimensions, else 1. + * @param {{scale?: number, amplify?: boolean}} [options] `scale`, a whole number: each pixel is drawn + * that many pixels square. The default is 2 when both pictures are under 200 pixels in both + * dimensions, else 1. `amplify` draws the difference panel for differences too small to see: matching + * pixels a dark grey copy of the second picture, and a differing pixel yellow when its largest + * channel difference is 1 grey level, shading to red at 128 and more. * @returns {{width: number, height: number, rgba: Uint8Array, sameSize: boolean, differing: number, scale: number}} */ -export function composeComparison(tb, vb6, labels = {}, { scale } = {}) { +export function composeComparison(tb, vb6, labels = {}, { scale, amplify = false } = {}) { const A = asImage(tb); const B = asImage(vb6); const { sameSize, differing } = comparePngs(A, B); @@ -400,9 +403,18 @@ export function composeComparison(tb, vb6, labels = {}, { scale } = {}) { }; const faint = ([r, g, b]) => { const luma = Math.round(0.299 * r + 0.587 * g + 0.114 * b); - const v = 255 - Math.round((255 - luma) * 0.3); + const v = amplify ? 24 + Math.round(luma * 0.25) : 255 - Math.round((255 - luma) * 0.3); return [v, v, v]; }; + // The colour of a differing pixel: pure red, or with `amplify` yellow for a difference of + // one grey level in its most different channel, shading to red at 128. + const marked = (ia, ib) => { + if (!amplify) return RED; + let d = 0; + for (let k = 0; k < 4; k++) d = Math.max(d, Math.abs(A.rgba[ia + k] - B.rgba[ib + k])); + const t = Math.min(1, Math.log2(d) / 7); + return [255, Math.round(255 * (1 - t)), 0]; + }; const top = GUTTER + strip; for (let panel = 0; panel < 3; panel++) { @@ -425,7 +437,7 @@ export function composeComparison(tb, vb6, labels = {}, { scale } = {}) { A.rgba[ia + 1] === B.rgba[ib + 1] && A.rgba[ia + 2] === B.rgba[ib + 2] && A.rgba[ia + 3] === B.rgba[ib + 3]; - colour = equal ? faint(q) : RED; + colour = equal ? faint(q) : marked(ia, ib); } for (let dy = 0; dy < S; dy++) for (let dx = 0; dx < S; dx++) put(left + x * S + dx, top + y * S + dy, colour); } diff --git a/scripts/lib/shot-annotate.mjs b/scripts/lib/shot-annotate.mjs new file mode 100644 index 000000000..a11ee4fa6 --- /dev/null +++ b/scripts/lib/shot-annotate.mjs @@ -0,0 +1,515 @@ +// The annotation overlay of shoot_docs.mjs: arrows, boxes, rings, underlines, labels +// and numbered badges drawn over the IDE's page, in one house style, anchored to +// what they point at, so that a retake after a new BETA redraws them where the +// things now are. The plan and the owner's decisions are in WIP.Screenshots.md. +// +// const { box } = await annotate(conn, [ +// { type: "ring", on: { css: ".buttonGroupItem", text: "Samples" } }, +// { type: "badge", n: 1, on: { css: ".buttonGroupItem", text: "Samples" }, side: "left" }, +// { type: "arrow", from: { of: A, at: "bottom", dy: 60 }, to: A, bend: 20 }, +// ]); +// ... capture a clip grown to include `box` ... +// await unannotate(conn); +// +// One SVG layer is added to the document: `position: fixed`, over everything, +// `pointer-events: none`, drawn in CSS pixels, so it scales with a 2x capture. Its id +// is LAYER_ID, which a cut-out's `keep` list can name. +// +// An anchor is one of +// { css, text?, own? } the first visible element matching `css`, in the document or in any +// open shadow root in it (in document order), whose text, with its +// white space collapsed, equals `text`, else starts with it +// (an exact match beats a prefix match; ties go to the first in +// document order, so the outermost). `own` takes the box of the +// element's own text nodes, leaving out the boxes of its children +// (a list row's title, without its icon) +// { code, nth? } a text search in the editor's model, the `nth` match (1 by +// default), placed with the editor's scrolled position, never a +// line number +// { span: [anchor, ...] } the box that holds the boxes of all the anchors (a row's label +// and its input, boxed as one) +// { of, at, dx?, dy? } a point of another anchor's box: `at` is center, top, bottom, +// left, right, top-left, top-right, bottom-left or bottom-right, +// moved by dx, dy +// { rect } a box given in CSS pixels: an anchor resolved earlier +// (resolveAnchors) and moved, as a composite moves its parts' +// +// A primitive is `{ type, ... }`, with `on` the anchor it marks: +// arrow from, to (an anchor, or a list of them: a fork, one arrow to each), bend? +// An end that is a box (not a point) is the box's edge on the line to the +// other end; the head stops 6 px short of it. `bend` curves the line by that +// many pixels, a positive one to the right of the direction of travel; +// `elbow: true` curves it to leave the start level and meet the tip vertically. +// `tipGap` and `fromGap` replace the 6 and 4 px of clearance at the two ends, +// for a target that has a box drawn round it (4 px of padding and a halo). +// box on, pad? (4), padX? (pad: the padding left and right) +// ring on, pad? (4), padX? (pad): a box whose corners are round to half its height; +// round a span of two lines, a wider padX keeps the round ends off the text +// underline on, gap? (3): a line under the anchor's box +// label text, on, side? (right; left, above, below), gap? (8), dx?, dy?, tone? +// (light, a white pill with red text; dark, a dark red pill with white text, +// for a dark UI) +// badge n, on, side? (left), gap? (8), dx?, dy?: a numbered red circle +// list select, at?, rows?, name?: the open list of a native