Skip to content

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

Return to the regular view of this page.

Design proposals and PRDs

The canonical bilingual home for OINK PRDs and designs that are still being evaluated.
Non-normative material

A proposal describes behaviour that may not exist. Current behaviour is defined by the contracts, accepted decisions, implementation, and owning checkers. Never use a proposal as a configuration reference.

This section is the canonical home for OINK product requirement documents, RFC-style designs, and unresolved maintainer proposals. Do not create a local plan/, plans/, proposal/, or parallel design tree in the theme repository or the documentation repository.

Active proposals

Proposal Current boundary
Backlinks and knowledge graph G1 (static backlinks) is accepted, implemented on the theme’s main branch, and ships with OINK 0.8.0; the local and global graphs (G2/G3) remain draft
Media convergence Partially implemented; the media-result contract and Landing resource metadata shipped, M3 resolved for native-image processing, retirement (M4) open
Bulk agent indexes Accepted (2026-08-27); both outputs are implemented on the theme’s main branch and ship with OINK 0.8.0, after which this proposal retires
Book publication pipeline Manifest and EPUB/PDF tooling are released — see Architecture; only consumer migration is still open here

The generated-configuration-schema proposal has been retired through the lifecycle: the behaviour is documented normatively in Configuration, the long-lived rationale moved to the generated configuration schema decision, and the draft text is preserved by Git history.

Where a new PRD goes

Create one English-primary page and its Simplified Chinese peer:

content/docs/design/proposals/<slug>.md
content/docs/design/proposals/<slug>.zh.md

Use explicit, stable English heading IDs in both files. Keep code, keys, paths, versions, and API names unchanged in Chinese. A proposal begins with visible draft status and includes:

  1. status, owner, date, and affected contract surface;
  2. context and evidence;
  3. goals and explicit non-goals;
  4. proposed behaviour and output/accessibility/security boundaries;
  5. compatibility and migration impact;
  6. implementation and owning-checker plan;
  7. acceptance criteria and open decisions;
  8. a decision log for later changes to the proposal itself.

Large experiments may add a dated page under ../research/, but temporary logs and generated artifacts stay outside Hugo content and outside Git.

Lifecycle

draft proposal
    ├── rejected/superseded → remove from the active tree; preserve Git history
    └── accepted
          ├── implementation + owning checker
          ├── affected EN/ZH contract
          ├── accepted Design decision when rationale is durable
          └── changelog, migration, and user docs when their audiences need them

Acceptance does not turn the PRD into a second contract. Move stable behaviour into the owning contract, stable rationale into Decisions, and user steps into the relevant guide. Then retire the proposal from active navigation. A local build, commit, tag, public module, consumer pin, and deployment remain separate completion states.

Review gate

Before implementation, reviewers confirm that the proposal does not duplicate an existing shell, resolver, component family, or data authority. During implementation, a changed design updates this bilingual proposal before code silently diverges. Acceptance requires the narrow theme checker, the real documentation site, rendered EN/ZH, relevant outputs, accessibility, and responsive review.

1 - Backlinks and knowledge graph

A draft three-stage design for deriving backlinks and local or global graph views from ordinary Hugo links.
G1 implemented; G2/G3 remain draft

On 2026-08-27 every G1 open decision was resolved and G1 (static backlinks) was accepted. It is implemented on the theme’s main branch and ships with OINK 0.8.0. The local and global graphs (G2/G3) stay draft pending real-world evidence from G1; their names and configuration are not public API until accepted.

Premise

Reverse navigation and a view of connected pages are properties of the link graph, not of [[wikilink]] spelling. Hugo already accepts ordinary Markdown links and ref / relref. OINK can derive a graph from content authors already write, without adding a parser, Goldmark extension, or parallel authoring syntax.

The first value is backlinks, not visualization. A static inbound-link list is useful without JavaScript and can degrade into print and Markdown. An interactive graph remains an optional enhancement over that complete list.

Goals and non-goals

Goals:

  • derive one language-local link index per build;
  • show deterministic inbound links on a page;
  • optionally show a bounded local neighbourhood;
  • optionally publish a whole-site view and a machine-readable graph;
  • preserve ordinary preview when an edited link is stale or incomplete.

Non-goals:

  • introducing [[wikilink]] syntax;
  • indexing external, mailto:, same-page anchor, or self links;
  • executing JavaScript to discover links already present in content;
  • turning a visualization into the only way to navigate;
  • promising perfect extraction from arbitrary shortcode parameters or raw HTML.

