Skip to content

Architecture contract

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

This is the architecture contract released with OINK 1.0.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.

The official compiled Font Awesome CSS is one stable, fingerprinted vendor stylesheet loaded before the fingerprinted main.css produced from theme and consumer SCSS. A site-style edit therefore does not invalidate the icon distribution, while ordinary cascade order still lets the site override it. Capability styles such as KaTeX, DocSearch, Swagger, and Asciinema remain separate and load only when used. Fingerprints make immutable URLs possible; the deployment host, not the Hugo theme, owns their HTTP cache headers.

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.

Interface localization

Implemented, not yet released

This locale expansion describes the feature branch. It is not a published module capability until a later release tag resolves through the Go proxy.

OINK ships native interface catalogs for the 31 locale filenames present in google/docsy@64f51c5, plus generic zh as the Simplified Chinese default:

ar az bg bn de en es et fa fi fr he hi hu it ja ko nl no oc pl pt-br ro ru
sr-cyrl sr-latn sv tr uk zh-cn zh-tw

That is a compatibility scope, not a runtime dependency on Docsy and not a claim that a consumer’s authored content has been translated. A new Docsy locale does not enter OINK automatically: it needs a complete OINK catalog and the same review as every existing locale.

i18n/en.yaml owns the 192-key schema. Every one of the 32 OINK bundles has exactly that key set and native UI text; an English value may remain only when it is a reviewed product name, punctuation token, conventional abbreviation, or genuine word shared by the target language. There are no generated English fallback blocks. zh and zh-cn carry Simplified Chinese, while zh-tw carries Traditional Chinese.

On the Hugo 0.160.x compatibility floor, a non-default generic zh language key must set the concrete locale: zh-CN value when the regional Chinese catalogs are also present. Bare locale: zh resolves in that configuration from Hugo 0.161 onward. This affects language configuration, not the i18n/zh.yaml catalog name.

Runtime placeholders such as %s, {count}, and {{ .Count }} may move to a grammatically natural position but must remain byte-for-byte identical. Values are scalars. Catalogs contain no hidden bidirectional controls; Arabic, Persian, and Hebrew direction still comes from the consumer language setting (direction: rtl), not from characters injected into translations. bin/check-i18n.py enforces the locale set, schema, value shape, placeholders, directional controls, and the small reviewed set of English-identical terms. Adding a visible string therefore means translating it in every bundle in the same change, not running a fallback generator.

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
LLMSFULL Opt-in per top-level section: one llms-full.txt per enabled section per language, that same Markdown concatenated in reading order
RSS Safe static summary or explicit omission
NAVJSON Opt-in per site: one navigation.json per language, serializing the navigation authority the sidebar and pager already read
BookManifest Opt-in ordered JSON handoff for a publication packager; never presented as an EPUB or PDF

Consumers opt into custom outputs; OINK does not force expensive Book aggregates. HTML gets the shared action and core layers plus stable first-party capability chunks selected by the page flags. Templated capabilities publish at most one chunk per language; flags choose script tags and never create a new combination bundle. 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.

LLMSFULL is enabled by a top-level section listing it in its _index front matter outputs; the theme never adds it to a site’s output set. One shared renderer produces the per-page Markdown and the bundle, so a bundle is that same semantic Markdown – no td- component markup – concatenated in the sidebar and pager reading order. Enabling it below the top level warns and emits nothing, so an ordinary build stays usable while --panicOnWarning blocks publication.

NAVJSON is enabled by the site’s outputs.home and publishes one navigation.json per language at the language root. It serializes the same authority chain the sidebar and pager read: an explicit data/docs_nav.json tree when present, the weighted content tree otherwise. Array order is the contract and weight is never serialized, and the output is notAlternative. schema/nav.v1.schema.json versions the format as a hand-authored contract artifact, edited with its templates and checker rather than by the generated configuration schemas’ drift gate. Both outputs default off, so a site that enables neither builds byte-identically; bin/check-agent-indexes.py owns them.

BookManifest is disabled unless a Book root explicitly lists it in outputs. It references that Book’s existing per-page Markdown and records derived page order, headings, numbered targets, and xrefs. It contains no publication metadata guessed by the theme and is not a distributable ebook.

The theme repository ships bin/book-epub.py and bin/book-pdf.py as explicit publication steps, with bin/check-book-epub.py and bin/check-book-pdf.py as their artifact gates. The EPUB packager combines BookManifest with the same whole-Book Print HTML and accepts consumer metadata separately. The PDF runner serves that Print output only on a temporary loopback address, invokes an explicit Chrome/Chromium binary behind a script-src 'none' Content Security Policy, and emits A4 pages with CSS page numbers. Both tools refuse missing or out-of-tree resources; network resources and output replacement each require a separate explicit flag. The network opt-in allows passive HTTP(S) media only; remote scripts and local-file schemes remain invalid. Relative assets in the EPUB metadata file resolve from that file’s directory, not from the caller’s working directory. No publication work runs during an ordinary Hugo build, and PDF remains Print-derived rather than another template output.

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;
  • emit no Speculation Rules by default: a named production consumer must first measure Sec-Purpose: prefetch requests, useful navigations, transferred bytes, and CSP impact with a reversible moderate experiment;
  • 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, meta, and print, exposed as --td-*-font-family. ui is the main face: body resolves through it, and heading through body, so one assignment moves chrome, prose, and headings together. 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.

params.ui.fonts reaches the same roles from configuration, for a site that would rather not mount SCSS or add a stylesheet. It names faces and never loads them: a family must be one the reader has or one the site declared in an @font-face of its own, which keeps the key outside the network contract. Values are gated to plain font family syntax and the emitted :root block is rebuilt from the matched parts; an unknown role or an unsafe value warns and is dropped alone. The block renders after the stylesheet, which is what lets an authored face outrank the preset at equal specificity. A shell reads in the site’s faces and owns none of its own: a Book sets its numbers and captions in the prose face, not in a technical one.

The accent family splits by role. Accent text – links, external URLs, inline code – follows the Bootstrap link family and --bs-code-color, which a theme color never redeclares; inline code is a fixed crimson pair so a page dense in identifiers reads as code and prose rather than code and links. Accent grounds – selected rows, the greyed ground a navigation row takes under the pointer, hover washes, the outline pill, rail and dot, chip hovers, a card’s hovered edge, a share button’s hover fill, selection, focus rings – follow --td-accent, --td-accent-rgb and --td-accent-hover, which default to the link family and are the only properties params.ui.theme_color emits. Ink that belongs to the shell rather than to the prose follows them too: the outline anchors the viewport is standing over, and a Book chapter’s headings under the pointer or keyboard focus, light in the section’s color, not in the link blue. theme_color and theme_color_dark take #rgb/#rrggbb; front matter and section cascades override the site value. An unconfigured site emits nothing. An unparseable value warns and keeps the default palette. A resolved color below 4.5:1 against the theme’s own canvas warns with a suppressible id and still ships: the check is advisory, and only a parse failure drops a color. The light color is the key: a theme_color_dark with no valid theme_color warns and is ignored, so a page is colored in both modes or in neither. An omitted dark half lightens toward white in 4% steps until it clears 4.5:1 on the dark canvas. Every emitted byte is formatted from parsed integer channels, never from author text. One resolver answers “what color is this page” for the head block and the sidebar root switcher alike.

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.