This is the multi-page printable view of this section. .

Return to the regular view of this page.

Scenario components

Configure complete reading, release, landing-page, and Book publishing workflows from local content and data.

Scenario Components solve a complete publishing job rather than one fragment of a page. Each scenario coordinates content, local data, navigation, runtime loading, accessibility, and non-HTML output behind one strict contract.

They complement the component reference: use that chapter to look up an individual writing primitive, and use this chapter when the job spans multiple pages, files, or output formats.

Choose a scenario

Scenario Use it when you need Primary source of truth
Sequential reading A manual, Book, or blog should have a dependable sequence Sidebar/content tree and page metadata
Releases and downloads Release facts, assets, and install paths must agree Front matter and data/download/
Landing pages A product page needs reusable full-width sections data/landing/ or inline section data
Book publishing Long-form work needs numbering, citations, and print Existing Book tree and stable page IDs

Shared guarantees

  • Local facts: normal builds do not fetch release state, stars, prices, screenshots, avatars, or other mutable facts from remote APIs.
  • Static first: HTML contains the complete content before progressive enhancement; a page receives JavaScript only for the capabilities it uses.
  • Strict input: malformed parameters, identifiers, URLs, checksums, or data records fail the build with a useful source position.
  • Output aware: HTML, print, Markdown, and RSS either receive a defined representation or deliberately omit interaction-only content.
  • One navigation authority: the visible content tree also drives pagers, Book tables of contents, and aggregate print order.
  • Language safe: shared facts use documented suffix fallback, while narrative data may be maintained per language.

Before adoption

Pin the minimum release that owns these contracts:

GO
require github.com/pgsty/oink v0.4.1

Use Hugo Extended 0.160.1 or newer. Adopt one scenario at a time, build every configured output, and inspect the result in each language. A local build, a public theme tag, a site version pin, and a hosted deployment are separate evidence gates; do not infer one from another.

For an existing Oink site, start with the 0.4.0 upgrade guide.

1 - Sequential reading and mathematics

Configure the shared docs, Book, and blog pager and render mathematics with local server-side KaTeX.

Oink 0.4.0 gives manuals, Books, and blogs a defined reading sequence. The same release also makes server-rendered mathematics a first-class content path.

Enable or narrow the pager

The pager is enabled by default for the docs, book, and blog content types. Replace that set explicitly when a site uses only some of them:

YAML
params:
  ui:
    pager:
      types: [docs, book, blog]

Only those three type names are valid. A page or section can opt out with a boolean front matter value:

YAML
---
pager: false
---

Interactive HTML renders only the previous or next destination that exists and adds matching <link rel="prev"> and <link rel="next"> elements to the page head. Print, Markdown, and RSS contain no pager markup or relations.

Understand reading order

Docs and Books use a pre-order traversal of the same navigation root as the sidebar: a section index precedes its visible children, and ordinary children follow weight order. If data/docs_nav.json supplies an explicit tree, that tree is authoritative for both sidebar and pager.

These visible navigation entries are not destinations:

  • pages hidden with toc_hide;
  • link-only placeholders using manualLink or manualLinkRelref;
  • non-linking rows marked sidebar_divider: true.

Blogs preserve Hugo’s section time order. This is deliberately different from the manual tree order.

Manuals normally live below the configured docs section. If manual pages deliberately live at the content root and /docs/ is only an overview, set:

YAML
params:
  ui:
    sidebar_root_enabled: true
    docs_root: home

docs_root accepts only section (the default) or home; an invalid value is a build error. With home, top-level toc_root: true overview sections remain outside the manual sequence.

Render delimiter mathematics

Hugo does not merge a theme’s Goldmark configuration into a consuming site, so the site must enable passthrough delimiters:

YAML
markup:
  goldmark:
    extensions:
      passthrough:
        enable: true
        delimiters:
          block: [['\[', '\]'], ['$$', '$$']]
          inline: [['\(', '\)']]