Delivery stages

Stage Deliverable Runtime Independent value
G1 Language-local link index and backlink list None Reverse navigation in HTML, Print, and Markdown
G2 Local graph around the current page Existing ECharts plus a small local runtime Spatial view with G1 as the accessible fallback
G3 Global graph page and graph data output Same runtime Whole-site exploration and machine-readable edges

Each stage is accepted separately. G1 does not wait for G2, and G2 does not force every page to load graph code.

Extraction contract

The proposed index scans source content once per language and records one edge per source/target pair. It strips fenced code and inline code before extracting ordinary Markdown links and ref / relref; then it resolves only internal pages, removes fragments for page identity, drops self-links, and deduplicates repeated references.

The implementation must test at least:

  • duplicate links collapse to one edge;
  • fenced and inline code produce no edge;
  • external, protocol-relative, mail, same-page anchor, and self links are excluded;
  • ref and relref are included;
  • each language produces an independent graph;
  • an unresolved derived edge warns or is reported by the focused checker without making ordinary hugo server unusable.

Raw source scanning has known omissions. A URL stored in a custom shortcode parameter or raw <a href> may not appear. Those omissions must be documented instead of hidden behind a claim of a complete semantic graph.

G1 renders an aside group in the right rail, a sibling of the table of contents and the taxonomy clouds: what is on this page beside what points at this page. The group is expanded by default and shows the first eight entries; the rest fold behind a native disclosure so a heavily referenced page cannot swallow the rail. The switch is the site key params.ui.backlinks (bare boolean, default off); a page overrides it with the prefix-free front matter key backlinks, and a section can cascade it. Order is deterministic: the stable page path — language-independent, naturally grouped with navigation, and needing no second ordering authority. The group uses ordinary links and is omitted when there are no inbound pages.

Unresolvable derived edges are dropped silently and recorded as a known gap: G1 is a local navigation enhancement, not a link checker, and having it report broken links for the site would only duplicate warnings.

Print and Markdown keep the readable list. RSS omits it unless feed-level research demonstrates that backlinks improve an article feed rather than creating noisy site navigation.

Interactive graph boundary

G2 reuses the locally vendored ECharts graph series. The current page is the centre; direct inbound and outbound neighbours form the default depth. A hard node cap prevents unreadable or expensive views. Keyboard focus, text alternatives, reduced motion, forced colours, narrow screens, and print are acceptance requirements, not later polish.

If JavaScript or ECharts is unavailable, G1 remains complete and visible. The runtime is loaded only on pages that render a graph and must join the existing feature-bundle key so unlike pages cannot collide in the asset cache.

Global output

G3 may add a dedicated graph page and an opt-in JSON output. The JSON schema would contain a version, language, nodes, and directed edges with stable URLs; it would not expose local file paths or unpublished pages. The output must be derived from the same index as G1 and G2 so three representations cannot drift.

Compatibility and migration

Ordinary Markdown remains unchanged, so content migration is unnecessary. Configuration names remain undecided until a prototype proves the smallest surface. The default for every interactive or global output is off; a static backlink list may be considered separately because it is local navigation with no network or browser state.

Acceptance criteria

Acceptance requires a focused graph checker, extraction fixtures, HTML/Print/ Markdown goldens, strict-build negative cases, browser accessibility and responsive tests, and a real bilingual-site build. Performance is measured on a representative large site, but a dated prototype timing is not a permanent budget.

Open decisions

Every G1 question is resolved (see the decision log). Still open, and owned by G2/G3:

  1. Does the local graph expose one depth or a tightly capped second depth?
  2. Which page metadata, if any, is useful enough to enter graph JSON?
  3. Is G3 useful enough to justify a new output format before G1 and G2 have production evidence?

Decision log

  • 2026-08-19: Drafted the three-stage design.
  • 2026-08-27: Resolved and accepted G1, scheduled for OINK 0.8.0. G1 is opt-in: the site key params.ui.backlinks is a bare boolean defaulting to off, pages override with backlinks, and no shell-type gating — policy belongs to the site and the page, not the shell. Ordering simplifies to a single stable-page-path sort, dropping the section → weight → title chain: one deterministic authority is enough for reverse navigation, and a multi-level sort would be a second navigation authority. Unresolvable edges drop silently and are recorded as a known gap, never warned. G2/G3 and the graph data output keep waiting for production evidence.
  • 2026-08-27: Design review moved the block from the page end to the right rail. Backlinks are page metadata and pair with the table of contents, while the page end is the reader’s completion zone — share, feedback, provenance, pager, comments. The rail group also adds the eight-entry cap, with the rest behind a native disclosure.

