Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
737243b
A minimal setup for PDF rendering.
KubaO May 15, 2026
e397c51
Render without justthedocs styling. Add custom styling for the print …
KubaO May 15, 2026
93a5dd4
Add basic book concatenator.
KubaO May 15, 2026
6552361
Add a simple build script, use local stylesheet links.
KubaO May 15, 2026
937046d
Remove the inapplicable markdownify filter.
KubaO May 15, 2026
3b62cfd
Work around the pagedjs space-squashing bug.
KubaO May 15, 2026
21dbcf2
Prevent markdown contents from spilling into the renderer.
KubaO May 16, 2026
4a39f9a
Write a plan for remaining work.
KubaO May 16, 2026
940846e
Refine the book structure.
KubaO May 16, 2026
e65cbf1
Add roman-numbered part heading pages and update the plan.
KubaO May 16, 2026
259ae31
Fix heading hierarchy.
KubaO May 16, 2026
2f8e2d0
Plan the sub-page nesting processing.
KubaO May 16, 2026
60f7e88
Get the PDF bookmarks and page headers in shape.
KubaO May 16, 2026
2d96962
Note that Python is for non-rendering use only.
KubaO May 16, 2026
97c10cd
Add the title page.
KubaO May 16, 2026
6a043e6
Update the plan with more details.
KubaO May 16, 2026
1f0cf79
Add permalink->anchor map.
KubaO May 16, 2026
571d79c
Fix chapter content hrefs.
KubaO May 16, 2026
3869fd9
Speed up the render by 8s.
KubaO May 16, 2026
2f7a623
Speed up the render by 9s.
KubaO May 16, 2026
ffd8d42
Speed up the render by 6s.
KubaO May 16, 2026
a6e8982
Update the plan.
KubaO May 16, 2026
754578c
Include rest of the sections of the book.
KubaO May 16, 2026
f617dce
Fix pdf bookmarks/outline structure, add chapter title pages for pack…
KubaO May 16, 2026
8087a45
Fix page numbering resets at the start of each part.
KubaO May 16, 2026
88e3735
Add the _site-pdf build into the primary build.
KubaO May 17, 2026
f35538a
Remove book.html from _site, where it didn't belong.
KubaO May 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.claude/
CLAUDE.md
/.htmltest.yml
node_modules/
578 changes: 578 additions & 0 deletions BOOKPLAN.md

Large diffs are not rendered by default.

8 changes: 5 additions & 3 deletions WIP.md
Original file line number Diff line number Diff line change
Expand Up @@ -420,16 +420,18 @@ WIP.md itself (and other files outside `docs/`) is not part of the Jekyll site a

## Scripts and tooling