Oink supplies the passthrough render hook and local KaTeX CSS. Formulae render server-side as KaTeX and MathML, and only formula pages receive the stylesheet. math: true by itself does not enable delimiter parsing.

Build a page containing both inline and display delimiters, then inspect the HTML for MathML rather than literal $$. Long display formulae scroll within the article column on screen and remain static in print.

Use the display-math escape hatch

When a site cannot enable Goldmark passthrough yet, use the parameter-free display form:

GO-HTML-TEMPLATE
{{</* eq */>}}E = mc^2{{</* /eq */>}}

This form is intentionally unnumbered. It creates no anchor, caption, or Book registry entry and emits a plain $$ block in Markdown and RSS. To create a numbered, referenceable equation, adopt the Book equation form and add a quoted num.

Validate the reading experience

  1. Compare sidebar order with the q/e shortcuts and visible pager.
  2. Confirm link-only, divider, and hidden entries are skipped.
  3. Inspect head relations at the first, middle, and last destination.
  4. Build from a subpath and confirm pager links remain on the current origin.
  5. Check that print, Markdown, and RSS omit interaction-only pager markup.
  6. Test formula pages in both color modes and print, then confirm an ordinary page does not load KaTeX CSS.

See Keyboard navigation for all reading keys.

2 - Releases and downloads

Keep release facts, archive links, checksums, and rolling or pinned download channels consistent without remote API calls.

Oink separates immutable release facts from their presentation. A release page owns the version and repository identity; local download data owns distribution channels. Cards, lists, checksum tables, documentation pages, and Landing pages derive from those records instead of copying URLs and commands into several templates.

Define release facts

Add a strict release map to the page front matter:

YAML
release:
  product: Pig
  version: 1.7.0
  repo: pgsty/pig
  tag: v1.7.0
  date: 2026-08-14
  prev: v1.6.0
  checksums: SHA256SUMS

version and repo are required. Omit tag to derive v{version} and omit date to use the page date. Optional product, prev, and checksums fields complete the record. Unknown keys, wrong types, or a repository outside the owner/name form fail the build.

For a simple GitHub release, the exact tag URL is also accepted as shorthand:

YAML
release: https://github.com/pgsty/pig/releases/tag/v1.7.0

Oink derives repository, release, archive, compare, checksum, and asset links locally. It does not call GitHub during a build and does not claim that a tag or asset exists remotely.

Render the release card

Place the parameter-free shortcode where the fact summary should appear:

GO-HTML-TEMPLATE
{{</* release-card */>}}

The invocation accepts no facts and no parameters; the page front matter is the only authority. HTML receives a semantic link card with no runtime. Print and RSS receive a static link list, and Markdown receives ordinary Markdown links.

Build a release index

A release section can opt into deterministic ordering:

YAML
---
title: Releases
layout: releases
release_group_by_product: true
release_products: [OINK, Pig]
---

Pages sort by normalized release date descending, then valid SemVer precedence, then a deterministic lexical fallback. The default is one global sequence. release_group_by_product: true requires each selected page to define product. release_products accepts one product or an array, matches the product string exactly, and filters before sorting. Invalid filters fail instead of rendering a plausible but empty page.

Publish checksum assets

Write exact sha*sum lines inside release-assets:

GO-HTML-TEMPLATE
{{</* release-assets group="auto" */>}}
0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef  pig-1.7.0-linux-amd64.tar.gz
fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210 *pig-1.7.0-darwin-arm64.tar.gz
{{</* /release-assets */>}}

Or commit a checksum file as a page or Hugo asset and reference exactly one source:

GO-HTML-TEMPLATE
{{</* release-assets src="release/SHA256SUMS" group="auto" */>}}

The parser rejects malformed lines with their line number, mixed algorithms, algorithm/hash-length disagreement, path-like filenames, and ambiguous input sources. HTML links each asset and conditionally loads one local copy runtime; print shows complete hashes without controls; Markdown and RSS emit full-hash tables.

group="auto" groups common platform and architecture names. Use algo only when declaring the expected checksum algorithm, and base only when the asset base differs from the URL derived from release facts.