2 - Media convergence

A draft for the remaining convergence between content images, numbered figures, Landing media, and featured-image selection.
Partially implemented

M1 (the shared media-result contract) and M2 (Landing resource metadata) are implemented on the theme’s main branch, and M3 is resolved as option 2: processing stays exclusively on native Markdown images, and the full fig source form remains a container whose parameter list deliberately excludes command/options. M4 (compatibility retirement) stays open pending a consumer inventory. The sections below are the original design record.

Current baseline

The content image hook, numbered fig, cards, and galleries resolve local page resources, section resources, global assets, static files, and explicit remote URLs through content/image-resolve.html. Raster resources can contribute intrinsic dimensions and processing derivatives. HTML Zoom eligibility is marked with data-td-image-zoom; the build-time detector only checks that theme-emitted marker.

Standalone Markdown images can already combine caption or Book numbering with processing and a link. Numbered image figures share td-figure and td-book-figure semantics. Landing media passes the shared URL trust policy, while featured images intentionally use a ranking resolver because their job is to select a representative image rather than render one explicit source.

Remaining problem

The shared safety boundary is stronger than the shared media model. Landing media still does not obtain the same page-resource metadata and processing result as body images. Featured-image selection and explicit image resolution have separate result shapes. Some compatibility class names remain in markup, and Book’s full fig form cannot express every processing option available to the native image hook.

The design question is therefore no longer “replace seven image entry points.” It is whether the remaining surfaces can share a small result contract without erasing their different semantics.

Goals and non-goals

Goals:

  • define one normalized media-result shape for URL, source URL, dimensions, alternative text, attribution, processability, and external status;
  • let explicit content images, Landing media, and representative images reuse that shape where their source semantics overlap;
  • keep figure markup and Zoom eligibility single-owned;
  • decide whether the full fig form needs processing or whether authors should use the native image form for processed numbered images;
  • retire compatibility markup only after consumer evidence and a release note.

Non-goals:

  • adding a third-party lightbox or remote image service;
  • changing image Zoom from opt-in to site policy by accident;
  • giving galleries a new caption, sequence, or carousel model;
  • merging non-image Book targets such as tables, equations, and examples into an image-only base class;
  • making featured-image ranking identical to explicit image resolution.

Proposed phases

M1 — Result contract

Document the fields returned by the content and representative-image resolvers, then extract the intersection into one internal media-result contract. Keep source ranking in the featured resolver and source resolution in the content resolver. This is an internal refactor with byte-stable output.

M2 — Landing resource metadata

Allow Landing items to resolve eligible local resources through the media contract, gaining intrinsic dimensions and the same URL/security decision. Explicit width and height in Landing data continue to win. Remote and static sources remain valid but cannot pretend to have processable-resource metadata.

M3 — Full figure capability decision

Choose one of two answers:

  1. add processing arguments to the full fig source form and normalize them through the same processing helper; or
  2. keep processing exclusively on native Markdown images and document full fig as the container for arbitrary numbered block content.

No implementation should leave both answers half-supported. Markdown/LLMS must link to the documented source or derivative consistently in both forms.

M4 — Compatibility retirement

Inventory downstream CSS and JavaScript before removing old image-element classes or attributes. If a compatibility name is still used, retain it for a documented release window or migrate the owning site in the same release train.

Safety, output, and accessibility

  • Image URLs keep the shared scheme and remote-host policy.
  • Missing required alternative text warns and renders a decorative fallback only where the current contract permits it.
  • Width and height never claim metadata that an SVG, static file, or remote source did not provide.
  • Linked images are not Zoom targets; the runtime preserves dialog focus, keyboard close, reduced motion, and narrow-screen containment.
  • Print, Markdown, RSS, and LLMS strip interaction markers while retaining the intended image, caption, attribution, number, and link.

Acceptance criteria

Each phase owns byte-level HTML and Markdown evidence, content and Landing resolver tests, URL/security checks, image-processing tests, Book targets, gallery/Zoom browser tests, and real-site EN/ZH narrow-screen review. The proposal is accepted only after the M3 capability choice is explicit.

