You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: BOOKPLAN.md
+8-4Lines changed: 8 additions & 4 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -17,9 +17,10 @@ or `build.bat` then `book.bat`. One Jekyll invocation produces three trees in pa
17
17
18
18
Touch points and what each one already exposes:
19
19
20
-
- [docs/book.html](docs/book.html) — iterator that concatenates every chapter into one HTML document. Permalink `/book.html`, layout `book-combined`. Contains: whitespace-pattern primitives (p1..p4, indent variants) for the pagedjs whitespace fix; the Roman numerals array; the title-page section (1.3); the front-matter loop (1.7) that emits `site.data.book.front_matter` entries inline between the title page and Part I; the per-part loop. Each part can be **flat** (page selectors directly on the part, plus an optional `landing_page:`) or **chaptered** (1.9; a `foreword_page:` and/or `landing_page:` plus a nested `chapters:` list, each chapter carrying its own selectors and divider page). Page selection across all three call sites (front-matter, flat part, chaptered chapter) is delegated to `_includes/book-collect-matches.html` so the selector schema stays in one place. Per-chapter body rendering — whitespace fix, heading depth shift, sub-page detection, id uniqueness, header-string emission — is factored out into `_includes/book-chapter-body.html` so the chapter-loop callers share one implementation. Insertion points for new front matter go **after** the title-page section and **before** the `{%- for part in site.data.book.parts -%}` opener.
21
-
- [docs/_includes/book-chapter-body.html](docs/_includes/book-chapter-body.html) — per-chapter body processing, called via `{% include book-chapter-body.html chapter=... %}` from each of book.html's chapter-loop callers. Handles markdownify, the pagedjs whitespace fix (consumes the `p1..p4` patterns from the outer scope), heading depth shift (1.5a + sub-page 1.6b), sub-page detection (1.6a, opt-out via `skip_sub_page_detection`), heading-id uniqueness (1.5b), compound running header (1.6c), and emits the final `<article>` block. Take-it-or-leave-it parameters cover the cases that don't fit the default: `article_class_override` (front-matter and part-foreword), `chapter_anchor_override` (root URL `/` fallback to `ch-introduction`), `skip_sub_page_detection` (front-matter entries and part landings don't share an index hierarchy with following chapters), `skip_base_heading_shift` (skips the 1.5a `+1` shift; paired with the part's `no_heading_shift` flag), `extra_heading_shift` (adds the 1.9 chaptered-part `+1` shift on top of 1.5a so class / module indexes nest under their chapter divider in the outline).
22
-
-[docs/_includes/book-collect-matches.html](docs/_includes/book-collect-matches.html) — shared selector logic for gathering pages that match a manifest entry. Called from each of book.html's chapter-loop callers via `{% include book-collect-matches.html entry=... %}` after the caller initialises an outer-scope `collected = "" | split: ""` array; the include appends every matching page to `collected` in selector order. Recognises four selector keys on the entry — `page:` (single URL), `pages:` (list of URLs), `nav_page:` (single nav-path), `nav_pages:` (list of nav-paths) — and one modifier, `no_descent:`, that flips every match from the default `contains` (starts-with) semantics to exact-equality. `landing_page:` and `foreword_page:` are not handled here; their first-emission / divider-styling semantics live in the caller.
20
+
- [docs/book.html](docs/book.html) — iterator that concatenates every chapter into one HTML document. Permalink `/book.html`, layout `book-combined`. Contains: the Roman numerals array; the title-page section (1.3); the front-matter loop (1.7) that emits `site.data.book.front_matter` entries inline between the title page and Part I; the per-part loop. Each part can be **flat** (page selectors directly on the part, plus an optional `landing_page:`) or **chaptered** (1.9; a `foreword_page:` and/or `landing_page:` plus a nested `chapters:` list, each chapter carrying its own selectors and divider page). Each chapter-loop caller reads its pre-resolved page list from `entry._chapters`, populated once at `:site, :pre_render` by `_plugins/book-resolve-chapters.rb` (so the selector schema stays in one place). Per-chapter body rendering is delegated to `_includes/book-chapter-body.html`, which in turn calls the `book_chapter_transform` Liquid filter (`_plugins/book-chapter-transform.rb`) for whitespace fix, heading-depth shift, heading-id rewrite, and intra-chapter href-anchor rewrite. Insertion points for new front matter go **after** the title-page section and **before** the `{%- for part in site.data.book.parts -%}` opener.
21
+
- [docs/_includes/book-chapter-body.html](docs/_includes/book-chapter-body.html) — per-chapter body processing, called via `{% include book-chapter-body.html chapter=... %}` from each of book.html's chapter-loop callers. Handles sub-page detection (1.6a, opt-out via `skip_sub_page_detection`), compound running header (1.6c), and emits the final `<article>` block. The heavier rewrites — markdownify, the pagedjs whitespace fix (1.5/2.1), the 1.5a heading-depth shift (+ the 1.6b sub-page and 1.9 chaptered-part additional shifts when applicable), the 1.5b heading-id prefix, and the intra-chapter `href="#..."` anchor prefix — are batched into one Ruby pass via the `body | book_chapter_transform: site.baseurl, heading_shift_n, chapter_anchor` filter call. Take-it-or-leave-it parameters cover the cases that don't fit the default: `article_class_override` (front-matter and part-foreword), `chapter_anchor_override` (root URL `/` fallback to `ch-introduction`), `skip_sub_page_detection` (front-matter entries and part landings don't share an index hierarchy with following chapters), `skip_base_heading_shift` (skips the 1.5a `+1` shift; paired with the part's `no_heading_shift` flag), `extra_heading_shift` (adds the 1.9 chaptered-part `+1` shift on top of 1.5a so class / module indexes nest under their chapter divider in the outline). The three `_*_heading_shift` parameters and `skip_base_heading_shift` combine into a single `heading_shift_n` integer the include passes to the filter; the filter then bumps each heading by exactly N levels in one regex pass (capping at h7-stub above source-h6), rather than running 0-3 cascading shift chains.
22
+
- [docs/_plugins/book-resolve-chapters.rb](docs/_plugins/book-resolve-chapters.rb) — `:site, :pre_render` generator that walks `_data/book.yml` (`front_matter:`, each flat part, each part's optional `foreword_page:`/`landing_page:`, and each chapter inside a chaptered part) and stashes the resolved page array on `entry._chapters` for `book.html` to iterate. Recognises four selector keys on the entry — `page:` (single URL), `pages:` (list of URLs), `nav_page:` (single nav-path), `nav_pages:` (list of nav-paths) — and one modifier, `no_descent:`, that flips every match from the default `contains` (starts-with) semantics to exact-equality. `landing_page:` and `foreword_page:` are not resolved here; their first-emission / divider-styling semantics live in `book.html`'s caller. Replaces the earlier per-render Liquid include `_includes/book-collect-matches.html` -- the `where_exp` / `where` / `concat` / `sort_by_nav_order` chains were running 37 times per build for ~1.5 s of Liquid expression-interpreter time; precomputing once at site:pre_render is free.
23
+
-[docs/_plugins/book-chapter-transform.rb](docs/_plugins/book-chapter-transform.rb) — registers the `book_chapter_transform` Liquid filter that takes a chapter body and applies, in one Ruby pass: the pagedjs inter-span whitespace fix (longest-first regex over `WHITESPACE_PATTERNS`), the N-level heading shift (1.5a + 1.6b + 1.9, where N is precomputed by the include from `skip_base_heading_shift` / `is_sub_page` / `extra_heading_shift`), the 1.5b `id="..."` prefix per chapter, and the corresponding `href="#..."` prefix for intra-chapter anchors. One filter call replaces a chain of ~36 `| replace:` invocations plus a 12-pattern whitespace span wrap from the prior in-template implementation (~3 cascading heading-shift passes × 12 replaces, plus the anchor-id 13-replace pass).
23
24
-[docs/_layouts/book-combined.html](docs/_layouts/book-combined.html) — minimal wrapper: `<html><head>` + `<title>{{ site.title }}</title>` + `rouge.css` + `print.css` + `{{ content }}`. No nav, no JS, no chrome. Pagedjs runs on the rendered output of this layout. The only layout the PDF pipeline uses; the older per-source-page `book` layout was retired alongside `_config-pdf.yml`.
24
25
-[docs/assets/css/print.css](docs/assets/css/print.css) — the book's design. Existing structural rules: `@page` (A4, 22mm margins, running header in `@top-right` via `string(chapter-title)`, page number in `@bottom-right`); `@page :first` (suppresses both — used by the title page); `@page divider` (suppresses both, used by part dividers via `page: divider`); `@page front-matter` (suppresses both, used by `article.front-matter` for 1.7 Introduction-style sections); `@page part-foreword` + `@page chapter-divider` (suppresses both, used by the 1.9 part foreword and per-chapter title pages); `article { break-before: page }`; per-chapter `string-set: chapter-title` on `article.page > .header-string`; the top-level vs sub-chapter heading-size split (`article.page:not(.sub-chapter) > h2:first-of-type` vs `article.page.sub-chapter > h3:first-of-type`); chapter-divider H2 typography (`article.chapter-divider h2` — 24pt centered, no border) plus its subtitle (`.chapter-subtitle` — 13pt italic).
25
26
- [docs/_data/book.yml](docs/_data/book.yml) — the manifest book.html iterates over. Schema: `parts:` is an ordered list of numbered parts. A **flat part** carries page-selectors directly (`page:` / `pages:` / `nav_page:` / `nav_pages:`) plus an optional `landing_page:`; a **chaptered part** (1.9) replaces the selectors with a `chapters:` list of per-chapter entries `{ title, subtitle, landing_page, page/pages/nav_page/nav_pages, ... }` and may carry a `foreword_page:` and/or a `landing_page:` on the part itself. Each chaptered chapter emits a full-page `<article class="chapter-divider">` title page followed by its landing page (with the source H1 stripped by the plugin) and the selector-matched content in `sort_by_nav_order` order. `front_matter:` is a sibling list of front-matter sections (1.7), same selector shape as a flat part. The selector keys: `pages:` is a list of URL substrings matched via `contains` (multiple entries can map to one Part / chapter — used for the Reference Section in 1.8 and for the VBA chapter's landing at `/tB/Packages/VBA` + members under `/tB/Modules/...`); `page:` is the singular alias. `nav_pages:` is the same shape against `page.data["nav_path"]` (populated by `_plugins/nav-path.rb`) — used when a section is most naturally expressed as a nav-tree branch rather than a URL prefix; `nav_page:` is its singular alias. A `no_descent: true` modifier on the entry switches every selector to exact-equality so a single index page can be picked up without sweeping its sub-pages. Additional control flags: `no_outline_entry:` suppresses the part-divider H1 / chapter-divider H2 (so the section's first content heading becomes the bookmark target); `no_heading_shift:` skips the 1.5a base shift for the part's entries (used when the source pages are already authored at H2-and-deeper). Available in Liquid as `site.data.book.parts` and `site.data.book.front_matter`.
@@ -45,7 +46,8 @@ Concretely for the PDF book:
45
46
- Git-derived build info (commit hash, commit date) → Jekyll plugin (`_plugins/build-info.rb`) that populates `site.data.build` on `:site, :post_read`. Not a pre-build Python step writing `_data/build.yml`.
46
47
- Chapter manifest → `_data/book.yml` (committed source of truth, hand-edited).
47
48
- Title page, colophon, TOC content → Liquid in `book.html` and the layouts.
- Chapter selector resolution (`page` / `pages` / `nav_page` / `nav_pages` / `no_descent`, the `sort_by_nav_order` ordering, and `foreword_page`/`landing_page` resolution) → Jekyll plugin (`_plugins/book-resolve-chapters.rb`) running at `:site, :pre_render`. The Liquid implementation (formerly in `_includes/book-collect-matches.html`) was running ~37 `where_exp` invocations per build for ~1.5 s of Liquid expression-interpreter time; resolving once into `entry._chapters` is free.
50
+
- Per-chapter body rewrites (pagedjs whitespace fix, heading-depth shift, heading-id prefix, intra-chapter href anchor prefix) → Jekyll plugin (`_plugins/book-chapter-transform.rb`), exposed as the `book_chapter_transform` Liquid filter that `_includes/book-chapter-body.html` calls once per chapter. The Liquid version was a chain of ~36 `| replace:` invocations plus a 12-pattern whitespace span wrap per chapter; the filter does the same passes in C-implemented regex over the body string, with the heading-shift cascade collapsed to a single bump-by-N regex.
49
51
- Cross-reference href rewrites → Jekyll plugin (`_plugins/book-href-rewrite.rb`), running on `:pages, :post_render`. The first cut was inline Liquid; the per-(chapter × permalink) loop burned ~21 s of render even after pre-computing per-permalink search/replace strings and gating each permalink on a common-prefix `contains`, vs ~50 ms in Ruby. Rule of thumb: use Liquid for per-chapter shaping; reach for a plugin when the work is N × M with large N and M.
50
52
51
53
The carve-out in WIP.md for `_plugins/offlinify.rb` is the same shape: build-time concerns tightly coupled to Jekyll's internal model belong in `_plugins/`, not in an external script.
@@ -190,6 +192,8 @@ Intra-chapter local links must be rewritten in lock-step. Patterns like `[**Coun
190
192
191
193
Both rewrites are mechanical text substitutions over the chapter body string, no parsing required.
192
194
195
+
**Implementation.** Landed first as the Liquid replace-chain shown in 1.5a plus a sibling pair for the `id=` / `href="#..."` prefixes, all inside `_includes/book-chapter-body.html`. Folded into the single Ruby filter `book_chapter_transform` (`_plugins/book-chapter-transform.rb`) once the per-chapter Liquid-replace dispatch became visible in the profile — ~36 `| replace:` invocations across the heading-shift cascade (12 source levels × 3 cascading passes) plus the 13-replace id/anchor prefix chain plus the 12-pattern whitespace span wrap, ~3.5 s of `Liquid::StandardFilters#replace` per build. The filter does the same passes in C-implemented regex with the cascade collapsed to a single bump-by-N pass; ~0.14 s for 718 chapter calls.
196
+
193
197
#### print.css updates
194
198
195
199
-`string-set: chapter-title content()` moves from `h1:first-of-type` to `h2:first-of-type`.
0 commit comments