Skip to content

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

Return to the regular view of this page.

Design and development

OINK maintainer contracts, accepted decisions, dated research, and proposals in one canonical bilingual section.
OINK 0.6.0 contract

This section publishes the maintainer contracts released with OINK 0.6.0, with Hugo Extended 0.160.1 as the compatibility floor. The canonical bilingual sources live in this repository under content/docs/design/.

This section is the durable design record for OINK. It complements the task-oriented guides elsewhere on the site: use those guides to build a site, and use this section to understand current invariants, the reasons behind them, the evidence used to evaluate alternatives, and work that is still only a proposal.

Reading this section

Layer Meaning
Contracts Normative behavior that compatible implementations must preserve
Decisions Accepted rationale and boundaries that explain current behavior
Research Dated, non-normative evidence that may need to be refreshed
Proposals Draft PRDs and RFCs; publication here is not proof of implementation

Contract map

Contract Authority
Architecture Build, configuration, diagnostics, featured images, output, security, accessibility, and performance
Components Component API, Book and release primitives, validation, and output degradation
Shell and navigation Navigation, search, blog presentation, actions, taxonomies, and page-end composition
Landing pages Landing data, the 22-section registry, runtime, accessibility, and outputs
Migration boundary Supported 0.4-to-current content and configuration migrations

Design records

Collection Contents
Decisions Accepted diagnostic, configuration, and authoring rationale
Research Goldmark probes and evidence from real OINK consumers
Proposals Active PRDs for knowledge graphs, media convergence, and machine-readable indexes

Create every new OINK PRD or RFC as an English and Chinese page pair under content/docs/design/proposals/. Do not create another repository-local plan/, plans/, or proposal/ tree. Once a proposal is accepted, update the implementation, owning checker, and relevant contract; preserve the stable rationale under Decisions and retire the draft through Git history and the changelog.

Authority and maintenance

This directory owns the maintainer design prose in English and Chinese. The theme repository owns executable facts: hugo.yaml owns published defaults; owning resolvers and checkers define optional shapes; layouts/ and assets/ own rendered behavior; check scripts and tests/goldens/ own validation; and VENDOR.json owns bundled versions, licenses, files, and checksums.

Whenever public behavior changes, update the implementation, its owning checker, and both language versions of the relevant contract in the same delivery. Tests should exercise behavior and output rather than pinning prose.

1 - Architecture contract

Repository assembly, configuration, diagnostics, output, performance, security, CSS, accessibility, and release-state boundaries.
OINK 0.6.0 contract

This is the architecture contract released with OINK 0.6.0. This page is the canonical English source; its Chinese peer is maintained beside it in content/docs/design/.

Repository and assembly

The repository root is a Hugo Module and complete theme, not a site or npm workspace. Hugo Extended compiles SCSS and templates. Browser runtimes and third-party assets are committed, so a normal build performs no network fetch. Public bilingual documentation, examples, and browser tests live in the sibling oink.pgsty.com repository; the theme repository keeps only narrow internal regression fixtures under tests/site/ and has no separate public example surface.

Generated public/ and resources/ trees are never source. Vendored runtimes, font families, and Font Awesome glyph definitions are supported distributions, not dead-code candidates; VENDOR.json and bin/check-vendor.py pin their integrity. OINK ships the complete supported Font Awesome distribution because consumer-authored content may use icons that theme templates do not.

Hugo types docs, book, blog, and swagger select the reading shells; params.ui.shell_types may add types. Landing is layout: landing. There is no article type or second blog shell: immersive pages are a blog presentation described in the shell contract.

layouts/_partials/shell/config.html resolves shared shell facts. Layouts must render through content/render.html before scripts.html, because render hooks and shortcodes register capability flags in the Page Store. Override the narrowest partial; superficially similar base templates remain separate where merging would change Hugo lookup precedence.

Configuration and diagnostics

Theme policy lives under params.ui.*; multi-setting integrations such as comments.giscus, plantuml, and drawio stay top-level. Boolean features use bare booleans unless they also have several settings. A page override drops the ui. prefix: params.ui.image_zoom becomes image_zoom, never a front-matter ui map. hugo.yaml declares published defaults; an owning resolver and its checker define any optional configuration shape or range.

Invalid input follows one rule: warn with the value, allowed shape, and safe fallback; then use that fallback or omit the unsafe feature. Ordinary hugo server therefore remains usable, while every publishing gate uses --panicOnWarning. The theme never calls errorf, and check-params.py enforces that boundary. Do not add speculative validation for unreachable states.

There is no generic renamed-key registry. A transition that still needs a migration diagnostic uses a targeted warning in its owning resolver plus a strict negative test; removed keys are never read as a compatibility path.

Network-capable features are explicit and degrade closed. PlantUML requires plantuml.svg_image_url, Draw.io requires drawio.drawio_server, and Algolia requires appId, apiKey, and indexName; incomplete configuration warns and emits no request. Draw.io loads only when rendered content contains PNG or SVG candidates, then inspects each distinct image URL once.

Hugo’s images is the single authored API; params.images is only the site-wide social fallback.

Source Reader thumbnail Social card
Page images, or bundled **featured*, *feature*, {*cover*,*thumbnail*} yes yes
Section cascade.images yes yes
Site params.images no yes

images: [] clears an explicit or cascaded value but does not disable bundled resource discovery. Only the first resolved image is representative. Local processable rasters may be cropped; SVG, static, and remote resources remain valid without Hugo image operations.

featured-image-resolve.html owns source ranking and relative/absolute URLs. A page’s bundled resource outranks an inherited cascade image. List thumbnails, Open Graph/Twitter/schema helpers, author avatars, Pinterest media, and blog presentation all consume that decision.

params.ui.featured_image is blog-only and defaults to none; front matter overrides it per page or cascade. banner renders a figure above a single-page title, wash colors its header, and hero paints the shell backdrop on single pages and section indexes. Missing images and non-HTML output render no image.

Outputs and runtime

Every base template sets Page.Store.tdOutputFormat:

Output Contract
HTML Complete semantic content; local runtime only for used capabilities
Print Expanded content; no shell navigation, search, or zoom runtime; the shared action layer supports explicit print controls
Markdown / LLMS Source-shaped Markdown without td- component markup
RSS Safe static summary or explicit omission

Consumers opt into custom outputs; OINK does not force expensive Book aggregates. HTML gets the shared action and core layers plus a feature bundle keyed by actual page capabilities and language. Print keeps the action layer and only runtimes required by rendered print features. Large third-party UMD files stay separate; unused feature runtimes stay absent.

Performance rules:

  • do not walk .Site.Pages per page when a site-level resource or partialCached result can own the work;
  • render .Content once and read Page Store flags only after it;
  • emit correct markup instead of scanning the DOM to repair it;
  • group browser work by resource URL, not DOM instance;
  • keep ordinary outputs opt-in when their aggregate cost is material;
  • validate reachable author input, not hypothetical internal states.

bin/measure-baseline.py measures build time, output weight, bundle count, and shortcode density. bin/sites/build-all.py builds maintained consumers in isolated snapshots.

Trust, CSS, and accessibility

