Section Authoring Playbook
How to author a section in the Hugo build: pure Markdown + shortcodes. The
mechanical rules are enforced by the tooling (npm test, npm run verify-section) — author, run the verifier, fix what it reports. You should not
have to memorize the mechanical rules; the lints know them.
This playbook describes the repository’s current Hugo architecture. Content is plain Markdown with Hugo shortcodes; React, JSX, MDX, and Nextra metadata are not valid authoring syntax.
0. Source-first workflow (required for AI agents)
Automated checks can prove that syntax renders and an authored answer grades against itself. They cannot prove that the transcription is faithful or that the answer is mathematically correct.
Before writing:
- Read
AGENTS.md, this playbook, and—when applicable—the Knowledge Check playbook. Inspectgit statusand preserve unrelated work. - Use the local source PDF as the content authority. Render and visually read the relevant section pages and Answer Key pages. Extracted text or the web edition may help locate prose, but neither is authoritative for formulas, diagrams, part labels, or answer context.
- Keep a source ledger in working notes: PDF page range, source section, every retained exercise number, its official answer location, and the component used. Record any response-mode adaptation or source discrepancy.
- Preserve the source’s objectives, exposition, definitions, procedures, examples, and mathematics. Omit only material explicitly allowed by the request/playbook. Never silently invent, condense, or “repair” source content; flag a suspected source error and show the evidence.
- For chapter-sized work, divide by section. Each worker must read the same playbook, inspect its own PDF pages, run its section verifier, and return its source ledger. The parent reconciles every section and runs the book-wide gates.
Existing sections authored before July 22, 2026 whose attribution footer explicitly discloses light condensation are grandfathered. Do not rewrite them solely to remove that disclosed condensation. New sections and substantive revisions follow the source-fidelity rule above; never use the grandfathering exception to justify new omissions.
1. Where the file goes
content/<subject>/<book>/<NN>-<chapter-slug>/<NN>-<section-slug>.md- Section landings (book cover, chapter overview) are
_index.md, notindex.md. - Ordering is by frontmatter
weight. Give each section the weight matching itsNNprefix. At book level, chapters and Knowledge Checks share one sequential weight order: insert a Knowledge Check immediately after its ending chapter, then continue with the next unused weight. A chapter’s weight therefore may differ from its chapter number after an earlier Knowledge Check. - Sidebar label defaults to the frontmatter
title. SetlinkTitle:only if the sidebar label should differ. - Numeric prefixes are zero-padded, start at
01, sequential, no gaps. The validator enforces this.
Frontmatter for a numbered section:
---
title: Add and Subtract Fractions with Common Denominators
description: >-
One to two sentences summarizing the section.
source_section: "4.4" # the section number in the SOURCE textbook
weight: 4
---Book root _index.md also needs license: and source: (and carries
cascade: {type: docs} so the whole book gets the docs sidebar). Chapter
_index.md needs source_chapter:. Knowledge-check pages need
source_chapters: (e.g. "1-6"). npm run validate checks all of this.
Every chapter _index.md contains a reader-facing overview in this exact
shape:
## Sections
- **Section title** — concise description derived from the section objectives.List every authored section once. Do not copy visible print prefixes such as
10.1, and do not fall back to a bare linked outline or title-only bullets.
The content lint enforces the descriptive bullet shape.
2. Writing patterns
- Math: KaTeX —
$...$inline,$$...$$display (multi-line$$fenced on their own lines). Group digits with{,}:$37{,}519{,}248$. Plain prose numbers need no$. A literal dollar sign (money) is\$. - Fractions:
\tfraceverywhere — inline, in Check: sentences, and inside$$arrays.\dfracis banned and plain\fracinside an array renders too tall; the lint enforces both. Nested fractions use\cfracin a display block. - Worked-example step tables: align relations with
\begin{array}{lrcl}, rowsexplanation & LHS &=& RHS \\[4pt]. Separate steps with\\[4pt](the lint rejects bare\\in{lrcl}arrays). - Prose inside math needs TeX spacing: ordinary spaces outside
\text{...}disappear. Write\text{If }n^2=mor\text{If}\ n^2=m, and use\between adjacent text commands. Never rely on source whitespace in\text{If} n,m \text{is}, or\text{square}\text{root}. The lint catches high-confidence joins; the rendered page is the final check. - Unlike MDX, a literal
{in prose is harmless in Hugo — only{{starts a shortcode. So set notation like{3, 6, 9}is fine in prose (though math sets still read best as$\{3,6,9\}$).
3. Exercises and components (shortcodes)
Fill-in (free-response math, graded in the browser):
{{< fillin
question="Find the sum: $\tfrac{3}{6} + \tfrac{2}{6}$"
answer="\frac{5}{6}"
answerDisplay="$\tfrac{5}{6}$"
hint="Add the numerators and keep the same denominator."
>}}LaTeX in answer uses a single backslash (Hugo passes shortcode params
verbatim — no MDX doubling). question/hint/answerDisplay support inline
$...$. The verifier runs every answer through the real grader: it must
self-grade correct.
Every regular-section exercise needs a concise, strategy-oriented hint.
Knowledge Checks deliberately omit hints.
When a prompt asks for an unordered collection of roots or solutions, add
answerMode="unordered":
{{< fillin
question="Solve $x^2=9$. Enter both solutions, separated by a comma."
answer="-3,3"
answerMode="unordered"
answerDisplay="$x=-3$ or $x=3$"
hint="Use the Square Root Property and include both signs."
>}}Use this mode only when order has no mathematical meaning. Ordered pairs, ordered triples, coordinate lists, sequences, and prompts that explicitly ask for larger-first/smaller-first order must keep the default ordered grading.
Every retained source “Try It” or check must become a real fillin,
multiplechoice, or graphplot component. Never ship a static prompt followed
by <details><summary>Check answer</summary> or plain answer prose. Choose the
component that matches the learner’s response; if the source interaction cannot
be represented faithfully, choose another source item or record the smallest
response-mode adaptation in the source ledger.
Learner-facing fields contain the exercise itself, not print UI:
- Remove prefixes such as
Try It 10.17.fromquestion,hint, andanswerDisplay; retain the source number in the ledger. - A standalone prompt does not keep a source part marker: write
Simplify $x^2$, notSimplify: (a) $x^2$. - Split a genuine multipart task into separate components unless one combined response is pedagogically necessary and supported by the grader.
Keep each consecutive interactive practice set to 2–3 questions maximum,
counting fill-ins, multiple-choice questions, and graphing questions together.
Choose prompts that cover the example’s distinct skills instead of repeating
near-identical arithmetic. Dedicated knowledge-check-XX-YY.md pages are exempt
because they are cumulative assessments.
Multiple choice (text mode) — options are one per line in the body; answer
must match an option string exactly:
{{< multiplechoice
question="Which symbol goes at the endpoint of $x > 2$?"
answer="parenthesis"
hint="Is $2 > 2$ true?"
>}}
parenthesis
bracket
{{< /multiplechoice >}}Multiple choice (graph mode) — “which graph is correct?”. Options are
prerendered <svg> blocks separated by a line ===OPT===, with
answerIndex (0-based). Generate each option SVG with the figure helper
(below). Every option needs an aria-label.
Graph it yourself (GraphPlot): config (answer + grid) is JSON in the body.
{{< graphplot
question="Graph the line $y = -4x$ by placing two points on it."
answerDisplay="$y = -4x$"
ariaLabel="A blank grid from −7 to 7 on both axes."
hint="The line passes through the origin."
>}}
{"answer": {"slope": -4, "intercept": 0}, "grid": {"xMin": -7, "xMax": 7, "yMin": -7, "yMax": 7}}
{{< /graphplot >}}answer shapes: {slope,intercept}, {x}, {y}, {system:[…]},
{quadratic:{a,b,c}}.
GraphPlot configuration is validated during npm test: grid bounds and steps
must be finite, minimums must be below maximums, snap/grid/tick steps must be
positive, and the answer shape must match one of the supported forms.
Callouts / cards (Hextra shortcodes):
{{< callout type="info" >}}
**Note.** Body markdown, including $math$.
{{< /callout >}}Static figures (Graph / NumberLine / Figure): these are prerendered SVG.
Hugo can’t run the geometry at build time, so generate the SVG with the helper
and paste the <div class="ap-figure">…</div> block it prints:
node tools/render-figure.mjs graph '{"ariaLabel":"The line y = 2x + 1.","lines":[{"slope":2,"intercept":1,"label":"y = 2x + 1"}]}'The accepted JSON properties map to the buildGraph, buildNumberLine, and
buildFigure helpers in assets/js/lib/graph-core.mjs. Every figure MUST
carry an ariaLabel.
Do not paste or download textbook equation images into content. Write equations
as KaTeX, recreate tabular relationships as Markdown tables, and use the figure
helper for diagrams and graphs so its accessible SVG is embedded directly in
the page. File-backed Markdown/HTML images, raster formats (.png, .jpg,
.webp, and similar), and inaccessible inline SVGs are rejected by the
content lint.
Recreated graphs and diagrams still require visual comparison with the PDF. Check geometry, labels, axes, endpoints, direction, and dark-mode contrast; a valid accessible SVG can still be mathematically wrong.
4. Verify (the workflow)
From the repository root:
- Reconcile the finished page against the source ledger and PDF images: objectives, examples, math, figures, retained exercises, and official answers must all match.
- Independently solve every touched interactive question and compare both the prompt and answer with the source Answer Key. Do not infer correctness from self-grading.
npm run verify-section -- content/<subject>/<book>/<ch>/<sec>.md— lints, renders every math run, and confirms that each fill-in answer is parseable by the real grader. Fix every ✗ and investigate warnings.- Run
npm test. It includes whole-repository structure validation, per-page real-grader verification, unit tests, repo-wide authoring lints, documentation consistency checks, and KaTeX parsing.npm run validateremains available as a focused structure-only command. - For chapter/bulk work, run
npm run buildandnpm run check:build(ornpm run ci). The production audit checks, among other things, that math pages load the pinned KaTeX CSS and that its hidden accessibility layer cannot appear as duplicate visible math. - Open every changed page with
npm run serve. Confirm real components render and grade, prose/math spacing is visible, formulas appear once, and figures match the PDF. Also open a changed chapter landing page. - Run
git diff --checkand report the changed/untracked files plus the source ledger and pages visually checked. Do not commit unless asked.
Done checklist
- PDF section and Answer Key pages visually inspected; source ledger complete.
- Source fidelity reconciled; any discrepancy/adaptation explicitly recorded.
- Frontmatter complete (
title,description,source_section,weight). - Chapter landing uses descriptive
**Title** — summarysection bullets. - Every retained practice item is a real component with no print-only label.
- Every touched answer independently solved and checked against the source key.
- No file-backed instructional images; recreated figures compared visually.
-
verify-sectionclean (✗ = 0). -
npm testgreen. - Production build/audit green for chapter or bulk work.
- Browser check confirms math appears once, spacing is correct, components work, and figures match the source.
- Handoff lists source evidence, visual pages, and all uncommitted files.