Define download channels once

Create data/download/pig.yaml:

YAML
version: 1.7.0
repo: pgsty/pig
published: true
channels:
  - id: script
    kind: rolling
    title: Install script
    title_zh: 安装脚本
    icon: fa-solid fa-bolt
    note: Tracks the rolling stable channel.
    note_zh: 跟随滚动稳定渠道。
    steps:
      - title: Install
        title_zh: 安装
        code: curl -fsSL https://repo.example.org/pig/install | bash
        lang: bash
  - id: source
    kind: pinned
    title: Source archive
    title_zh: 源码归档
    icon: fa-solid fa-code-branch
    url: https://github.com/pgsty/pig/archive/refs/tags/${tag}.tar.gz
    steps:
      - title: Clone the tag
        title_zh: 克隆标签
        code: git clone --branch ${tag} https://github.com/pgsty/pig.git
        lang: bash
  - id: assets
    kind: pinned
    title: Release assets
    title_zh: 发布资产
    icon: fa-solid fa-box-open
    checksums_src: release/pig-SHA256SUMS

A record needs a string version directly or through params.version, plus a non-empty channels array. Every channel needs a unique anchor-safe id, one kind (rolling or pinned), and a localized title.

Shared fields resolve from the exact language suffix, then the primary-language suffix, then the unsuffixed field. For example, Chinese may resolve title_zh_cn, then title_zh, then title. Only a pinned channel’s url and steps[].code may interpolate ${version} or ${tag}. Rolling channels reject all interpolation so a stable command cannot accidentally pretend to be pinned.

Render downloads

Reference the data key with one positional parameter:

GO-HTML-TEMPLATE
{{</* download "pig" */>}}

HTML renders a channel index and static-first sections. Code steps reuse Oink’s enhanced code renderer; checksum channels reuse Release Assets. Print expands the same safe content, Markdown emits headings, source fences, and full hashes, and RSS omits the component.

Set published: false before the immutable release exists. Rolling channels remain usable, while pinned channels show a non-linking pending state, omit pinned commands, and disable asset links and copy controls. Flip the fact only after the tag and assets resolve; never paste speculative links into prose.

A Landing page can consume the same record with a download section:

YAML
sections:
  - type: download
    data:
      title: Download Pig
      keys: [pig]

Release checklist

  1. Confirm the version, tag, previous tag, repository, and date from the source release process.
  2. Validate every checksum against the published artifact before committing it.
  3. Build HTML, print, and Markdown; inspect complete hashes outside HTML.
  4. Test published: false before publication and true only after the remote tag and assets resolve.
  5. Verify every language and a subpath deployment.
  6. Treat source completion, theme tag publication, module resolution, consumer pinning, and hosted availability as separate gates.

3 - Landing pages

Compose reusable, full-width product pages from local, language-aware data and Oink’s validated section registry.

A Landing page is a regular Hugo content page with a full-width scenario shell. It keeps the site navbar, Command Palette, and configured footer, but removes the docs sidebar and table-of-contents rail. Content remains local and server-rendered; no frontend build or remote fact API is required.

The homepage continues to use data/home/<lang>.yaml, but now shares the same renderer and section contracts.

Create a Landing page

Create a regular content file and name its local data key:

YAML
---
title: Pricing
layout: landing
landing: pricing
outputs: [HTML, print, markdown]
---

Put the English and Chinese narrative data in separate files:

TEXT
data/
└── landing/
    └── pricing/
        ├── en.yaml
        └── zh.yaml

A non-home page resolves data in this order:

  1. sections written directly in page front matter;
  2. data/landing/<key>/<exact-language>.yaml;
  3. the exact-language entry in data/landing/<key>.yaml;
  4. the English or unsuffixed local record.

Use per-language files for narrative content. Shared fact fields may use an exact language suffix, then a primary-language suffix, then an unsuffixed fallback; language tags normalize - to _. For example, title_zh_cn precedes title_zh, which precedes title. camelCase suffix aliases are not accepted.

Compose sections

