Tome platform print layer — document assembly, template registry, and Pandoc-driven PDF generation for structured reports.
npm install @wabbit/tome-printOverview
@wabbit/tome-print
Document assembly, a print-template registry, and Pandoc-driven PDF generation for structured reports (e.g. @wabbit/tome-deals' proposal/quote documents). domain layer per root ARCHITECTURE.md — zero @wabbit/* dependencies, consumed by @wabbit/tome-deals as an optional lazy layer (via hasLayer('@wabbit/tome-print'), not a hard import).
Install
pnpm add @wabbit/tome-printNo peer dependencies — this is a standalone Node package (document assembly + a CLI wrapper around two external binaries). It ships zero React and zero .tsx files.
External requirements — two system binaries, not one: the ./cli build path (tome-print bin → src/cli/build.ts) needs both:
- Pandoc — Markdown → JSON AST (
pandoc -t json).findPandoc()triespandoconPATHfirst, then known Windows install locations (%LOCALAPPDATA%\Pandoc\pandoc.exe,C:\Program Files\Pandoc\pandoc.exe); if none resolve, the CLI exits with an install-url error rather than a cryptic spawn failure. - WeasyPrint (Python) — HTML → PDF, invoked as a bare
weasyprintshell command with no path-search fallback. If it's missing,build.tscatches theexecSyncfailure and printspip install weasyprint, then preserves the intermediate HTML at.tome-print-tmp/report.htmlnext to the target PDF so the run isn't a total loss.
Neither binary is vendored by pnpm add — both are separate host installs.
60-second quickstart
import { registerPrintTemplate, assembleDocument, mapDocument, extractMeta } from '@wabbit/tome-print'
registerPrintTemplate('quote-line-item', (block, meta) => `<tr><td>${block.data.label}</td></tr>`)
const { meta, blocks } = mapDocument(pandocAst) // ./mapper — frontmatter + AST → PrintBlock[]
const html = assembleDocument(blocks, meta, 'report.css') // ./document — 3 args: blocks, meta, CSS href for the <link>CLI (Markdown → PDF via Pandoc + WeasyPrint, positional args — no subcommand, no flags):
pnpm exec tome-print ./report.md ./report.pdf # output arg optional — defaults to <input-basename>.pdf beside the inputSet TOME_PRINT_WORDMARK=/abs/path/to/logo.svg before running the CLI to brand the editorial templates with a real wordmark; unset (or a missing file) falls back to a plain-text brand mark — the CLI warns but does not fail.
Public API
| Export | Subpath | Description | |---|---|---| | DocumentMeta, PrintBlock, PrintTemplate | ./types | Core shapes — PrintTemplate = (block, meta) => string | | registerPrintTemplate(slug, template) | ./registry | Register a template function for a block slug | | getPrintTemplate(slug), hasPrintTemplate(slug), getAllPrintTemplates() | ./registry | Registry lookups | | renderBlock(block, meta) | ./registry | Render one block via its registered template | | assembleDocument(blocks, meta, cssPath) | root (./document) | Walk a PrintBlock[] and assemble the final document HTML; cssPath is the stylesheet href written into the <link> tag (relative or absolute — the CLI resolves it to a temp-dir-local report.css before handing off to WeasyPrint) | | mapDocument(pandocAst) | root (./mapper) | Map a Pandoc AST to { meta, blocks } | | extractMeta(pandocAst) | root (./mapper/frontmatter) | Pull DocumentMeta out of a document's frontmatter | | renderInline, renderInlines | ./richtext | Inline-node renderers | | renderBlockNode, renderBlocks | ./richtext | Block-node renderers | | tome-print (bin) | ./cli | Pandoc-driven Markdown → PDF build CLI — parses <input.md> via pandoc -t json, maps the AST, assembles HTML, then renders to PDF | | Template registration side-effect | ./templates | import '@wabbit/tome-print/templates' registers every built-in template; assembleDocument also imports this internally |
Built-in templates (registered via ./templates's side-effect barrel)
editorial-opener, prose-section, pull-quote, signal-data-table, editorial-bridge, editorial-colophon, metric-strip, data-chart — one file per template under src/templates/, each calling registerPrintTemplate() at import time. A consumer's own block templates (e.g. a quote-line-item slug from a deal artifact) register the same way, either from an app-owned module imported once at startup or alongside ./templates's barrel.
Server / client posture
Not applicable — this package has no .tsx files and no React dependency at all. It's pure Node.js: Pandoc + WeasyPrint CLI orchestration (node:child_process, node:fs), AST mapping, and string-template rendering. Every export is safe to call from a build script, a Payload server hook, or a Node CLI; none of it can run in a browser bundle (it shells out to native binaries and touches the filesystem directly).
Extending
New print templates: registerPrintTemplate(slug, (block, meta) => htmlString), then import the registering module from ./templates's barrel so assembleDocument's internal import '@wabbit/tome-print/templates' picks it up as a side effect. New document sources beyond Pandoc AST would need a new ./mapper sibling implementing the same { meta, blocks } output shape consumed by assembleDocument.
Design history
docs/superpowers/specs/2026-04-24-tome-print-layer-design.md— original designdocs/claude-gotchas.md→ Payload / Typing / Layer Design section — the cross-layer optional-lookup pattern@wabbit/tome-dealsuses to reach this package (hasLayer()+ lazyrequire(), never a static import) is documented under "Cross-layer runtime calls go through the layer registry, not dynamic import" and "registerLayer MUST use lazyrequire()in try/catch, never static import"
Exports
@wabbit/tome-print@wabbit/tome-print/types@wabbit/tome-print/registry@wabbit/tome-print/templates@wabbit/tome-print/richtext@wabbit/tome-print/mapper
Changelog
36e537a: Every package now declares an explicit `sideEffects` field (38 added; motion/engine/forms already correct). Registration-bearing modules (render files' `registerRenderer`, `blocks/*/index.ts` `defineBlock` self-registration, widget `register.ts` files, productHooks, permission self-registrations, print templates, chrome built-in variants) are listed so bundlers can tree-shake everything else WITHOUT dropping import-time registrations — previously the field was unset, which blocked cross-module tree-shaking through the barrels entirely. Never blanket `false` on a package with registration or CSS.
- 36e537a: Every package now declares an explicit `sideEffects` field (38 added; motion/engine/forms already correct). Registration-bearing modules (render files' `registerRenderer`, `blocks/*/index.ts` `defineBlock` self-registration, widget `register.ts` files, productHooks, permission self-registrations, print templates, chrome built-in variants) are listed so bundlers can tree-shake everything else WITHOUT dropping import-time registrations — previously the field was unset, which blocked cross-module tree-shaking through the barrels entirely. Never blanket `false` on a package with registration or CSS.
bed3f90: Docs-manifest emitter pipeline (W3 ship-readiness). `@wabbit/tome-blocks-core` now ships a standalone Node ESM CLI at `scripts/emit-docs-manifests.mjs` that emits per-package documentation manifests (index.json, packages/<slug>.json, changelog.json) by reading what packages already carry — READMEs, the payload-free `<pkg>/meta` block-usage barrels, package.json exports maps, and CHANGELOG.md. It is the docs-pipeline sibling of the gallery source extractor and is consumed by host sites at prebuild: `node node_modules/@wabbit/tome-blocks-core/scripts/emit-docs-manifests.mjs --output-dir <dir> --scope <scope.json>`. To let the emitter import block metadata uniformly without dragging Payload config into a build script, the `./meta` payload-free subpath (BlockMetaEntry[]) is extended to the remaining offered blocks packs — agency-essentials, catalog-pack, lms-pack, org-pack, and signal-theme — mirroring the existing editorial-pack / marketing-starter / content-writer / extras barrels. Each block's `BlockMeta` was relocated verbatim into a payload-free sibling meta module and re-imported by its block config; no meta values changed. Every supported-core package additionally adds `CHANGELOG.md` to its published `files` array so the next publish cascade ships changelogs the emitter can read from installed tarballs at prebuild.
- bed3f90: Docs-manifest emitter pipeline (W3 ship-readiness). `@wabbit/tome-blocks-core` now ships a standalone Node ESM CLI at `scripts/emit-docs-manifests.mjs` that emits per-package documentation manifests (index.json, packages/<slug>.json, changelog.json) by reading what packages already carry — READMEs, the payload-free `<pkg>/meta` block-usage barrels, package.json exports maps, and CHANGELOG.md. It is the docs-pipeline sibling of the gallery source extractor and is consumed by host sites at prebuild: `node node_modules/@wabbit/tome-blocks-core/scripts/emit-docs-manifests.mjs --output-dir <dir> --scope <scope.json>`. To let the emitter import block metadata uniformly without dragging Payload config into a build script, the `./meta` payload-free subpath (BlockMetaEntry[]) is extended to the remaining offered blocks packs — agency-essentials, catalog-pack, lms-pack, org-pack, and signal-theme — mirroring the existing editorial-pack / marketing-starter / content-writer / extras barrels. Each block's `BlockMeta` was relocated verbatim into a payload-free sibling meta module and re-imported by its block config; no meta values changed. Every supported-core package additionally adds `CHANGELOG.md` to its published `files` array so the next publish cascade ships changelogs the emitter can read from installed tarballs at prebuild.
233c45e: Add tsup build pipeline: emit ESM+CJS+DTS to `dist/`, rewrite `package.json` exports/main/module/types to point at `dist`, copy CSS assets (report.css + fonts/) via standalone post-build script, expose CLI bin entry. Fixes bundler-incompatible raw `.ts` source distribution.
- 233c45e: Add tsup build pipeline: emit ESM+CJS+DTS to `dist/`, rewrite `package.json` exports/main/module/types to point at `dist`, copy CSS assets (report.css + fonts/) via standalone post-build script, expose CLI bin entry. Fixes bundler-incompatible raw `.ts` source distribution.