Authoring a house-style math mdBook
Canonical examples on disk: ~/projects/books/math/calculus (formal) and
~/projects/books/math/14-day-derivatives (informal, day-per-lesson). Copy from
them; don't reinvent. Full file templates: see REFERENCE.md.
This skill covers two book families that share the toolchain below: math books (MathJax/LaTeX-heavy — the formula and answer-audit sections assume these) and technical / code books (developer-facing prose + code samples, ~20 chapters plus appendices — e.g. an async-Rust or systems topic). For the latter, read Technical / code books next, then apply the scaffold, build, and review sections with the substitutions it notes.
For a code-centric programming book (Go / Python / Rust), prefer the dedicated
mdbook-programmingskill. It shares this toolchain but adds the machinery a code book needs: every displayed sample is externalized to a real file and compiled/tested by a checker wired into the build. Where it and the Technical / code books notes below disagree — especially on code samples — mdbook-programming wins (externalize and verify; do not hand-type code into a Markdown fence). Use the notes below only for a lightly-code book that doesn't warrant that machinery.
Technical / code books (prose + code, not just math)
The same toolchain (mdbook + mdbook-pandoc PDF, mdbook-admonish, justfile, git, multi-agent review) authors a developer-facing technical book — a language, runtime, or systems topic taught deeply at several levels, including deep ones. Differences from a math book:
- Read the spec first. The book request lives in
docs/prompt.md; read it before planning. Capture findings/outline as adocs/artifact rather than only reading — concrete intermediate notes survive a session ending mid-book. - Plan, then propose — before writing. Design a table of contents of about 20 chapters plus 2–4 appendices, split into several major sections. Propose the chapter allocation, title, audience, and tone to the user and get agreement before drafting. Defaults: audience intermediate-to-senior developers; tone direct, developer-to-developer.
- File layout (instead of math
chNN/day-per-lesson files):
PDF lands insrc/SUMMARY.md preface.md ch01.md … ch20.md appendix-a.md appendix-b.md appendix-c.md afterword.mdbook/pandoc/pdf/<book-title>.pdf, HTML inbook/html/.git initat scaffold time and keep the wholebook/tree out of git (.gitignore:/book/,.DS_Store). - Code samples carry the argument. Every sample must firmly support the claim the surrounding text makes — if the prose says X is faster / safe / blocking, the code must actually demonstrate X. A sample that merely looks plausible is a defect. Fence code plainly (no admonish) so it survives the xelatex PDF. For diagrams, prefer the Typst/CeTZ figure pipeline (see Diagrams — use real vector figures); a plain ASCII fence is acceptable only for a trivial flowchart.
- Preamble: use the light preamble (no theorem environments) — these books
rarely need
amsthm. - Write one gold-standard chapter first, verify it builds to HTML + PDF, then author the rest (one agent per chapter — see Parallel authoring).
Toolchain — pin these versions (the #1 time sink when unpinned)
cargo install mdbook --version '0.4.52' --force
cargo install mdbook-pandoc --version '0.10.6' --force
cargo install mdbook-admonish --force # 1.20.x
# pandoc >= 3.x with xelatex (system package); plus `just`
The triple (mdbook 0.4.52, mdbook-pandoc 0.10.6, mdbook-admonish 1.20) is
load-bearing: mdbook-pandoc < 0.10 has no math support and silently renders
$x^2$ as literal dollars in the PDF — a real build shipped that way for weeks
(hours lost across three debug sessions). 0.11 needs mdbook 0.5; admonish 1.20
doesn't support 0.5 yet, so don't bump one without the others.
Reusable maintenance scripts
This skill ships scripts/ — copy the directory into a new
book and wire it into just test. They are generic (uv/PEP-723) and configured by
flags, so a new book needs no code edits. Gates a clean mdbook build does NOT
provide:
chapter-audit.py— markdown source health (math/prose separation checks).check-refs.py— everyTheorem N.M/§N.K/Chapter N/Eq. (N.M)/Appendix Xand cross-volumeV1 …citation resolves. The convention-drift gate; run after any renumbering.pdf-math-check.py— raw TeX leaked into the rendered PDF.answer-audit.py— every claimed answer substituted back through sympy.run-review.py+aggregate-review.py— multi-agent review orchestration.
See scripts/README.md for flags and the answer-audit schema.
Scaffold checklist
book.toml—[output.html] mathjax-support = true, admonish preprocessor,[output.pandoc.profile.pdf]withpdf-engine = "xelatex"andinclude-in-header = ["theme/pandoc/preamble.tex"]. (Template in REFERENCE.)theme/head.hbs— MathJax delimiter config. Note: although it lists\(...\), that form does NOT survive mdBook's Markdown step — author inline math as$...$(see the delimiter warning below).theme/figures.css+theme/figures.js— figure numbering/captions (optional but standard). For real diagrams, also copy the Typst/CeTZ figure pipeline —theme/figures/preamble.typ,scripts/typst-build, and the two pandoc filterstheme/pandoc/{image-blocks-to-figures,rewrite-figure-ext}.lua— from~/projects/books/math/geometry, and wire them intobook.toml, thejustfile(figuresrecipe +export BOOK_ROOT), and.gitignore(/src/figures/built/). See Diagrams — use real vector figures. Do not ship ASCII diagrams.theme/pandoc/preamble.tex— amsmath/microtype/fancyhdr/hyperref. Use the light preamble (no theorem environments) for informal books.- Run
mdbook-admonish install .once — writesmdbook-admonish.css(referenced byadditional-css). src/SUMMARY.md— table of contents (every chapter file must be listed here or mdbook warns and skips it).justfile—build/serve/pdf/clean. (Template in REFERENCE.).gitignore—/book/,.DS_Store,.uv-cache/,__pycache__/(the last two are created by thescripts/tools and must not be committed).
Verify the skeleton builds (mdbook build) with ONE real chapter before writing
the rest.
Formula rendering across HTML · PDF · serve
A green mdbook build is NOT proof math rendered. The build exits 0 while
the PDF shows literal $$ \frac{a}{b} $$. Always confirm:
pdftotext book/pandoc/pdf/*.pdf - | awk '/Definition 1/' shows rendered Unicode,
not raw TeX — or just run scripts/pdf-math-check.py.
math = trueis mandatory and the key name is exact.book.tomlneeds[output.pandoc.markdown.extensions]math = true— nottex_math_dollars. Missing it ⇒ no PDF math.\frac, not\dfrac, inline.\dfracforces display-size fractions inside$…$, bloating line height and pushing punctuation onto its own line.\fracis compact inline and auto-full-size in$$…$$.- No
[...](...)inside$…$. Pandoc's tokenizer reads it as a markdown link and errorsUnable to normalize link. Use\bigl( \bigr),\left[ \right]. - Never literal
▮/✓/ Unicode arrows in source. STIX fonts lack the glyphs (Missing characterwarning, dropped in PDF). Use$\blacksquare$,$\checkmark$,\to. This is a cross-book hard rule. mdbook-admonish installregeneratesmdbook-admonish.csswith@mediablocks that mdbook-pandoc's CSS parser rejects (invalid @ rule '@media'). Re-strip them after every admonish install.servereload occasionally caches stale MathJax; hard-refresh the browser before trusting "the formula still shows raw text."
Full PDF-hazard table (hyperref, appendix labels, {=latex} blocks, page
numbering, enumitem spacing) is in REFERENCE.md.
Diagrams — use real vector figures (Typst/CeTZ), not ASCII
Math diagrams are load-bearing pedagogy — build them as real vector figures,
never ASCII art. For a math book, hand-drawn ASCII diagrams look amateurish and
have been explicitly rejected by the author ("The diagrams in this volume are
horrific. They are really bad."). Use the Typst + CeTZ pipeline, copied
wholesale from ~/projects/books/math/geometry — it produces clean, labeled,
STIX-font vector figures in both HTML and the PDF.
Pipeline. Each figure is a Typst source figures/<chapter>/<name>.typ that
draws with the CeTZ package. scripts/typst-build walks figures/**/*.typ and
compiles each (mtime-cached) to both src/figures/built/<chapter>/<name>.svg
(for HTML) and .pdf (for xelatex). Markdown references the SVG as a
standalone paragraph — the caption may contain inline $…$ math:

Wiring — copy these four things from the geometry book:
theme/figures/preamble.typ— every.typbegins with#import "@preview/cetz:0.4.0",#import "../../theme/figures/preamble.typ": *,#show: figure-setup.figure-setupsets an auto-size transparent page and theSTIX Two Textfont (so figure text matches the book); the file also defines the grayscale stroke palettegfx-stroke/gfx-thin/gfx-arc.- Two pandoc Lua filters, added to
[output.pandoc.profile.pdf] filtersinbook.toml:image-blocks-to-figures.lua(wraps a standalone image paragraph in a numbered, captionedFigure) andrewrite-figure-ext.lua(rewritesfigures/built/….svg→.pdfon the xelatex path — xelatex cannot\includegraphicsan SVG; it resolves to an absolute path via$BOOK_ROOT). HTML is untouched (mdbook's HTML pipeline never runs pandoc). justfile— afiguresrecipe (./scripts/typst-build) thatbuildandtestdepend on, plusexport BOOK_ROOT := justfile_directory()at the top so the filter finds the built PDFs regardless of cwd..gitignore— add/src/figures/built/(built artifacts stay out of git, like/book/). The.typsources are committed.
Requires typst and rsvg-convert installed (brew install typst librsvg).
Hard rules (each one cost a debug cycle):
- A figure reference must be TOP-LEVEL markdown — never inside a
admonishblock. Inside a tcolorbox the image becomes a LaTeX float and the PDF dies with! LaTeX Error: Not in outer par mode.If the diagram belongs to a worked example, place the image immediately before/after the admonish box and refer to it as "the figure below/above." - Typst variable shadowing eats math glyphs. A local
let theta = 40degshadows thethetasymbol inside[$theta$], so the label prints the number40deginstead of θ. Name angle variablesang/angle-rad, nevertheta/phi/pi. In QA, confirm θ/π render as glyphs, not numbers. - Restrict source greps to
*.md.check-math-delims(and any recursivegrep … src/) will match the built binary.pdffiles undersrc/figures/built/and false-fail; add--include='*.md'(or-I).
Authoring workflow. Author 1–2 gold-standard .typ templates yourself first —
a labeled triangle, and a plotted curve with axes (sample the function into a point
array and draw with line(..pts); axes are gfx-thin lines with
mark: (end: ">")) — and confirm one builds to HTML + PDF. Then fan out one agent
per chapter (see Parallel authoring); each agent must (a) read the two
gold-standard .typ files + theme/figures/preamble.typ + a geometry example for
circles/arcs, (b) restate the top-level-image and shadowing rules, and (c) render
every figure to PNG (rsvg-convert -z 3 <svg> -o /tmp/x.png) and visually inspect
it, iterating until labels don't overlap and the math is right. QA the whole set
by rendering all built SVGs to PNG and tiling them into contact sheets (a
uv/PEP-723 pillow script), then eyeball the sheets — this catches shadowing bugs
and overlaps a build cannot.
Writing conventions (match the exemplar exactly)
- Math delimiters: inline
$ ... $, display$$ ... $$. Do NOT use\( ... \)inline — mdBook's Markdown parser (pulldown-cmark) strips the backslash before ASCII punctuation, so\(,\),\,,\;are eaten before MathJax sees them and the math renders as literal text (e.g.(f'(x))). Display$$...$$is safe because$isn't escaped. Inside math, use only backslash+letter macros (\frac,\dfrac,\sin,\to); avoid spacing macros\,and\;(write a normal space instead) and set-brace\{ \}(use\lbrace \rbrace). This is the single most common way to silently break a math mdBook — the HTML still builds, it just shows raw text. Guard it with a check that grepssrc/for\\[(),;]and fails the build. - Bulk-fixing delimiters safely. To convert a book that wrongly used
\(...\):sed -E -i '' 's/\\\(/$/g; s/\\\)/$/g; s/\\[,;]/ /g' src/*.md staging/*.md. Back upsrc/first and dry-run on one file — the spacing macros are a shell-escaping trap:s/\;/ /g(one backslash) matches every semicolon and silently eats prose;and HTML entities. The character class\\[,;]sidesteps it. Always rebuild and confirm survived (grep -c 'nbsp;') and inline math now renders before moving on. - ASCII-only outside math. Literal Unicode in prose (arrows
→, superscripts²/ˣ,½) renders in HTML but throws "Missing character" warnings and drops glyphs in the xelatex PDF (STIX fonts lack them). Write\to,x^2,e^xinside math instead. Chapter H1s and SUMMARY entries are NOT math-rendered in the PDF/sidebar — keep them plain ASCII (e^x, noteˣ). - Admonish titles are plain text, not math:
title="powers of x", nevertitle="powers of $x$"(it renders literally in the title bar). - Callouts:
admonish example(worked examples),tip(hints),warning(common mistakes),note(asides/next-up),abstract/info(boxed rules). Don't nest them. Keep one blank line discipline; mismatched```fences break the build. - Diagrams: build real vector figures, not ASCII art. For a math book,
diagram quality matters — use the Typst/CeTZ pipeline (see Diagrams — use real
vector figures); do not hand-draw ASCII diagrams. A plain fenced ASCII block
(monospace Menlo, which has the box-drawing glyphs
│ ┌ ┐ └ ┘ ├ ┤ ┬ ┴ ─and·; ASCII inside only —x^2,-, never Unicode superscripts/minus) is tolerable only for a trivial flowchart in a lightly-illustrated code book, and never inside admonish or math. - Write one gold-standard chapter first, then have every other chapter match its header block, section order, and callout usage.
Parallel authoring (for multi-chapter books)
Independent chapters → dispatch one agent per chapter (see
superpowers:dispatching-parallel-agents). Each agent prompt must: (a) tell it to
Read the exemplar chapter + a conventions/PLAN doc first, (b) give a precise
per-chapter content spec, (c) restate the ASCII-outside-math and delimiter rules.
Known gotcha: sub-agents sometimes leak their tool-call closing tags
(</content>, </invoke>, antml: fragments) into the file tail. After a parallel
run, always:
grep -rn '</content>\|</invoke>\|antml:\|<parameter' src/ and strip any hits.
Answers/solutions pattern: have each chapter agent write its answer key to a
staging/<chapter>-answers.md fragment (avoids write conflicts on one file), then
assemble src/answers.md from the fragments via a just answers recipe so it's
reproducible.
Multi-agent review (for the formal sibling-series books)
The rigorous books (algebra/geometry/precalculus/calculus) review each chapter
with a team of specialist agents before accepting it. Orchestrated by
scripts/run-review.py (snapshot + manifest of agents to dispatch) and
scripts/aggregate-review.py (roll verdicts into summary.md). Roster lives as
docs/review-agents/<name>-reviewer-prompt.md files (auto-discovered):
- math (correctness — the highest-stakes; a wrong answer trains a wrong habit), clarity, voice (AI-slop list), consistency (numbering/refs), source-originality, and a topic-specific applied reviewer (applied-physics / applied-trig — dispatched only when the chapter has §Applications or ExamStyle). Problems volumes add a coverage reviewer.
Hard-won process rules (each prevented a wasted round):
- A chapter is APPROVED iff every agent report says
## Verdict: APPROVED— trust per-agent verdicts, not the aggregate header's bullet count. - Every fix dispatch carries the brief: "re-read the chapter after your edit and flag anything you may have introduced." Fixes introduce new errors; this is the single highest-leverage instruction.
- 3-round cap, then escalate to the user with a written root-cause summary.
- The applied reviewer is the highest-yield specialist — treat its findings as blocking even when the math is algebraically correct.
- Dispatch all agents for a round in one parallel batch (17 agents return in
~the time of 1). See
superpowers:dispatching-parallel-agents.
Review roster for technical / code books
A technical book is reviewed by a team that attacks the technology and the prose, not the math. Dispatch one agent per lens in a single parallel batch; each must identify problem areas and offer a concrete alternative — revised text, or a changed argument/point:
- technical-accuracy — claims where the technology does not behave as the text describes (the highest-stakes lens; treat its findings as blocking).
- code-supports-claim — code samples that do not firmly demonstrate the point the surrounding text is making.
- logical-flow — places where the text does not follow from one point to the next.
- voice (AI-slop) — passages that read like generated AI text rather than a human developer; flag and rewrite.
Same process rules as above: a chapter is APPROVED only when every agent report
says ## Verdict: APPROVED; every fix dispatch carries the brief "re-read the
chapter after your edit and flag anything you introduced"; 3-round cap, then
escalate to the user with a root-cause summary.
Editing an existing book
- Renumbering / retitling ripples widely. Inserting a mid-book chapter or
changing the day count touches:
book.tomltitle+ PDFoutput-file,SUMMARY.mdheading and entries, everyDay N/N days/Beyond the N daysreference, the per-chapter answer-fragment list in the justfile, and internal "tomorrow"/cross-links.grep -rniE 'day 14|14 days|fourteen' src/and fix each — but do not touch sibling-book titles (e.g. a realLimits in 14 Daysreference). Then regenerateanswers.mdand re-run the gate. - PDF page count (sanity check):
mdls -name kMDItemNumberOfPages -raw <pdf>.
Final verification (before declaring done)
Codify these as just test recipes (see REFERENCE.md) so the gate is one command
and deploy can depend on it. Run the gate after every major change, not just
at the end — batching verification lets bugs compound across commits. When a bug
slips past the gate, fix the bug and tighten the gate in the same session.
-
mdbook buildproduces HTML and PDF with no[WARNING]/ "Missing character" lines (missing-character = a Unicode glyph the PDF font lacks; fix the source).just testmust includemdbook build— the cheaper checks do not resolve{{#include}}directives, so a self-referencing include can sit in HEAD while everything else stays green. -
scripts/chapter-audit.pyclean — odd$,\(-delimiters, fence parity, literal Unicode, leaked tool tags, SUMMARY coverage, placeholders. -
scripts/pdf-math-check.pyclean — no raw TeX survived into the PDF. - For problems/solutions books:
scripts/answer-audit.pyclean — the primary correctness gate. A polishedProblem / Setup / Solutionblock can still be mathematically wrong; only the CAS substitution catches a wrong sign or coefficient. Build it in Phase 0; write the entry when you write the solution. -
src/SUMMARY.md: all# Part/# Appendicesheadings sit before the---suffix-chapter separator — a# heading+ list after---failsmdbook testwith "Suffix chapters cannot be followed by a list." -
src/answers.mdreproduces from thestaging/fragments (regenerate, diff). - Run an adversarial math-checker agent over all chapters + answers (recompute every example) — even with answer-audit, prose-answer/proof problems need it.