Open decisions

  1. Is one shared result struct enough, or would a common lower-level URL/resource record keep resolver ownership clearer?
  2. Should Landing consume resource attribution, or only dimensions and URL?
  3. Does full fig processing solve a real consumer need now that native images support numbering, captions, links, and processing together?
  4. Which emitted compatibility names are still used by real consumers?

3 - Bulk agent indexes

Optional per-section full-text bundles and a stable navigation JSON, built on OINK’s existing Markdown outputs and navigation authority.
Implemented; ships with OINK 0.8.0

Accepted on 2026-08-27 after resolving every open decision. Both outputs – the per-top-level-section LLMSFULL bundles and the NAVJSON navigation tree at the language root – are implemented on the theme’s main branch and ship with OINK 0.8.0, after which this proposal retires; released behaviour is owned by the Architecture contract. OINK already shipped per-page Markdown, the in-language llms.txt, the HTML discovery link, and Copy Markdown; this page covers only the two new outputs.

Current baseline

A site can enable Hugo’s Markdown output for pages and sections, and the LLMS home output that generates llms.txt. OINK renders shortcodes as semantic Markdown, keeps source URLs and the in-language LLMS index discoverable, and Copy Markdown reads the same alternative output URL. The theme declares output formats but never forces a site’s outputs choice.

Navigation already has an authority chain: an explicit data/docs_nav.json tree when present, otherwise the content tree and weights. The sidebar, the pager, and declared section indexes share that authority. Machine navigation output must derive from the same tree rather than invent ordering.

Goals and non-goals

Goals:

  • Optionally assemble an in-language full-text bundle for each explicitly enabled top-level section;
  • Optionally publish a versioned navigation JSON for agents and external tools;
  • Reuse the human site’s page renderer, page-inclusion rules, and navigation authority;
  • Keep every output opt-in through Hugo output configuration;
  • Validate links, language isolation, media types, and deterministic order.

Non-goals:

  • Replacing per-page Markdown or llms.txt;
  • A new params.oink.* configuration tree;
  • Scraping generated public/ files during the Hugo build;
  • Embedding private source paths, draft pages, or cross-language fallback;
  • Promising that one giant full-text file fits every model context.

Full-text bundle

The llms-full.txt output concatenates the same semantic Markdown used for per-page output, with a stable, visible separator and source URL between pages. Version 1 implements per-top-level-section bundles only: each top-level section that explicitly enables the format in its _index front matter outputs gets one file per language. The whole-site single-file shape is deferred until real-site evidence shows per-section bundling is not enough — a giant single file both outgrows model contexts and couples every section’s updates to one artifact.

Hugo output configuration decides which sections receive the format, never a theme parameter. The theme ships a checker that reports mismatches between intent and actual output, but must not modify the site’s output set.

The bundle is assembled inside Hugo through the shared page-render partial; it never reads sibling artifacts from public/ and never depends on output build order. File size is reported as evidence; no arbitrary threshold may let --panicOnWarning reject an otherwise legal publish.

Navigation JSON

The navigation JSON is a home output beside llms.txt: one file per language at the language root. It carries a schema version, the language, the root node, and recursive ordered nodes. Page nodes carry a stable ID (the in-language permalink path), title, HTML URL, Markdown URL when enabled, kind, and children — plus the description when one exists. Explicit external navigation nodes carry only a label, a URL, and the external kind.

Nodes do not serialize weight: array order is the contract, weight is the private mechanism that derives it, and publishing it would invite consumers to re-sort. The output follows the same visibility and ordering rules as the rendered sidebar, excluding drafts, headless resources, hidden navigation items, and pages unavailable in the current language, and never serializes local filenames.

The format owns its JSON Schema (schema/nav.v1.schema.json, a hand-authored, versioned contract artifact outside the generated configuration schema’s drift gate) and golden fixtures, and is marked notAlternative so Hugo never advertises it as a page-level alternate.

Discovery and output boundaries

llms.txt lists the enabled bundles and the navigation JSON by default — discovery belongs in the index file; that is what it is for. The HTML head keeps discovering per-page Markdown and the in-language LLMS index without stuffing every bulk artifact into every page.

Shortcodes, Landing sections, Book targets, and interactive components keep their current Markdown degradation. The new outputs may not add component HTML, scripts, comments, feedback controls, or navigation chrome.

Compatibility and migration

Both outputs default off; a site that does not enable them is byte-identical. Enabling is a site-side Hugo outputs choice — no new params keys, no renames, no migration steps. Disabling an output is a complete exit with no residue.