Authors may enable Goldmark unsafe; configuration and component parameters are not raw HTML. The shared attribute policy consumes an allowlist, validates class tokens, passes data-* and aria-*, and warns while dropping style, srcdoc, on*, reserved, and unknown attributes. URL helpers reject dangerous schemes and protocol-relative URLs where local or explicit absolute URLs are required. Promised remote URLs remain supported but are never fetched at build time.

Theme output uses td- classes, data-td-* attributes, and --td-* custom properties; author markers such as .steps, .cards, and .full-width stay unprefixed. CSS supports RTL, print, forced colors, reduced motion, long tokens, and narrow viewports. Theme-owned decorative icons carry aria-hidden; pages with task lists or raw authored Font Awesome elements alone load the authored accessibility repair.

Font roles are ui, body, heading, code, display, metadata, and print, exposed as --td-*-font-family. params.ui.typography is technical or system; both compile into one stylesheet with no runtime. Legacy Bootstrap/Docsy Sass variables continue to seed these roles.

Release states

Source complete, locally validated, committed, tagged, pushed, pinned by a consumer, deployed, and production-identical are distinct states. A local Hugo build proves only local validation.

2 - Component contract

The maintainer contract for OINK authoring primitives, validation, Book and release behavior, and output degradation.
OINK 0.6.0 contract

This is the component contract released with OINK 0.6.0. This page is the canonical English source; its Chinese peer is maintained beside it in content/docs/design/.

Tutorials and exhaustive examples belong in the reader-facing Components section. This page defines the API and behavior that those guides rely on.

Authoring model

Use ordinary Markdown when one block plus attributes can express a component. Use shortcodes for compound bodies or facts Markdown cannot carry. There is no parallel component registry. Native forms require:

markup:
  goldmark:
    renderer: { unsafe: true }
    parser:
      wrapStandAloneImageWithinParagraph: false
      attribute: { block: true }

Only {{%/* steps */%}} uses percent delimiters because its body belongs to the page outline; every other shortcode uses angle delimiters. Compound bodies pass through content/render-block.html with a unique ID scope. Shortcode and component parameter captions, labels, titles, and names are plain text; Markdown belongs in bodies. Landing narrative fields follow their own contract. An icon is one Font Awesome class pair. Components expose safe classes and attributes, not arbitrary color or inline style.

Public API

OINK has 29 shortcodes:

  • core: tabs, tab, steps, cards, card, fields, field, include, kbd, badge, param, comment, contributors, asciinema;
  • Book: fig, tbl, eq, eg, xref, book-toc, book-figures, book-tables, book-equations, book-examples;
  • release: release-card, release-assets, download;
  • OpenAPI: swagger, redoc.
Component Native form Shortcode form HTML runtime
Callout > [!TYPE], fold, {icon=} none none
Tabs adjacent fences/tables with {tab= group= value=} tabs / tab tabs on used pages
Steps ordered list + {.steps} steps none
Cards link list + {.cards} cards / card none
Fields table + {.fields} fields / field none
FileTree filetree data fence none divider only with comments
Gallery gallery data fence none shared Image Zoom when eligible
Image Markdown image + block attributes none Image Zoom when eligible
Table attributes, caption, number, or tabs tbl for compound Book tables tabs when tabbed
Book target image/table/passthrough/fence + {num=} fig, tbl, eq, eg none
Release assets checksums data fence release-assets copy in HTML
Diagram/data mermaid, plantuml, markmap, math, chem, echarts, infographic fences none selected local runtime only

Validation

Invalid author input follows the architecture contract: warn, use the documented safe fallback or omit the component, and let --panicOnWarning make the same diagnostic fatal at publication gates. Named and positional forms are not mixed. Book target IDs match [A-Za-z][A-Za-z0-9_.:-]*; Book numbers match [0-9A-Za-z.-]+; classes are token-validated. Hook and shortcode targets share one page registry, so collisions cannot produce duplicate output IDs.

URLs use content/url.html. Images resolve through page resources, section resources, global assets, then static or explicit remote URLs. Local rasters carry intrinsic dimensions; SVG, static, and remote sources remain valid but cannot use Hugo image operations.

Component behavior

Callouts and tabs

Callout types are note, tip, important, warning, caution, success, danger, question, example, quote, and details; - starts folded and + expanded. Unknown types remain visible as neutral callouts without JS.

Adjacent tabs group only when consecutive and of the same block kind. group enables hash #<group>-<value> and storage td-tabs:v1:<group>; ungrouped tabs use neither. HTML exposes every panel before JS, print expands them, Markdown retains authored source, and RSS receives the rendered text summary. The full form supports arbitrary Markdown; tab.label is required, value is required exactly with a parent group, and an orphan tab warns and renders nothing.

Steps, cards, fields, and tables

Native steps accept ordinary block content. Use the shortcode only when a step must contain a percent-delimited container. Native cards are link lists; the full form adds bodies, badges, icons, and images. Native fields map the first column to the name, the last to the description, and middle columns through meta= or headings; the full form allows block descriptions. card and field are valid only inside their parents.

Field anchors are field-<name> with lowercase punctuation runs collapsed to hyphens, so params.ui.typography becomes field-params-ui-typography. Duplicate anchors receive positional suffixes.

The table hook owns responsive wrapping and captions. .matrix makes the first column row headers; .full-width widens normal or matrix tables. .fields cannot combine with matrix, full-width, numbering, or tabs; numbering and tabs are also mutually exclusive.

The Markdown image hook is the ordinary image API. Inline images stay inline; block images become figures with caption or num. Allowed image attributes are id, num, caption, width, height, link, command, and options plus shared safe attributes. command and options appear together and use Hugo Fit, Resize, Fill, or Crop on processable local resources. A plain linked image uses Markdown syntax; the link attribute therefore requires a caption or number. Linked and decorative images do not load Zoom.

Gallery accepts one Markdown image per line with optional description, link, and class. FileTree accepts indentation, - name, optional /, comments, and validated icon/tone/open/type attributes. Markdown preserves authored source; print renders expanded static figures and trees.

All code highlighting uses Chroma. Common fence attributes include title, copy, wrap, collapse, label, id, line options, tabs, and Book num/caption. Copy returns authored source. ECharts input is declarative JSON/YAML; callbacks use $fn:<name> from window.OinkEchartsFunctions, never embedded script execution.

Book

The book type extends the docs shell and follows the content tree or data/docs_nav.json. book_number, book_part, book_kind, and book_status are presentation metadata; they do not change Hugo publication state.

Numbered kinds are fig, tbl, eq, and eg, with default ID <kind>-<num>. eg needs a caption; eq without num is an unnumbered display formula. xref names exactly one kind plus optional page/anchor, or an anchor with explicit text. A numbered example is one framed body and caption.

Footnotes belong to the page document. Native numbered tables and fences keep them there. A shortcode body is a separate Goldmark document, so footnote references in tbl, eg, fig, card, tab, field, or include warn and remain literal; code-shaped text is ignored by that check.

book-toc follows navigation order at depth 1–3; the four book-* indexes collect one target kind each. Whole-Book print rewrites cross-page links and namespaces ordinary headings and footnotes while preserving explicit target IDs. Consumers opt into that potentially expensive output.

Release and download

Release front matter is one release_url in the form https://github.com/<owner>/<repo>/releases/tag/<tag>; owner, project, and tag come from the URL and date from the page. No remote release state is fetched. The removed release map, release_products, and release_group_by_product warn with their replacement and are not compatibility paths. The section index lists every page, using parsed project tag when available and the page title otherwise.

