Jekyll site (just-the-docs theme) deploying to docs.twinbasic.com. Source under docs/.
Reference documentation is complete for all twelve packages, adapted from primary sources (Microsoft VBA-Docs CC-BY-4.0 for the runtime library, .twin source for the twinBASIC-specific packages). The CEF and WebView2 packages also carry a tutorial set.
| Package | Reference | Tutorials |
|---|---|---|
| VBA package | done | — |
| VBRUN package | done | — |
| VB package | done | — |
| WebView2Package | done | done |
| Assert package | done | — |
| CustomControls / CustomControlsPackage | done | — |
| cefPackage (CEF) | done | done |
| WinEventLogLib | done | — |
| WinNamedPipesLib | done | — |
| WinServicesLib | done | — |
| tbIDE | done | — |
| WinNativeCommonCtls | done | — |
The rest of this file is the maintenance guide for updating existing pages or adding new ones — high-level package surface notes, page templates, cross-section linking conventions, and the integrity check.
docs/Reference/Core/— language statements/keywords (Dim,For-Next,Sub, ...).docs/Reference/<Package>/<Mod>/— runtime library (VBA, VBRUN), grouped by modules.docs/Reference/<Package>/<Mod>/index.md— module landing page listing its members.docs/Reference/VB/<Class>.md— single-file class page. No current VB class uses this shape; all VB classes are folder-style.docs/Reference/VB/<Class>/index.md— folder-style class page (e.g.CheckBox/index.md,CheckMark/index.md).docs/Reference/WebView2/— WebView2 package: the WebView2 control class plus its small wrapper classes (request / response / headers / environment options) and thewv2…enumerations.docs/Reference/CustomControls/— CustomControls package: the eight Waynes… custom controls, their sharedStyles/helper classes (Fill,Borders,Corners,TextRendering, …), theFramework/DESIGNER surface (interfaces, CoClasses, theCanvas/SerializeInfoUDTs), and theEnumerations/(CornerShape,FillPattern,DockMode, …).docs/Reference/CEF/— CEF (Chromium Embedded Framework) package: the CefBrowser control, itsEnvironmentOptionssub-page, and the two user-facing enumerations (CefLogSeverity,cefPrintOrientation). This is a much smaller surface than WebView2 — the package is currently BETA and many WebView2-equivalent features are not yet exposed.docs/Reference/WinEventLogLib/— Windows Event Log package: the genericEventLog(Of T1, T2)class and theEventLogHelperPublicmodule with its singleRegisterEventLogInternalhelper. Three pages total —index.md,EventLog.md,EventLogHelperPublic.md.docs/Reference/WinNamedPipesLib/— Windows Named Pipes package: the IOCP-based async pipe framework —NamedPipeServer+NamedPipeServerConnectionon the server side,NamedPipeClientManager+NamedPipeClientConnectionon the client side. Five pages total (index.md+ one per class).docs/Reference/WinServicesLib/— Windows Services package: a thin OS-services wrapper.Services(predeclared singleton) coordinates one or moreServiceManagerconfigurations;ServiceCreator(Of T)is the generic factory the dispatcher uses to instantiate each user-definedITbServiceclass;ServiceStateis a read-only state snapshot for an installed service. Four public enums (ServiceTypeConstants,ServiceStartConstants,ServiceControlCodeConstants,ServiceStatusConstants) live underEnumerations/.docs/Reference/WinNativeCommonCtls/— Windows Native Common Controls compatibility package: a VB6-compatible Microsoft Common Controls 6.0 (MSCOMCTL.OCX) replacement, written on top of the Win32 ComCtl32 controls. Eight controls (DTPicker, ImageList, ListView, MonthView, ProgressBar, Slider, TreeView, UpDown), plus eight sub-object classes (ListImages / ListImage, ListItems / ListItem, ColumnHeaders / ColumnHeader, Nodes / Node) reached through container properties on the three collection-bearing controls, plus ~16 user-facing enumerations. Each control is a<Name>BaseCtl([COMCreatable(False)]) plus a thin<Name>leaf tagged[WindowsControl(...)]— the same split VB-package and CEF use.docs/Reference/tbIDE/— IDE Extensibility package (this is the addin SDK). The package is type-only — it ships public interfaces + CoClasses that an addin DLL binds to; every implementation behind them lives in the twinBASIC IDE itself. The user-facing surface is one entry-point factory (tbCreateCompilerAddin) plus ~20 CoClasses grouped by role: the addin contract (AddIn), the root API (Host), the loadedProject, the editors collection (Editor/CodeEditor/Editors), the virtual file system (FileSystem/FileSystemItem/Folder/File), the in-IDE UI surface (Toolbar/Toolbars/Button/ToolWindow/ToolWindows), the HTML DOM inside a tool window (HtmlElement/HtmlElements/HtmlElementProperty/HtmlElementProperties/HtmlEventProperty/HtmlEventProperties), theDebugConsole,KeyboardShortcuts,Themes, and the single concrete user-instantiable helper classAddinTimer. Flat layout — one page per CoClass / Class plus the index landing.docs/Reference/Statements.md— alphabetical index of language statements.docs/Reference/Procedures and Functions.md— alphabetical index of procedures/functions.docs/_includes/footer_custom.html— overrides the theme's footer slot; renders the copyright line and, whenvba_attribution: trueis set in a page's frontmatter, an additional CC-BY-4.0 attribution line beneath it.
Per-package content-shape references live in sibling files. Open the relevant one when updating an existing page or adding a new one; the actual rendered docs under docs/Reference/<Package>/ remain the source of truth.
- WebView2 Package — the
WebView2control + wrapper classes +wv2…enums. - Assert Package — three sibling modules (
Exact/Strict/Permissive) with identical 15-member APIs but different comparison semantics. - CustomControls Package — eight
Waynes…custom controls + sharedStyles/helpers +Framework/DESIGNER surface +Enumerations/. - CEF Package — the
CefBrowsercontrol +EnvironmentOptionssub-page + two enums; smaller surface than WebView2 (currently BETA). - WinEventLogLib Package — the generic
EventLog(Of T1, T2)class +EventLogHelperPublicmodule + the message-table backing pattern. - WinNamedPipesLib Package — IOCP-based async pipe framework: server + client manager + per-side connection classes + the
Cookie/ transient-Data()/ManualMessageLoopidioms. - WinServicesLib Package — thin OS-services wrapper:
Servicessingleton +ServiceManager+ServiceCreator(Of T)+ServiceState+ITbService+ four enums. - tbIDE Package — the addin SDK (type-only compiler package): 23 CoClasses +
AddinTimer+ the HTML/DOM[COMExtensible]surface + samples 10–15 idiom map. - WinNativeCommonCtls Package — VB6-compatible
MSCOMCTL.OCXreplacement: 8 controls + 8 sub-objects + per-control nested enums + 10 module-level enums.
The three "winlibs" packages — WinServicesLib, WinEventLogLib, and WinNamedPipesLib — share an essential set of integration idioms: composition-delegation on EventLog(Of …), the ManualMessageLoopEnter / Leave pattern coupling NamedPipeServer to a service's ChangeState handler, and PropertyBag as the canonical pipe payload. When working on any of the three, check the other two for cross-references.
Match the existing style. Worked examples to imitate:
- Core statement:
docs/Reference/Core/Const.md,docs/Reference/Core/Dim.md,docs/Reference/Core/Call.md. - VBA module function:
docs/Reference/VBA/Interaction/AppActivate.md,docs/Reference/VBA/Interaction/Beep.md. - VBA property with
Core/redirect:docs/Reference/VBA/DateTime/Date.md. - VBRUN module member:
docs/Reference/VBRUN/AmbientProperties/BackColor.md,docs/Reference/VBRUN/PropertyBag/index.md. - VB control class (folder-style; all current VB classes):
docs/Reference/VB/CheckBox/index.md,docs/Reference/VB/CheckMark/index.md. - Assert module page (single-file, all members inline):
docs/Reference/Assert/Exact.md. - CEF control class (folder-style with a sub-page):
docs/Reference/CEF/CefBrowser/index.md+docs/Reference/CEF/CefBrowser/EnvironmentOptions.md. - Generic class (single-file,
(Of T1, T2)):docs/Reference/WinEventLogLib/EventLog.md. - Folder-style control with collection sub-objects: pattern to follow for WinNativeCommonCtls's
ImageList/,ListView/,TreeView/—<Container>/index.mdfor the control's own surface plus sibling<Container>/<SubObject>.mdper collection / item. Mirror CustomControls'sWaynesButton/+WaynesButton/WaynesButtonState.mdshape.
Skeleton:
---
title: <Symbol>
parent: <Statements | Procedures and Functions | <Mod> Module | <Package> Package>
# Pick the permalink that matches the section:
# Core → /tB/Core/<Symbol>
# VBA module → /tB/Modules/<Mod>/<Symbol> (legacy URL scheme retained)
# VBRUN module → /tB/Packages/VBRUN/<Mod>/<Symbol>
# VB class → /tB/Packages/VB/<Class> (or /tB/Packages/VB/<Class>/ for folder-style)
# WinNativeCommonCtls control → /tB/Packages/WinNativeCommonCtls/<Class> (single-file)
# or /tB/Packages/WinNativeCommonCtls/<Container>/ (folder-style)
permalink: /tB/Core/<Symbol>
redirect_from: # only if relocated; e.g. moved from Core/ to a Module/
- /tB/Core/<Symbol>
vba_attribution: true # omit for VB package pages (fully original content)
---
# <Symbol>
{: .no_toc }
<one-line description>
Syntax: **<Symbol>** [ *args* ]
*arg1*
: *required* | *optional* description.
<remarks paragraphs>
### Example
This example...
```tb
' code
```
### See Also
- [Other](OtherSymbol)Formatting conventions:
**...**for keywords/literal tokens;*...*for placeholders/arguments.- Code blocks use
```tb(the twinBASIC lexer registered indocs/_plugins/twinbasic.rb). - Parameter lists use the kramdown
term+: definitionindentation pattern (NOT the MS-style markdown table). - Set
vba_attribution: truein the frontmatter on any page derived from VBA-Docs; omit it on fully original content (e.g. VB package pages). The flag drives an extra line in the site footer.
Relative links resolve against the rendered URL (the page's permalink:), not the file path. Pages that share a URL folder can use bare names ([Y](Y)); crossing folders needs ../ to climb out.
The URL prefixes are not uniform across packages — VBA pages live one segment shallower than VBRUN pages, so cross-package links are asymmetric:
- Core statement →
/tB/Core/<Symbol> - VBA module member →
/tB/Modules/<Mod>/<Symbol>(legacy scheme retained) - VBRUN module member →
/tB/Packages/VBRUN/<Mod>/<Symbol> - VB class →
/tB/Packages/VB/<Class>, or/tB/Packages/VB/<Class>/for folder-style classes (one extra segment) - WebView2 class →
/tB/Packages/WebView2/<Class>(or/tB/Packages/WebView2/<Class>/for folder-style — used byWebView2/) - WebView2 enumeration →
/tB/Packages/WebView2/Enumerations/<Enum>(one segment deeper than a class, parallel to VBRUN'sConstants/<Enum>) - Assert module →
/tB/Packages/Assert/<Mod>(single-page-per-module; same depth as a single-file VB class) - CustomControls control →
/tB/Packages/CustomControls/<Control>(single-file) or/tB/Packages/CustomControls/<Control>/(folder-style — used byWaynesButton/,WaynesForm/,WaynesGrid/,WaynesSlider/,WaynesTextBox/) - CustomControls style helper →
/tB/Packages/CustomControls/Styles/<Name> - CustomControls framework symbol →
/tB/Packages/CustomControls/Framework/<Name> - CustomControls enumeration →
/tB/Packages/CustomControls/Enumerations/<Enum> - CEF
CefBrowserclass →/tB/Packages/CEF/CefBrowser/(folder-style — has theEnvironmentOptionssub-page) - CEF
EnvironmentOptionssub-page →/tB/Packages/CEF/CefBrowser/EnvironmentOptions - CEF enumeration →
/tB/Packages/CEF/Enumerations/<Enum> - WinEventLogLib class →
/tB/Packages/WinEventLogLib/EventLog(single-file; same depth as a single-file VB class) - WinEventLogLib module →
/tB/Packages/WinEventLogLib/EventLogHelperPublic(single-file; same depth as an Assert module) - WinNamedPipesLib class →
/tB/Packages/WinNamedPipesLib/<Class>(single-file; same depth as a single-file VB class) - WinServicesLib class / interface →
/tB/Packages/WinServicesLib/<Class>(single-file; same depth as a single-file VB class) - WinServicesLib enumeration →
/tB/Packages/WinServicesLib/Enumerations/<Enum>(one segment deeper, parallel to WebView2 / CEF / CustomControls) - tbIDE class / CoClass →
/tB/Packages/tbIDE/<Class>(single-file; same depth as a single-file VB class — noEnumerations/sub-folder, the nested enums live on their declaring class's page) - WinNativeCommonCtls control →
/tB/Packages/WinNativeCommonCtls/<Class>(single-file — used byDTPicker,MonthView,ProgressBar,Slider,UpDown) or/tB/Packages/WinNativeCommonCtls/<Container>/(folder-style — used byImageList/,ListView/,TreeView/, each carrying their sub-object companion pages) - WinNativeCommonCtls sub-object →
/tB/Packages/WinNativeCommonCtls/<Container>/<SubObject>(e.g.ImageList/ListImage,ListView/ListItem,TreeView/Node) - WinNativeCommonCtls enumeration →
/tB/Packages/WinNativeCommonCtls/Enumerations/<Enum>(one segment deeper, parallel to WebView2 / CEF / CustomControls / WinServicesLib)
Common patterns:
| From | To | Link |
|---|---|---|
| any page | sibling in same URL folder | [Y](Y) |
VBA Modules/<Mod>/X |
VBA Modules/<OtherMod>/Y |
[Y](../<OtherMod>/Y) |
VBA Modules/<Mod>/X |
Core/Y |
[Y](../../Core/Y) |
VBA Modules/<Mod>/X |
VBRUN Packages/VBRUN/<Mod>/Y |
[Y](../../Packages/VBRUN/<Mod>/Y) |
VBA Modules/<Mod>/X |
VB Packages/VB/Y |
[Y](../../Packages/VB/Y) |
VBA Modules/<Mod>/X |
WebView2 Packages/WebView2/Y |
[Y](../../Packages/WebView2/Y) |
VBRUN Packages/VBRUN/<Mod>/X |
VBRUN Packages/VBRUN/<OtherMod>/Y |
[Y](../<OtherMod>/Y) |
VBRUN Packages/VBRUN/<Mod>/X |
Core/Y |
[Y](../../../Core/Y) |
VBRUN Packages/VBRUN/<Mod>/X |
VBA Modules/<Mod>/Y |
[Y](../../../Modules/<Mod>/Y) |
VBRUN Packages/VBRUN/<Mod>/X |
WebView2 Packages/WebView2/Y |
[Y](../../WebView2/Y) |
VB Packages/VB/X (single-file) |
VB Packages/VB/Y (sibling) |
[Y](Y) |
VB Packages/VB/X (single-file) |
VBRUN Packages/VBRUN/<Mod>/Y |
[Y](../VBRUN/<Mod>/Y) |
VB Packages/VB/X (single-file) |
Core/Y |
[Y](../../Core/Y) |
VB Packages/VB/<Class>/index |
VB Packages/VB/<OtherClass> |
[Y](../<OtherClass>) |
VB Packages/VB/<Class>/index |
VBRUN Packages/VBRUN/<Mod>/Y |
[Y](../../VBRUN/<Mod>/Y) |
VB Packages/VB/<Class>/index |
Core/Y |
[Y](../../../Core/Y) |
WebView2 Packages/WebView2/X (single-file) |
sibling Packages/WebView2/Y |
[Y](Y) |
WebView2 Packages/WebView2/X (single-file) |
Packages/WebView2/Enumerations/Y |
[Y](Enumerations/Y) |
WebView2 Packages/WebView2/X (single-file) |
VBRUN Packages/VBRUN/<Mod>/Y |
[Y](../VBRUN/<Mod>/Y) |
WebView2 Packages/WebView2/X (single-file) |
VB Packages/VB/Y |
[Y](../VB/Y) |
WebView2 Packages/WebView2/X (single-file) |
Core/Y |
[Y](../../Core/Y) |
WebView2 Packages/WebView2/<Class>/index |
sibling Packages/WebView2/Y |
[Y](../Y) |
WebView2 Packages/WebView2/<Class>/index |
Packages/WebView2/Enumerations/Y |
[Y](../Enumerations/Y) |
WebView2 Packages/WebView2/<Class>/index |
VBRUN Packages/VBRUN/<Mod>/Y |
[Y](../../VBRUN/<Mod>/Y) |
WebView2 Packages/WebView2/<Class>/index |
Core/Y |
[Y](../../../Core/Y) |
WebView2 Packages/WebView2/Enumerations/X |
sibling Enumerations/Y |
[Y](Y) |
WebView2 Packages/WebView2/Enumerations/X |
Packages/WebView2/<Class> (single-file) |
[Y](../<Class>) |
Assert Packages/Assert/<Mod> |
sibling Packages/Assert/<OtherMod> |
[Y](<OtherMod>) |
Assert Packages/Assert/<Mod> |
VBRUN Packages/VBRUN/<Mod>/Y |
[Y](../VBRUN/<Mod>/Y) |
Assert Packages/Assert/<Mod> |
VBA Modules/<Mod>/Y |
[Y](../../Modules/<Mod>/Y) |
Assert Packages/Assert/<Mod> |
Core/Y |
[Y](../../Core/Y) |
CC Packages/CustomControls/X (single-file) |
sibling Packages/CustomControls/Y |
[Y](Y) |
CC Packages/CustomControls/X (single-file) |
Packages/CustomControls/Styles/Y |
[Y](Styles/Y) |
CC Packages/CustomControls/X (single-file) |
Packages/CustomControls/Framework/Y |
[Y](Framework/Y) |
CC Packages/CustomControls/X (single-file) |
Packages/CustomControls/Enumerations/Y |
[Y](Enumerations/Y) |
CC Packages/CustomControls/X (single-file) |
VB Packages/VB/Y |
[Y](../VB/Y) |
CC Packages/CustomControls/X (single-file) |
Core/Y |
[Y](../../Core/Y) |
CC Packages/CustomControls/<Control>/index |
sibling Packages/CustomControls/Y |
[Y](../Y) |
CC Packages/CustomControls/<Control>/index |
Packages/CustomControls/Styles/Y |
[Y](../Styles/Y) |
CC Packages/CustomControls/<Control>/index |
Packages/CustomControls/Enumerations/Y |
[Y](../Enumerations/Y) |
CC Packages/CustomControls/<Control>/index |
Core/Y |
[Y](../../../Core/Y) |
CC Packages/CustomControls/Styles/X |
sibling Styles/Y |
[Y](Y) |
CC Packages/CustomControls/Styles/X |
Packages/CustomControls/<Control> (single-file) |
[Y](../<Control>) |
CC Packages/CustomControls/Styles/X |
Packages/CustomControls/Enumerations/Y |
[Y](../Enumerations/Y) |
CC Packages/CustomControls/Styles/X |
Core/Y |
[Y](../../../Core/Y) |
CC Packages/CustomControls/Framework/X |
sibling Framework/Y |
[Y](Y) |
CC Packages/CustomControls/Framework/X |
Packages/CustomControls/<Control> (single-file) |
[Y](../<Control>) |
CC Packages/CustomControls/Enumerations/X |
sibling Enumerations/Y |
[Y](Y) |
CC Packages/CustomControls/Enumerations/X |
Packages/CustomControls/<Control> (single-file) |
[Y](../<Control>) |
CEF Packages/CEF/index |
CEF Packages/CEF/CefBrowser/ |
[Y](CefBrowser/) |
CEF Packages/CEF/index |
CEF Packages/CEF/Enumerations/Y |
[Y](Enumerations/Y) |
CEF Packages/CEF/index |
WebView2 Packages/WebView2/Y |
[Y](../WebView2/Y) |
CEF Packages/CEF/CefBrowser/index |
CEF Packages/CEF/CefBrowser/EnvironmentOptions |
[Y](EnvironmentOptions) |
CEF Packages/CEF/CefBrowser/index |
CEF Packages/CEF/Enumerations/Y |
[Y](../Enumerations/Y) |
CEF Packages/CEF/CefBrowser/index |
WebView2 Packages/WebView2/Y |
[Y](../../WebView2/Y) |
CEF Packages/CEF/CefBrowser/index |
VB Packages/VB/Y |
[Y](../../VB/Y) |
CEF Packages/CEF/CefBrowser/index |
Core/Y |
[Y](../../../Core/Y) |
CEF Packages/CEF/CefBrowser/EnvironmentOptions |
CEF Packages/CEF/CefBrowser/ (parent) |
[Y](.) |
CEF Packages/CEF/CefBrowser/EnvironmentOptions |
CEF Packages/CEF/Enumerations/Y |
[Y](../Enumerations/Y) |
CEF Packages/CEF/Enumerations/X |
sibling Enumerations/Y |
[Y](Y) |
CEF Packages/CEF/Enumerations/X |
CEF Packages/CEF/CefBrowser/ (folder-style) |
[Y](../CefBrowser/) |
CEF Packages/CEF/Enumerations/X |
CEF Packages/CEF/CefBrowser/EnvironmentOptions |
[Y](../CefBrowser/EnvironmentOptions) |
WinEventLogLib Packages/WinEventLogLib/X |
sibling Packages/WinEventLogLib/Y |
[Y](Y) |
WinEventLogLib Packages/WinEventLogLib/X |
VBA Modules/<Mod>/Y |
[Y](../../Modules/<Mod>/Y) |
WinEventLogLib Packages/WinEventLogLib/X |
Core/Y |
[Y](../../Core/Y) |
WinNamedPipesLib Packages/WinNamedPipesLib/X |
sibling Packages/WinNamedPipesLib/Y |
[Y](Y) |
WinNamedPipesLib Packages/WinNamedPipesLib/X |
VBA Modules/<Mod>/Y |
[Y](../../Modules/<Mod>/Y) |
WinNamedPipesLib Packages/WinNamedPipesLib/X |
Core/Y |
[Y](../../Core/Y) |
WinNamedPipesLib Packages/WinNamedPipesLib/X |
WinServicesLib Packages/WinServicesLib/Y |
[Y](../WinServicesLib/Y) |
WinNamedPipesLib Packages/WinNamedPipesLib/X |
WinEventLogLib Packages/WinEventLogLib/Y |
[Y](../WinEventLogLib/Y) |
WinServicesLib Packages/WinServicesLib/X (single-file) |
sibling Packages/WinServicesLib/Y |
[Y](Y) |
WinServicesLib Packages/WinServicesLib/X (single-file) |
Packages/WinServicesLib/Enumerations/Y |
[Y](Enumerations/Y) |
WinServicesLib Packages/WinServicesLib/X (single-file) |
WinEventLogLib Packages/WinEventLogLib/Y |
[Y](../WinEventLogLib/Y) |
WinServicesLib Packages/WinServicesLib/X (single-file) |
WinNamedPipesLib Packages/WinNamedPipesLib/Y |
[Y](../WinNamedPipesLib/Y) |
WinServicesLib Packages/WinServicesLib/X (single-file) |
VBRUN Packages/VBRUN/<Mod>/Y |
[Y](../VBRUN/<Mod>/Y) |
WinServicesLib Packages/WinServicesLib/X (single-file) |
Core/Y |
[Y](../../Core/Y) |
WinServicesLib Packages/WinServicesLib/Enumerations/X |
sibling Enumerations/Y |
[Y](Y) |
WinServicesLib Packages/WinServicesLib/Enumerations/X |
Packages/WinServicesLib/<Class> |
[Y](../<Class>) |
WinServicesLib Packages/WinServicesLib/Enumerations/X |
WinEventLogLib Packages/WinEventLogLib/Y |
[Y](../../WinEventLogLib/Y) |
tbIDE Packages/tbIDE/X |
sibling Packages/tbIDE/Y |
[Y](Y) |
tbIDE Packages/tbIDE/X |
VBA Modules/<Mod>/Y |
[Y](../../Modules/<Mod>/Y) |
tbIDE Packages/tbIDE/X |
VBRUN Packages/VBRUN/<Mod>/Y |
[Y](../VBRUN/<Mod>/Y) |
tbIDE Packages/tbIDE/X |
VB Packages/VB/Y |
[Y](../VB/Y) |
tbIDE Packages/tbIDE/X |
Core/Y |
[Y](../../Core/Y) |
WNCC Packages/WinNativeCommonCtls/X (single-file) |
sibling Packages/WinNativeCommonCtls/Y |
[Y](Y) |
WNCC Packages/WinNativeCommonCtls/X (single-file) |
Packages/WinNativeCommonCtls/<Container>/ (folder-style) |
[Y](<Container>/) |
WNCC Packages/WinNativeCommonCtls/X (single-file) |
Packages/WinNativeCommonCtls/Enumerations/Y |
[Y](Enumerations/Y) |
WNCC Packages/WinNativeCommonCtls/X (single-file) |
VBRUN Packages/VBRUN/<Mod>/Y |
[Y](../VBRUN/<Mod>/Y) |
WNCC Packages/WinNativeCommonCtls/X (single-file) |
VB Packages/VB/Y |
[Y](../VB/Y) |
WNCC Packages/WinNativeCommonCtls/X (single-file) |
Core/Y |
[Y](../../Core/Y) |
WNCC Packages/WinNativeCommonCtls/<Container>/index |
sibling Packages/WinNativeCommonCtls/Y (single-file) |
[Y](../Y) |
WNCC Packages/WinNativeCommonCtls/<Container>/index |
Packages/WinNativeCommonCtls/<OtherContainer>/ |
[Y](../<OtherContainer>/) |
WNCC Packages/WinNativeCommonCtls/<Container>/index |
Packages/WinNativeCommonCtls/Enumerations/Y |
[Y](../Enumerations/Y) |
WNCC Packages/WinNativeCommonCtls/<Container>/index |
VBRUN Packages/VBRUN/<Mod>/Y |
[Y](../../VBRUN/<Mod>/Y) |
WNCC Packages/WinNativeCommonCtls/<Container>/index |
Core/Y |
[Y](../../../Core/Y) |
WNCC Packages/WinNativeCommonCtls/<Container>/<Sub> |
sibling <Container>/<OtherSub> |
[Y](<OtherSub>) |
WNCC Packages/WinNativeCommonCtls/<Container>/<Sub> |
parent <Container>/ (index) |
[Y](.) |
WNCC Packages/WinNativeCommonCtls/<Container>/<Sub> |
sibling control (single-file) | [Y](../<OtherControl>) |
WNCC Packages/WinNativeCommonCtls/<Container>/<Sub> |
Packages/WinNativeCommonCtls/Enumerations/Y |
[Y](../Enumerations/Y) |
WNCC Packages/WinNativeCommonCtls/Enumerations/X |
sibling Enumerations/Y |
[Y](Y) |
WNCC Packages/WinNativeCommonCtls/Enumerations/X |
Packages/WinNativeCommonCtls/<Class> (single-file) |
[Y](../<Class>) |
WNCC Packages/WinNativeCommonCtls/Enumerations/X |
Packages/WinNativeCommonCtls/<Container>/ (folder-style) |
[Y](../<Container>/) |
WinEventLogLib Packages/WinEventLogLib/X |
WinServicesLib Packages/WinServicesLib/Y |
[Y](../WinServicesLib/Y) |
WinEventLogLib Packages/WinEventLogLib/X |
WinNamedPipesLib Packages/WinNamedPipesLib/Y |
[Y](../WinNamedPipesLib/Y) |
Core/X |
VBA Modules/<Mod>/Y |
[Y](../Modules/<Mod>/Y) |
Core/X |
VBRUN Packages/VBRUN/<Mod>/Y |
[Y](../Packages/VBRUN/<Mod>/Y) |
Core/X |
VB Packages/VB/Y |
[Y](../Packages/VB/Y) |
Core/X |
WebView2 Packages/WebView2/Y |
[Y](../Packages/WebView2/Y) |
Core/X |
Assert Packages/Assert/<Mod> |
[Y](../Packages/Assert/<Mod>) |
Core/X |
CC Packages/CustomControls/Y |
[Y](../Packages/CustomControls/Y) |
Core/X |
CEF Packages/CEF/Y |
[Y](../Packages/CEF/Y) |
Core/X |
WinEventLogLib Packages/WinEventLogLib/Y |
[Y](../Packages/WinEventLogLib/Y) |
Core/X |
WinNamedPipesLib Packages/WinNamedPipesLib/Y |
[Y](../Packages/WinNamedPipesLib/Y) |
Core/X |
WinServicesLib Packages/WinServicesLib/Y |
[Y](../Packages/WinServicesLib/Y) |
Core/X |
tbIDE Packages/tbIDE/Y |
[Y](../Packages/tbIDE/Y) |
Core/X |
WNCC Packages/WinNativeCommonCtls/Y |
[Y](../Packages/WinNativeCommonCtls/Y) |
Core/X |
Core/Y (sibling) |
[Y](Y) |
Always link to the canonical location (the page's permalink:), not to a redirect_from alias. Pages that have moved out of Core/ retain a redirect_from: /tB/Core/<X> so legacy links still work, but forward-style links should point at the new home.
- Decide placement — pick the package's section convention:
- Pure language keyword (parsed by the compiler, no runtime call) →
docs/Reference/Core/. - Runtime function/property →
docs/Reference/<Package>/<Mod>/. Addredirect_from: /tB/Core/<name>so legacytB/Core/<name>links still work. - VB control class →
docs/Reference/VB/<Class>.md(single-file) ordocs/Reference/VB/<Class>/index.md(folder-style). - WebView2 →
docs/Reference/WebView2/<Class>.md(single-file) or<Class>/index.md(folder-style; the mainWebView2class uses it). Enums underWebView2/Enumerations/; the one public Type underWebView2/Types/. - Assert module →
docs/Reference/Assert/<Mod>.md— one page per module with all 15 members inline. - CustomControls control → single-file under
docs/Reference/CustomControls/<Control>.md, or folder-style when a state-holder / options sub-page is required. Shared style helpers underStyles/, framework symbols underFramework/, enums underEnumerations/. - CEF →
docs/Reference/CEF/CefBrowser/index.md(folder-style with theEnvironmentOptionssub-page); enums underCEF/Enumerations/. - WinEventLogLib / WinNamedPipesLib / WinServicesLib → flat, one page per public class under
docs/Reference/<Pkg>/<Class>.md. WinServicesLib enums live underWinServicesLib/Enumerations/. - tbIDE → flat, one page per CoClass / Class under
docs/Reference/tbIDE/<Class>.md; nested enums fold onto their declaring class's page (noEnumerations/sub-folder). - WinNativeCommonCtls → single-file (
DTPicker,MonthView,ProgressBar,Slider,UpDown) or folder-style (ImageList/,ListView/,TreeView/) when the control has sub-object companions. Module-level enums underEnumerations/; per-control nested enums fold onto the declaring control's page. - Pick
<Mod>from VBA's grouping (Information, Interaction, Strings, FileSystem, DateTime, Math, Financial, Conversion, ...) and the existing folders underReference/<Package>/.
- Pure language keyword (parsed by the compiler, no runtime call) →
- Flag tB deviations with a
> [!NOTE]callout (see next section). - Update the parent index — turn an unlinked bullet into a link with a short blurb. Match the existing style of the page. If a new package is being added, also extend
docs/Reference/Packages.mdto list it. - Add the page to
Reference/Statements.mdorReference/Procedures and Functions.mdif it's a statement or callable and not already listed there. - Run the site integrity check after the batch and before committing.
Add a > [!NOTE] callout or rewrite the affected section when source diverges. Known cases:
Date,Date$,Time,Time$are properties in twinBASIC, not functions/statements — seedocs/Reference/VBA/DateTime/Date.mdfor the pattern.Decimaldata type is reserved but not currently supported. Note where applicable.- twinBASIC adds
Continue, attribute syntax[Documentation("...")], and other features documented underdocs/Features/. - Some VBA-Docs pages have Office-host-specific Application objects — irrelevant; omit.
- Mac-specific notes from VBA-Docs are typically irrelevant; trim.
When in doubt about a tB-specific behavior, check docs/Features/ and docs/Reference/index.md before assuming VBA semantics carry over.
The audience is international: standard-English readers worldwide, often non-native, who may not parse idiomatic software-developer jargon. Use plain English in reference and tutorial prose.
The guiding principle: replace metaphors imported from outside programming; keep vocabulary with a specific technical meaning inside Win32 / COM / event-driven programming. If a phrase is the kind of thing a reader would have to look up in a tech blog, it doesn't belong in reference prose.
The vocabulary tables further down cover word choice. The rules in this subsection cover sentence shape and voice — the structural side of writing for an international audience.
-
Page opening. One-sentence verb-phrase summary directly under the H1, in present tense, no preamble. Good: "Activates an application window." / "Writes an Error-type entry to the log." Avoid: "The Const statement is used to declare constants in place of literal values." For class pages, a noun-phrase descriptor is acceptable — "A CheckBox is a Win32 native control that displays..."
-
Voice and tense. Active voice by default. Passive only for subjectless operations where there is no obvious agent — "the entry is written", "the constant is private by default". Present tense for behavior —
returns, notwill return. Don't give the class human traits: it doesn't "decide", "want", or "know" — it returns, raises, contains. -
Sentence shape. One idea per sentence. Prefer two short sentences over one compound sentence with nested clauses. Em-dash (
—) for parenthetical asides; reserve parentheses for code-ish notation like(default). -
Person and pronouns. Reference body prose uses third-person impersonal — "the constant", "the source", "the entry". Rewrite "you" to the impersonal form even in VBA-derived pages. "You" is acceptable inside
Examplelead-ins and in tutorial prose. Avoid first-person ("we", "I"). -
Parameter descriptions. Italic
*required*/*optional*flag, then a short prose description. Lead with the type when it matters — "A String naming the source...", "A T1 value naming the event...". Don't restate the parameter's name inside its own definition. Property setters omit the flag — the[ = *value* ]brackets on the syntax line carry that information. -
Callout severity. Three severity levels, used distinctly:
> [!NOTE]— twinBASIC-vs-VBA deviations, behavior clarifications, useful caveats. Not for marketing/why-bother prose — that should be a plain paragraph.> [!IMPORTANT]— requirements that affect correctness: admin rights, threading constraints, ordering.> [!WARNING]— operations that can corrupt state or lose data.
One callout per concern; don't stack a NOTE and an IMPORTANT for the same point.
-
See Also. Last section on the page, after
Example. Format:- [Symbol](Symbol) <noun>where<noun>is the kind: statement, function, property, method, class, module, package. Pages with annotations use- [Symbol](Symbol) -- short description— the--source renders as a typographic dash via kramdown's smart_quotes (en-dash for--, em-dash for---). Don't write literal—in source; keep--for consistency across the docs. Order by conceptual proximity, not strict alphabetical.
| Term | Use instead |
|---|---|
at rest (idle state) |
idle, in its default state |
bake in / baked into |
embedded, stored, included |
broker (as verb) |
manages, handles |
carry / carries (figurative) |
has, contains, includes |
catches up |
resumes, processes the queue |
comes up (a connection) |
is established, becomes ready |
drive / driven (figurative) |
controlled by, determined by, powered by |
for free (figurative) |
as a side effect, without extra effort |
hand off / hand over / hand back |
returns, passes, delivers |
hand-rolled |
manually constructed, custom-built |
handful / handy |
a few; useful |
heavy hitter / heavy-hitter (figurative) |
main items, biggest items, most significant |
in flight / in-flight |
pending, in progress |
in one shot |
in a single call |
in order to |
to |
kick off / kicks off |
start, begin |
land (figurative — "where the call lands") |
appears, arrives, ends up at |
leverage / leveraging |
use, take advantage of |
load-bearing (figurative) |
essential, critical, central |
mid-call |
during the call |
on the wire |
transmitted, over the network |
orchestration / orchestrate |
coordination, manual handling |
picks up (figurative) |
receives, reads, captures, inherits |
pinned to (UI layout) |
attached to, fixed to |
reach for / reaching into |
use, access |
sensible defaults |
reasonable defaults, or list them inline |
spin up / spins up |
start, create |
stash (as verb) |
store, save |
sticks (figurative — "the zoom sticks") |
is preserved, is retained |
surface (as verb) |
expose, appear as, make available, raise |
surface area (figurative API surface) |
set of members, interface |
swallow (a keystroke) |
consume, discard |
taps into |
hooks into, intercepts |
tear down (figurative) |
destroy, unload |
twirling (UI animation) |
spinning, or describe concretely |
under the hood |
internally |
utilize |
use |
walk (as verb — "walk the chain", "walks the children") |
traverse, go through, iterate over |
walks through (tutorials) |
demonstrates, explains, describes step by step |
wire up / wired up / wired in |
connect, attach, link |
Vague compliments that add no information. Be concrete instead — if a feature is fast, say what it is faster than; if an API is small, say how many members it has.
powerfulrobustclean(as a vague compliment — "clean architecture"; literal uses like "clean shutdown" stay)rich(vague — "rich information"; literal "Rich Text Format" stays)easily(filler — "easily share" → "share")
Don't over-correct these — they are precise technical vocabulary or otherwise pull their weight:
- Programming / Win32 / COM:
no-op,round-trip/round-tripping,fire-and-forget,marshal/marshalled(between threads),pump(messages) /message pump,spawn(a thread or process),mixin,first-class(type),boilerplate,falls through,short-circuit,idiom/idiomatic,canonical. - Standard prose:
ends up,modern,lightweight,talk to(interop),out of the box,on the fly,work around/workaround. - Vivid but tolerable:
ship/ships with,bridge(figurative),cascade(figurative),fold in(compile-time emit). - Audience-appropriate VB6 vocabulary:
drop it onto a form. - Marketing register (Videos section only — promotional copy):
sneak peek,game-changing,drop-in,seamless.
Some kept terms are referenced by in-doc anchors. The most prominent is idiom / idiomatic — WIP.WinEventLogLib.md and WIP.WinServicesLib.md reference anchors like #service-host-idiom and #composition-delegation-idiom. Don't rename these casually; if you do, add redirect_from aliases to preserve legacy links.
Kramdown's smart_quotes feature (enabled by default in this site) converts the ASCII source forms to typographic characters at build time:
| Source | Rendered | Use for |
|---|---|---|
-- |
en-dash – |
bullet-list separator (rule 7), ranges |
--- |
em-dash — |
parenthetical asides (rule 3), breaks in thought |
The source uses the ASCII forms; the rendered HTML uses the typographic characters. Literal – or — in docs/ markdown source is forbidden — see the Don'ts at the end of this file. scripts/convert_em_dash_separators.py is the canonical normaliser if any literals slip back in.
WIP.md itself (and other files outside docs/) is not part of the Jekyll site and is exempt — literal em-dashes here render directly in the GitHub viewer, which is fine.
Anything that participates in rendering the online site, the offline site, or the PDF book is handled by Jekyll — Liquid templates, includes, layouts, data files (_data/*.yml), and Ruby plugins under _plugins/. Build-time concerns tightly coupled to Jekyll's internal model (URL knowledge, page model, render order, site.data injection) belong in a _plugins/*.rb plugin. Existing examples: _plugins/offlinify.rb (the offline-site link rewriter) and _plugins/build-info.rb (the git commit-hash capture that stamps the PDF title page). Adding a Python pre-build step that writes a YAML file into _data/ and then invokes Jekyll is not the way — bundle exec jekyll build and book.bat must remain self-contained.
Python scripts are reserved for non-render concerns: one-off content conversion (e.g. scripts/convert_em_dash_separators.py), repo audits, dev tooling, link checks beyond check.bat, anything that runs outside a Jekyll build. They should never be a prerequisite for the render pipeline.
From docs/:
bundle exec jekyll build(orbuild.bat) — builds three trees in a single Jekyll run: the online copy at_site/, afile://-browsable copy at_site-offline/, and the sparse pagedjs source at_site-pdf/. The offline pass (_plugins/offlinify.rb, activated byalso_build_offline: truein_config.yml) adds ~3-5s and the PDF pass (_plugins/pdfify.rb, activated byalso_build_pdf: true) adds <1s on top of the normal ~13s build. The PDF plugin capturesbook.html's rendered output (the concatenated chapter document built via_layouts/book-combined.html) at:pages, :post_render, drops the page fromsite.pagesat:site, :post_renderso_site/book.htmlis never written, and at:site, :post_writewrites the captured bytes into_site-pdf/book.htmlalong withassets/css/print.css,assets/css/rouge.css, and every relative<img src=>target -- just what pagedjs needs to render the book PDF. The companionoffline_exclude: [..., book.html]entry in_config.ymlkeepsofflinify.rbfrom copying book.html into_site-offline/: offlinify's per-page hook fires before pdfify's:site, :post_render(Jekyll fires every per-page hook before any site-level post-render hook), so during offlinify's passbook.htmlis still insite.pagesand the exclude is what makes it skip writing the offline copy. Whenalso_build_pdf: falsethe exclude does the same job from a different angle -- pdfify never runs,book.htmlrenders normally to_site/, and the exclude still keeps it out of_site-offline/. After Jekyll's WRITE phase, the offline plugin walks_site/, copies binary assets verbatim into_site-offline/, and for each HTML and CSS file rewrites every root-absolutehref/src/url()to a page-relative path with the resolved file extension (/FAQ→../../FAQ.html,/Tutorials/CEF/→../../Tutorials/CEF/index.html). It also patches the offline copy ofassets/js/just-the-docs.jsin two places —navLink()to match the active nav entry by resolved DOMlink.hrefrather thandocument.location.pathname(the upstream pathname-vs-attribute compare returns no match underfile://, leaving the sidebar with no.activeclass so the nav appears collapsed on every navigation), andinitSearch()to read the lunr index fromwindow.SEARCH_DATArather than fetchingsearch-data.jsonoverXMLHttpRequest(XHR tofile://resources is blocked by browsers; classic<script src=>is not). To support that, the plugin (a) generates_site-offline/assets/js/search-data.jsonce per build by wrapping the renderedsearch-data.jsoninwindow.SEARCH_DATA = {...};, and (b) injects two<script>tags per page right beforejust-the-docs.js: one that setswindow.OFFLINE_SITE_ROOTto the per-page relative prefix to the offline site root, and one that loadssearch-data.js. The patchedinitSearch()rewrites everydoc.urlfrom a root-absolute permalink (/tB/Core/Const) to a page-relative path (<OFFLINE_SITE_ROOT>tB/Core/Const.html) so search-result clicks land on the actual file regardless of which page the user is on.bundle exec jekyll serve(orserve.bat) — local server atlocalhost:4000. Note that_site-offline/is also produced on the initial build, but live-reload only updates_site/; manual rebuild needed for offline updates.check.bat— link check (offlinescripts/check_links.mjsagainst_site/and_site-offline/; the offline pass also runs--forbid 'https://docs.twinbasic.com'to catch surviving live-site links).book.bat— renders the PDF from_site-pdf/book.htmlviapagedjs-cliinto_pdf/book.pdf. Runbuild.batfirst to populate_site-pdf/.
The HTML whitespace compression that wraps every page's render chain is handled by _plugins/html-compress.rb rather than the just-the-docs theme's vendor/compress.html Liquid layout — see _plugins/html-compress.md for the full writeup. The Liquid layout's per-page cost in the profile was ~2.4s of Liquid filter dispatch (a split: " " | join: " " over the outside-of-<pre> content, lowering to a per-page Array allocation of every whitespace-delimited token across 837 pages — millions of small String objects). The layout is short-circuited via compress_html.ignore.envs: all in _config.yml; it then outputs a bare {{ content }} and the plugin takes over at :pages, :post_render / :documents, :post_render with priority :normal, doing the same pre-block-protected whitespace collapse via content.split(PRE_BLOCK_RE).each { |s| s.split(" ").join(" ") } in C-implemented Ruby. The :normal priority is the middle tier of a three-level convention across the site's :post_render hooks: mutators (book-href-rewrite) run at :high, this cleanup pass at :normal, readers (pdfify, offlinify) at :low. The invariant "compress runs after every mutator and before every reader" therefore holds by construction; no downstream plugin has to be whitespace-aware. Pages whose layout chain doesn't reach vendor/compress are gated out via a :site, :pre_render precompute that walks site.layouts[name].data["layout"] for every layout key and marks the entire compress-reaching chain (default → table_wrappers → vendor/compress) -- jekyll-redirect-from stubs, the SCSS-derived CSS pages, and assets/js/zzzz-search-data.json all stay un-gated and pass through verbatim. book.html (which uses the minimal book-combined layout that has no parent) is also outside that chain but is explicitly added to the compress-eligible set at the end of the precompute, so the same whitespace collapse runs on it -- saves paged.js's render-time WhiteSpaceFilter ~37k DOM mutations (~28k textContent overwrites + ~9k removeChild calls) at the cost of ~480 ms once per Jekyll build. Output is byte-identical to the layout-based version: a recursive diff -rq of _site/ against a vendor/compress.html baseline reports zero differences across all ~840 HTML pages, 290 redirect stubs, every CSS / JSON / SVG / image asset. The plugin's correctness depended on two non-obvious details that broke an earlier cut -- the layout-chain walk has to compare against the layout key ("vendor/compress") rather than layout.name (which carries the .html extension), and the per-segment split(" ").join(" ") strips trailing whitespace that the Liquid layout's template re-adds via its trailing-newline source character, so the plugin captures content.end_with?("\n") before the split and re-appends a \n after the join. Both regressions surfaced as nonzero diff -rq counts during development and are flagged in the plugin's header comment and _plugins/html-compress.md.
Two profilers are wired in for diagnosing slow builds. Both run a full Jekyll build with all three trees (_site/, _site-offline/, _site-pdf/) and write results into _profile/out/ (gitignored).
profile-rbspy.bat— sampling profiler (99 Hz, ~1.7x slowdown). Writes_profile/out/jekyll-build.speedscope.json. Drop into speedscope.app for timeline / sandwich / left-heavy views — the closest thing to vernier's Firefox-profiler UI you can get on Windows. Requires_profile/rbspy.exe(gitignored, ~6 MB); on a fresh checkout grab it from rbspy's GitHub releases — therbspy-x86_64-pc-windows-msvc.exe.zipasset, renamed torbspy.exeand placed in_profile/.profile-rubyprof.bat— instrumentation profiler (TracePoint-based, ~2.2x slowdown). Writes_profile/out/callgrind.out.*(open in KCachegrind / QCachegrind), plusjekyll-build.flat.txtandjekyll-build.graph.txtfor quick text-based inspection. Activated bygem "ruby-prof", force_ruby_platform: truein the Gemfile — the platform-precompiled gem ships no.sofor Ruby 3.4+ onx64-mingw-ucrt, so the extension is built from source atbundle installtime.
The shared runner _profile/build.rb invokes Jekyll::Commands::Build.process({}) directly. rbspy's CreateProcess-based launcher on Windows cannot resolve the bundle.cmd / bundle.bat shims, so both wrappers spawn ruby.exe against this script rather than going through bundle exec. _profile/profile.rb wraps the same build.rb in a RubyProf::Profile. Neither wrapper auto-activates inside normal build.bat / serve.bat runs.
The first useful finding from a baseline profile: Offlinify#rewrite_html! is the single largest non-library hotspot (~6% self-time, ~3s of a ~30s instrumented build), with Offlinify#compute_relative a distant second; everything else is Liquid rendering (BlockBody#render, Context#evaluate, Variable#render) inside the Jekyll/Liquid stack itself.
After the html-compress plugin landed (vendor/compress.html short-circuited, the Ruby plugin doing the whitespace pass), the top per-filter costs on a ~39 s ruby-prof run break down as follows. Numbers from Liquid::Strainer#invoke's children in _profile/out/jekyll-build.graph.txt:
| Filter | Total time | Calls | µs/call |
|---|---|---|---|
markdownify |
4.605 s | 1,802 | 2,555 |
where_exp |
1.484 s | 37 | 40,108 |
replace |
0.606 s | 87,991 | 6.9 |
relative_url |
0.503 s | 11,417 | 44 |
absolute_url |
0.396 s | 1,675 | 236 |
normalize_whitespace |
0.329 s | 4,261 | 77 |
strip_html |
0.248 s | 8,261 | 30 |
Two structurally different outliers in that list:
markdownify(4.6 s / 11.7 % of build) -- 1,802 explicit| markdownifyfilter invocations across templates. Only three call sites:_includes/head_seo.html(page.title | markdownifyandsite.title | markdownify, ~1,674 calls),_includes/book-chapter-body.html(include.chapter.content | markdownify, ~100 of the 745 chapter passes -- most chapters' content starts with<and skips), andbook.html's part subtitle / intro (~24). Jekyll's markdown cache deduplicates these (3,146Converters::Markdown#convertcalls back only 1,975 actual kramdown parses), so the 4.6 s is mostly filter dispatch + cache-lookup overhead, not kramdown work. Of the 836 page titles, only 2 (*, *=and\, \=) contain markdown-active characters; the other 834 paths through themarkdownify | strip_html | normalize_whitespace | escape_oncepipeline reduce toescape_once(title).where_exp(1.5 s / 37 calls × 40 ms) -- ~40 ms per call is the per-element Liquid expression interpreter cost onsite.pages(~837 entries). All 37 calls come from_includes/book-collect-matches.html(thesite.pages | where_exp: "p", "p.url contains prefix"and"p.nav_path contains np"sweeps) and onebook.htmlsite (collected | where_exp: "p", "p.url != part_landing_url"to strip a landing page from the prefix sweep).
replace is the third bucket worth tracking: 87,991 calls but only 0.6 s -- ~7 µs per call. Of those, ~36 k come from _includes/book-chapter-body.html's heading-shift chains (12 replaces × 3 cascading shift passes) and anchor-id prefix replaces (13 replaces). The per-call cost is tiny but the volume adds up.
Ranked by estimated wall-clock saving on the current Windows machine:
-
book-collect-matches.html→ Ruby precompute. [LANDED] Moved everywhere_exp/where/concat/sort_by_nav_orderchain driven by_data/book.ymlinto a:site, :pre_renderplugin (_plugins/book-resolve-chapters.rb) that stashes the resolved chapter array on each front-matter entry / flat part / chaptered-part chapter.book.htmlreadsentry._chaptersdirectly;_includes/book-collect-matches.htmldeleted. TheJekyll::Filters#where_exprow disappears from ruby-prof's filter table (was 1.484 s / 37 calls), and the overallLiquid::Strainer#invoketotal dropped from 8.902 s to 6.687 s in instrumented runs.Wall-clock effect on the current Windows machine (5
--profileruns each, before/after; one outlier in the before set inflates its stddev):Phase / template Before (mean +- sd) After (mean +- sd) Delta RENDER total 11.93 +- 2.11 s 9.53 +- 0.12 s -2.40 s book.html1.68 +- 1.09 s 0.58 +- 0.03 s -1.10 s _includes/book-collect-matches.html0.71 +- 0.46 s 0.00 s (removed) -0.71 s _includes/book-chapter-body.html0.81 +- 0.51 s 0.51 +- 0.03 s -0.30 s The before-run stddevs are large because one of the 5 baseline runs was a clear 5 s outlier; outlier-excluded, the RENDER delta is closer to -1.4 s. The after-run stddev is tight across the same 5-run sample, so the speedup itself is robust at >1 s. Output is byte-identical to baseline (verified by
diff -rqon all three of_site/,_site-offline/,_site-pdf/). -
head_seo.htmlmarkdownify precompute. [LANDED] Moved the entire per-page derivation chain (markdownify | strip_html | normalize_whitespace | escape_oncefor page + site title,absolute_urlfor canonical,absolute_url | uri_escapefor logo, homepage URL test) into a:site, :pre_renderplugin (_plugins/seo-precompute.rb) that stashes the assembled values onpage.data["_seo_*"]andsite.config["_seo_*"].head_seo.htmlthen reads them back aspage._seo_full_title/site._seo_site_titleetc. via the Drop fallback to data/config -- no per-render filter dispatch.Ruby-prof effect (post-chapter-precompute baseline vs post-SEO-precompute, instrumented build):
Metric Before After Delta Total instrumented wall 39.28 s 36.90 s -2.38 s Liquid::Strainer#invoketotal6.69 s / 190,973 calls 5.97 s / 179,266 calls -0.72 s / -11,707 calls Jekyll::Filters#markdownifycalls1,802 128 -1,674 Jekyll::Filters#markdownifytotal4.61 s 3.69 s -0.92 s Jekyll::Filters::URLFilters#absolute_urlcalls1,675 1 -1,674 Liquid::BlockBody#rendertotal18.38 s 16.14 s -2.24 s Liquid::Context#stacktotal18.19 s 15.50 s -2.70 s Liquid::Variable#rendertotal10.05 s 8.96 s -1.09 s The
BlockBody#render/Context#stack/Variable#renderdrops reflect the eliminated{%- assign -%}/{%- if -%}blocks in head_seo.html (dropped from ~85 lines of Liquid logic to ~20 lines of straight output). The 128 remainingmarkdownifycalls come frombook.html's part subtitle/intro (~24) andbook-chapter-body.html's per-chapterchapter.content | markdownify(~100 chapters whose content doesn't start with<); both candidates for a follow-up pass (see #3). NewJekyll::SeoPrecompute#absolute_urladds 0.44 s for 846 calls, replacing 1,675 filter calls that totalled 0.40 s -- essentially flat, but the absolute_url filter had its own per-build cache, so the swap is a wash on this axis. Output byte-identical to baseline (diff -rqclean on all three of_site/,_site-offline/,_site-pdf/). -
book-chapter-body.htmlheading-shift + anchor-prefixreplacechain → Ruby pass. [LANDED] Replaced the per-chapter chain of 0-3 heading-shift cascades (12 replaces each), the 12-pattern whitespace span wrapping, and the 13-replace anchor-id prefix pass with a single Liquid filterbook_chapter_transform(_plugins/book-chapter-transform.rb). The filter takes the body, the site baseurl, a precomputedheading_shift_n(0-3, derived in Liquid fromskip_base_heading_shift/is_sub_page/extra_heading_shift), and the chapter anchor; does all seven passes in one method with no intermediate string allocations beyond what the regex engine produces internally (the seventh pass, added later, strips<details>/<summary>tags so collapsible sections like the FAQ render as flat content in the PDF). The deadp1_search/p1_replace/ ... whitespace-pattern declarations were also removed frombook.html's prologue.The single-pass heading shift (one regex bumping each level by N, capping at h7-stub for source levels above 6) is equivalent to N applications of the bottom-up cascade chain -- each source heading lands at
level + Norh7-stubregardless of how many sequential passes the chain ran, since the cascade structure was an artifact of Liquid not having a bump-by-N primitive, not a semantic requirement.Ruby-prof effect (post-SEO baseline vs post-chapter-transform):
Metric Before After Delta Total instrumented wall 36.90 s 34.78 s -2.12 s Liquid::Strainer#invoketotal5.97 s / 179,266 calls 5.45 s / 122,397 calls -0.52 s / -56,869 calls Liquid::StandardFilters#replacecalls87,991 48,577 -39,414 Liquid::StandardFilters#replacetotal0.58 s 0.33 s -0.25 s new BookChapterTransform#book_chapter_transform-- 0.14 s / 718 calls +0.14 s Liquid::BlockBody#rendertotal16.14 s 14.43 s -1.71 s Liquid::Context#stacktotal15.50 s 13.78 s -1.71 s Liquid::Variable#rendertotal8.96 s 7.82 s -1.14 s The Liquid framework drops (
BlockBody#render,Context#stack,Variable#render) again outweigh the filter-dispatch drop -- they capture the eliminated{%- unless -%}/{%- if -%}blocks plus the chained| replace:pipeline AST nodes. The new filter does ~190 µs per call across 718 invocations, covering the same work the eliminated 39 k Liquid replaces did. Output byte-identical to baseline (diff -rqclean on_site/,_site-offline/,_site-pdf/). -
JekyllGFMAdmonitions defer-body-parse. [LANDED] Extended
_plugins/jekyll-gfm-admonitions-patch.rbwith two method overrides onJekyllGFMAdmonitions::GFMAdmonitionConverter. The first replacesadmonition_htmlso the admonition body is spliced intodoc.contentas raw markdown inside a<div ... markdown='1'>wrapper, deferring the per-admonition@markdown.convert(text)call to the page-level kramdown pass (which already runs withparse_block_html: trueper_config.yml). One combined kramdown pass replaces 1 + N parses for each of the site's 508 admonitions. The second overridesprocess_docto preserve the leading newline(s) in the code-block stash placeholder substitution -- without this, the gem's(?:^|\n)(?<!>)\s*\``.*?```regex consumes the blank line between an admonition body and a following fenced code block, the placeholder ends up appended to the last>-prefixed body line, the admonition regex pulls it into the body capture, and either kramdown renders it as an empty` (gem behaviour) or the code block is spliced inside the admonition div (deferred-body behaviour). With the override, placeholders stay on their own line outside the body capture.Ruby-prof effect (post-CT baseline vs post-GFM-patch):
Metric Before After Delta GFMAdmonitionConverter#generatetotal0.690 s / 1 call 0.108 s / 1 call -0.582 s admonition_htmlcalls508 508 (same dispatch, now does only string concat) @markdown.convert(text)calls from admonition_html508 0 -508 Wall-clock effect on 3-run uninstrumented means (busy dev machine, but consistent within each set):
Phase Before After Delta done in ...total11.47 s 11.13 s -0.34 s GFMA: Generator ran in ...216 ms 93 ms -123 ms Output is not byte-identical to baseline: 12 files differ. Eleven are real bug fixes that were latent in the unpatched gem -- 5 pages had their fenced code block lost (the code-block-stash-eats-the-blank-line bug above;
Tutorials/Arrays.md,Tutorials/CustomControls/Painting.md,tB/Packages/WebView2/WebView2/index.md,tB/Packages/WinNamedPipesLib/NamedPipeClientConnection.md,tB/Packages/WinServicesLib/ServiceManager.md), 1 page had a\\\\source sequence collapsed to\\by the gem's second markdown pass (tB/Core/RightShift.md-- the body is now parsed once, so**\\\\**renders as<strong>\\</strong>not<strong>\</strong>), 1 page had its loose-list items rendered as<li>text</li>instead of CommonMark's<li><p>text</p></li>because the gem's pre-rendered admonition HTML changed the surrounding paragraph context (Documentation/Development.md), and the remaining 5 are cosmetic whitespace nits inside admonitions that themselves contain a fenced code block (tB/Core/If-Then-Else.md,tB/Core/Option.md,tB/Modules/Interaction/InputBox.md,tB/Packages/CEF/CefBrowser/index.md,tB/Packages/tbIDE/HtmlElement.md). The 12th file isassets/js/search-data.json, derived from page contents so it tracks them. Lychee link check is clean (8170 OK, 0 errorsfor online;6824 OK, 0 errorsfor offline).A separate investigation looked at
NavIntegrityCheck::Generator#generate(0.436 s / 1 call in the post-CT profile, attributed to 855Jekyll::FrontmatterDefaults#findwalks). The plugin usespage.data[key]fortitle/nav_exclude/parent/grand_parent, and Jekyll'sPage#initializesetsdata.default_proc = proc { site.frontmatter_defaults.find(...) }, so every missing key fell through to a full defaults walk. Switching todata.fetch(key, nil)bypasses the default_proc, but the resulting wall-clock delta is only ~50-80 ms: NavIntegrityCheck was warmingFrontmatterDefaults's internal@matched_set_cache(keyed bypath-type), andNavTreePrecompute::Generator#ordered_children_forwas the cache's biggest beneficiary. With NavIntegrityCheck skipping the walk, NavTreePrecompute pays the cache-miss cost itself -- ~430 ms moves from one stack to the other, leaving only the per-call dispatch overhead recovered. The patch was reverted.
The four landed optimizations together (chapter precompute, SEO precompute, chapter-body transform, GFM defer-body-parse) shrank ruby-prof's instrumented build wall from ~41.7 s (immediately post-html-compress baseline) down to ~34 s. The cumulative profile-table picture, comparing the post-html-compress baseline to the post-GFM state:
| Metric | Post-html-compress | Post-GFM | Delta |
|---|---|---|---|
| Total instrumented wall | 39.30 s | 34.78 s* | -4.52 s |
Liquid::Strainer#invoke total |
8.90 s / 191,365 calls | 5.45 s / 122,397 calls | -3.45 s / -68,968 calls |
where_exp calls |
37 | 0 | -37 |
markdownify calls |
1,802 | 128 | -1,674 |
absolute_url filter calls |
1,675 | 1 | -1,674 |
replace calls |
87,991 | 48,577 | -39,414 |
GFMAdmonitionConverter#generate total |
0.690 s | 0.108 s | -0.582 s |
Liquid::BlockBody#render total |
18.38 s | 14.43 s | -3.95 s |
Liquid::Context#stack total |
18.19 s | 13.78 s | -4.41 s |
* Instrumented totals are noisy on the current Windows dev machine (single-run range ~9 s across consecutive identical runs); the per-method numbers above are stable across runs and are the more reliable signal.
What's left of the per-filter table is approximately what kramdown / Rouge actually parse and emit: the 128 remaining markdownify calls are the per-chapter chapter.content | markdownify in book-chapter-body.html plus book.html's part subtitle / intro markdown. Each of those is unique input, so Jekyll's converter cache rarely hits and the kramdown parse itself dominates. Further savings on this axis would need either (a) reusing the already-rendered _site/<page>.html instead of re-parsing source markdown for the book, or (b) accepting kramdown's parse cost as the floor and looking elsewhere -- the next-biggest non-library hotspot is Offlinify#rewrite_html! at ~2 s of self-time, already heavily optimised (see _plugins/offlinify.md).
After a batch of changes, verify the site builds clean and all links resolve. From the docs/ folder, run:
build.bat && check.batcheck.bat runs scripts/check_links.mjs in offline mode against both _site/ and _site-offline/ — it catches broken intra-site links, missing pages, malformed redirect_from entries (the most common breakage when adding new pages or moving content between sections), and (via --forbid 'https://docs.twinbasic.com' on the offline pass) any extracted link that still points at the live docs site after the offlinify rewrite. A clean run is the bar for "ready to commit".
Requires build.bat to have produced an up-to-date _site/.
The build itself includes an additional guard: _plugins/nav-integrity-check.rb runs during the GENERATE phase and aborts the build if any nav-visible page has a parent: (or parent: + grand_parent:) that does not resolve to exactly one page in the nav tree. It catches two failure modes:
- Ambiguity — multiple pages share the title declared in
parent:andgrand_parent:is either absent or insufficient to disambiguate. The page would silently appear under every matching parent. - Orphan — no page has the title declared in
parent:. The page would silently disappear from the navigation sidebar.
Favor concise one-line git commit messages.
- Don't commit
.claude/orCLAUDE.md— both gitignored. (WIP.mdis committed;CLAUDE.mdis just a local@WIP.mdimport shim.) - Don't touch
_site/or_site-offline/(build outputs, gitignored). - Don't write literal en-dash
–or em-dash—indocs/markdown source. Use--(renders as en-dash) or---(renders as em-dash) — kramdown's smart_quotes does the conversion at build time.scripts/convert_em_dash_separators.pynormalises any strays. - Don't push or force-push without explicit user request.
- Don't invent semantics — read the relevant primary source before paraphrasing (VBA-Docs for VBA-derived pages; the package's
.twinsources for twinBASIC-specific ones). - Don't add boilerplate sections (Remarks, See Also) if the source has nothing meaningful for them.
- Never add
Co-Authored-By:(or any "Co-authored by" / "Generated with Claude" / similar) trailers to commit messages. Repository policy. Plain commit messages only.