diff --git a/.github/workflows/jekyll-gh-pages.yml b/.github/workflows/tbdocs-gh-pages.yml similarity index 100% rename from .github/workflows/jekyll-gh-pages.yml rename to .github/workflows/tbdocs-gh-pages.yml diff --git a/builder/PLAN-13.md b/builder/PLAN-13.md new file mode 100644 index 00000000..4190530c --- /dev/null +++ b/builder/PLAN-13.md @@ -0,0 +1,499 @@ +## PLAN-13: Phase 13 --- Inline SVG embedding with zoom / export controls + +Replaces `` rendering with build-time SVG inlining, +and unifies the gantt-chart injection with the same pipeline. Every +`.svg` image referenced from markdown (including `.dot`-generated +diagrams) is read from disk at render time, embedded directly in the +page HTML, and wrapped in a controls bar (Download SVG, Copy SVG, +Download PNG, Copy PNG) with a click-to-zoom fullscreen overlay. + +The gantt chart on the BuildInfo page flows through the same +markdown-it plugin as every other SVG. Because the gantt's timing +data is not available until after the build completes, the plugin +inlines a committed placeholder SVG; a minimal post-build step +substitutes the real content into the already-written HTML. The +wrapper markup, controls, zoom wiring, and JS are identical --- +the only difference is *when* the SVG content arrives. + +What Phase 13 does NOT do: + +- Add dark-mode colour support for Graphviz `.dot` diagrams. The + gantt SVG already carries `html.dark-mode` CSS rules that work + because it is inline; the `.dot` SVGs generated by + `@hpcc-js/wasm-graphviz` do not. Adding dark-mode classes to + `.dot` source files is a separate concern. +- Change the existing `.dot` → `.svg` regeneration pipeline + (`dot.mjs`). Diagrams are still rendered from `.dot` source by + WASM Graphviz; this plan only changes how the resulting `.svg` + files are embedded in pages. (One `.dot` source fix WAS needed: + `pdf-render-pipeline.dot` had `margin=12` in its `graph [...]` + defaults block, which Graphviz interprets as 12 *inches* on the + root graph --- producing a 2239×2406 pt SVG with 864 pt of dead + space. Moved the `margin=12` to each `subgraph cluster_*` where + it means 12 *points* of intra-cluster padding.) +- Touch the offline or PDF build passes. Inline SVGs are already + part of the page HTML by the time those passes run; no + rewriting is needed. +- Add new npm dependencies. + +Target wall-clock impact: negligible. The three `.dot` SVGs +total ~30 KB; reading them adds < 1 ms to the dispatch task. The +per-page render cost is unchanged (string concatenation vs. +`` tag emission). + +--- + +## 1. Current state + +SVG images in markdown (`![alt](/assets/images/dot/foo.svg)`) render +as ``. The browser makes a +separate HTTP request per SVG. Page CSS cannot reach the SVG +internals (dark-mode styling, link colours, etc.). No zoom or +download controls exist. + +The gantt chart on the BuildInfo page has a separate code path: +`injectGanttChart()` in `tbdocs.mjs` (lines 756--816) runs after +the build completes and patches the written HTML, injecting the +inline SVG, four control links, a PNG-export function, and a +click-to-zoom script. This is ~60 lines of self-contained HTML / +JS generation that duplicates the pattern every future SVG would need. + +--- + +## 2. Architecture + +### 2.1. Data flow: SVG contents through the pipeline + +``` + dot.mjs (seed) dispatch (main thread) + ┌─────────────────┐ ┌──────────────────────────┐ + │ .dot → .svg │──staticFiles──▸ │ reads .svg file contents │ + │ regeneration │ │ into svgContentsMap │ + └─────────────────┘ │ packs into sharedSAB │ + └────────────┬─────────────┘ + docs/assets/images/gantt.svg │ + (committed placeholder) │ + ───── also in staticFiles ────────────────────────┘ + │ + ┌──────────────────────┘ + ▼ + ┌──────────────────────────────────┐ + │ cpu-worker renderEnvInit │ + │ unpackShared → svgContentsMap │ + │ createMarkdownIt({ ..., │ + │ svgContents }) │ + └──────────────┬───────────────────┘ + ▼ + ┌──────────────────────────────────┐ + │ svgInlinePlugin (image renderer) │ + │ • src ends in .svg? │ + │ • content in ctx.svgContents? │ + │ → emit wrapper + inline SVG │ + │ → set page.hasSvg = true │ + └──────────────┬───────────────────┘ + ▼ + ┌──────────────────────────────────┐ + │ templatePhase / renderHead │ + │ page.hasSvg → include │ + │ svg-inline.js \n` + : "") +``` + +`defer` keeps the load non-blocking, same as `theme-switch.js`. + +### 5.4. `svg-inline.js` + +New file at `docs/assets/js/svg-inline.js`. ~80 lines, no +dependencies, IIFE. + +Behaviours: + +1. **CSS injection**. On load, inject a `
${downloadLink}${copyLink}${downloadPng}${copyPng}
`; - const zoomScript = [ - `\n` : "") + (bu ? ` \n` : "") + ` \n` + + (page.hasSvg ? ` \n` : "") + ` \n` + headSeoBlock(page, site) + init.faviconLink + diff --git a/docs/Documentation/BuildInfo.md b/docs/Documentation/BuildInfo.md index f1c4d185..982e7243 100644 --- a/docs/Documentation/BuildInfo.md +++ b/docs/Documentation/BuildInfo.md @@ -11,4 +11,4 @@ permalink: /Documentation/Development/BuildInfo Gantt chart of this build's task timeline. - +![Build task timeline](/assets/images/gantt.svg) diff --git a/docs/Documentation/Builder.md b/docs/Documentation/Builder.md index 9401dca8..3c28c1eb 100644 --- a/docs/Documentation/Builder.md +++ b/docs/Documentation/Builder.md @@ -87,7 +87,7 @@ Modules grouped by role. Each entry has one line; deep-dive in [Pipeline Stages] | File | Role | |---|---| -| [`render.mjs`](https://github.com/twinbasic/documentation/blob/main/builder/render.mjs) | markdown-it configuration + plugin stack + `renderPhase`. Built once on main and once per worker. | +| [`render.mjs`](https://github.com/twinbasic/documentation/blob/main/builder/render.mjs) | markdown-it configuration + plugin stack (including `svgInlinePlugin` for build-time SVG embedding) + `renderPhase`. Built once on main and once per worker. | | [`highlight.mjs`](https://github.com/twinbasic/documentation/blob/main/builder/highlight.mjs) | Shiki bootstrap + the bundled twinBASIC grammar. Emits the just-the-docs wrapper structure. | | [`highlight-theme.mjs`](https://github.com/twinbasic/documentation/blob/main/builder/highlight-theme.mjs) | Loads `Light.theme` + `Dark.theme`, emits `tb-highlight.css` + scope-to-class lookup. | | [`template.mjs`](https://github.com/twinbasic/documentation/blob/main/builder/template.mjs) | `templatePhase` (per-page layout wrap) + `buildInitConfig` + `renderSidebar`. JS template literals; no template engine. | @@ -333,11 +333,42 @@ Three flags on the pool make the reuse safe: `serve.mjs` writes to `docs/_serve/` --- disjoint from `build.bat`'s `_site/` family. A one-off `build.bat` run during a serve session never touches the tree the live preview is showing. +## SVG inlining + +Markdown `![alt](/assets/images/foo.svg)` references to build-local SVGs are replaced at render time with the SVG content inlined directly in the HTML. The feature removes the browser round-trip for separate SVG files and adds interactive controls (zoom, download, clipboard copy) to every inlined diagram. + +The pipeline: + +1. **`dispatch.execute()`** reads every `.svg` static file into a `svgContentsMap` keyed by `srcRel`. The map is packed into the shared SAB and broadcast to every render worker. +2. **`renderEnvInit`** on each worker unpacks `svgContentsMap` and passes it as `svgContents` to `createMarkdownIt`. +3. **`svgInlinePlugin`** in `render.mjs` overrides the markdown-it image renderer. When the `src` ends in `.svg` and the file's content exists in `ctx.svgContents`, the plugin replaces the `` tag with a wrapper structure containing the raw SVG, four control links (Download SVG, Copy SVG, Download PNG, Copy PNG), and a click-to-zoom container. The plugin also sets `page.hasSvg = true`. +4. **`templatePhase`** conditionally includes `