Each sections entry is either a type string or a map. A map may set type, read a differently named key, supply a stable id, disable itself with enabled: false, or carry one-off inline data:

YAML
sections:
  - type: hero
    data:
      eyebrow: Local-first documentation
      title: Publish a product page with Hugo
      lead: Complete server-rendered content, enhanced only when needed.
      actions:
        - { label: Read the docs, url: /docs/, style: primary }
  - type: metrics
    key: project-facts
  - type: command-box
    data:
      title: Install
      code: hugo mod get github.com/pgsty/[email protected]
      lang: bash
  - type: download
    data:
      title: Download
      keys: [product]
  - cta

project-facts:
  title: Local facts
  items:
    - { value: 21, label: Section types }
    - { value: 0, label: Runtime fact requests }

Use canonical hyphenated type names. Underscores in existing homepage data are normalized for compatibility. Unknown types warn instead of silently disappearing. A deliberate site-owned partial remains an escape hatch, but it is a local template contract rather than portable Landing data.

Section registry

Oink 0.4.0 provides 21 canonical section types:

Type Use it for
hero Primary message, actions, and theme-aware artwork
metrics Compact facts, numbers, links, and count-up enhancement
capabilities Alternating feature narratives and specialist visual panels
principles Numbered product or operating principles
cards Generic feature, benefit, service, or path collections
logo-wall Tools or partners in a grid or CSS-only marquee
gallery Screenshots or icon-led examples
testimonials Quotations with optional attribution
contributors People, roles, avatars, and profile links
faq Native disclosures or a static flat question list
markdown Free-form prose
cta One final action or a compact action group
pricing Product tiers, prices, features, and calls to action
pricing-compare Feature comparison matrices across pricing tiers
command-box A focused copyable command and optional note
steps Ordered procedures with optional command examples
timeline Dated milestones, roadmaps, and release histories
code-plate Chroma code or validated line arrays in a presentation panel
case-study Evidence-led stories with metrics, quotation, and source
download One or more validated data/download/ records
bar-chart Comparable non-negative values normalized without chart JS

The existing homepage configuration documents the shared collection and Hero fields. For the nine scenario-oriented types, begin from a small entry and let strict validation identify a missing or invalid field. The Oink repository’s exampleSite data is the complete executable reference.

Keep facts local

Pricing, stars, screenshots, avatars, quotes, and download state must exist before Hugo starts. Refresh them in a site-owned maintenance or CI job, review the diff, then commit or generate local data. Do not add browser fetches to a section.

Optional local chrome facts are also strict:

YAML
params:
  offlineSearch: true
  ui:
    landing_search: true
    github_stars: 2189
    alt_site:
      label: 中文站
      url: https://example.cn/

landing_search must be a boolean and only exposes the existing local Command Palette when offlineSearch is also enabled. github_stars is a committed string or number, not a GitHub API request. alt_site requires a label and an absolute HTTP(S) URL.

Progressive enhancement and accessibility

HTML sets one page flag and conditionally loads landing.js. That runtime enhances reveal, count-up, copy, theme-image, and compact-menu behavior; the server-rendered document remains complete with JavaScript disabled.

Marquee duplication is CSS-only. The duplicate track is hidden from assistive technology and interaction, and a localized checkbox pauses motion without JavaScript. Reduced-motion preferences disable movement and reveal transitions; forced-colors mode preserves controls and state distinctions. The compact menu uses real links and buttons, traps no focus, and does not duplicate the desktop navigation tree.

Output and validation

Output Contract
HTML Complete static content, then conditional progressive enhancement
Print Content retained; motion surfaces become static; controls removed
Markdown Headings, prose, lists, tables, and code without component classes
RSS Landing sections omitted

Before publication, test with JavaScript disabled, reduced motion, forced colors, keyboard-only input, both color modes, each language, and a subpath base URL. Confirm every internal link and asset retains the deployment prefix. The inherited Docsy block shortcodes remain compatible, but use Landing data for new pages rather than adding another custom HTML layer.

4 - 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.