Any new helper script (content conversion, link checks beyond `check.bat`, etc.) should be written in **Python**. Do not add new Ruby code to this repo. The only Ruby allowed is the existing Jekyll/`just-the-docs` build chain (`Gemfile`, `Gemfile.lock`, `_plugins/`) — that stays as-is. The one carve-out is `_plugins/offlinify.rb`, the link rewriter that powers the offline build (see [Build / preview](#build--preview)); future build-time concerns that are tightly coupled to Jekyll's internal model may go there too, but anything that can stand alone should still be Python.
**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.

## Build / preview

From `docs/`:

- `bundle exec jekyll build` (or `build.bat`) — builds the online copy to `_site/` **and** a `file://`-browsable copy to `_site-offline/` in a single Jekyll run. The offline pass adds ~3-5s on top of the normal ~13s build; activated by `also_build_offline: true` in `_config.yml`. After Jekyll's WRITE phase, `_plugins/offlinify.rb` walks `_site/`, copies binary assets verbatim into `_site-offline/`, and for each HTML and CSS file rewrites every root-absolute `href` / `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 of `assets/js/just-the-docs.js` in two places — `navLink()` to match the active nav entry by resolved DOM `link.href` rather than `document.location.pathname` (the upstream pathname-vs-attribute compare returns no match under `file://`, leaving the sidebar with no `.active` class so the nav appears collapsed on every navigation), and `initSearch()` to read the lunr index from `window.SEARCH_DATA` rather than fetching `search-data.json` over `XMLHttpRequest` (XHR to `file://` resources is blocked by browsers; classic `<script src=>` is not). To support that, the plugin (a) generates `_site-offline/assets/js/search-data.js` once per build by wrapping the rendered `search-data.json` in `window.SEARCH_DATA = {...};`, and (b) injects two `<script>` tags per page right before `just-the-docs.js`: one that sets `window.OFFLINE_SITE_ROOT` to the per-page relative prefix to the offline site root, and one that loads `search-data.js`. The patched `initSearch()` rewrites every `doc.url` from 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 build` (or `build.bat`) — builds three trees in a single Jekyll run: the online copy at `_site/`, a `file://`-browsable copy at `_site-offline/`, and the sparse pagedjs source at `_site-pdf/`. The offline pass (`_plugins/offlinify.rb`, activated by `also_build_offline: true` in `_config.yml`) adds ~3-5s and the PDF pass (`_plugins/pdfify.rb`, activated by `also_build_pdf: true`) adds <1s on top of the normal ~13s build. The PDF plugin copies `_site/book.html` (the concatenated chapter document rendered via `_layouts/book-combined.html`) verbatim into `_site-pdf/`, along with `assets/css/print.css`, `assets/css/rouge.css`, and every relative `<img src=>` target -- just what pagedjs needs to render the book PDF. After the copy, the plugin deletes `_site/book.html`: the concatenated document is a build artifact for the PDF render path alone, not a public page on the online site. The companion `offline_exclude: [..., book.html]` entry in `_config.yml` keeps `offlinify.rb` from copying it into `_site-offline/`. The two safeguards are independent -- the exclude pattern works regardless of whether offlinify walks `_site/` before or after pdfify's delete, and pdfify's delete works regardless of whether offlinify is enabled. 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-absolute `href` / `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 of `assets/js/just-the-docs.js` in two places — `navLink()` to match the active nav entry by resolved DOM `link.href` rather than `document.location.pathname` (the upstream pathname-vs-attribute compare returns no match under `file://`, leaving the sidebar with no `.active` class so the nav appears collapsed on every navigation), and `initSearch()` to read the lunr index from `window.SEARCH_DATA` rather than fetching `search-data.json` over `XMLHttpRequest` (XHR to `file://` resources is blocked by browsers; classic `<script src=>` is not). To support that, the plugin (a) generates `_site-offline/assets/js/search-data.js` once per build by wrapping the rendered `search-data.json` in `window.SEARCH_DATA = {...};`, and (b) injects two `<script>` tags per page right before `just-the-docs.js`: one that sets `window.OFFLINE_SITE_ROOT` to the per-page relative prefix to the offline site root, and one that loads `search-data.js`. The patched `initSearch()` rewrites every `doc.url` from 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` (or `serve.bat`) — local server at `localhost: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 (offline Lychee against `_site/`).
- `build-offline.bat` — produce **only** the offline copy, writing directly to `_site-offline/` (no `_site/` is generated). Layers `_config_offline.yml` over `_config.yml` to set `offline_build: true` (activates the plugin's standalone in-place mode) and override `also_build_offline: false`. The output tree is byte-equivalent to what the combined build writes to `_site-offline/` — same URL rewriting, same JS patches, same offline-search wiring. Faster than the combined build when only the offline copy is wanted (no `_site/` rendered, no per-file copy step). Useful for shipping just the offline copy as a downloadable bundle.
- `book.bat` — renders the PDF from `_site-pdf/book.html` via `pagedjs-cli` into `_pdf/book.pdf`. Run `build.bat` first to populate `_site-pdf/`.

## Site integrity check

Expand Down
6 changes: 4 additions & 2 deletions docs/.gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
_site
_site-offline
_site/
_site-offline/
_site-pdf/
_pdf/
.sass-cache
.jekyll-cache
.jekyll-metadata
Expand Down
6 changes: 2 additions & 4 deletions docs/Miscellaneous/Documentation Development.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,17 +183,15 @@ Also ensure that Jekyll is in the PATH. To adjust the path on Windows, press <kb

### Building

To build the documentation, i.e. render it from `.md` files to the `_site` folder:
To build the documentation, i.e. render it from `.md` files into the `_site/` (online), `_site-offline/` (offline mirror), and `_site-pdf/` (sparse PDF source) folders:

bundle exec jekyll build

or, on Windows only:

build.bat

To produce **only** the offline-browsable copy (no `_site/`), writing directly to `_site-offline/`:

build-offline.bat
A single Jekyll run produces all three trees; toggle `also_build_offline` / `also_build_pdf` in `_config.yml` to skip a sibling output if you only want `_site/`.

### Checking Link Integrity

Expand Down
17 changes: 15 additions & 2 deletions docs/_config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -134,11 +134,22 @@ gh_offline_link_url: "https://github.com/twinbasic/documentation/releases/latest
# the online _site/ output is unaffected.
also_build_offline: true

# When true, the Pdfify plugin (_plugins/pdfify.rb) runs at the end of
# the build and emits a sparse _site-pdf/ tree containing only the
# files pagedjs-cli needs: book.html, the two stylesheets the book
# layout links (print.css, rouge.css), and every image referenced
# from book.html. book.bat then renders the PDF from _site-pdf/book.html.
# Adds <1s to the build (it's a few file copies); the online _site/
# output is unaffected.
also_build_pdf: true

# Patterns for files Jekyll produces in _site/ that have no purpose
# in the offline tree -- Pages / crawler metadata, jekyll-redirect-
# from output, Windows batch scripts Jekyll picks up from the source
# directory. The online _site/ keeps them; offlinify strips them
# from _site-offline/.
# directory, and the concatenated `book.html` that exists only to
# feed `_plugins/pdfify.rb` (which copies it to _site-pdf/ and deletes
# the _site/ copy). The online _site/ keeps the metadata files;
# offlinify strips them from _site-offline/.
#
# Patterns are File.fnmatch-style with FNM_PATHNAME, matched against
# each file's path relative to the site root. `*` does NOT cross
Expand All @@ -148,6 +159,7 @@ offline_exclude:
- CNAME
- robots.txt
- sitemap.xml
- book.html

# Excludes for both the build (Jekyll won't try to process these as
# source) and the watcher (`jekyll serve` won't trigger a rebuild
Expand All @@ -157,5 +169,6 @@ offline_exclude:
# rebuild loop. Keep this in sync with also_build_offline above.
exclude:
- _site-offline
- _site-pdf
- redirects.json
- "*.bat"
27 changes: 0 additions & 27 deletions docs/_config_offline.yml

This file was deleted.

181 changes: 181 additions & 0 deletions docs/_data/book.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
# Chapter manifest for the twinBASIC documentation PDF.
#
# `front_matter` is an ordered list of sections that emit between the
# title page and the first numbered Part. No divider page, no part
# number; each entry just contributes its chapter(s) inline. Each
# entry has:
# title -- the conceptual section name (used by future TOC and as
# the anchor seed when the URL would otherwise produce an
# empty anchor, e.g. the root `/` page).
# page -- single absolute URL (alternative to `prefixes`); use
# when the section is exactly one page (the root index).
# prefixes -- URL-prefix list (alternative to `page`); same shape as
# on a numbered part.
#
# `parts` is an ordered list of numbered book parts. Each part has:
# title -- shown on the part divider page and in the global TOC.
# subtitle -- optional second-line descriptor on the divider.
# prefixes -- one or more URL prefixes; book.html gathers every
# page in `site.pages` whose URL contains a prefix,
# sorts by URL, and emits each as a chapter. Used by
# "flat" parts that map directly to a folder of pages.
# page -- single absolute URL alternative to `prefixes`, used
# for one-chapter parts (e.g. the FAQ). Match is
# exact, not contains-based, so a one-page section
# doesn't accidentally sweep in siblings.
# foreword_page -- single absolute URL emitted as a `<article
# class="part-foreword">` right after the part
# divider, before any chapter dividers. No running
# header on these pages (CSS suppresses chrome via a
# named page). Used by the Packages part to surface
# the `/tB/Packages/` landing as introductory
# material for the whole section.
# chapters -- ordered list of per-chapter entries (1.9). Each
# chapter has its own divider page and its own
# chapter-level content; the part becomes a
# container for several distinct subjects rather
# than a flat folder. Each entry has:
# title -- the chapter divider title.
# subtitle -- optional descriptor.
# landing_page -- the chapter's intro page; always
# emitted first within the
# chapter, and the plugin strips
# its source H1 so the chapter
# divider's H2 is the sole
# outline entry for the chapter.
# prefixes -- additional URL prefixes for
# content pages (members, etc.);
# sorted by URL after the
# landing. A page that matches
# a prefix AND is the landing is
# emitted only once (as the
# landing).
#
# Index pages (URL ending in `/`) sort before sibling content pages
# under ASCII lexicographic order, so each part naturally opens with
# its package landing page followed by the individual symbols.

front_matter:
- title: Introduction
page: /

parts:
- title: Features
subtitle: Language additions, attributes, tooling, and packaging
prefixes:
- /Features/

- title: Frequently Asked Questions
subtitle: Status, scope, and how to get help
page: /FAQ

- title: Tutorials
subtitle: Worked examples for CEF, WebView2, and CustomControls
prefixes:
- /Tutorials/

- title: The Core Language
subtitle: Statements, operators, and built-in keywords
prefixes:
- /tB/Core/

- title: Reference Section
subtitle: Alphabetical indexes of statements, operators, and compiler constants, plus the project glossary
# The live site has a top-level "Reference Section" nav item that groups
# these alphabetical-index pages. The five Reference/* pages plus the
# Controls table and the Glossary live at three different URL prefixes;
# `contains`-match catches all of them. (Attributes also belongs here in
# the live nav but its permalink is /tB/Core/Attributes, which the Core
# part above already sweeps up; we leave it there rather than introduce
# an excludes schema for one page.)
prefixes:
- /Reference
- /tB/Controls
- /tB/Gloss

- title: Packages
subtitle: The runtime and library packages shipped with twinBASIC
# The /tB/Packages/ landing page is the part's foreword; its content
# (an overview of all built-in packages) sits between the part divider
# and the first chapter divider, with no running header. Each package
# below becomes a chapter with a dedicated chapter-divider title
# page; the chapter's own source landing page emits next (with its
# source H1 stripped by the plugin to avoid the redundancy with the
# chapter divider's H2), followed by the package's class / module /
# control pages in URL order.
foreword_page: /tB/Packages/
chapters:
- title: VBA Package
subtitle: Standard runtime modules --- Strings, Math, FileSystem, and the rest
# The VBA landing lives at /tB/Packages/VBA (added when Packages
# was promoted to a top-level nav item), while individual module
# members keep their legacy /tB/Modules/... URLs.
landing_page: /tB/Packages/VBA
prefixes:
- /tB/Modules/

- title: VBRUN Package
subtitle: Runtime types for controls, errors, and the property bag
landing_page: /tB/Packages/VBRUN/
prefixes:
- /tB/Packages/VBRUN/

- title: VB Package
subtitle: Classic VB6 forms and intrinsic controls
landing_page: /tB/Packages/VB/
prefixes:
- /tB/Packages/VB/

- title: WebView2 Package
subtitle: Chromium-based browser control via Microsoft Edge WebView2
landing_page: /tB/Packages/WebView2/
prefixes:
- /tB/Packages/WebView2/

- title: Assert Package
subtitle: Test assertions with Exact, Strict, and Permissive comparison
landing_page: /tB/Packages/Assert/
prefixes:
- /tB/Packages/Assert/

- title: CustomControls Package
subtitle: The WaynesControls suite and its supporting framework
landing_page: /tB/Packages/CustomControls/
prefixes:
- /tB/Packages/CustomControls/

- title: CEF Package
subtitle: Chromium Embedded Framework browser control
landing_page: /tB/Packages/CEF/
prefixes:
- /tB/Packages/CEF/

- title: WinEventLogLib Package
subtitle: Windows Event Log integration
landing_page: /tB/Packages/WinEventLogLib/
prefixes:
- /tB/Packages/WinEventLogLib/

- title: WinNamedPipesLib Package
subtitle: Asynchronous named-pipe framework over IOCP
landing_page: /tB/Packages/WinNamedPipesLib/
prefixes:
- /tB/Packages/WinNamedPipesLib/

- title: WinServicesLib Package
subtitle: Windows Services hosting
landing_page: /tB/Packages/WinServicesLib/
prefixes:
- /tB/Packages/WinServicesLib/

- title: tbIDE Package
subtitle: IDE Extensibility --- the addin SDK
landing_page: /tB/Packages/tbIDE/
prefixes:
- /tB/Packages/tbIDE/

- title: WinNativeCommonCtls Package
subtitle: VB6-compatible Common Controls replacement on top of Win32 ComCtl32
landing_page: /tB/Packages/WinNativeCommonCtls/
prefixes:
- /tB/Packages/WinNativeCommonCtls/
Loading
Loading