Checksums accept canonical lines or one source resource, never both; filenames cannot be paths. HTML adds local copy, while static outputs expose full hashes.

Downloads use data/download/<key>.yaml. Channels are rolling or pinned; only pinned URLs and commands interpolate ${version} and ${tag}. Before publication, rolling channels remain usable and pinned channels show pending. Markdown renders the complete channel list; RSS omits the component.

Verification

Shared output rules live in the architecture contract; exceptions are defined with their components above. Markdown and RSS set no browser runtime flags; Print retains only flags required by rendered print features. Source checks cover parameters, hook policy, runtime isolation, and migration; output checks compare HTML, print, Markdown, RSS, and LLMS goldens; browser tests cover interactive surfaces. Migration is documented in the migration contract.

3 - Shell and navigation contract

Navigation authorities, immersive blog presentation, search, actions, taxonomies, indexes, and page-end composition.
OINK 0.6.0 contract

This is the shell and navigation contract released with OINK 0.6.0. This page is the canonical English source; its Chinese peer is maintained beside it in content/docs/design/.

Authorities and navigation

Concern Authority
Global navigation Hugo menus.main
Docs / Book sidebar and pager content tree or data/docs_nav.json
Root switcher resolved top-level content roots
Discovery per-language local search index
Page and Palette actions shared action registry

No feature introduces another menu or page tree. One menu child level is interactive; deeper levels warn and flatten beneath linked group headings. External links use target="_blank" rel="noopener noreferrer"; internal links remain language- and subpath-aware.

Navbar desktop and drawer views project one tree, and every dropdown panel is one moderate column of icon-and-title rows — the mega panel and its columns menu parameter are retired, and a configured columns warns while keeping the single column. Menu descriptions are configuration data only. The link tree stays true-centered at every width: text links from lg, icon links below. Between lg and md the end edge keeps search, version, language, theme, and GitHub with no menu button. Below md those utilities move to the footline dock, and Home or explicit Landing pages add one drawer entry beside search that opens the full labelled tree; no other width or surface renders a drawer entry. Language links target the page translation or that language’s home, stay relative when languages share a host/base path, and become absolute only for language-specific baseURLs; hreflang stays absolute. navbar_autohide applies to fine pointers from 768px, never touch or drawer widths, and the hidden bar keeps its slot: the layout reserves the navbar band in both states, revealing fades the bar in place without covering resting content, and hero pages ignore the policy in favour of their overlay bar.

Sidebar and pager share root and order. manual_link, build.render: link, dividers, hidden nodes, and placeholders retain their documented semantics. sidebar_icon_policy is all (default), groups, or none; icons are one Font Awesome class pair. Invalid policies follow the shared warning/fallback contract.

Immersive blog presentation

There is no article type or second shell. Immersive reading is four independent keys on the ordinary blog shell, set on a page or section cascade; the section index repeats values it also needs:

featured_image: hero
toc_style: flow
toc_taxonomies: false
sidebar_enabled: false

The blog shell renders no breadcrumb by default—an article reads as a standalone piece—so the recipe needs no key for it. breadcrumb remains an ordinary key a page or cascade may still set either way, on any shell.

hero uses the shared featured image as a decorative full-bleed backdrop on single pages and section indexes. With no image it renders the normal opening; banner and wash remain single-page modes. The navbar overlays a hero on a contrast scrim and scrolls with it.

toc_style is fixed or flow; flow places a wider rail beside the article and pins it only after scrolling. Its resting place aligns with the article’s info line, or its description where a page has no info line. docs-shell.js measures the offset because a title wraps to an unknown number of lines; without JavaScript the rail starts where the article starts. toc_taxonomies: false removes term clouds; a rail with neither TOC nor clouds renders nothing. notoc remains the page-level TOC opt-out. These switches do not change bylines, tags, series, pager order, feeds, or page-end composition, and the rail disappears below the xl breakpoint.

Search, actions, and runtime

params.offline_search opts into a local per-language index. When enabled it also builds under hugo server by default; set offline_search_on_serve: false for large edit loops. HTML search appears on Home, shell pages, and Landing when landing_search is enabled. Other non-shell pages and Print omit the dialog, Lunr, and Palette.

Search metadata is search_keywords, search_boost (default 1), and search_exclude. The index carries URL, title, taxonomies, excerpt, headings, description, body/summary, root, section, type, keywords, boost, breadcrumb, and icon. Fixture budget is 2 MiB raw / 512 KiB gzip. Sites may return extra strings from hooks/search-keywords-extra.html.

Built-in action IDs are copy_markdown, copy_link, open_chatgpt, open_claude, view_markdown, view_history, edit_page, create_child_page, create_issue, create_project_issue, print_section, print, switch_theme, switch_language, switch_version, and open_github. copy_link is Palette-only outside the share bar. Site commands under languages.<lang>.params.ui.command_palette.commands may open a safe URL or invoke a built-in ID, never inject JavaScript.

The Palette has empty, text-search, and > command modes; quick links derive from navigation. It has no history, semantic search, personalization, or remote fallback. Search queries stay in-browser and no default telemetry is sent.

OinkSurfaceCoordinator arbitrates Palette, drawer, root, language, and version menus. Surfaces own focus restoration and Escape. Keyboard navigation ignores editable controls and modals: /, \, f, c open search/commands; j/k move headings; q/e move pages; h changes presentation; l/y, t, and r open language, theme, and root choices. Sidebar WASD/Arrow navigation uses real focus without rewriting Tab order.

The outline derives cursor and visible-heading range from one heading model and the scroller’s computed scroll-padding-top; its SVG line and dot share the same animated values so they cannot drift. No speculative DOM repair pass is allowed.

Share

params.ui.share is empty by default and accepts any ordered subset of 16 targets: x, bluesky, mastodon, facebook, linkedin, reddit, hackernews, telegram, whatsapp, line, pinterest, weibo, chatgpt, claude, email, and copy. A page list replaces its inherited list; share: false opts out. Unknown entries warn and are dropped. Only regular pages render the bar; print, Markdown, and RSS omit it.

Targets are plain intent links carrying the page permalink/title, plus the local copy_link button. Pinterest media comes from the shared featured-image resolver. ChatGPT and Claude receive build-time permalink prompts and are independent of page-menu assistant actions. Discord has no public intent target and is deliberately absent.

The bar loads no platform SDK, iframe, script, stylesheet, counter, or campaign parameter and makes no request until a reader activates a link. It is one accessible labeled glyph row. share/items.html resolves targets and share/bar.html renders them.

Annotation

Page annotation resolves descriptors in annotation-items.html and renders them through page-meta-lastmod.html; either may be overridden narrowly. Lines appear in this order:

Line Condition
Last modified Lastmod is set
Upstream front matter upstream_link is non-empty
Translation configured authoritative language has a translation and this page has authored text

upstream_link is per-page; a cascade counts, and upstream_link: "" opts out. Other upstream facts resolve site params → data/upstreams[upstream_source] → front matter: upstream_name, upstream_copyright, upstream_license, upstream_notice, optional upstream_ref, and upstream_modified. The first four are required with a link. Invalid or incomplete attribution warns and emits no legal notice; unsupported URLs are refused. Publication gates reject the warning with --panicOnWarning.

