Book publishing

Publish long-form content with one navigation tree, numbered components, stable cross-references, generated indexes, and whole-Book print HTML.

Oink’s Book capability extends the documentation shell. It uses the existing content tree or data/docs_nav.json, the same breadcrumbs and pager, and the same output-aware component system. It does not introduce a second chapter manifest or a parallel navigation implementation.

Create the Book root

A section Book declares its type, requests the outputs it needs, and cascades the type to descendants:

YAML
---
title: Systems Handbook
type: book
book_kind: book
book_number: B
outputs: [HTML, print, markdown]
cascade:
  type: book
---

The Book root is always the current first section, even when the site enables a sidebar root switcher. Its navigation cannot leak into sibling docs or blogs. If a site overrides params.ui.shell_types, retain book in that list.

Request print explicitly: Oink never adds an expensive aggregate output to a consumer configuration. For a section Book, the relevant Hugo output kind is section; use home only when the Book is the site root:

YAML
outputs:
  section: [HTML, print]
params:
  ui:
    shell_types: [docs, book, blog, swagger]
    sidebar_headings: 3
    book_draft_banner: true

sidebar_headings accepts false, true (level 2), or a maximum heading level from 2 through 4. It projects the active page’s Hugo fragment tree below the chapter row. Use explicit heading IDs for anything that will be cited:

MARKDOWN
## Synchronous replication {#sec_replication_sync}

Generated heading slugs are convenient navigation, not a durable citation API.

Describe chapters

Chapters may use the established metadata namespace:

YAML
---
title: Replication
book_kind: chapter
book_number: 3
book_part: II
book_status: draft
weight: 30
---

book_number appears beside titles in the page, sidebar, and generated Book table of contents. book_status: draft is a visible editorial label and does not change Hugo’s publication state. Set book_draft_banner: true to add a localized page notice as well.

Add numbered components

The numbered forms of fig, tbl, and eq require a quoted num matching letters, numbers, dots, or hyphens. Their default IDs are fig-<num>, tbl-<num>, and eq-<num>; set an explicit stable ID when preserving an existing public anchor.

Figures

GO-HTML-TEMPLATE
{{< fig num="2-1" id="office_2003" src="/fig/office.png"
    caption="The Word 2003 interface" alt="Word 2003 with stacked toolbars"
    width="960" height="640" >}}

New figures should always provide meaningful alt. title is a migration alias for caption and the two are mutually exclusive. A figure may use src or inner Markdown content, never both. URLs, class tokens, and positive image dimensions are validated.

Tables

GO-HTML-TEMPLATE
{{< tbl num="2-1" id="output-matrix" caption="Output behavior by surface." >}}
| Surface | Label | Anchor |
| --- | --- | --- |
| HTML | Visible | Stable |
| Print | Visible | Stable |
{{< /tbl >}}

The component keeps label, Markdown table, caption, and anchor inside one semantic figure. It does not simulate captions with a heading.

Equations

GO-HTML-TEMPLATE
{{< eq num="5.3" id="eq-capacity" caption="Capacity approximation." >}}
X \approx \frac{C}{R+Z}
{{< /eq >}}

Equation content goes directly through local server-side KaTeX, even if the site has not enabled Goldmark passthrough. The parameter-free eq form remains an unnumbered display-math escape hatch and cannot be an xref target.

Duplicate IDs, or two components of the same kind claiming one number with different IDs, are build errors. Captions are plain text; figure and table body content follows the page’s Markdown policy.

Cross-reference safely

Reference a numbered target by kind and number:

GO-HTML-TEMPLATE
See {{< xref fig="2-1" anchor="office_2003" >}}.

Reference a heading on another page with explicit link text:

GO-HTML-TEMPLATE
See {{< xref page="../replication" anchor="sec_replication_sync" >}}synchronous replication{{< /xref >}}.

xref accepts at most one kind (fig, tbl, or eq), plus optional page and anchor. A kind supplies the localized default label and derives the default anchor. An anchor-only reference requires inner text. Cross-page lookup uses Hugo’s current-language page resolution, so source never hard-codes an /en/ route.

References are order-independent and may appear before their targets. In whole-Book print, Book-aware xrefs become document-local fragments. Ordinary Markdown cross-page links intentionally remain site URLs, so use xref for citations that must work inside the aggregate.

Generate Book indexes

Build a table of contents from the same ordered Book tree:

GO-HTML-TEMPLATE
{{< book-toc depth=3 >}}

Depth 1 lists chapters, depth 2 includes nested sections, and depth 3 also projects each page’s heading tree. drafts=false filters visible editorial drafts from this generated list only; it does not unpublish their pages.

Generate figure, table, or equation lists:

GO-HTML-TEMPLATE
{{< book-figures >}}
{{< book-figures kind="tbl" >}}
{{< book-figures kind="eq" >}}

These shortcodes trigger and aggregate descendant content deterministically, then link to stable target IDs. They do not require a copied registry file.

Publish whole-Book print

The Book root’s print output emits a cover, local table of contents, then the root and visible descendants in reading order. no_print: true, link-only nodes, sidebar dividers, and hidden placeholders do not become chapters.

Numbered target IDs remain byte-stable. Page-local Markdown heading IDs receive a source-page prefix in the aggregate, so repeated anchors such as summary remain unique; generated heading links are rewritten accordingly. Book ToC, figure-list, and xref destinations become document-local.

The result is print-oriented HTML, not a network-dependent PDF/EPUB pipeline. Pagination, PDF conversion, and EPUB packaging remain site-owned concerns.

Migrate existing books

Inventory first, transform only unambiguous forms, and stop rather than guessing missing numbers, captions, alternatives, or targets. Preserve existing public IDs independently from display numbers. Run migrations on a branch, retain a machine-readable before/after report, validate every skipped record, and require a zero-change second run.

The Oink v0.4.0 source ships a dry-run-first, idempotent migration tool and observed-site recipes for TPME, DDIA, and pg-internal. Treat those as executable patterns for the named source forms, not universal caption heuristics.

Validate a Book

  1. Compare the sidebar, pager, generated ToC, and whole-Book chapter order.
  2. Verify every numbered target ID is unique and every xref reaches a matching kind and number in each language.
  3. Confirm numbered figure alternatives are meaningful and caption compatible.
  4. Inspect standalone HTML, Markdown, print, and the whole-Book aggregate.
  5. Test repeated heading names and cross-chapter citations in aggregate print.
  6. Run the theme’s scripts/check-book.py when working from a theme checkout, or implement the same rendered-anchor checks in consumer CI.