Implementation and owning-checker plan

  1. Output formats: LLMSFULL (text/plain, baseName: llms-full, notAlternative, section level) and NAVJSON (application/json, notAlternative, home level), declared beside the existing MARKDOWN/LLMS definitions.
  2. Templates: the section llms-full layout concatenates the shared per-page Markdown render partial in navigation order; the home navigation JSON layout walks the existing navigation authority partial rather than introducing a second tree traversal.
  3. Owning checker: a new bin/check-agent-indexes.py validates, on the tests/site fixture, language isolation, link resolvability, sidebar-consistent order, schema compliance, and byte-stable rebuilds, and reports each bundle’s bytes and page count (report only, no ceiling gate).
  4. Goldens: the check-goldens.py matrix gains llms-full and navigation JSON fixtures.
  5. Documentation: the site gains a bilingual agent-index guide; llms.txt discovery behaviour folds into the existing LLMS documentation; this proposal retires through the lifecycle.

Acceptance criteria

  • EN and ZH outputs contain only their own language’s pages and URLs.
  • Every listed Markdown URL exists; every navigation URL resolves or is explicitly external.
  • Order under the same root matches the rendered sidebar and pager.
  • The navigation JSON validates against schema/nav.v1.schema.json.
  • With a pinned Hugo version and inputs, rebuilding the same sources is byte-stable.
  • With the new formats off, HTML, Markdown, Print, RSS, and LLMS goldens show no regression.
  • A large-site fixture proves per-top-level-section bundling rather than a file for every nested section.

Decision log

  • 2026-08-20: Drafted; the bundle listed whole-site and per-section shapes, and the navigation JSON’s location was undecided.
  • 2026-08-27: Resolved the five open decisions and accepted the proposal. Version 1 builds per-top-level-section bundles only, deferring the whole-site file until real evidence; the navigation JSON is a home output; schema v1 node metadata is the minimal set (stable ID, title, HTML URL, Markdown URL, kind, children, optional description) with weight never serialized; llms.txt lists both enabled artifacts by default; the checker reports size evidence without enforcing any model-context ceiling.

4 - Book publication pipeline

Move Book semantic lowering and generic EPUB/PDF packaging into OINK without taking publication facts away from consumers.
Draft proposal with implemented publication tooling

The opt-in BookManifest, the generic EPUB and PDF runners and their artifact validation are released, and the normative account of them is in Architecture. No build publishes either file on its own. What remains open here, and only here, is consumer migration.

Context and evidence

OINK already owns Book navigation order, numbered figures, tables, equations, examples, cross-references, whole-Book Print HTML, heading and footnote namespacing, and per-page Markdown degradation. The missing boundary is a machine-readable whole-Book handoff that a generic packager can consume.

DDIA currently carries a substantial EPUB preprocessor that follows OINK’s numbered primitives, cross-page links, footnotes, image paths, and Book order. TPME retains an older exporter whose historical root-file inputs no longer match its current Hugo content tree. The first proves that publication is real; the second proves that a consumer-local recipe can quietly become stale.

An EPUB is not one rendered template. It is a ZIP container with publication metadata, a resource manifest, a spine, navigation, content documents, styles, and media. Hugo can render an intermediate output, but a packaging tool must produce and validate the final file.

Goals

  1. Give every Book primitive one theme-owned semantic result for publication.
  2. Publish an opt-in whole-Book intermediate with deterministic page order, stable targets, and cross-references; resolve local resources from the semantic Print document only during explicit packaging.
  3. Provide a generic, versioned EPUB packager and a version-pinned Print-to-PDF runner that consumers invoke with their own publication facts.
  4. Prove the boundary on two structurally different public Book consumers.

Non-goals

  • Do not enable an expensive aggregate output for every site or section.
  • Do not call a Markdown, JSON, or HTML intermediate an EPUB.
  • Do not infer title, author, cover, ISBN, edition, rights, or release policy.
  • Do not fetch remote images or services during an ordinary Hugo build.
  • Do not add a second Book shell, duplicate navigation authority, or a generic publication-configuration namespace.
  • Do not promise pixel-identical PDF pagination across browser engines.

Ownership boundary