upstream_modified changes the credit verb and links commit history; it adds no line. The notice page carries full license/warranty text. Translation notice is opt-in through params.ui.translation_notice, cascades as the page key translation_notice, skips generated or bodyless pages, and can be disabled on a natively authored page with translation_notice: false.

Authors and series

A blog article head is title, info line, term badges, byline, then the series strip; the description leads the body below them. The info line (article-info.html) always carries the date; with reading_time on it adds the word count and the minutes. Front matter upstream_link—the same per-page fact the annotation attributes—adds a localized link to the original, gated by the shared URL policy. Term rows are bare badge runs whose taxonomy name lives on the group label, not as a visible prefix. A term badge is a solid brand chip with its label knocked out of the fill, led by the taxonomy’s term glyph. taxonomy-icon.html owns the vocabulary—each taxonomy pairs a whole-taxonomy glyph with a term glyph (folder-open/folder, tags/tag, cubes/cube, users/user-pen, book-bookmark/book for series, generic shapes)—and params.ui.taxonomy_icons overrides a pair with one string for both surfaces or a taxonomy/term map; unusable input warns and keeps the built-in. The right-rail cloud wears the whole-taxonomy glyph on its head alone: cloud chips and the term-archive filter chips stay text plus count, because repeating the glyph beside an announced taxonomy is noise. The byline carries the people alone—portrait, name, and the profile’s one-line bio—with no label and no date. List rows, cards, and term archives share one metadata line of the same shape: date, one localized author-and-section phrase, then word count and minutes behind the same reading_time switch. Under that sentence sits one wrapping badge line with every taxonomy’s terms, taxonomies in alphabetical order, each badge wearing its term glyph; cards leave out authors, whom their sentence already names.

Authors activate only through taxonomies: {author: authors}. The profile term page owns display name, summary, body, and featured-image avatar; an absent profile falls back to link title, initial, and archive. authors-resolve.html preserves front-matter order for article heads, list rows, and one RSS dc:creator per author. Legacy author remains unchanged when authors is absent; when both exist, authors wins without warning. Custom author taxonomy plurals behave as ordinary taxonomies.

Series activate only through taxonomies: {series: series}. Term pages own the introduction; no parameter, data file, cover model, or runtime is added. A page uses series: [name] and optional series_weight. series-pages.html orders weighted members first by weight, then unweighted members by ascending date, with Path tie-breaks; strip and term page share it. The first named series gets one HTML/print strip: a closed disclosure line—series name, part M of N—that expands, and prints expanded, to the reading order, one member per line; singleton series and non-HTML outputs omit it. Numbering, cross-references, and aggregate output remain Book concerns.

The default article taxonomy chips omit reserved authors and series because their dedicated surfaces already carry them. Explicit params.taxonomy.page_header restores either.

Blog indexes and page composition

Blog section indexes use params.ui.blog_index: list (default) and cards are one flat run, newest first, sharing blog_index_size pagination—the metadata line’s dates make year headings redundant; table shows the whole section as date/title/tag rows without pagination. Cards use the shared lead image, localized date/author/section metadata, tags, and a three-line summary. Term and taxonomy pages stay row lists.

params.ui.blog_index_toggle renders all three forms for the current paginator slice and lets readers cycle them. The configured form controls first paint, local storage may override it, and hidden forms load no images. A front-matter value or cascade overrides the site mode per section. A table published without the toggle remains a complete, unpaginated archive.

params.logo is always the brand mark; params.wordmark, or the site title, is the text half hidden at compact widths. Docs, Book, Blog, and Swagger share one shell model. Page-end order is Share, Feedback, Annotation, Pager, Comments. Docs/Book pager follows sidebar preorder; Blog uses weight then reverse date; pager: false opts out. Static outputs omit pager UI.

Every rendered footer style keeps an icon-only utility dock at the end of its bottom line: version, language, theme, then keyboard help. Its menus open upward; the version trigger never exposes the current branch or release label. The fat footer’s collapse chevron follows the dock. Below lg the bottom line gives up its copyright/center/dock columns and stacks them as three centered full-width rows, the dock last. These global controls do not render in the sidebar footer, and footer_style: none removes the whole bottom line.

There is no archive shell, arbitrary-depth flyout, second navigation authority, query upload, or browser compatibility shim for removed config. Feedback emits only docs_feedback through an existing gtag, stores the choice locally, and does not replace Giscus.

Verification

bin/check-navigation-contract.py, bin/check-shell.py, JS tests, output goldens, and the consumer browser suite cover navigation, language/subpath links, blog variants, page-end order, keyboard behavior, accessibility, and responsive layout.

4 - Landing contract

The maintainer contract for landing data, the built-in section registry, language resolution, runtime, accessibility, and outputs.
OINK 0.6.0 contract

This is the landing-page contract released with OINK 0.6.0. This page is the canonical English source; its Chinese peer is maintained beside it in content/docs/design/.

Shared rules live in the architecture and component contracts; migration belongs in the migration contract.

Shell and data

Any regular page may declare layout: landing. It renders navbar, full-width canvas, and footer without docs sidebars or TOC rails. The homepage keeps data/home/<lang>.yaml as a compatible authoring path through the same renderer.

A non-home page resolves sections from inline front matter, data/landing/<key>/<lang>.yaml, an exact-language entry in one data/landing/<key>.yaml, then English or unsuffixed local data. Landing never fetches mutable facts; stars, prices, screenshots, and avatars are committed or generated before Hugo runs.

params.ui.landing_search defaults to true and enables the existing local Palette only when offline_search is enabled. params.ui.github_stars and params.ui.alt_site are optional local chrome facts.

Section registry

The registry has exactly 22 built-ins:

  • hero, metrics, capabilities, principles, cards, logo-wall, gallery, testimonials, contributors, faq, markdown, cta;
  • pricing, pricing-compare, command-box, steps, timeline, code-plate, preview, case-study, download, bar-chart.

An entry is a type string or a map with type, key, id, enabled, inline data, or a deliberate local partial. Authors provide unique IDs; OINK normalizes them to anchor-safe values. Unknown types follow the shared warn-and-safe-fallback policy; they never vanish silently, and --panicOnWarning rejects them at publication. landing/ partials own built-ins; removed home/ partial names are not an API.

preview places Markdown source beside RenderString output through the site’s hooks, so its content registers the same runtimes as docs content. The source pane uses Chroma and a file name, default page.md. Markdown output uses a four-backtick markdown fence; RSS omits it. Pane labels are theme i18n.

hero.align is start or center. Center is text-only; combining it with an image warns and falls back to start, preserving the image. download consumes the same data/download/<key>.yaml schema as the shortcode and introduces no second channel, version, publication, or interpolation model.

Language, runtime, and accessibility

Narrative files may be language-specific. Shared fact fields resolve <field>_<exact language> with - normalized to _, then <field>_<primary language>, then the unsuffixed field. camelCase aliases are not accepted. Narrative fields render inline or block Markdown through the site’s hooks; values reused as accessible names are plainified. Section copy is site data; only theme controls use OINK i18n.