OINK owns The consumer owns
Book order derived from the existing navigation authority Which language, edition, and Book root to publish
Semantic lowering for fig, tbl, eq, eg, xrefs, headings, and footnotes Title, authors, identifier, cover, rights, and imprint
Stable intermediate schema and generic packager behavior Optional chapter exclusions and publication-specific front/back matter
EPUB structure/link validation and Print-to-PDF runner Release automation, signing, distribution, and legal approval
Theme fixtures and compatibility checks Content correctness and final artifact approval

The consumer passes facts; it does not patch OINK markup. OINK supplies semantics; it does not decide whether a book may be distributed.

Proposed behavior

The first implementation is an opt-in Book manifest, not a final ebook. It references the already published per-page Markdown and records only facts the theme can derive honestly:

  • schema version and language;
  • Book root and flattened page order;
  • page title, optional Book number, HTML URL, and Markdown URL;
  • stable heading and numbered-object targets;
  • cross-page references.

The manifest is emitted only for a Book root that explicitly enables the output. The normal HTML, Print, Markdown, RSS, search, and navigation builds remain byte-for-byte independent of that opt-in.

The OINK EPUB packager consumes the manifest and the existing whole-Book Print HTML, whose semantic output already contains namespaced headings and footnotes, numbered targets, raw authored anchors, MathML, and static interaction fallbacks. It rewrites only publication URLs, invokes pinned Pandoc 3.10, and validates the result with EPUBCheck plus OINK’s internal-target checker. A consumer supplies a small metadata file and cover. Per-page Markdown remains in the manifest as an auditable source-shaped output, not as a second semantic conversion path.

Local resources must exist under the generated public/ tree and are packaged without network access. A consumer that knowingly retains a remote image must opt in with --allow-remote-resources; this permits passive HTTP(S) media only, never remote scripts or local-file schemes. The tool refuses to replace an existing EPUB unless --force is explicit.

PDF derives from the same whole-Book Print HTML. The runner exposes the build on a temporary loopback server with script-src 'none', blocks external resources by default, invokes an explicit Chrome/Chromium binary, and refuses implicit replacement. Print CSS owns A4 size, paper-safe code wrapping, full-width numbered equations, and page numbers. The checker uses Poppler to verify the PDF structure, A4 geometry, extractable Book titles, and sampled page-number margins; final approval still includes rendered-page review.

The reference workflow remains four explicit steps, not a new Hugo mode:

python3 /path/to/oink/bin/book-epub.py --manifest public/book/book.json \
  --public public --metadata metadata.yaml --output output/book.epub
python3 /path/to/oink/bin/check-book-epub.py output/book.epub \
  --manifest public/book/book.json
python3 /path/to/oink/bin/book-pdf.py --manifest public/book/book.json \
  --public public --chrome /path/to/chrome-headless-shell --output output/book.pdf
python3 /path/to/oink/bin/check-book-pdf.py output/book.pdf \
  --manifest public/book/book.json

Output, accessibility, and security

  • HTML and existing outputs do not load an exporter or gain browser runtime.
  • The intermediate preserves document language, heading hierarchy, alternative text, table headers, link text, and source order.
  • Interactive controls degrade to their existing static Markdown/Print forms.
  • Resource paths must resolve inside the build output or be explicit outbound links; packaging never follows an arbitrary local path from authored content.
  • No consumer value becomes raw HTML, CSS, command arguments, or a filesystem path without the same validation and normalization used by current outputs.

Compatibility and migration

This is additive and opt-in. Existing Book sites keep their current outputs and scripts. The DDIA pilot removes consumer-side transformations only after the theme intermediate accounts for every currently validated chapter, numbered object, footnote, image, and internal link. TPME is the second-consumer gate; no DDIA-specific route, label, or chapter list may enter the generic schema.

Prototype evidence

An isolated opt-in build against the first manifest prototype produced these results without modifying either consumer checkout:

Consumer Ordered pages Headings Raw Markdown anchors Numbered targets Xrefs Unresolved
DDIA 23 597 33 131 (106 figures, 3 tables, 22 examples) 292 0
TPME 18 295 946 41 (31 figures, 10 tables) 1,062 0

Neither manifest contained a duplicate numbered target ID. Strict builds added about 0.28 seconds for DDIA and 0.22 seconds for TPME in this local sample. The large TPME raw-anchor count is decisive: the packager must consume a rendered output where those explicit anchors survive. Whole-Book Print HTML already does so while also carrying namespaced headings, footnotes, numbered targets, and MathML; the manifest must not duplicate the whole document tree.

The same isolated snapshots were then packaged through one generic command:

Consumer EPUB chapters Typed targets Package size OINK package/link check EPUBCheck 5.3.0
DDIA 23 131 22.9 MB 0 errors 0 errors, 0 warnings
TPME 18 41 2.2 MB 0 errors 0 errors, 0 warnings

DDIA’s one remote poster required the explicit network-resource opt-in; TPME packaged entirely from local output. The generic checker verifies every page anchor and each target’s kind and num against BookManifest, rather than depending on the old DDIA preprocessor’s wrapper classes. The theme fixture also passes after a minified Hugo build. Publication CI pins Pandoc 3.10 and EPUBCheck 5.3.0 by version and archive digest, in a separate job from the Hugo compatibility matrix.

The Print-to-PDF pilot uses Chrome for Testing headless shell 151.0.7922.34, pinned by archive digest in the same publication CI job:

Consumer Book pages PDF pages Package size Structural/text/page-number check Rendered review
Theme fixture 5 23 1.1 MB 0 errors cover, tables, code, equations, footnotes
DDIA 23 527 60.3 MB 0 errors CJK, tables, figures, code, references, end matter
TPME 18 197 8.4 MB 0 errors CJK, wide tables, code, callouts, end matter

All three PDFs are tagged, unencrypted A4 documents. The real-consumer checker found every manifest page title and the first, middle, and last CSS page number. Visual review exposed and fixed three pre-existing Print defects: child math did not propagate the KaTeX stylesheet to the Book aggregate; a broad Bootstrap column reset matched KaTeX col-align-* internals; and pre > code defeated paper-edge wrapping. These are narrow print fixes, not exporter-specific DOM rewrites.

Implementation plan

  1. Done: extract the Print page sequence into one shared Book partial without changing rendered Print output.
  2. Done: add the disabled-by-default manifest and fixture checker.
  3. Done: package isolated DDIA and TPME snapshots with one generic EPUB path, then validate typed targets, internal links, and EPUB 3.3 conformance.
  4. Done: render the theme fixture plus isolated DDIA and TPME snapshots with one pinned Chrome runner; validate and visually review representative pages.
  5. Next consumer migration: replace DDIA’s semantic preprocessor with metadata plus one invocation only after its repository independently accepts the new publication gate.

Acceptance criteria

  • Default sites publish no new aggregate file and incur no material build cost.
  • The pinned Hugo Extended 0.165.0 toolchain builds the opt-in fixture with warnings fatal.
  • Existing HTML, Print, Markdown, RSS, navigation, search, and browser tests pass.
  • The DDIA pilot retains 23 chapters and all 106 figures, 3 tables, 22 examples, and internal links as typed semantic targets with zero unresolved target.
  • TPME produces an artifact through the same schema and packager.
  • No consumer script contains OINK primitive-specific regular expressions.
  • EPUBCheck and OINK’s package/link checker pass; the PDF structure/text/page checker and representative rendered-page review pass.

Open decisions

  1. Should consumer migration happen before or after the next OINK release tag?

Decision log

  • 2026-08-24: Drafted the theme/consumer ownership boundary. Chose an opt-in semantic intermediate before any final EPUB API or implementation.
  • 2026-08-24: The DDIA/TPME isolated pilot resolved the first format decision: keep one JSON manifest and consume existing whole-Book Print HTML; do not add a generated whole-Book Markdown output or another semantic lowering path.
  • 2026-08-24: The packager consumes whole-Book Print HTML, so the manifest uses the existing no_print exclusion too. This keeps one publication sequence and avoids a second output-specific exclusion key.
  • 2026-08-24: Implemented the generic EPUB path and pinned Pandoc 3.10 plus EPUBCheck 5.3.0 in publication CI. Isolated DDIA and TPME packages pass both the typed-target/internal-link checker and official EPUB 3.3 validation.
  • 2026-08-24: Remote publication resources remain rejected by default. DDIA’s historical remote poster exercises an explicit opt-in instead of weakening the default or adding a DDIA-specific rewrite.
  • 2026-08-24: Added the loopback Print-to-PDF runner and pinned Chrome for Testing headless shell 151.0.7922.34 by archive digest. Theme, DDIA, and TPME PDFs pass structural, text, A4, and page-number checks plus rendered review.
  • 2026-08-24: PDF review fixed only the owning Print contracts: aggregate math capability propagation, Bootstrap column-selector scope, code wrapping, single-column numbered equations, and CSS page margins.