Interactive HTML sets hasLanding, which conditionally adds only landing.js. The runtime reuses OinkSurfaceCoordinator and owns reveal, count-up, copy, compact-menu, and theme-image enhancement. Server output remains complete without JavaScript.

Marquee duplication is CSS-only; the duplicate is aria-hidden and inert, and a localized checkbox persists pause without JS. Reduced motion disables motion, forced colors preserves controls, and theme images follow the shared theme event. Navbar mega-menu columns accept 1–4. The compact menu uses real links/buttons, traps no focus, and does not duplicate the desktop tree.

Outputs and compatibility

Output Contract
HTML Full static sections plus progressive enhancement
Print Static grids and content; controls removed
Markdown Headings, prose, lists, tables, and code without theme classes
RSS Landing sections omitted

Non-HTML output sets no Landing flag or runtime. Root-relative links and assets honor deployment subpaths; normal builds download no images.

Removed 0.4 component forms belong to the migration toolkit, not parallel Landing implementations. OINK adds no pricing-period toggle, remote-fact API, hotspot editor, visual builder, or second registry. Existing homepage data and explicit custom section partials remain valid.

5 - OINK migration boundary

The supported source, configuration, and validation boundaries for migration from OINK 0.4 through OINK 0.6.0.
OINK 0.6.0 contract

This is the migration contract released with OINK 0.6.0. This page is the canonical English source; its Chinese peer is maintained beside it in content/docs/design/.

This is source and configuration guidance, not a release ledger. Local source, commit, tag, push, consumer pin, deployment, and production parity remain separate states. For the reader-facing upgrade procedure, see Upgrade.

Toolkit scope

bin/migrations/oink06.py only scans and automatically rewrites Markdown files under a site’s content directory, including supported YAML front matter. It does not rewrite Hugo configuration, data files, layouts, assets, modules, or generated output. TOML/JSON front matter and ambiguous Markdown are reported with positions for manual review.

Dry-run is the default and a completed migration is idempotent:

python3 bin/migrations/oink06.py report --sites <dir>... --md report.md --json report.json
python3 bin/migrations/oink06.py migrate --site <dir>
python3 bin/migrations/oink06.py migrate --site <dir> --write
python3 bin/migrations/oink06.py check --site <dir>

Code fences are not rewritten. book_figures.py retains narrow TPME, DDIA v1/v2, and pg-internal profiles; it is not a generic parser.

0.4 content to current forms

Removed form Current form Toolkit key
alert, details, pageinfo, raw disclosure > [!TYPE] callout callout
tabpane, legacy tab, code-group, code-tab adjacent {tab=} blocks or tabs / tab tabs
FileTree shortcodes or {.filetree} list filetree fence filetree
Gallery shortcodes or {.gallery} list gallery fence gallery
ECharts / infographic shortcode same-named data fence datafence
Docsy card families .cards list or cards / card cards
imgproc, image Markdown image + attributes image
readfile include include
fence filename= title= fencetitle
badge outline= remove outline badge
leaf example, book-figures kind= eg, explicit book-* index eg
percent-delimited fields angle-delimited fields / field fieldsdelim
Docsy _param placeholders and card header= highlights Font Awesome / badge / param or callout param_placeholders
unsupported legacy shortcodes manual review with source position reportonly

Configuration and front matter

The following configuration changes are manual; the toolkit may report matching front-matter keys but never edits site configuration.

Old Current
offlineSearch* offline_search*
disable_click2copy_chroma ui.code_copy (inverted)
content_width `reading_width: slim
github_url github_repo
ui.no_left_sidebar ui.sidebar_enabled (inverted)
breadcrumb aliases ui.breadcrumb
ui.scrollSpy ui.scroll_spy (inverted)
ui.showLightDarkModeMenu ui.dark_mode.show_menu
ui.readingtime ui.reading_time
ui.ul_show ui.sidebar_expand_levels
ui.docs_root ui.docs_sidebar_root
ui.pager ui.pager_types
{ enable: bool } annotation/zoom/keyboard/reading maps bare booleans
ui.typography.preset ui.typography
print.disable_toc print.toc (inverted)

Prism, rss_sections, and algolia_docsearch are removed. Chroma is the only highlighter; Algolia configuration is search.algolia. Page overrides drop the ui. prefix. Legacy hide_feedback, hide_readingtime, exclude_search, content_width, camelCase manual links, and nested front-matter ui maps are reported with replacements.

0.5 to 0.6

  • Replace upstream_attribution with upstream_link plus upstream_name, upstream_copyright, upstream_license, and upstream_notice; rename downstream_modified to upstream_modified.
  • Replace the release map with one GitHub release_url; remove release_products and release_group_by_product from release indexes.
  • Blog and default dates now default to ISO 2006-01-02; retain explicit time_format_blog or time_format_default for prose dates.

Removed names warn and take the documented safe fallback or render nothing; ordinary previews continue, while --panicOnWarning rejects them at a strict gate. blog_index_toggle, featured_image: hero, toc_style, and toc_taxonomies are additive opt-ins. They introduce no content type; immersive reading stays on the ordinary blog shell.

Prerequisites and validation

Enable Goldmark unsafe rendering, block attributes, and standalone block images as shown in the component contract. Enable passthrough explicitly for \(...\), \[...\], or $$...$$; Hugo does not merge theme markup config.

Run the smallest source and output checks for the changed contract, both supported Hugo versions, JS tests when runtime changes, and strict root and subpath builds. For maintained sites, inspect representative EN/ZH Docs and Blog routes at desktop and narrow widths, then record pin, deployment, and hosted parity separately.

6 - Design decisions

Accepted choices that explain why OINK’s public contracts and implementation have their present shape.
Accepted rationale

A decision explains why OINK chose one compatible design over another. The five contracts above it remain the normative description of current behaviour; implementation and owning checkers remain the executable facts.

OINK used to keep reviews, PRDs, and execution notes in a local plan/ directory. That made useful reasoning hard to discover and allowed abandoned designs to look authoritative. Accepted reasoning now lives here, in the same bilingual, versioned site as the contracts it supports.

Decision map

Decision What it settles
Warnings and safe fallbacks Why ordinary preview survives invalid input while publication remains strict
Configuration model Where configuration belongs, how pages override it, and why OINK has no parallel configuration namespace
Markdown-first authoring Why native Markdown is preferred and Docs, Blog, Book, and Landing extend shared systems

Record format

An accepted decision records context, the choice, consequences, and the proof that makes the choice current. It does not reproduce a parameter reference or a tutorial. Every decision links to its owning contract and verification surface, and its English and Simplified Chinese pages change together.

When a decision changes, update the implementation, checker, affected contract, and decision record in one delivery. Preserve the old answer in Git history and the release changelog instead of leaving two active answers in the navigation tree.

6.1 - Warnings and safe fallbacks

Invalid author input warns and degrades safely during preview; –panicOnWarning restores a hard publication gate.
Decision

OINK does not call Hugo’s errorf. Invalid author or site input emits a warning and either uses a documented safe fallback or omits the invalid fragment. Release and deployment builds use --panicOnWarning, so the same warning remains a hard publishing failure.

Context

Hugo builds the whole site as one transaction. An errorf raised while one page is being edited makes every URL served by that rebuild return an error, including unrelated pages and the home page. The server process survives and recovers after the input is fixed, but collaborative preview is unavailable in the meantime.

A warning has a different development cost. The affected value can fall back, the rest of the site remains inspectable, and the author receives a precise message. A publication build still fails because OINK’s CI and integration gates add --panicOnWarning.

Decision

Validation follows four rules:

  1. Name the invalid key and value, the allowed shape, and the fallback.
  2. Include a page position when the value came from page front matter; avoid repeating one site-wide warning for every page.
  3. Never pass an invalid value into a later operation. Validate first, then render from the normalized value.
  4. Where no honest fallback exists, warn and render nothing. Do not invent content, make a network request, or emit an unsafe URL merely to keep going.

The shared enum, boolean, CSS-length, and number shapes live in layouts/_partials/validate.html. Domain resolvers may add narrower checks, but they preserve the same warning/fallback contract.

Safety boundary

Continuing a build never means continuing with unsafe output. A rejected CSS length falls back before it reaches a style attribute. An incomplete remote service configuration omits the component before the browser can make a request. An unsafe action URL is dropped. The protection is the absence of the bad output, not the act of terminating Hugo.

This also separates editing from publication cleanly:

Stage Invalid input
hugo server or an ordinary local build Warn, fall back or omit, keep other pages available
CI, release validation, deployment The same warning becomes a non-zero build under --panicOnWarning

Consequences

  • Every fallback is part of the public contract and must match the default declared by the theme.
  • A change from failure to fallback also changes its tests. A negative test proves ordinary build survival, the warning text, the rendered fallback, and strict-build failure.
  • Checkers must test the rejected output directly. A URL security test, for example, asserts that the unsafe URL is absent instead of treating any build failure as sufficient proof.
  • The source-level parameter checker protects the invariant that theme layouts contain no errorf calls.

Verification

The owning references are the architecture contract, bin/check-params.py, and strict builds of both the theme fixture and this integration site.

6.2 - Configuration model

OINK extends Hugo and Docsy-compatible configuration without creating a second namespace or a parallel global resolver.
Decision

OINK keeps Hugo’s native keys and useful Docsy-compatible keys in place, places theme presentation and behaviour under params.ui.*, and exposes a matching top-level front-matter key for a page override. It does not add a params.oink.* tree or a registry that shadows Hugo’s configuration model.

Context

OINK inherits a mature configuration surface and adds shells, content output, and local interaction. Earlier designs attempted to move every theme-owned key under a new namespace and resolve a complete configuration dictionary once per page. That produced a second language beside Hugo’s own keys, complicated section cascades, and made migration larger than the behaviour it was meant to control.

The current model keeps ownership visible instead:

Layer Responsibility Examples
Hugo Site identity, languages, menus, outputs, taxonomies, markup, modules baseURL, languages, outputs
Site facts and integrations Repository, version, author, local search, comments, external services params.github_repo, params.version, params.comments
OINK interface Shell, navigation, presentation, and local interaction params.ui.sidebar_*, params.ui.typography, params.ui.share
Page or section A narrow override of an eligible site default sidebar_enabled, featured_image, share
Data files Structured facts and ordered content that are not switches data/landing, data/download, data/docs_nav.json

Decision

The configuration API follows these rules:

  1. Site facts remain at the established top level. Interface choices belong under params.ui.*.
  2. A page override drops the ui. prefix and otherwise keeps the same name. A section cascade can apply that top-level key to its descendants.
  3. Boolean features use a scalar where that is the complete policy. A map is reserved for features with real subordinate settings; an established map may accept a boolean shorthand.
  4. Names are positive, snake_case, and grouped by function. Closely related settings share a prefix instead of growing another nested resolver.
  5. Theme defaults are declared in the theme’s hugo.yaml. Templates may add a derived default only when one static value would erase a deliberate shell-specific distinction.
  6. Each feature family owns its normalization and validation. A shared helper supplies common shapes, but there is no global compatibility registry that silently rewrites arbitrary old keys.

The complete current key list, types, and defaults live in the configuration reference. This decision records the placement rules; it is not a second parameter catalogue.

Compatibility

Public renames receive a targeted warning from the owning resolver, a migration note, and a negative test. Removed or misspelled keys do not justify a permanent alias layer. Hugo and third-party camelCase keys remain camelCase where changing them would break their native API; OINK-owned additions use snake_case.

Page values resolve through Hugo’s ordinary front-matter and cascade model. OINK does not ask authors to put a nested ui: tree in front matter and does not promise to merge arbitrary nested page maps.

Consequences

  • Adding a public setting requires a declared default or an explicitly derived default, an owning resolver, documentation, and a positive and negative test.
  • Configuration guides link to the one reference table instead of repeating types and defaults.
  • A new data structure is justified by ordered or repeated facts, not merely by a desire to avoid adding a parameter.
  • Invalid scalar values follow the warning and fallback decision.

Verification

bin/check-params.py audits declared defaults, page aliases, warning behaviour, and the no-errorf invariant. The public reference and its Chinese peer are checked in the integration site’s bilingual and rendered-link suites.

6.3 - Markdown-first authoring

Native Markdown carries common semantics; shortcodes fill real capability gaps, and content models extend shared shells instead of forking them.
Decision

Prefer a native Markdown form when Goldmark can preserve the intended semantics. Keep a shortcode only when it provides a capability the native form cannot express. Add a content scenario by extending an existing shell and data model, not by creating a parallel rendering system.

Context

OINK serves short manuals, large references, release archives, landing pages, and books. A survey of eleven consumer sites covered more than five thousand Markdown files and exposed both extremes: pages with almost no theme syntax and pages assembled from many nested shortcodes and local layout overrides.

A component API optimized only for the second group becomes a private DSL. An API optimized only for plain Markdown leaves books, rich figures, tab groups, and structured releases to site-local HTML. The useful boundary is capability, not novelty.

Decision

OINK applies the following order:

  1. Native Markdown first. Lists become Steps, Cards, or FileTree markers; tables become Fields or matrices; blockquotes become callouts; fenced code, images, and passthrough blocks carry attributes through render hooks.
  2. Shortcodes for missing capability. A full-form shortcode remains where CommonMark indentation, nested containers, processing options, or cross-page registration cannot express the same result safely.
  3. One semantic implementation. Native and full forms normalize into the same partials and output contract. They are not two components that merely look alike.
  4. One extension line. A new Landing section joins the section registry; a new Blog presentation remains a Blog variant; Book numbering joins the content primitive and navigation systems. OINK does not add a second card, landing, navigation, or article shell for one feature.
  5. Facts stay outside presentation strings. Versions, repositories, dates, and ordered records come from front matter, site parameters, or data files. A shortcode argument is not a second source of truth.

Output contract

An authoring form is complete only when its semantic content has a deliberate result in every enabled output:

Output Requirement
HTML Semantic server-rendered content; JavaScript only enhances it
Print Static, expanded, and free of controls that require interaction
Markdown / LLMS Source-shaped prose, links, lists, tables, and fences; no component HTML
RSS Safe static content or an explicit omission

This requirement prevents an attractive HTML-only component from silently damaging agent output, feeds, or a printable book.

Trust and presentation

Render hooks and shortcodes consume explicit allowlists. Unsafe URL schemes, inline event handlers, and arbitrary style input are dropped. Author-provided classes are accepted only on the documented surfaces where downstream site CSS is part of the established authoring contract. Icons use one Font Awesome class pair; OINK does not invent a second icon-ID language.

Consequences

  • A proposed component must first show why Markdown plus an existing hook is insufficient.
  • Keeping a full-form shortcode requires a named capability and tests for both forms reaching the same normalized output.
  • Shell variants use independent presentation keys so opting into a hero or a flow outline does not change taxonomies, feeds, pager order, or content type.
  • Consumer evidence is dated research, not a permanent excuse to freeze an accidental syntax. The current public surface remains defined by the component contract and shell contract.

Verification

The authoring contract is exercised by theme component, Book, output, and golden checkers, then by this site’s bilingual examples and browser suites. The Goldmark facts behind the native forms are recorded in block-attribute research.

7 - Design research

Dated experiments and consumer evidence used to make OINK design decisions, without normative force.
Evidence, not a contract

Research records what was measured, with which inputs and tool versions. Results may explain a decision, but they do not override the current contracts or implementation.

Research belongs in the public Design tree when another maintainer can inspect its method, understand its limits, and repeat the relevant check. Raw agent transcripts, temporary build logs, and local absolute paths do not meet that standard.

Research map

Record Evidence
Goldmark block attributes Render-hook visibility and CommonMark container limits on the supported Hugo floor
Consumer and migration evidence A dated corpus survey plus deterministic Book migration results

Publication rules

A research record states its date, inputs, relevant versions, method, result, and known limits. Volatile counts are labeled as snapshots. External framework comparisons are refreshed from primary sources before publication and distilled into OINK-relevant conclusions rather than copied as a competitor catalogue.

When a result becomes a stable product choice, link it from an accepted decision. When it proposes behaviour that does not exist, move the design question to Proposals.

7.1 - Goldmark block-attribute evidence

Reproducible findings for lists, images, tables, passthrough blocks, fences, callouts, and nested containers on Hugo 0.160.1 and 0.164.0.
Verified snapshot

These probes produced byte-identical relevant output on Hugo Extended 0.160.1 and 0.164.0. They explain OINK’s native component forms; the current component contract remains authoritative.

Method

The probe used a minimal Hugo site without OINK templates. Render hooks printed their context fields and .Attributes as visible markers. The site enabled Goldmark block attributes, passthrough delimiters for inline and block math, unsafe rendering for the deliberately inspected raw HTML, and wrapStandAloneImageWithinParagraph: false.

Each source shape was rendered with the compatibility-floor Hugo and the then current Hugo version. Relevant output was compared byte for byte. The findings below record platform behaviour, not visual styling.

Findings

Source shape Hook result Design consequence
Ordered list with paragraphs, fences, callouts, nested lists, and {.steps} The class attaches to the outer <ol> and rich list-item blocks survive A Markdown list is the native Steps form
Heading inside a list item The heading remains inside <li> and enters .TableOfContents Native Steps can carry navigable headings
Nested list with {.filetree} The class attaches to the outer <ul> FileTree needs no wrapper merely to preserve hierarchy
Standalone image plus {#id num= caption= .class} render-image receives IsBlock=true and all attributes A Book figure can have a native image form
Inline image inside a paragraph IsBlock=false; the image receives no block attributes Inline images cannot use the block-figure contract
Block math plus {#id num=} render-passthrough receives block type and attributes A numbered equation can use the native passthrough form
Table plus {.fields #id num= caption=} render-table receives the class and named attributes Field tables, matrix markers, captions, and Book numbering can share one hook
Fenced code plus {#id num= caption=} The code-block hook receives the attributes A numbered example can be the fence itself
Callout plus {icon= tab=} The blockquote hook receives callout metadata and attributes Folding, inline title markup, icon, and tab metadata can coexist
Attribute line separated from its block by a blank line The attribute silently disappears Source checks must reject orphan attribute lines
Adjacent tables with tab= Each table hook receives its own tab label Adjacent-block tabs can extend beyond code fences

Container boundary

Hugo’s % shortcode delimiter renders .Inner as Markdown, but its template must put a blank line before and after that inner Markdown. Without both blank lines, a following list may be treated as literal HTML-block content instead of Markdown.

A multi-line % container inside a CommonMark list item has a harder limit: the generated HTML is not indented as list content, so the list closes before the container and restarts afterwards. This is why OINK keeps a full Steps form for steps that must contain another full container. Ordinary rich blocks, fences, and < shortcodes do not have that limitation.

Nested % shortcodes also receive already rendered inner HTML in the relevant collector shape. A collector that requires the child’s original Markdown uses < delimiters and renders the captured body through the shared scoped block renderer.

Attribute ownership

An available attribute is not automatically a public attribute. Every hook owns a documented allowlist. style and inline on* handlers are rejected; URL-bearing values pass the shared URL policy. A site class is retained only on the surfaces where downstream CSS is an established extension mechanism.

The experiment also showed that gallery images inside list items can be block images while still receiving no knowledge of their parent list’s marker. A runtime may therefore need either a theme-emitted marker or a narrow structural fallback; it cannot assume the image hook sees arbitrary ancestors.

Limits and verification

These results cover Hugo 0.160.1 and 0.164.0 with the stated Goldmark settings. They do not promise identical behaviour for a site that changes those settings or for a later Hugo release. A Hugo-floor change reruns the focused component, Book, table, gallery, and Markdown-output checks before this snapshot is updated.

7.2 - Consumer and migration evidence

A dated corpus snapshot that shaped OINK’s shells, authoring primitives, and deterministic Book migration policy.
Dated corpus snapshot

These counts describe the repositories inspected in August 2026. They are evidence for design choices, not live product metrics or compatibility promises.

Corpus

The authoring survey scanned the content/ trees of eleven OINK consumer sites: 5,325 Markdown files, of which 5,293 had YAML front matter. The set included single-language English and Chinese references, bilingual product sites, release archives, custom landing pages, and separate Book consumers.

The survey deliberately measured source Markdown rather than generated HTML. It counted shortcode calls, fenced-code attributes, callouts, table markers, raw HTML, front-matter keys, content types, and site-local layouts. A later Book-focused pass added five long-form consumers.

Findings that changed the design

Evidence Resulting choice
Content ranged from nearly plain Markdown to pages with many nested components Native Markdown is the default form; a full form survives only for a named capability gap
Documentation, Blog, Landing, releases, and books repeatedly reimplemented navigation or cards locally Extend the shared shell, registry, and primitive rather than adding a parallel system
Site-specific table classes were common, while canonical Field-table headings were rare Hook attributes use an allowlist but preserve documented site-class extension points; Fields cannot be inferred from arbitrary two-column tables
Book sites carried private figure, table, equation, example, and cross-reference conventions Numbered primitives and migration profiles need deterministic classification, stable IDs, and rendered-target verification
Sites mixed single-language, peer-file bilingual, and generated-language content Language authority and generation boundaries must be explicit; a migration never treats an untracked generated tree as source
Rich HTML pages still needed print, Markdown, feeds, and agent output Every component declares its output degradation before its interactive HTML is accepted

The evidence also rejected several attractive additions. Documentation sites did not justify a second Landing system; Book sites did not need a new cover component; a serial archive did not justify a new shell type; and remote API collection belonged to site-side CI rather than a Hugo theme that promises local builds.

Block and table evidence

A focused pass over eleven sites plus Book consumers found 11,484 pipe tables. Only eleven already matched the strict Field-table heading vocabulary, while roughly 874 were reference-style tables and about 1,300 were compatibility matrices. The result was explicit .fields and .matrix markers rather than shape guessing.

The same pass found eighteen Steps blocks in the eleven-site corpus. They all used the full form with headings and rich content. Platform probes showed that a native ordered list could carry most of that content, while another full % container inside a list item could not. OINK therefore keeps both forms for a technical capability boundary, not merely for stylistic preference.

Deterministic Book migration

Three dated dry-run profiles tested whether the migration rules could account for every recognized source without inventing semantics:

Profile snapshot Classified result Manual boundary
DDIA v2 106 figures, 3 tables, 22 code examples, and all 304 relevant links accounted for One caption link flattened to visible text; no unaccounted skip
DDIA v1 90 numbered figures and 203 matching references 14 decorative or unnumbered images deliberately left alone
TPME 31 figures, 10 tables, 44 numbered references, and 1,018 generic stable references No skipped recognized item
Private Book profile 119 figures, 5 tables, and 136 numbered references 3 ambiguous images retained for manual review

Each profile was dry-run first, wrote only after its ambiguity boundary was understood, produced zero changes on a second run, built with warnings fatal, and passed rendered kind/number/anchor checks. The public migration toolkit and current profile boundaries are documented in Writing a book and the migration contract.

Limits

These counts should not be copied into product marketing or used as a current site inventory. Repeating the research requires a fresh repository list and a new dated report. Paths, uncommitted content, private repository names, raw agent transcripts, and generated build artifacts are intentionally excluded from this public record.

8 - 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 Draft; no graph or backlink implementation exists
Media convergence Draft; records only the remaining cross-surface convergence after the shared content resolver and Zoom marker landed
Bulk agent indexes Draft; per-page Markdown and llms.txt already exist, while bulk text and navigation JSON do not

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.

8.1 - Backlinks and knowledge graph

A draft three-stage design for deriving backlinks and local or global graph views from ordinary Hugo links.
Draft PRD — not implemented

OINK currently has no backlink block, local graph, global graph page, or graph output format. Names and configuration in this proposal are not public API until the proposal is accepted and the contracts change.

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 a short, ordered list near the page end. Order is deterministic: section, then navigation weight, title, and stable path as the final tie-break. The block uses ordinary links and headings, has no disclosure-only content, and is omitted when there are no inbound pages.

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

  1. Is G1 opt-in, opt-out, or enabled only for selected shell types?
  2. Does the local graph expose one depth or a tightly capped second depth?
  3. Which page metadata, if any, is useful enough to enter graph JSON?
  4. Should unresolved heuristic edges stay silent while a dedicated link checker reports them, or should deduplicated preview warnings be visible?
  5. Is G3 useful enough to justify a new output format before G1 and G2 have production evidence?

8.2 - Media convergence

A draft for the remaining convergence between content images, numbered figures, Landing media, and featured-image selection.
Draft PRD — remaining work only

OINK already has a shared content-image resolver, one Zoom marker, processed Markdown images, numbered figures, and safe Landing URL handling. This page proposes only the unresolved convergence; it must not be read as a list of missing current features.

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?

8.3 - Bulk agent indexes

A draft for optional llms-full text bundles and a stable navigation JSON derived from OINK’s existing Markdown output and navigation authority.
Draft PRD — partial premise already exists

OINK already supports per-page Markdown, language-local llms.txt, HTML discovery links, and Copy Markdown. It does not currently publish llms-full.txt or a navigation JSON. Only those remaining outputs are proposed here.

Current baseline

A site opts into Hugo’s Markdown output for pages and sections and the LLMS home output for llms.txt. OINK renders shortcodes into semantic Markdown, keeps the source URL and language-local LLMS index discoverable, and derives Copy Markdown from the same alternative output URL. The theme declares output formats but does not force a site’s outputs selection.

Navigation already has an authority chain: an explicit data/docs_nav.json tree where present, otherwise the content tree and weights. Sidebar, pager, and declared section indexes share that authority. A machine navigation output must derive from it rather than inventing another order.

Goals and non-goals

Goals:

  • optionally assemble a language-local full-text bundle for small sites or one bundle per top-level section for larger sites;
  • optionally publish a versioned navigation JSON for agents and external tools;
  • reuse the same Markdown page renderer, page inclusion policy, and navigation authority as the human site;
  • keep every output opt-in through Hugo’s output configuration;
  • validate links, language isolation, media types, and deterministic order.

Non-goals:

  • replacing per-page Markdown or llms.txt;
  • creating a params.oink.* configuration tree;
  • scraping generated public/ files during the Hugo build;
  • embedding private source paths, draft pages, or cross-language fallbacks;
  • promising that one giant bundle is appropriate for every model context.

Full-text bundle

The proposed llms-full.txt output concatenates the same semantic Markdown used by each page output. Pages are separated by a stable visible delimiter and source URL. A site chooses one of two deployment shapes:

Shape Placement Intended use
Site bundle One language-local file at the language root Small, focused sites
Section bundles One file for each explicitly enabled top-level section Large references and books

Hugo output configuration, not a theme parameter, decides which pages receive the format. The theme may provide a checker that reports a mismatch between an intended shape and the actual outputs, but it does not mutate the site’s output set.

The bundle is assembled inside Hugo from one shared page-rendering partial. It does not read sibling artifacts from public/ or depend on output build order. Size is reported as evidence; an arbitrary warning threshold must not make --panicOnWarning fail an otherwise valid publication.

Navigation JSON

The proposed JSON contains a schema version, language, roots, and recursively ordered nodes. A page node has a stable ID, title, HTML URL, Markdown URL where enabled, kind/type, weight, and children. An explicit external navigation node contains only its label, URL, and external kind.

The output follows the same visibility and ordering rules as the rendered sidebar. It excludes drafts, headless resources, hidden navigation entries, and pages unavailable in the current language. It never serializes a local filename.

The format receives its own JSON Schema and golden fixtures. It is marked notAlternative so Hugo does not advertise it as a page-level alternate.

Discovery and output boundaries

llms.txt may link to enabled bulk bundles and the navigation JSON. HTML head discovery continues to advertise per-page Markdown and the language-local LLMS index; it does not add every bulk artifact to every page.

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

Acceptance criteria

  • EN and ZH outputs contain only their own language pages and URLs.
  • Every listed Markdown URL exists; every navigation URL resolves or is an explicit external node.
  • Ordering matches the rendered sidebar and pager for the same root.
  • Rebuilding with the same source produces byte-stable output under pinned Hugo versions and inputs.
  • HTML, Markdown, Print, RSS, and LLMS goldens show no regression when the new formats are disabled.
  • A large-site fixture demonstrates section bundles without generating a file for every nested section.

Open decisions

  1. Are both full-text deployment shapes needed, or is section-only safer?
  2. Should navigation JSON be a home output or a dedicated content page backed by a resource template?
  3. Which node metadata is stable enough for schema version 1?
  4. Should llms.txt list navigation JSON by default when it exists?
  5. What size evidence should the checker report without enforcing an arbitrary model-context limit?