This is the multi-page printable view of this section. .
Oink Release Notes
- 1: OINK 1.0.0 — Stable contracts, a supported starter, and a complete public release
- 2: OINK 0.8.2 — Go 1.26 module compatibility for the Hugo Themes builder
- 3: OINK 0.8.1 — Stable Print anchors, resilient Book numbers, and release-ready packaging
- 4: Oink 0.8.0 — A whole section in one fetch, the sidebar as data, and who links here
- 5: Oink 0.7.1 — Nothing leaves the page, and bad input stops breaking builds
- 6: Oink 0.7.0 — A theme color, one typographic voice, and diagrams you can read
- 7: Oink 0.6.0 — Immersive blogs, safer builds, smaller internals
- 8: Oink 0.5.0 — Component API v5 and the converged configuration
- 9: Oink 0.4.0 — Scenario components for complete publishing workflows
- 10: Oink 0.3.0 — Authoring, navigation, and a lighter page
- 11: Oink 0.2.0 — Richer content and sharper presentation
- 12: Oink 0.1.0 — A stable local-first foundation
1 - OINK 1.0.0 — Stable contracts, a supported starter, and a complete public release
OINK 1.0.0 is a stability milestone, not a last-minute API reset. It promotes
the component, configuration, content, output, and maintainer contracts built
through the 0.x releases to the first major version. This note rolls up every
theme change in v0.8.0..v1.0.0; the 0.8.0 agent outputs and backlinks are the
starting baseline rather than being counted again as new 1.0 work.
An existing 0.8.0, 0.8.1, or 0.8.2 site needs no content or configuration migration. Pin the new module, run the warning-strict build, and review the rendered site as you would for any theme upgrade.
At a glance
- The current authoring, shell, landing, Book, release, Print, Markdown, and agent-output contracts are now the OINK 1.0 stable surface.
- Single-page Print preserves ordinary page heading and footnote IDs; only multi-page section and whole-Book aggregates namespace page-local targets.
- Book sidebar numbers remain atomic beside long, wrapping titles.
- Theme CI, the documentation site, and OINK Starter use Go 1.27 and Hugo Extended 0.165.0. The declared Hugo compatibility floor remains Extended 0.160.1.
- OINK Starter is the supported first route into the framework, with neutral Docs, Blog, and Book content and strict GitHub Pages and Cloudflare Pages workflows.
- The README, theme metadata, case-study links, badges, and optimized 3:2 Hugo Themes gallery images now describe and present the same product that the code ships.
What 1.0 makes stable
The version number covers contracts, not a promise that the interface will stop evolving. OINK can keep adding components and optional outputs in 1.x; the point of 1.0 is that an ordinary site should not have to relearn or rewrite the current foundations on every minor release.
| Surface | 1.0 contract |
|---|---|
| Content | Native Markdown remains the source; components preserve explicit static degradation for non-interactive outputs |
| Configuration | params.ui.* owns theme policy, page overrides drop the prefix, and invalid author input warns with a safe fallback |
| Shells | Docs, Blog, Book, Swagger/Redoc, and Landing keep distinct, documented responsibilities |
| Outputs | HTML, RSS, Print, Markdown, LLMS, LLMSFULL, NAVJSON, and BookManifest keep explicit opt-in and degradation boundaries |
| Runtime | Vendored assets stay local and capability code loads only where rendered content needs it |
| Maintenance | Implementation, owning checker, bilingual contract, release state, consumer pin, and deployment remain separate evidence |
The normative English and Chinese records live under
Design. Their status now reads released-v1.0.0; dated
research and active proposals remain evidence or future work, not hidden 1.0
features.
Everything changed after 0.8.0
The complete source comparison is
v0.8.0...v1.0.0.
It contains a deliberately small stabilization set:
| Area | Change | User-visible effect |
|---|---|---|
| Book sidebar | Fixed the number cell and added a compiled-CSS regression assertion | A long title wraps without compressing, clipping, or splitting its chapter number |
| Print anchors | Distinguished a page Print from section and Book aggregates, then refreshed the output golden | A fragment valid on an ordinary page remains valid on that page’s Print view; aggregate IDs remain collision-free |
| Theme CI | Replaced the historical Hugo matrix with one pinned Extended 0.165.0 toolchain and explicitly pinned Go 1.27 for module-mode coverage | Release evidence now matches the current upstream toolchain while the 0.160.1 Hugo floor stays documented separately |
| Public README | Rebuilt the first-run path around OINK Starter, capability and compatibility tables, production cases, docs links, and the Docsy boundary | A visitor can evaluate the project without reverse-engineering its regression site |
| Hugo Themes media | Replaced the old captures with optimized 3:2 landing-page images | The gallery receives the required aspect ratio at 166,526-byte and 68,488-byte PNG sizes without browser chrome |
| Theme metadata | Expanded the description, tags, and features; normalized the OINK wordmark; recorded Docsy as the original theme | Catalogue attribution and discovery now match the repository’s actual scope |
| Module directive | 0.8.2 temporarily admitted the module to the former Go 1.26 upstream builder; 1.0 follows the newly updated upstream workflow on Go 1.27 | This changes module admission only; OINK still contains no Go source and the rendered theme is unchanged by the directive |
There is no component rename, configuration-key removal, default flip, or content-syntax migration in this range.
Correct Print identities
Page-local IDs and aggregate-document IDs solve different problems. An
ordinary page and that page’s own Print representation are two views of the
same document, so their authored or Goldmark-generated heading and footnote
IDs should match. A section Print or whole-Book Print combines many source
pages, so two chapters may both contain #overview or fn:1; those targets
must gain a source-page namespace.
| Output | Heading and footnote IDs |
|---|---|
| Ordinary HTML page | Authored or Goldmark-generated page-local IDs |
| Single-page Print | The same page-local IDs as ordinary HTML |
| Multi-page section Print | Source-page namespace added |
| Whole-Book Print | Source-page namespace added |
Book figures, tables, equations, examples, and rewritten cross-page links keep their existing explicit target rules. The fix narrows namespacing to the two outputs that actually aggregate documents.
A supported first mile
OINK Starter is now part of the supported release surface rather than an informal demo. It starts as a small, neutral project site: three language profiles, Docs, Blog, Book, local assets, and two warning-strict deployment workflows. It deliberately excludes OINK’s own analytics account, comments, brand, documentation corpus, browser suite, and maintainer fixtures.
The Starter tutorial moves from shallow changes to deep ones: establish an unchanged baseline, set identity, choose languages, replace home data, rewrite content and navigation, add brand, enable complete integrations, run the strict build, and only then deploy. Existing Hugo sites can still take the smaller module-from-scratch path.
Toolchain and compatibility
The official Hugo Themes update workflow moved to Go 1.27 and Hugo 0.165.0 on the day of this release. OINK 1.0 follows that current publication baseline:
| Dependency | OINK 1.0 policy |
|---|---|
| Hugo | Extended 0.160.1 or newer; release, site, and browser validation pin 0.165.0 |
| Go | 1.27 or newer for Hugo Module resolution |
| Node.js | Not required to build or run a consumer site |
The short-lived 0.8.2 patch lowered only the module’s go directive so the
previous official updater, fixed on Go 1.26 with local toolchain selection,
could admit the theme. Once upstream moved to 1.27, keeping that exception no
longer described the release environment. OINK itself is still templates,
styles, assets, and checkers rather than Go source. An offline archive or Git
submodule installation does not ask Go to resolve the module.
Upgrade
Commit both go.mod and go.sum. Then inspect representative Docs, Blog,
Book, Print, language, light/dark, and narrow-screen routes. A successful local
build, the public tag, a resolved module checksum, a consumer pin, deployment,
and live rendering remain separate release states.
The repository-level ledger remains in CHANGELOG.md.
2 - OINK 0.8.2 — Go 1.26 module compatibility for the Hugo Themes builder
OINK 0.8.2 is a module-metadata compatibility release. It changes no template, asset, component API, configuration key, content syntax, or rendered output. Sites already using 0.8.1 need no content migration.
At a glance
- The theme module now declares Go 1.26 instead of Go 1.27.
- The official Hugo Themes builder can import OINK under its pinned Go 1.26
toolchain with
GOTOOLCHAIN=local. - Hugo Extended 0.160.1 remains the declared compatibility floor; OINK Starter continues to build with Hugo Extended 0.165.0.
- OINK contains no Go source and uses no Go 1.27 language or module feature, so the lower directive changes module admission only.
Why this patch exists
The OINK 0.8.1 release was built and tested with Go 1.27. Its go.mod therefore
declared go 1.27.0, even though the repository is a Hugo Module made of
templates and assets rather than Go packages. The official Hugo Themes pipeline
runs Go 1.26 with automatic toolchain switching disabled. It correctly rejected
the higher directive before reading the theme metadata.
OINK 0.8.2 declares the oldest Go toolchain the current release workflow needs: Go 1.26. The module remains byte-for-byte identical at the rendered surface; only the compatibility gate that admits the module has changed.
Compatibility
| Dependency | OINK 0.8.2 requirement |
|---|---|
| Hugo | Extended 0.160.1 or newer |
| Go for Hugo Module resolution | 1.26 or newer |
| Node.js | Not required |
An offline archive or Git submodule installation does not require Go because it does not ask Hugo to resolve a module.
Upgrade
Commit go.mod and go.sum, then run the site’s warning-strict production
build. The complete change list is in
CHANGELOG.md.
3 - OINK 0.8.1 — Stable Print anchors, resilient Book numbers, and release-ready packaging
OINK 0.8.1 closes two narrow rendering defects and brings the public project surface into line with the framework that shipped in 0.8.0. It changes no component API, configuration key, content syntax, or compatibility floor. An existing 0.8.0 site can update its module pin without migrating content.
At a glance
- A page rendered by itself in Print now keeps exactly the ordinary page’s heading and footnote IDs. Section and whole-Book Print still namespace those page-local IDs because several source pages share one aggregate document.
- A Book sidebar number is now an atomic fixed-width cell. A long title may wrap, but it cannot shrink, clip, or wrap the number beside it.
- Continuous integration uses one pinned Hugo Extended 0.165.0 toolchain for theme, publication, site, and browser checks. Hugo Extended 0.160.1 remains the declared consumer compatibility floor.
- The project README now makes OINK Starter the primary route, documents the capability and compatibility boundaries, shows representative production sites, and explains why OINK is a distinct theme rather than a Docsy skin.
- New 3:2 Hugo Themes gallery media, richer theme metadata, and explicit Docsy attribution complete the submission package without adding page assets to a consumer site.
Print IDs match the surface being rendered
Heading and footnote IDs are page-local facts in ordinary HTML. Before 0.8.1, the same page rendered alone as Print received the prefix reserved for an aggregate, so a URL fragment that worked on the regular page did not identify the corresponding element in that page’s Print output.
The rule is now explicit:
| Output | Heading and footnote IDs |
|---|---|
| Ordinary HTML page | Authored or Goldmark-generated page-local IDs |
| Single-page Print | The same page-local IDs as ordinary HTML |
| Multi-page section Print | Source-page namespace added to page-local IDs |
| Whole-Book Print | Source-page namespace added to page-local IDs |
Explicit Book targets such as figures, tables, equations, and examples remain
stable. Aggregate output still rewrites its cross-page links to the namespaced
heading and footnote targets, preventing two chapters with #overview or
fn:1 from producing duplicate IDs in one document.
Book numbers stay readable beside long titles
The Book sidebar uses a number cell followed by a title cell. The number cell could previously inherit shrinking and overflow behaviour from a long title, which made chapter numbers clip or break across lines at narrow widths. The number is now non-shrinking and atomic; only the title wraps. This is a CSS-only correction and does not change Book numbering or navigation order.
A clearer first route into OINK
The recommended starting point is now the small
pgsty/oink-starter template rather
than a clone of the documentation regression site. It includes neutral Docs,
Blog, and Book content, three language profiles, and warning-strict GitHub
Pages and Cloudflare Pages workflows without OINK’s own analytics, comments,
test harness, or brand.
The new Starter tutorial takes that baseline through identity, languages, home data, content and navigation, brand, integrations, strict builds, and deployment in that order. Existing Hugo sites can still use the smaller from-scratch module path.
Upgrade
Commit both go.mod and go.sum, then run the site’s warning-strict production
build. No content or configuration migration is required. The complete change
list is in
CHANGELOG.md.
4 - Oink 0.8.0 — A whole section in one fetch, the sidebar as data, and who links here
Oink 0.8.0 changes no component API and needs no content edits. Two of its
three additions serve the reader that arrives as a program. Every page already
publishes a .md twin, which serves an agent that knows the page it wants; an
agent that wants the manual still had to crawl it a page at a time, discovering
links as it went. Two new output formats answer the other half: give me the
whole section, and tell me what is in the site before I fetch anything. The
third addition is for the reader who is a person — a page’s right rail can now
list the pages that link to it.
At a glance
LLMSFULLpublishesllms-full.txtper top-level section: every page of the section, in sidebar reading order, in one file.NAVJSONpublishesnavigation.jsonper language: the sidebar tree as data, versioned by a JSON Schema.params.ui.backlinkslists the pages that link to a page in its right rail, derived at build time from the links already in your Markdown.- All three are opt-in and none is ever turned on for you. A site that asks for none of them builds byte-for-byte what it built before.
llms.txtlists whichever ones you enabled, so discovery stays in the file an agent already fetches.- A
data/docs_nav.jsonnode without achildrenkey no longer crashes the build.
The full-text bundle
LLMSFULL collapses a whole section into one file: llms-full.txt at the
section root, holding every page of that section concatenated in the order the
sidebar and pager present them, each page introduced by a separator carrying
its source URL. For an agent, /docs/llms-full.txt is one fetch where the
alternative was one fetch per page plus a link graph to follow — and the
result is ordered, so the section reads as a manual rather than as a bag of
pages.
A section asks for it in its own front matter; the theme never adds it to a site’s output set:
Front matter outputs replaces the site-level list wholesale, so write back
the formats the section already had. It is per language, so _index.zh.md
repeats the line to get the Chinese bundle.
What a page contributes is the same semantic Markdown its own .md
carries — not a second rendering of it. The per-page Markdown body moved into
a shared partial that both outputs call, so a bundle entry is byte-identical
to that page’s .md and the two cannot drift apart. Order comes from the same
authority the sidebar reads: the explicit data/docs_nav.json tree where a
docs or book section declares one, the weighted content tree otherwise.
Pages held out of the sidebar stay out of the bundle.
A bundle belongs to a top-level section, and there is no whole-site variant:
an agent that wants everything reads one bundle per section. Listing
LLMSFULL further down the tree warns and emits nothing, so hugo server
keeps working while a publishing build with --panicOnWarning stops there.
This site’s docs section has it enabled: https://oink.pgsty.com/docs/llms-full.txt is the entire documentation in one fetch. Details, including the file’s exact shape: Full-text bundle.
Navigation JSON
The sidebar is the site’s table of contents, and an agent that can read it
plans a route before spending a single fetch on content. NAVJSON publishes
it as data — navigation.json, one file per language at the language root.
The site turns it on for the home page:
The tree is not a second description of the site’s structure. It serializes
the same authority the sidebar and the pager already read, through the same
partial: the explicit data/docs_nav.json tree where one is declared, the
weighted content tree everywhere else. A check asserts that the docs subtree
flattens to exactly the page sequence the full-text bundle emits — two
template paths, one authority.
Each node carries an id (the path with the language prefix removed, so the
same page has the same id in every language), the absolute url, the
markdown URL where the page publishes one, title, description, a kind,
and its ordered children. Two properties are worth reading as promises
rather than as implementation details:
- Array order is the contract. The ordering has already been applied, and
weightis never serialized — a consumer re-sorting the array would disagree with the sidebar the array came from. - The format is versioned.
schemaVersionis1, and the contract ships in the theme repository asschema/nav.v1.schema.json. Validate against it if you consume the file.
This site’s https://oink.pgsty.com/navigation.json is a live instance. Details, including the placeholder rows and the full key table: Navigation JSON.
Who links here
A reader who lands on a page from search can see where it points and nothing about where it sits. Backlinks close that half: the pages that link to this one appear as a “Backlinks” group in the right rail below the table of contents, expanded by default; more than eight entries fold behind a disclosure. One key turns it on:
A page overrides it with the front matter key backlinks, and a section
cascades it to everything below.
The index is derived at build time from what you already wrote: ordinary
Markdown links and ref / relref in the page source. There is no
[[wikilink]] syntax to adopt, nothing to migrate, and no JavaScript — the
links are in the HTML, they are in the page’s Markdown output, and they are
there for a reader with scripts off. Code fences and inline code are stripped
before scanning; repeated links to one target merge into one entry; self links,
external links and same-page anchors never count; each language has its own
graph. Order is the stable page path, so the same content always builds the
same list, and when nothing links in there is no block at all.
One honest limit: reading the source misses a URL buried in a custom
shortcode’s parameters or in a raw <a href>, and a destination that will not
resolve is dropped quietly. This is navigation, not link checking — a link
checker is still the tool for finding broken links.
This site enables it site-wide: look at the right rail of any docs page, and the most-referenced page — Configuration — lists more than forty inbound links. Details: Backlinks.
Discovery stays in llms.txt
Neither file is an alternate representation of a page, so neither appears in
<head> or gains a page action. Instead llms.txt — the file an agent
fetches first anyway — grows a ## Full-text bundles list of this language’s
bundles, and lists this language’s navigation.json in its site index. Both
entries appear only where the site actually publishes the file: the theme
never points at something it did not emit.
A childless nav node no longer crashes the build
A node in data/docs_nav.json with no children key ended the build with a
reflection error from inside the sidebar walker. The walker assumed every node
had the key, which holds for generated JSON and does not hold for JSON someone
writes by hand — where a leaf is naturally written as a node without children.
Authored data now degrades instead of erroring: the childless node renders as
the leaf it is.
Upgrading
Nothing changes until you ask for it. No component API changed and no content
edits are required — the two formats are declared in outputs, backlinks are
one boolean under params.ui, and a site that asks for none of the three
publishes what it published under 0.7.1. The two output formats, and the shape
of what they produce, are on
AI-agent support; the backlink switch is on
Navigation and menus.
The complete list is in CHANGELOG.md.
5 - Oink 0.7.1 — Nothing leaves the page, and bad input stops breaking builds
Oink 0.7.1 changes no component API and needs no content edits. It closes the code findings from an external review of the 0.7.0 line: one real privacy leak, a class of configuration values that could stop a build outright, and three components that had never been taught what a non-HTML output is.
At a glance
- Swagger UI no longer contacts the online validator. A published API page made a third-party request on every view; it no longer makes any.
- A URL written into site configuration now passes the same safety check as one written by an author.
- A mistyped number or boolean in
paramswarns and falls back instead of ending an ordinaryhugo serversession. swagger,redocandasciinemaemit a plain link in print, Markdown and RSS, and load their runtime only in interactive HTML.
Swagger stopped phoning home
Swagger UI ships with an online validator enabled by default, pointed at
validator.swagger.io. It skips that request for localhost, which is why no
local preview and no browser test ever showed it — and why every deployed
API page was quietly handing your spec URL to a third party. On an intranet
that URL is an internal hostname.
The initializer now pins validatorUrl: null, and it moved out of an inline
<script> into a cacheable js/chunks/swagger-init.js. A normal build still
downloads nothing, and now a normal view uploads nothing.
Configured URLs go through the same gate as authored ones
Two settings reached an href unchecked: the custom links in
params.ui.page_context_menu.links and the archived-site banner’s
params.url_latest_version. A javascript: URL in either one rendered as a
working, clickable script.
Both now run through the theme’s shared URL policy: an unsupported scheme warns and the link is dropped rather than repaired. The archived-version banner additionally escapes its URL where it is written into the page, because clearing the scheme is not the same as being safe inside an HTML attribute.
Custom links also skip entries with a missing or non-text name, and the separator above them only appears when a link actually survives.
Bad configuration warns; it no longer kills the preview
The theme’s rule has always been that invalid author or config input warns,
falls back to a documented default, and keeps hugo server usable — with
--panicOnWarning turning that warning into a failure where you publish.
A group of numeric and boolean keys had never been wired into it.
Before 0.7.1, a value like blog_index_size: nope ended the build with a Go
template error. Others were worse for being quiet: sidebar_width_min: -50
emitted a negative pixel width without a word, and blog_index_columns: 2.5
fed a fraction into a CSS grid.
Every numeric and boolean key now resolves through the shared validator:
| Input | Before | Now |
|---|---|---|
blog_index_size: nope |
build fails | warns, uses 12 |
blog_index_size: 0 |
silently becomes 12 |
warns, uses 12 |
sidebar_width_min: -50 |
emits -50px |
warns, uses 220 |
sidebar_width_min: 300 with max: 200 |
inverted layout | warns, uses 220/480 |
blog_index_columns: 2.5 |
fraction into CSS | warns, uses 3 |
sidebar_item_overflow: clip |
silently ellipsis |
warns, uses ellipsis |
print.toc: nope |
silently true |
warns, uses true |
The same treatment reaches the Landing sections: the hero’s media.ratio and
media.max_width, the capabilities board’s columns and rules, and a
marquee’s rows. The hero’s two style inputs were the notable ones — they were
written straight into a style attribute, so a page’s own front matter could
put arbitrary CSS on the page. ratio now takes exactly two track sizes
('1fr 240px'), max_width a plain CSS length.
If you have been running with a value the theme silently corrected, you will
see a new warning. That is the point — build once with --panicOnWarning after
upgrading to find them.
OpenAPI and terminal recordings respect the other outputs
Every Oink component renders once and then adapts to the output it lands in:
interactive HTML, static print, plain Markdown for agents, and RSS. Sixteen
components already did this. swagger, redoc and asciinema did not — they
rendered their interactive markup into all four.
The result was a Markdown output carrying <div class="td-asciinema"> and a
JSON config block, a printed page holding an empty container where a player
should be, and a single-page print that actually downloaded the player runtime
to show one frozen frame.
All three now read the output format:
| Output | What you get |
|---|---|
| HTML | the full interactive component |
| a labelled static link showing the URL | |
| Markdown / LLMS | a plain Markdown link, nothing else |
| RSS | the same plain link |
Only interactive HTML registers the runtime, so print and machine outputs load
no player, no Swagger bundle and no ReDoc bundle. A cast or spec URL now also
goes through the shared URL policy, and a mistyped speed, cols, rows or
marker time warns and is ignored instead of ending the build.
Smaller repairs
- The capabilities board’s rule bars render at their authored widths. The template had always emitted them; the stylesheet never read them.
- The generated configuration schemas match what Hugo actually parses. Trailing
comments in
hugo.yamlwere leaking into eleven default values —print.tocshipped as the string"true # section print views…"— and four comment blocks documented the wrong key. Keys that exist only to warn about a rename no longer appear in editor completion. - A
herowhosemediais not a map warns and drops the media instead of ending the build.
Upgrading
No content, configuration or template changes are required. One thing to do
after upgrading: run a build with --panicOnWarning once. Configuration that
used to be corrected silently now says so, and that build is where you will
see it.
The complete list is in CHANGELOG.md.
6 - Oink 0.7.0 — A theme color, one typographic voice, and diagrams you can read
Oink 0.7.0 changes no component API. It works on the two surfaces a reader actually spends time in — the shell around the page and the type on it — and finishes a fence that had never been designed, only inherited.
At a glance
params.ui.theme_colorgives a section its own accent, applied to the shell’s grounds rather than to the prose.params.ui.fontsreaches all seven typography roles; a Book no longer carries a face of its own.- A
mermaidfence is a figure: centred, unframed, redrawn in place when the colour scheme changes, and openable at full size in a pan/zoom dialog. - Inline code is crimson ink on a hairline wash instead of a grey pill.
- First-party browser behaviour publishes as stable capability chunks under
js/chunks/, so a page selects scripts instead of minting its own bundle. - Configuration schemas are generated from the resolvers, not maintained by hand.
A theme color
params.ui.theme_color takes a #rgb or #rrggbb hex and tints the shell’s
accent grounds: the selected sidebar row and the ground its neighbours take
under the pointer, hover washes, the outline pill with its travelling rail and
dot, tag and chip hovers, a card’s hovered edge, a share button’s hover fill,
text selection, and focus rings.
A section can set its own, and a page opts out of an inherited one with
theme_color: false. What it deliberately leaves alone is the reading
surface — prose links, external URLs and inline code keep the brand palette in
every section — so a colored section is a quiet signal of place rather than a
recolor of the page.
One typographic voice
params.ui.fonts reaches the theme’s seven typography roles from
configuration, so a site can change its voice without shipping a stylesheet.
A Book no longer carries typography of its own. Its numbers and captions used
to render in a bundled monospace with a Latin subset only, which split a
Chinese caption across two faces mid-sentence — the digits in one face and the
characters in whatever fallback the reader happened to have. They now inherit
the surrounding face, with tabular-nums holding the sidebar column aligned.
Diagrams you can actually read
A mermaid fence used to be five lines of passthrough: it handed Mermaid the
<pre> of a code block and let startOnLoad decide the rest. Three defects
followed from that one decision, and all three needed the same fix — keeping
the source readable after Mermaid has run.
The fence now emits a figure holding an empty stage and its source as JSON,
the shape echarts and infographic already use, and a runtime owns when
each diagram is drawn.
Centred, and unframed. Mermaid emits width="100%" with a max-width at
the diagram’s own size, so anything narrower than the column sat against the
start edge with up to 300px of empty bordered box beside it — the border
being the code block’s. There is deliberately no alignment attribute: a
diagram is a figure, and no reader wanted one flush right.
Openable at its own size. Mermaid does not overflow a narrow column, it
shrinks to fit one, so overflow-x never offered a way back: on a 390px
phone the sequence diagram on this site’s own Mermaid page rendered at 35% of
its natural width, turning 14px labels into five. Hovering a diagram — or
reaching it with the keyboard — now reveals a control in its corner that
renders the diagram a second time into a dialog at full size. Drag to pan,
zoom with the wheel, a two-finger pinch or +/-, reset with 0, close with
Esc. A diagram that would have to shrink past half size to fit opens at 1:1
at its starting corner instead of as a thumbnail, and zooming back out always
reaches the whole diagram however large it is.
No more page reload on a colour-scheme change. The old runtime reloaded the whole page on every theme change on any page holding a diagram, citing a Mermaid limitation from the 8.x era. Mermaid 11 re-initializes cleanly, so the diagrams are redrawn in place, and each stage holds its height across the swap so nothing moves under the reader.
A diagram inside a tab that is not the open one now renders at its proper
size. Inside display: none every text measurement returns zero, and Mermaid
wrote the resulting max-width: 16px into the SVG for good; revealing the tab
never recovered it.
Markdown, RSS and Print carry the fence source. Print had been carrying a
<pre class="mermaid"> that no runtime ever reached, at font-size: 0, so a
printed diagram was a blank gap.
The reading surface
Inline code is crimson ink on a hairline wash rather than a grey pill. The old tint made every token a lozenge; the much fainter wash now marks token boundaries while the monospace face, weight and hue do the identifying, which keeps a token-dense paragraph readable instead of turning it into a field of grey controls.
The series strip is a panel the width of the bar rather than a stack of links, taxonomy chips are quiet at rest and light up under the pointer, the navbar’s dropdown panels breathe in and out instead of popping, and link hover leaves the muted navy for a vivid azure.
Build and infrastructure
- First-party browser behaviour publishes as stable capability chunks under
js/chunks/. A page selects script tags by capability instead of minting a bundle of its own, so the chunks cache across pages. bin/generate-config-schema.pyprojects the schemas from the resolvers, and CI fails when a newparamskey lands without one.- An opt-in
BookManifestoutput records the Book sequence with stable ids. - One media-result contract sits behind every resolved image.
- Google Analytics is limited to interactive HTML output; Print and machine outputs no longer carry it.
- The Book publication job renders its PDF. It never had:
chrome-headless-shellneeds unprivileged user namespaces, which Ubuntu 24.04 restricts through AppArmor, and the job had failed on every run it ever had.
Upgrading
Nothing in the component API changed, so no content edits are required. Two things are worth knowing:
- A
mermaidfence no longer renders<pre class="mermaid">. Site CSS targeting that selector matches nothing now; the diagram is afigure.td-diagramholding.td-diagram__stage. - If a site pins the theme version in a check of its own, that assertion moves with the pin.
The complete list is in CHANGELOG.md.
7 - Oink 0.6.0 — Immersive blogs, safer builds, smaller internals
Oink 0.6.0 keeps the component API established in 0.5 and improves the systems around it: long-form reading, Blog discovery, attribution, release publishing, build resilience, and maintainability.
There is no new article type or second shell. Immersive reading is a
configuration of the existing Blog shell, so articles remain in the same lists,
feeds, taxonomies, series, and pager sequence.
At a glance
- The Blog shell gains a full-bleed
heroimage and an in-flow outline rail. - Blog publishing gains profiles and bylines, series order, list/cards/table indexes, and a local-first share bar.
- Vendored and translated pages gain optional, validated attribution.
- The theme no longer calls
errorf: ordinary previews warn and safely degrade; publishing builds remain strict through--panicOnWarning. - Release metadata is now one
release_url, not a duplicated fact map. - Repeated template work, page bundles, and checker builds have been reduced without pruning Font Awesome or other public authoring assets.
Immersive Blog presentation
An immersive page uses four independent front matter keys:
Put the same keys in a section cascade to apply them to its posts. Hugo resolves cascade values on the section index that declares them as well as on its descendants, so a section that should share the presentation writes the keys once.
hero paints the resolved featured image behind the opening and masks it out
before the article text. The ordinary navbar remains available and uses a
fading scrim over the artwork. toc_style: flow gives the outline a wider
in-flow rail that begins with the article and becomes sticky on scroll.
toc_taxonomies: false removes term clouds from that rail.
The Blog shell omits breadcrumbs by default; breadcrumb: true restores one
for a page or cascade that wants it.
Each switch is independent. A missing image produces the normal opening; an empty TOC and disabled clouds produce no empty rail. Nothing changes the page’s Blog membership or output formats.
The Blog, completed
Featured images
params.ui.featured_image and the page key featured_image support:
| Mode | Presentation |
|---|---|
none |
No article image; the default |
banner |
A framed 16:9 image above the title |
wash |
A low-opacity image behind the article header |
hero |
A full-bleed Blog backdrop |
All modes reuse the representative-image resolver also used by list thumbnails and social metadata. Missing images are valid, and non-HTML outputs keep their static source-shaped forms.
Authors
Declare taxonomies: {author: authors}. The author term page is the profile:
its title is the name, its description and body are the biography, and its
representative image is the portrait. Posts list authors with
authors: [vonng, oink]; the order is preserved. The legacy author:
string remains the fallback when the taxonomy is not used.
Series
Declare taxonomies: {series: series}. Posts name one or more terms with
series and may set series_weight. Weighted members come first in
ascending order; unweighted members follow by ascending date. The article and
term page share that resolver, so the position strip and archive cannot drift.
Three index forms
| Key | Default | Meaning |
|---|---|---|
ui.blog_index |
list |
list, cards, or table |
ui.blog_index_columns |
3 |
Card columns |
ui.blog_index_size |
12 |
Posts per list/cards page |
ui.blog_index_toggle |
false |
Reader-side cycle among all three forms |
List and cards share year grouping and pagination. A standalone table is a complete unpaginated archive. With the reader toggle enabled, all three forms share the current paginator slice; the full archive is not repeated on every generated page. The configured form controls first paint, and a local preference may override it.
Sharing
params.ui.share is an ordered list drawn from x, bluesky,
mastodon, facebook, linkedin, reddit, hackernews,
telegram, whatsapp, line, pinterest, weibo, chatgpt,
claude, email, and copy. Empty is the default; share: false
opts out one page.
The bar uses plain intent links and the local copy action. It loads no platform SDK, iframe, counter, or third-party stylesheet.
Page annotation
upstream_link is the per-page source URL. Its companion facts are
upstream_name, upstream_copyright, upstream_license,
upstream_notice, upstream_ref, and upstream_modified. Constants may
come from site parameters, a data/upstreams entry, or front matter.
Incomplete facts, unknown licences, unsafe URLs, and invalid types warn and
omit the whole attribution line. Strict builds reject the warning.
upstream_link: "" explicitly opts a page out of an inherited attribution.
params.ui.translation_notice optionally names the authoritative language.
It is never imposed as page front matter; a page may opt out with
translation_notice: false.
Warnings instead of preview outages
The theme contains no errorf calls. Simple scalar values share
validate.html; components retain the checks that understand their own
records and markup.
Invalid input follows one rule:
- warn with the bad value and the safe fallback or omission;
- do not emit unsafe or misleading output;
- let an ordinary
hugo servercontinue; - let
--panicOnWarningstop CI and publication.
This preserves strict gates without letting one typo take every preview URL down.
Outline rail
The outline exposes a visible range and a current cursor on one SVG path. The
cursor carries aria-current="location"; reduced-motion and unsupported
registered-property engines fall back without detaching the cursor from the
line.
Fixed and simplified
- Mounted content no longer publishes build-machine paths in Edit, History, or Create Child URLs.
- Generic
data-*andaria-*values use one HTML-escaping emitter. - Incomplete Algolia credentials emit no container, CSS, or JavaScript.
- Draw.io loads only on pages with PNG/SVG candidates and inspects each distinct URL once.
- Page actions, pager state, language targets, and section-index children reuse page- or site-scoped results instead of repeating full-site work.
- Language-neutral feature bundles are shared across translations.
- Field anchors are derived from field names and remain unique within a page.
- Print aggregates namespace headings and footnotes without changing regular page IDs.
- The maintained invalid-input checker batches equivalent cases: the content primitive stage now starts Hugo six times instead of 160 while preserving every warning and fallback assertion.
- Obsolete CSS, i18n keys, rejected Article-shell artifacts, duplicate checker blocks, and narrative code comments have been removed. The complete supported Font Awesome distribution remains intact.
Configuration
| Key | Default | Notes |
|---|---|---|
ui.featured_image |
none |
none / banner / wash / hero |
ui.toc_style |
fixed |
fixed / flow |
ui.toc_taxonomies |
true |
Show taxonomy clouds in the right rail |
ui.blog_index |
list |
list / cards / table |
ui.blog_index_columns |
3 |
Card columns |
ui.blog_index_size |
12 |
List/cards page size |
ui.blog_index_toggle |
false |
Reader-side three-form cycle |
ui.share |
[] |
Ordered share targets |
ui.translation_notice |
false |
Optional authoritative language |
time_format_blog |
2006-01-02 |
Changed default |
time_format_default |
2006-01-02 |
Changed default |
The default shell and pager type lists remain docs, book, blog, and
swagger where applicable. No article type is added.
Migration
From 0.5:
- Keep explicit prose date formats if ISO dates are not desired.
- Ensure publishing commands use
--panicOnWarning. - Replace the old
releasemap withrelease_url: https://github.com/<owner>/<repo>/releases/tag/<tag>. - Replace
upstream_attributionwithupstream_link, anddownstream_modifiedwithupstream_modified. - Do not migrate content to
type: article; use the Blog presentation keys shown above.
The migration tool operates on content Markdown and supported YAML front matter.
Configuration-file mappings remain an explicit maintainer task. From 0.4, keep
the established sequence: report, migrate --write, then check.
Verification
The 0.6.0 release is verified with:
- Hugo Extended 0.160.1 and 0.164.0;
- 40 HTML/print/Markdown/RSS/LLMS golden surfaces;
- 85 migration tests and 38 browser-runtime tests;
- strict example, Hugo Module, system-font, legacy-font, and invalid-config builds;
- the bilingual project-site build and its non-browser regression suite;
- representative large-site performance measurement and real EN/ZH browser checks.
Local validation, a commit, a tag, a push, a consumer pin, and deployment remain separate release states.
Full change set
8 - Oink 0.5.0 — Component API v5 and the converged configuration
Oink 0.5.0 is the API-freeze release. It contains the change set that the 1.0 line will freeze: the component API v5 (native Markdown forms first, 29 shortcodes as full forms), configuration and front matter keys converged on three rules, one naming namespace for everything the theme emits, the removal of the 0.x compatibility layer and of the Docsy leftovers no site used, and a migration toolkit that rewrites a 0.4 site. Every retired key, shape, or shortcode fails the build with a message that names its replacement.
This is a breaking release for every 0.4 site. Read At a glance first, then the migration guide; the reference sections in between list every change with its old and new form.
At a glance
- Content: most components are written as Markdown —
> [!TYPE]callouts,{.steps}and{.cards}lists,{.fields}/{.matrix}/{caption=}/{#id num=}/{tab=}tables,```filetree/```gallery/```echarts/```infographic/```checksumsdata fences, adjacent code fences as tabs, and the Markdown image with an attribute line. Of the 53 shortcodes in 0.4.2, 32 are gone or renamed and 8 are new; 29 remain as full forms.scripts/migrations/oink06.pyrewrites content. - Configuration: three rules — a switch is the bare feature name, single-key
maps are flattened, a front matter key is the site key without
ui.. Some forty keys are renamed or reshaped; every old one fails the build with its replacement. Every theme default is declared in the theme’shugo.yaml. - Front matter: no
ui:block any more; page overrides are bare keys (section_index: cards),page_context_menumirrors the site map,manualLink*aremanual_link*,hide_*/exclude_searchare gone. - Namespace: theme classes are
td-*, data attributesdata-td-*, custom properties--td-*, JS globalsOink*; theoink-*set and Docsy leftovers (leaf,has-child,nav-*, …) are gone. Callout labels arecallout_*i18n keys. - Removed: the
home/**adapter partials,outputformat.html,td/render-heading.html, the Docsy community page andparams.links, thetd/code-dark/td/color-adjustments-dark/td/gcs-search-dark/td/extraSass files,.td-box*and-bg-*palette classes, Prism, Open Sans,click-to-copy.js,swaggerui(nowswagger). - Behaviour: headings carry a self-link, print content is rendered once per build (a real race fixed), three cacheable JS bundles, print pages load 8 KB of JS instead of 100 KB, shell motion honours reduced motion by construction, giscus palettes ship with the theme and load only where comments render.
- Migration:
oink06.py report → migrate --write → checkfor content and front matter, then a build whose errors are the configuration checklist. - Release hardening: a two-round adversarial review repaired the client-side namespace migration, action-registry load order, fail-closed migration input, multi-instance OpenAPI embeds, shared attribute and image-URL policies, and consuming-site configuration preflight before the API freeze.
Component API v5
Native forms first
The v5 principle: a component that a Markdown block can express is written as Markdown; a shortcode exists only for what a block cannot carry. Render hooks recognise the native forms, and one attribute policy governs every hook.
Components: native form and full form
Callout,> [!NOTE] Titleblockquote;[!TYPE]-folded /[!TYPE]+open; optional{icon="fa-solid fa-x"}; typesnote tip important warning caution success danger question example quote details. No shortcode.Tabs,Native: adjacent fences (or tables) with
{tab= group= value=}.Shortcode:
tabs group= default= label=tab label= value=…/tab/tabs.Steps,Native:
1.list +{.steps}.Shortcode:
stepswith headings — the only shortcode written with the%delimiters (its body is page-level Markdown); headings inside steps enter the TOC.Cards,Native: link list +
{.cards}.Shortcode:
cardscard title= link= icon= badge= image= image_alt=|decorative=body/card/cards.Fields,Native: table +
{.fields [caption=] [id=] [meta="type required default -"]}— first column name, last column description, middle columns metadata chips.Shortcode:
fields label= id= class=field name= type= required= default=body/field/fields— for block-level descriptions (this list is one). Both forms render the same chips; every entry gets a#field-<name>anchor.FileTree,```filetree {title=}fence, one- name[/] # comment {icon= tone= open= type=}line per entry; 2/4-space, tab, ortreeindentation. CSS + native<details>; the comment column is aligned at build time. No shortcode.Gallery,```galleryfence, one # description {link= class=}line per image; alt is required, items are Zoom-eligible. No shortcode.Image,plus an attribute line{#id num= caption= width= height= link= command= options=}for figures, numbering, links, and Hugo image processing.imgprocis retired; there is no image shortcode.Table family,{.full-width}{.fields}{.matrix}{caption=}{#id}{#id num= caption=}{tab= group= value=}; site classes pass through. Exclusivity: fields ⟂ matrix / full-width / num; num ⟂ tab.Fig / Tbl / Eq / Eg,Native: image / table /
$$block / fence +{#id num= caption=}(default idsfig-,tbl-,eq-,eg-<num>).Shortcode:
figtbleqeg(egcaption required).Xref,Native: plain Markdown links (kind-less).
Shortcode:
xref fig|tbl|eq|eg="…" [page=] [anchor=].Book indexes,book-tocbook-figuresbook-tablesbook-equationsbook-examples— nokind=parameter.Code fences,Fence attributes
{title copy wrap collapse label id tab group value num caption lineNos hl_lines lineNoStart anchorLineNos tabWidth}; Chroma only.Data fences,mermaid plantuml markmap math chem echarts infographic checksums filetree gallery;echartsis declarative,$fn:<name>callbacks come fromwindow.OinkEchartsFunctions.Leaves,kbdbadgeparamincludecommentcontributorsasciinema(raw<kbd>also works);badgehas nooutline,paramis scalar only.Release / OpenAPI,release-cardrelease-assetsdownload/swaggerredoc; thechecksumsfence is the native release form.
The 29 shortcodes: core 14 (tabs tab steps cards card fields field include kbd badge param comment contributors asciinema), Book 10 (fig tbl eq eg xref book-toc book-figures book-tables book-equations book-examples), Release 3,
OpenAPI 2. Nested names (tab, card, field) are valid only inside their
parent; every shortcode validates its parameters, and an unknown parameter
fails the build (asciinema, redoc, swagger, param, comment, and
steps accepted anything silently in 0.4).
Removed shortcodes and their replacements
The chip on each entry is the toolkit key (scripts/migrations/oink06.py migrate --only <key>); manual means the report lists it and a human edits.
Removed shortcodes and their replacements
alert · details · td-page-notice,0.4:
alert color=… title=…,details,td-page-notice(all%shortcodes), raw<details><summary>.0.5.0:
> [!TYPE] titlecallouts,> [!DETAILS]-for a folded block.tabpane · tab · code-group · code-tab,0.4:
tabpanewithtab header=…(both%shortcodes),code-groupwithcode-tab.0.5.0: adjacent fences with
{tab= group= value=}(code-only panes), ortabswithtabfor mixed content.filetree · filetree/folder · filetree/file,0.4:
filetreewithfiletree/folderandfiletree/file; the interim{.filetree}list marker.0.5.0: the
```filetreefence —labelbecomestitle;open,icon,color,comment,linkare kept.gallery · gallery/image,0.4:
gallerywithgallery/image; an image list +{.gallery}.0.5.0: the
```galleryfence.echarts · infographic,0.4:
echarts,infographicshortcodes.0.5.0: same-named data fences;
$fn:callbacks are unchanged,jssub-fences move towindow.OinkEchartsFunctions.doc-cards · doc-card · nav-cards · nav-card · card · cardpane · doc-carousel,0.4: the Docsy card family and the OINK
doc-cards/nav-cardswrappers.0.5.0:
cardswithcard, or a link list +{.cards}.cardkeeps its name as the child ofcards, with a different contract.imgproc,0.4:
imgproc …(and the pre-releaseimage …).0.5.0:
+{command= options= caption=}on the attribute line.readfile,0.4:
readfile file=….0.5.0:
include file=… [code=true lang=…]— page resources, then assets, then content-relative paths.fence filename=,0.4:
{filename="x"}on a fence.0.5.0:
{title="x"}.badge outline=,0.4:
badge … outline=….0.5.0: drop
outline— there is one badge appearance.example · book-figures kind=,0.4: self-closing
example …+ fence;book-figures kind="tbl".0.5.0:
eg…/eg;book-tables,book-equations,book-examples.fields · field (percent form),0.4:
fields/fieldwritten with%delimiters (never shipped).0.5.0:
fields/field._param · iframe · conditional-text · netlify · kind-less xref,Reported with
file:linefor manual review;_paramplaceholders are handled by theparam_placeholderstransform.blocks/cover · blocks/feature · blocks/lead · blocks/link-down · blocks/section,0.5.0:
layout: landingwithsections(data file or inline front matter). Reported, not rewritten.swaggerui,Renamed to
swagger; change the call.pageinfo,Write a
> [!NOTE]callout instead.td/site-build-info/netlify.md,Removed without replacement.
New in 0.5.0 relative to 0.4.2: tabs, cards, include, eg,
book-tables, book-equations, book-examples, and swagger (renamed).
card and tab keep their names but are now children of cards / tabs
with a different contract.
There is no image shortcode: the render hook resolves page resources,
section resources, global assets, and static or remote paths for Markdown
images, fig, and configuration image sources alike, and carries captions,
numbering, links, and Hugo image processing (command, options) on the
attribute line — everything imgproc did.
The block-attribute policy
Every render hook (table, image, code block, passthrough, blockquote, heading)
shares one policy: allowlisted keys are consumed by the hook, class is
token-validated and passed through, data-* and aria-* pass through, and
style, on*, and any unknown key fail the build. Site CSS classes on
content are legitimate and keep working; inline styles and handlers never
reach the output.
Code fences
{filename="x"}is{title="x"}.titleandfilenameare mutually exclusive on the same fence.- The Prism path is gone.
params.prism_syntax_highlighting,static/js/prism.js, andstatic/css/prism.cssno longer exist; Chroma withparams.highlight_classes(defaulttrue) is the only highlighter. Prism could not coexist withtab,group,value,num, andcaption, so any 0.4 site using tabs or numbered examples already failed with it enabled. - The Copy control follows
copy=all|command|true|falseon the fence, then the session-lexer default (console,shell-session→command), thenall.params.ui.code_copy: falsechanges the site-wide default only; a fence that namescopystill gets what it asks for. The olddisable_click2copy_chromasilently overrode an explicit author value. - Docsy’s
click-to-copy.js(never loaded since 0.3) and its.td-click-to-copystyles are removed.
Configuration
The three rules
- A boolean switch is the bare feature name:
ui.annotation: true, notui.annotation.enableand notui.annotation_enabled. The only_enabledsuffixes left areui.navbar_enabled,ui.sidebar_enabled, andui.sidebar_root_enabled, whose bare names would collide with sibling families. - A single-key map is flattened to a scalar. A map survives only for a feature
with several settings —
comments,ui.feedback,ui.page_context_menu,ui.dark_mode,ui.command_palette,ui.alt_site,taxonomy,print,search,plantuml,drawio,mermaid,copyright,ui.taxonomy_icons— and the on/off ones among them also accept a bare boolean (comments: false,plantuml: false,dark_mode: true,feedback: true,page_context_menu: false). - A front matter key is the site key with its
ui.prefix dropped, without exception (see Front matter).
Keys are snake_case, positive, and named for what they do. camelCase survives
only where a value is passed straight through to an external runtime
(comments.giscus.* carries giscus’s own attribute names, mermaid.* is
handed to mermaid.initialize()).
Every old key or shape fails the build with a message that names the
replacement — layouts/_partials/config-legacy.html for site configuration,
layouts/_partials/front-matter-legacy.html for pages — so an upgrade is a
matter of following the errors one by one. Nothing is silently ignored.
Renamed and reshaped site keys
| 0.4 | 0.5.0 | Note |
|---|---|---|
offlineSearch, offlineSearchIndex, offlineSearchMaxResults, offlineSearchOnServe, offlineSearchSummaryLength |
offline_search, offline_search_index, offline_search_max_results, offline_search_on_serve, offline_search_summary_length |
environment override: HUGOxPARAMSxOFFLINE_SEARCH_ON_SERVE=true (Hugo’s alternate x delimiter; _ cannot address snake_case keys) |
ui.showLightDarkModeMenu (true / false / "enable-only (experimental)") |
ui.dark_mode — true, or { enable, show_menu } |
show_menu: true implies enable |
ui.scrollSpy.disable |
ui.scroll_spy |
inverted; default false |
ui.no_left_sidebar |
ui.sidebar_enabled |
inverted |
ui.breadcrumb_disable |
ui.breadcrumb |
inverted; default true |
print.disable_toc |
print.toc |
inverted; default true |
disable_click2copy_chroma |
ui.code_copy |
inverted; sets the default only |
ui.readingtime.enable |
ui.reading_time |
bare boolean |
ui.ul_show |
ui.sidebar_expand_levels |
default 2 |
Taxonomy.taxonomyCloud, .taxonomyCloudTitle, .taxonomyPageHeader |
taxonomy.cloud, .cloud_title, .page_header |
one lowercase map |
ui.annotation.enable, ui.image_zoom.enable, ui.keyboard_nav.enable |
ui.annotation, ui.image_zoom, ui.keyboard_nav |
bare booleans |
ui.typography.preset |
ui.typography |
technical | system; environment override HUGO_PARAMS_UI_TYPOGRAPHY=system |
ui.pager.types |
ui.pager_types |
[docs, book, blog] |
markmap.enable |
markmap |
bare boolean |
content_width (slim | norm | wide) |
reading_width (slim | normal | wide) |
Book reading measure; body class td-book-content--normal, token --td-book-content-normal |
ui.docs_root |
ui.docs_sidebar_root |
section | home |
github_url |
github_repo |
edit, history, and issue links derive from the repo |
algolia_docsearch |
search.algolia with appId, apiKey, indexName |
fails the build |
rss_sections |
removed | it was never read |
params.links.user[] / .developer[] |
removed | the Docsy community page is gone |
plantuml.enable, drawio.enable |
unchanged, and the maps accept plantuml: false / drawio: false |
|
comments.enable |
unchanged, and comments: false is accepted |
|
comments.giscus.lightTheme / darkTheme |
unset by default | the theme’s own palettes are the default (see Styles and assets) |
Every theme default is now declared in the theme’s hugo.yaml with its value
range in a comment. Previously template-only fallbacks that are now declared:
offline_search: false, offline_search_summary_length: 70,
ui.breadcrumb: true, ui.reading_time: false, ui.dark_mode: false,
ui.docs_sidebar_root: section, ui.sidebar_icon_policy: all,
ui.section_index_columns: 2, ui.code_copy: true, print.toc: true,
print.section_break_wordcount: 50, markmap: false, plantuml.enable: false,
drawio.enable: false, github_branch: main. Two defaults stay derived and
are documented as such: ui.quick_links (from docs_section and
blog_section) and ui.taxonomy_icons (built-in categories/tags
glyphs). The template fallbacks for ui.sidebar_expand_levels (2) and
ui.sidebar_menu_truncate (2000) match the declared values.
Unchanged Docsy keys that keep working as they are: github_repo,
github_project_repo, github_branch, github_subdir,
path_base_for_github_subdir, time_format_blog, time_format_default,
version, versions, version_menu, version_menu_pagelinks,
archived_version, url_latest_version, copyright, description,
author, gcs_engine_id, search.algolia.*, mermaid, plantuml.*,
drawio.*, ui.sidebar_menu_compact, ui.sidebar_menu_foldable,
ui.sidebar_menu_truncate, ui.sidebar_cache_limit,
ui.sidebar_root_enabled, ui.feedback.{enable,reasons}.
Fail-loud, not silent
Configuring more than one search backend (offline_search, gcs_engine_id,
search.algolia) now fails the build (it warned before). PlantUML without
plantuml.svg_image_url, Diagrams.net without drawio.drawio_server, and
Algolia without all three credentials still fail the build, as in 0.4. Build
messages follow one shape — <component>: <subject> <expectation>; got <value> at <position> — lower case, one preposition for the location,
configuration errors naming the full params. path; they no longer point at
documentation URLs.
Front matter
The page key is the site key without its ui. prefix, and front matter never
carries a ui: block. A section cascade works the same way
(cascade: { params: { section_index: cards } } or the bare key). One resolver
(ui-param.html) reads the page value, else the site value, for every
params.ui.* setting a page may override: sidebar_menu_compact,
sidebar_menu_foldable, sidebar_expand_levels, sidebar_width_min,
sidebar_width_max, sidebar_item_overflow, sidebar_headings,
sidebar_enabled, section_index, section_index_columns, lastmod_commit,
breadcrumb, scroll_spy, code_copy, keyboard_nav, book_draft_banner,
plus the explicit page keys navbar_enabled, navbar_autohide,
footer_style, annotation, feedback, image_zoom, reading_time,
page_context_menu, comments, page_width, reading_width.
| 0.4 front matter | 0.5.0 |
|---|---|
params: { ui: { <key>: … } } (any key) |
<key>: … at the top level (or under params:) |
params.ui.image_zoom.enable |
image_zoom: true | false |
params.ui.keyboard_nav.enable, params.ui.annotation.enable |
keyboard_nav, annotation (bare booleans) |
annotation: { enable: … } |
annotation: true | false |
context_menu |
page_context_menu (true | false, or { enable, assistant_links }) |
assistant_links (top level) |
page_context_menu: { assistant_links: false } — a page can only narrow the site policy |
hide_readingtime: true |
reading_time: false |
hide_feedback: true |
feedback: false |
exclude_search, excludeSearch |
search_exclude |
content_width: norm |
reading_width: normal |
manualLink, manualLinkTitle, manualLinkTarget, manualLinkRelref |
manual_link, manual_link_title, manual_link_target, manual_link_relref |
body_class: td-no-left-sidebar |
sidebar_enabled: false |
contributingUrl |
removed with the community page |
Icon |
icon (Hugo is case-insensitive; the theme reads the lowercase form) |
Unchanged page keys: toc_hide, toc_root, notoc, no_print, no_list,
simple_list, hide_summary, sidebar_root_for, sidebar_divider,
sidebar_expanded, sidebar_root_menu, sidebar_root_link_self,
search_keywords, search_boost, pager, landing, sections,
book_number, book_status, release, release_products,
release_group_by_product, upstream_attribution, downstream_modified,
byline, author, body_class.
scripts/migrations/oink06.py migrate --only frontmatter rewrites all of the
renamed page keys, including inside cascade: maps and lists.
Templates, partials, and layouts
Removed, with what a site that copied or called them should use instead:
| 0.4 | 0.5.0 |
|---|---|
_partials/home/** (18 adapters), _partials/home-data.html |
_partials/landing/**, landing/home-data.html |
_partials/outputformat.html |
.Store.Get "tdOutputFormat" (html | print | markdown | rss, set by every base template) |
_partials/td/render-heading.html and a site-side _markup/render-heading.html that calls it |
the theme’s own _markup/render-heading.html — delete the site override |
layouts/community/list.html, layouts/docs/community.html, _partials/community_links.html |
none — the Docsy community page is gone |
_partials/taxonomy_terms_article.html, taxonomy_terms_article_wrapper.html, taxonomy_terms_cloud.html |
taxonomy-terms-article.html, taxonomy-terms-article-wrapper.html, taxonomy-terms-cloud.html |
_partials/taxonomy_terms_clouds.html, code/markdown-escape.html |
dead in 0.4 already; shell/taxonomy-terms-clouds.html, content/markdown-escape.html |
_shortcodes/swaggerui.html |
_shortcodes/swagger.html |
layouts/_default/_markup/render-* copied from 0.4 |
compare against 0.5.0 before keeping any override — every hook changed |
Other template-level changes a site with overrides should know about:
- Both sidebar sources — the content tree and an explicit
data/docs_nav.json— render every row throughshell/sidebar-node.html.shell/config.htmlremains the single resolver for brand, logo, and section configuration. - Every content-rendering layout calls
content/render.htmlinstead of.Content(that is where the Image Zoom candidate scan runs). - Print:
print/page-content.htmlrenders each page’s print content exactly once per build throughpartialCached;print/render.html,print/content.html,book/print.html, and thesingle.print.htmllayouts read that. Sites that copied a 0.4 print template should drop the copy — the 0.4 pipeline raced on the page store when a section that is itself a section was aggregated by its parent. - The theme owns the heading render hook. Every heading carries its id and a
hover-revealed self-link (
.td-heading-self-link, labelui_heading_self_link); print and RSS strip the link. - The DocSearch container is one
#td-docsearchelement; the two hard-coded#docsearch-0/1ids are gone.
Styles and assets
One namespace
Everything the theme emits is namespaced, and scripts/check-namespace.py
keeps it that way. Site CSS or JS that hooked the old names must move:
| Kind | 0.4 | 0.5.0 |
|---|---|---|
| Classes | oink-* (landing subsystem), leaf, has-child, active-path, is-open, is-active, is-hidden, is-disabled, landing-header, landing-nav, landing-container, article-meta, pageinfo, nav-*, taxonomy-*, ul-N |
td-* throughout; the site header and nav are td-site-header, td-site-nav, td-site-container |
| Data attributes | data-oink-* |
data-td-* |
| Custom properties | --oink-*, --term-* |
--td-* |
| JS globals | oink* / echartsFunctions |
window.OinkActions, OinkEchartsFunctions, OinkLanding, OinkSearchEngine, OinkSurfaceCoordinator |
| Author markers (unprefixed, unchanged) | — | {.steps} {.cards} {.fields} {.matrix} {.full-width} |
Sass and tokens
Removed Sass files (a site’s _styles_project.scss that imports them fails
to compile): td/code-dark, td/color-adjustments-dark,
td/gcs-search-dark, td/extra, td/extra/bs-defaults, td/extra/buttons,
td/extra/main-container, td/extra/navbar, td/boxes (.td-box,
.td-box--<color>, .td-box--height-*), td/colors (.-bg-<name>,
.-text-<name>). Removed variables: $td-box-colors, $td-print-font-name,
$td-enable-webfonts.
Renamed or new tokens: --td-book-content-norm → --td-book-content-normal
(and .td-book-content--norm → --normal); --td-print-font-family keeps
its role but follows --td-body-font-family in both presets; new
--td-motion-duration-fast (100 ms), --td-motion-duration (150 ms),
--td-motion-duration-slow (250 ms), which every shell transition uses and
which prefers-reduced-motion: reduce sets to 0.
Typography: Inter for UI and prose (variable weight, Latin/Latin-ext/Cyrillic/
Greek/Vietnamese subsets served by unicode-range; CJK and emoji fall through
to the platform stack), borderless inline code, quiet code cards with a
hover-revealed Copy control, Mintlify-style field rows, a page-end pager of
two text links, and a rule above card section indexes. Open Sans (18 woff2
subsets, 652 KB, published to every site for a print-only face) is gone; a
site that wants a different face on paper sets --td-print-font-family in its
own stylesheet. The system preset still requests no brand fonts.
Shell chrome icons are Font Awesome class pairs dispensed by
shell/icon.html (<i class="td-shell-icon td-shell-icon--<name> fa-solid fa-…">)
instead of inline SVG; --td-shell-icon-size sets the box.
Published assets
- Three JavaScript bundles instead of one per feature combination:
js/actions.jsandjs/core.jsare byte-identical on every page and stay cached; only a smalljs/page-<hash>.jsvaries. ECharts is its own<script>. Print output loads 7.9 KB instead of 100 KB. static/css/giscus-oink-{light,dark}.cssare gone. The palettes ship asassets/css/giscus-{light,dark}.css, are published only on pages that render comments, and are the defaultcomments.giscus.lightTheme/darkTheme; a site that pointed at the old paths removes those two lines (or names a giscus built-in theme / its own stylesheet URL).- Gone:
static/js/prism.js,static/css/prism.css,static/webfonts/open-sans/,assets/js/click-to-copy.js.VENDOR.jsonand the vendor tree hashes are regenerated.
i18n
- Callout labels are namespaced keys:
callout_note,callout_tip,callout_important,callout_warning,callout_caution,callout_success,callout_danger,callout_question,callout_example,callout_quote,callout_details. The theme no longer claims bare top-level keys such asnote,example, orquote; a site that overrode those in its owni18n/renames them. - Removed:
community_join,community_introduce,community_learn,community_using,community_develop,community_contribute,community_how_to,community_guideline. - Added:
ui_heading_self_linkandui_field_self_link(English fallback in every locale; reviewed Chinese variants). - All 32 locale files keep exact key parity (174 keys).
Data files
data/home/<lang>.yaml(ordata/home.yaml) must listsections; the implicithero → metrics → capabilities → principles → ctaorder is gone and its absence fails the build.- The fat footer reads
data/footer/<lang>.yaml(ordata/footer.yaml) only. Afooterkey insidedata/homefails the build naming the new location. data/landing/<key>/<lang>.yaml,data/docs_nav.json,data/download/<key>.yaml, anddata/brand.yamlare unchanged.
Behaviour and output changes
- Headings carry a self-link revealed on hover; the anchor is stripped from
print and RSS output, and Markdown output (
RenderShortcodes) is unaffected. - Print aggregates render each page’s content exactly once per build. In 0.4
a chapter that was itself a section was rendered by its own print output and
by its parent’s, concurrently, and the two renders raced on the page store —
the visible symptom was intermittent duplicate
td-code-…ids in_print/. <main>no longer carriesrole="main", and the sidebar<aside>no longer duplicates the inner<nav>’s “Section navigation” label.ui.dark_mode: trueturns on both the dark palette and the System / Light / Dark menu;show_menu: truealone impliesenable.ui.code_copy: falsesets the default only (see Code fences).- The navbar renders on the home page; callout titles meet contrast; Gallery
items are Zoom-eligible on the same terms as other images; the tabs runtime
keeps its run boundaries, unique peer ids, and print titles; FileTree and
the whole shell honour
prefers-reduced-motion. - The table render hook runs in print and RSS output, so tables keep caption,
number, and scroll container outside interactive HTML;
fieldsfrom either form produce one rendering, and every entry gets a#field-<name>anchor. llms.txtreadsparams.ui.docs_sectionand lists documentation pages with their descriptions.- Image resolver errors are labelled by the caller (
image:for a Markdown image, the shortcode name forfig), and configuration image sources are held to the same URL policy as content.
Release-candidate hardening
The final review found one systemic migration gap: templates emitted the new
data-td-* contract while several runtimes and test mocks still read the old
dataset names. It also found that the action manifest followed the synchronous
action-registry bundle, so the registry could initialize empty. Both are fixed,
with structural checks that reject either regression. Page actions, Command
Palette search, code copying and collapse, feedback identity, disclosure
labels, Giscus themes, Image Zoom labels, and Asciinema timers now exercise the
same attributes in tests and in the rendered DOM.
The same hardening pass also:
- makes migration
report,migrate, andcheckreject missing, empty, unreadable, or non-UTF-8 targets instead of reporting a misleading clean result, and parses JSON front matter with a JSON decoder; - runs legacy front-matter guards in Markdown, RSS, and aggregate print output, and validates boolean/map shapes for page comments and other overrides;
- preserves accepted
data-*/aria-*attributes through data fences and callouts, while keeping chart booleans strict; - gives every Swagger and ReDoc embed a unique instance without replacing
window.onloador publishingwindow.ui; - applies the shared URL policy to shell logos, wordmarks, and configured featured images; and
- ships
scripts/check-site-markup.py, which checks the resolved consuming-site configuration for the three Goldmark settings required by native forms.
Migration guide
The order matters: content first (the toolkit is dry-run by default and idempotent), then the build errors drive the configuration and layout edits.
Before rewriting content, verify that the consuming site can render the native forms:
1. Inventory
The report lists, per site, every 0.4 construct the toolkit will rewrite,
what it will not touch (with file:line and a reason), and what would still
be flagged afterwards.
2. Content and front matter
Transforms, in apply order: frontmatter (page keys, including cascade:),
callout, param_placeholders, tabs, filetree, gallery, datafence,
cards, fieldsdelim, image, include, fencetitle, badge, eg,
reportonly. --only <key> selects a subset. Text inside fences is never
rewritten; TOML/JSON front matter is reported, not rewritten. Across the eleven
in-house sites the front matter transform touched 628 files with zero findings.
Manual follow-ups the report lists: swaggerui → swagger, pageinfo →
callout, _param placeholders, iframe/conditional-text/blocks/*,
kind-less xref, and js sub-fences of echarts that must become
window.OinkEchartsFunctions entries.
3. Configuration
Build the site. Each old key fails with its replacement:
A typical 0.4 hugo.yaml becomes:
Delete params.links, prism_syntax_highlighting, rss_sections,
github_url (use github_repo), algolia_docsearch, and the giscus
lightTheme / darkTheme URLs.
4. Cascades and section indexes
A cascade that set params.ui.* becomes bare keys — the transform handles
_index.md files, but check hand-written cascades in hugo.yaml:
5. Sass, layouts, and site scripts
assets/scss/_styles_project.scss: remove@import 'td/color-adjustments-dark','td/code-dark','td/extra','td/extra/bs-defaults','td/gcs-search-dark'; drop rules that targeted.td-navbar-cover,.td-navbar-transparent,.td-box*,-bg-*,oink-*,--oink-*.assets/scss/_variables_project.scss: drop$td-print-font-name,$td-enable-webfonts,$td-box-colors.layouts/: replacepartial "home-data.html"/"home/section.html"withlanding/…; replacepartial "outputformat.html"with.Store.Get "tdOutputFormat"; delete a_markup/render-heading.htmlthat calledtd/render-heading.html; renametaxonomy_terms_*calls; compare every other copied partial or hook with 0.5.0 before keeping it.- Site JS and tests:
oink-*ids anddata-oink-*attributes aretd-*/data-td-*; the action manifest is#td-action-manifest; the per-page bundle isjs/page-<hash>.js, withjs/actions.jsandjs/core.jsbeside it. - Site
i18n/overrides: renamenote,tip, … tocallout_note,callout_tip, ….
6. Data
Move the footer: map out of data/home/<lang>.yaml into
data/footer/<lang>.yaml; make sure data/home/<lang>.yaml lists sections.
7. Verify
Then check the surfaces that changed most: a docs page with code tabs and a
callout, a page with images (zoom on, zoom off), a Book chapter and its
_print/ aggregate, index.md Markdown output, an RSS feed, the home landing
page, and the dark palette. Once the v0.5.0 tag is pushed, pin it:
Compatibility
- Hugo Extended 0.160.1 remains the minimum; CI runs 0.160.1 and 0.164.0, and now also builds a consumer site in Hugo Module mode.
- The module path remains
github.com/pgsty/oink; consumers still need no Node.js, no CDN, and no build-time download. - There is no compatibility layer for 0.4: renamed keys, shapes, shortcodes, partials, and classes fail the build or vanish, by design. The old-key errors are the migration guide; the Docsy-origin entries among them also serve sites coming from Docsy.
- Docsy keys that were reasonable stay unchanged (see the list under
Configuration); the
sidebar_*family keeps its names. - Interactive features remain opt-in:
offline_search,ui.image_zoom,comments,ui.feedback,ui.dark_mode,page_context_menu.assistant_linksare off unless a site turns them on.
Verification
Theme CI: 34 check scripts (i18n parity, taxonomy, font tokens, the
navigation / component / content-primitive / Book contracts, runtime
isolation, sidebar icons, search, actions, palette, reading, release assets,
downloads, landing, Book migrations, shared scenarios, keyboard, shell,
namespace, parameters, vendor inventory, output structure and security,
four-state goldens over 30 surfaces, code blocks, content and media
primitives, Image Zoom, Gallery, components), the browser runtime unit tests,
the migration toolkit tests (85), the warning-strict fixture site on Hugo
0.160.1 and 0.164.0, the system typography preset, legacy Sass overrides,
an invalid preset failing the build, and the new Module-mode consumer build.
scripts/check-params.py builds one site per retired key (32 site keys, 14
page keys) and asserts that each fails naming its replacement.
This project site builds warning-free on 0.5.0 after the migration described above. The final gate ran the complete matrix on Hugo 0.160.1 and 0.164.0; its media assertions accept each supported Hugo release’s opaque derivative cache hash while still checking the rendered URL shape, dimensions, alt semantics, and Zoom exclusion. Source validation, the local annotated tag, remote tag publication, consumer pins, and deployment remain separately auditable gates.
Full change set
See the complete source diff from
v0.4.2 to v0.5.0 and
the theme’s CHANGELOG.md.
9 - Oink 0.4.0 — Scenario components for complete publishing workflows
Oink 0.4.0 delivers the complete Scenario Components system. The original design
separated Reading & Release, Landing, and Book work into 0.4, 0.5, and 0.6
milestones. The public release consolidates those tracks into one signed
v0.4.0 tag so consumers can adopt one coherent contract instead of a chain of
interdependent previews.
The release remains local-first: consumer sites still build with Hugo Extended and Go, without Node.js, a browser-time API, or a CDN. Interactive behavior is progressive enhancement; HTML, print, Markdown, and RSS output retain the content needed to understand each surface.
Release highlights
Reading and release
Documentation, Book, and blog pages now have a sequential pager derived from the
same flattened navigation tree readers see in the sidebar. Previous and next
links also appear as same-origin rel metadata in the document head. Explicit
navigation data, link-only entries, sidebar dividers, and blog time order keep
their own semantics instead of becoming accidental destinations.
Math can use Goldmark passthrough with the theme’s local KaTeX renderer. Sites
that cannot enable passthrough yet have a strict, parameter-free eq escape
hatch for display math. Numbered Book equations use the same shortcode only when
an explicit num is supplied.
Release pages can render local front-matter facts, release cards, checksums, and
asset lists without querying GitHub from the browser. A validated
data/download/<key>.yaml model feeds both the download shortcode and Landing
download sections, with separate rolling and pinned channels and an explicit
pending-release state.
See Sequential reading and mathematics and Releases and downloads for the complete contracts.
Landing pages
The data-driven homepage renderer is now a reusable layout: landing shell for
ordinary pages. A page may load inline data or language-aware records from
data/landing/<key>/, then compose 21 built-in sections including pricing,
comparison tables, command boxes, steps, timelines, code plates, case studies,
downloads, and bar charts.
All facts are local at build time. Optional reveal, count-up, copy, theme-image, and compact-menu behavior loads only when a Landing page needs it. Without JavaScript, the same page remains complete; marquees pause on focus or request, respect reduced motion, and hide duplicate tracks from assistive technology.
See Landing pages for data resolution, all 21 section types, local-fact rules, and the output matrix.
Book publishing
Long-form manuals can declare Book metadata on the existing documentation shell.
Chapters gain draft labels, active-page sidebar headings, and semantic fig,
tbl, numbered eq, and language-aware xref targets. Whole-Book figure lists
and tables of contents use those same registries.
An opt-in aggregate print document rewrites cross-chapter component links into document-local references and namespaces repeated heading IDs. The accompanying migration tool is dry-run first and idempotent, with reproducible TPME, DDIA, and pg-internal recipes, machine-readable reports, ambiguity skips, and a second-run zero-change check.
See Book publishing for the authoring and migration contract.
Keyboard and shell
The shell now supports single-key reading navigation. w and s move through
the sidebar, a and d fold or unfold groups, j and k move through the
page outline, and q and e follow the sequential pager. h toggles a session
reading mode; l, t, f, and c switch language, theme, search, and command
surfaces. Every binding yields to editable controls, composition, held
modifiers, and dialogs.
The navbar now spans documentation, blog, taxonomy, and Swagger layouts with one
compact state instead of a second mobile menu. Page actions move into the
breadcrumb row as a Copy Markdown split button. Footers support validated fat,
slim, and none styles, while readers can collapse the fat footer’s link grid
and retain that preference.
See Keyboard navigation and Navigation and menus.
Compatibility and behavior changes
- Hugo Extended 0.160.1 remains the minimum supported version.
- The module path remains
github.com/pgsty/oink; consumers still need no frontend toolchain. - Pagers default to the
docs,book, andblogcontent types. Set an explicit type list orpager: falsewhen a page should opt out. /now opens full search;\opens command-only mode. The>prefix still selects command mode inside the Palette.params.footer_icpandparams.footer_icp_urlare replaced by one inline Markdown value,params.footer_center_info. An explicit empty string hides the center region.params.ui.navbar_enableddefaults totrue. Sites can override it globally, by section cascade, or on one page.- Legacy homepage data and Docsy block shortcodes remain compatible, but new Landing work should use the canonical section registry.
Upgrade to 0.4.0
- Pin the signed tag and tidy the module graph.
- Replace ICP-specific footer fields with
footer_center_infoif the site used them. - Review pager defaults,
/and\shortcuts, and any local navbar or footer overrides. - Remove copied theme partials only after comparing their local differences with the 0.4 implementation.
- Build representative docs, blog, Landing, Book, print, Markdown, mobile, and color-mode surfaces.
Follow the project-site 0.4.0 upgrade guide for the consumer checklist. The theme repository retains the frozen PRD 5 migration reference.
Verification
The signed tag resolves to the same commit as the released theme source. Theme CI exercises Hugo Extended 0.160.1 and 0.164.0, 32-locale parity, vendored assets, runtime unit tests, every PRD 4/5/6 contract, and the warning-strict example site. The project site pins the public tag and covers bilingual source, rendered Markdown, internal links, alternate builds, browser behavior, and the full multilingual WCAG AA matrix.
Representative documentation, portal, Book, and archive sites also build from
the public v0.4.0 module with workspaces disabled.
Source validation, a public tag, a consumer pin, and hosted deployment are separate evidence gates. Publishing this note does not replace a smoke test of the deployed URL after the site pipeline finishes.
Full change set
See the complete source diff from v0.3.0 to v0.4.0.
10 - Oink 0.3.0 — Authoring, navigation, and a lighter page
Publication gate: the linked tag must resolve, the project site must pin that exact tag, and the hosted checks must pass. Until then, treat this source page as release-candidate material.
Oink 0.3.0 is the authoring and navigation release. Writing a page gets a modern code-block presentation and a set of small, everyday components; reading one gets nested navigation and a Command Palette; and every page gets measurably lighter because jQuery is gone.
The module path, the minimum Hugo version, and the Hugo-only consumer build are unchanged. Three changes can affect an existing site and are documented in Breaking changes.
Release highlights
Code blocks and code groups
Ordinary fenced code blocks now render a real code surface: an optional
filename, a language label, a copy button rendered by the server rather than
injected by script, opt-in wrapping, and collapsing for long listings. Hugo’s
own highlighting options — line numbers, line anchors, hl_lines, tab width —
keep working exactly as before.
Copy behaviour is deterministic rather than guessed. Session lexers such as
console and shell-session default to copying commands without prompts and
output; every other language copies the whole block. copy=command is rejected
on lexers that cannot distinguish the two, because silently copying the wrong
thing is worse than a build error.
The code-group shortcode groups alternatives — package managers, languages,
platforms — into synchronized tabs with a stable URL hash, so a link can open
the exact variant a reader needs. Legacy tabpane content keeps working and
keeps its storage key.
See Code blocks for the full attribute contract.
Everyday content primitives
Alongside the existing large components, 0.3.0 adds the small ones authors
actually reach for daily: badge, kbd, fields, filetree, gallery, and
an opt-in image_zoom. All of them render semantic HTML, none of the
non-interactive ones load JavaScript, and each has a defined presentation in
print and Markdown output as well as HTML.
A standalone public icon shortcode is deliberately still deferred; components
use a private, allowlisted registry for their own decoration until that API is
designed properly.
See Components for each contract.
Navigation and Command Palette
Top-level menus support one level of dropdowns on desktop and matching accordions on mobile, with the parent link and the disclosure control operated independently so a parent stays navigable. Flat menus are untouched.
Local search becomes a Command Palette with three modes: an empty query offering
quick links and page actions, a text query returning grouped page results, and a
> prefix that searches commands only. Pages can contribute search_keywords,
a positive search_boost, and canonical exclusion; ranking applies the same
boost on both the Lunr and CJK substring paths.
Page actions and palette commands now run through one shared registry, so Copy
text, Open in ChatGPT, Open in Claude, View source, View edit history, printing,
and switching theme, language, or version behave identically wherever they are
invoked. Assistant prompts resolve the browser URL at activation time,
preserving the deployed host, query string, and fragment; history links derive
from the same repository path as Edit this page. Assistant handoff links are
disabled by default; sites must opt in with
params.ui.page_context_menu.assistant_links: true. On activation, the full URL
leaves the site, so do not place secrets in its query or fragment.
Press / outside an editable control to open the Palette directly in command
mode. Cmd/Ctrl-K remains the general entry point, and the single-character
shortcut yields to inputs, textareas, selects, and contenteditable regions.
Sidebars gain an icon-density policy — all, groups, or none. The
compatibility default stays all; the starter example opts into groups.
See the migration reference for the complete configuration surface.
Typography presets
Font choices move behind seven semantic --td-*-font-family roles covering UI,
body, headings, code, display text, metadata, and print. Two validated presets
ship: technical, which preserves the current Oink appearance, and system,
which uses the platform stack and requests no Oink brand fonts at all. Existing
Docsy and Bootstrap Sass font variables seed the roles, so prior overrides keep
working.
This is the typography slice of a larger design-token effort. Colour, surface, radius, density, and appearance presets are not part of this release.
See Typography tokens.
A lighter page
jQuery is gone. It was previously fetched render-blocking in <head> on every
page — 87.5 KB before any content — while the theme’s own architecture loads
feature runtimes only on pages that use them. Nothing in the shell needed it,
and the superseded offline-search.js runtime it powered was already replaced
by the Command Palette.
Two other costs were removed rather than accepted. The active output format is now read from the page store instead of being re-derived thousands of times per build, and the shell configuration is cached per language; on a 576-page build that is 357ms of template time reduced to 72ms, with byte-identical output. CJK search folds its index fields once instead of re-lowercasing the entire corpus on every keystroke, taking an 800-document query from 3.44ms to 0.34ms per character typed.
On the measured project-site snapshot, removing jQuery and the superseded search runtime saved about 88 KB from a typical documentation page’s combined CSS and JavaScript. Exact totals vary as later candidate assets change.
Correctness and localization
This release also closes several less visible correctness gaps. Markdown pages
link to llms.txt only when the active language actually publishes one, and the
index no longer treats off-site menu chrome as content. Internal configured
commands stay under a subpath deployment, while shared content types resolve to
the right product root. The archived-version banner and Giscus fallback are now
localized, and print or Markdown output strips interaction-only Image Zoom
attributes regardless of quoting style. Legacy search links also percent-encode
their query text instead of truncating a query at &.
Browser runtime tests now run in theme CI instead of relying on Hugo bundling as their only signal. Terminal recordings also wait for their configured font before fitting the player, avoiding geometry based on a fallback font.
Breaking changes
jQuery is no longer loaded. The third-party inventory previously listed it
as part of the UI foundation, so a consuming site’s own scripts may rely on the
global $. No theme feature requires it. Sites that need it must now bundle it
through project JavaScript:
static/js/tabpane-persist.js is removed. assets/js/code-tabs.js took
over the legacy persistence contract, keeping the td-tp-persist storage key
and data attribute, so authored tab content is unaffected. Only a site that
referenced the published file path directly needs to drop that reference.
Body and heading typography roles apply directly to content. A site that
previously restyled raw body or heading selectors should move to the matching
--td-*-font-family role or the established Sass variable:
Upgrade to 0.3.0
- Check whether any project JavaScript depends on the global
$, and bundle jQuery yourself if so. - Remove any direct reference to
static/js/tabpane-persist.js; authoredtabpanecontent itself does not change. - Move raw
bodyor heading font overrides to the typography roles. - Decide whether to opt into assistant handoff links. If enabled, review URLs for sensitive query or fragment data and disclose the third-party boundary.
- Update the Hugo Module and tidy the module graph.
- Build and inspect representative documentation, blog, mobile, print, and color-mode pages.
No Markdown content rewrite is required. Existing fenced code blocks, tabpane
content, flat menus, shortcodes, and ordinary Docsy-compatible pages continue to
work unchanged.
Compatibility
| Contract | Oink 0.3.0 |
|---|---|
| Hugo | Extended 0.160.1 or newer; unchanged |
| Module path | github.com/pgsty/oink; unchanged |
| Consumer frontend toolchain | None; unchanged |
| Required content migration | None |
| Required configuration migration | None; assistant links are opt-in |
| Required project-JS migration | Only if it depends on global jQuery |
Verification
The 0.3.0 candidate is exercised through the sibling Oink project site, so the site builds against the candidate theme rather than its last pinned release. Before publication, the theme gate must pass the complete contract suite, a warning-free example-site build on the minimum and current Hugo versions, both typography presets, and the browser runtime unit tests. The site gate must pass formatting, bilingual page pairs and stable heading IDs, rendered Markdown and internal links, Hugo Module fixtures, alternate-configuration builds, Markdown and favicon goldens, responsive and component browser behaviour, and axe accessibility checks. The tag, public-module resolution, site version pin, and hosted smoke tests remain separate post-approval gates.
Full change set
See the complete source diff from v0.2.1 to v0.3.0.
11 - Oink 0.2.0 — Richer content and sharper presentation
Oink 0.2.0 focuses on the parts readers and authors touch most: the homepage, brand presentation, blog discovery, section indexes, and instructional content. It also turns the Oink project site into a clearer bilingual reference for the theme’s current contracts.
The module path, minimum Hugo version, and Hugo-only consumer build remain unchanged. The one configuration rename that can affect an existing site is documented in Breaking change.
Release highlights
Homepage and brand
The homepage now composes 12 built-in section types from an ordered sections
list. A string selects data with the same name; a map can reuse a presentation
through a different key, disable a block without deleting its data, or carry a
small one-off block inline. Sites without sections retain the 0.1.x homepage
order, so explicit composition is additive rather than a required migration.
The data-driven homepage can now place responsive artwork beside the Hero. Authors may configure one shared image or separate Light and Dark sources, plus meaningful alternative text when the artwork carries information. The layout adapts from a two-column desktop Hero to a compact mobile presentation without requiring a site-level template override.
Oink also adds params.wordmark. A configured wordmark is used consistently in
the landing navigation, documentation header, drawer, and footer; sites that
only configure params.logo keep the existing mark-plus-title presentation.
Component boards on the homepage can become real navigation. Items accept links, optional external-link behavior, compact styling, and one to four columns. Decorative boards remain non-interactive, preserving the 0.1.0 contract.
See Homepage and footer for the complete data shape.
Blog and release publishing
Blog rows now treat images and summaries as one responsive layout. Featured images no longer force the text outside tablet-width containers, summaries can break long machine-generated tokens, and posts without images use the full text width. The byline’s section name is now a link, and RSS moves into the same action rail used by the rest of the page.
Categories and tags use the same collapsible group grammar as the TOC and page actions. Terms render as scan-friendly rows with count badges in both the wide rail and the mobile drawer. Section indexes are quieter, descriptions have more room, and last-modified metadata follows the child-page index instead of interrupting the page introduction.
The Oink project site now separates upstream Docsy history, Oink engineering articles, and versioned Oink release notes into distinct bilingual sections. That makes release reports discoverable without presenting inherited Docsy posts as Oink releases.
Content components
0.2.0 adds a Markdown-first steps shortcode. Direct child headings become
automatically numbered steps connected by a guide line; moving, adding, or
removing a step updates the visible sequence without maintaining numbers by
hand. Authors can mark a supporting heading with class="no-step-marker" so it
does not consume a number.
Asciinema recordings gain a polished terminal frame, title bar, compact control bar, color-mode-aware styling, and a font contract passed directly into the player. This avoids the player falling back to a different terminal font while keeping recordings responsive and readable in both themes.
ECharts callback blocks keep the established trusted-author model: callback code is executable content that must be reviewed like inline HTML or another custom integration. The renderer no longer emits a redundant warning for every reviewed callback block.
See Shortcodes for the new steps contract and Oink components for the broader component model.
Documentation and tests
The independent project site receives a matching documentation pass:
- Expands English and Chinese homepage and component examples.
- Documents all 12 composable homepage sections and uses the relevant ones on the project landing page.
- Adds a real Asciinema installation recording and a dedicated giscus guide.
- Moves examples under the documentation tree and removes obsolete community and maintainer-only pages.
- Consolidates Hugo configuration into the root
hugo.ymland retires the old Netlify-specific tooling. - Isolates browser tests from live reload and keeps responsive, accessibility, translation, rendered-Markdown, and link checks in the release gate.
These are project-site changes, not new runtime dependencies for theme consumers.
Breaking change
0.2.0 renames the inherited featured-image setting from default_featured_image
to default_featured. Update page, section cascade, and site-level
configuration where the old key appears:
The implicit theme placeholder is also removed. If no post image, matching page
resource, or explicit default_featured exists, Oink now renders a clean
text-only list entry. Set default_featured to a site-owned image when a whole
section should keep a visual identity; set it to false to make the opt-out
explicit.
There is no compatibility alias for the old key. This is the only required configuration migration in 0.2.0.
Upgrade to 0.2.0
- Replace every
default_featured_imagesetting withdefault_featured. - Update the Hugo Module and tidy the module graph.
- Build the site and inspect representative homepage, blog, documentation, mobile, and color-mode pages.
No Markdown content rewrite is required. Existing homepage sections, logo-only branding, shortcodes, and ordinary Docsy-compatible pages continue to work.
Compatibility
| Contract | Oink 0.2.0 |
|---|---|
| Hugo | Extended 0.160.1 or newer; unchanged |
| Module path | github.com/pgsty/oink; unchanged |
| Consumer frontend toolchain | None; unchanged |
| Required content migration | None |
| Required configuration migration | Rename default_featured_image |
Verification
The 0.2.0 candidate is exercised through the sibling Oink project site so the site builds against the candidate theme rather than only its last pinned release. The release gate covers formatting, bilingual page pairs and stable heading IDs, rendered Markdown and internal links, Hugo Module fixtures, responsive browser behavior, and axe accessibility checks.
Full change set
See the complete source diff from v0.1.0 to v0.2.0.
12 - Oink 0.1.0 — A stable local-first foundation
Oink 0.1.0 is the first stable release of the Oink theme. It incorporates the 0.0.1 implementation preview and the stabilization work that followed: one coherent documentation shell, a Hugo-only consumer build, local-first browser assets, multilingual behavior derived from Hugo, and reusable content components.
This release keeps the module path github.com/pgsty/oink and requires Hugo
Extended 0.160.1 or newer. A consuming site does not need Node.js, npm, PostCSS,
Autoprefixer, or a CDN to build and serve theme-owned functionality.
Release highlights
Local-first theme foundation
Oink ships the styles, fonts, icons, local search, diagrams, API documentation runtimes, and content-component runtimes that it owns. Optional assets load only on pages that use them, and the distributable repository is a root Hugo Module rather than an embedded project site or frontend workspace.
The release also establishes the core product contracts:
- Hugo languages and translation objects drive language routes, switching,
hreflang, writing direction, and locale metadata. - The theme supports single-language, multilingual, and RTL sites without PGSTY-specific domain assumptions.
- Asciinema, ECharts, Infographic, diagrams, API references, tabs, cards, and other reusable components share local, page-scoped runtimes.
- GitHub Discussions comments are available through an opt-in giscus integration. No external comments script loads unless a site enables it.
- Docsy-compatible content organization, menus, taxonomies, print outputs, and extension hooks remain available.
Responsive shell
The documentation, blog, and API-reference layouts now use one responsive shell. Desktop navigation, the resizable sidebar, the table of contents (TOC), page actions, taxonomies, version selection, and the footer follow the same visual and interaction grammar.
On tablets and phones, Oink moves the TOC, page actions, categories, and tags into the navigation drawer instead of rendering a second copy. This preserves unique IDs and keeps scroll tracking, disclosures, and copy actions working across live viewport changes. Language and color controls remain reachable at every width, and the color selector exposes Auto, Light, and Dark as explicit preferences.
Navigation entries gain consistent icons, the mobile menu traps keyboard focus, footer columns fill the available width, and the compact page-action menu no longer duplicates the right rail. Copy Markdown, view Markdown, edit, issue, and print actions now come from one implementation.
Publishing and content
Syntax highlighting now uses class-based Chroma output with coordinated light
and dark palettes. Code remains readable before JavaScript initializes a color
mode, and sites can still opt out with params.highlight_classes: false.
Blog lists gain a deterministic featured-image resolver. In 0.1.0, it checks
front matter images, a matching page resource, inherited
default_featured_image, site parameters, and finally the theme placeholder.
The same resolver serves both modern blog rows and the compatible legacy
partial.
The new ink-mark logo and the placeholder artwork respond correctly to all four
system-theme and selected-theme combinations. Oink now declares the used
color-scheme in both directions, so an explicit site preference wins over the
operating-system preference.
Accessibility and correctness
0.1.0 fixes a set of issues found during desktop, mobile, print, and assistive technology review:
- Corrects heading order, landmark names, task-list labels, and printable list semantics.
- Keeps blog rows inside the viewport at tablet widths and lets long URLs or identifiers wrap safely.
- Builds GitHub issue links with correctly encoded titles and URLs.
- Localizes the 404 page and removes hard-coded punctuation from translated accessible names.
- Gives iframe embeds titles and lazy loading, registers their resize helper once, and safely handles cross-origin frames.
- Emits one
contentinfolandmark per page and keeps theme extension partials and opt-in SCSS entry points available to consumer sites.
The compatibility audit also removes genuinely unreachable legacy shell code
while restoring files that downstream sites can import directly. Reachability is
evaluated from consumer layouts and _styles_project.scss, not only from the
theme’s own entry points.
Upgrade to 0.1.0
Update the Hugo Module and rebuild the site:
No content migration is required. If a site imports Oink partials or SCSS directly, build that site as part of the upgrade so its customization surface is checked alongside the theme.
Compatibility
| Contract | Oink 0.1.0 |
|---|---|
| Hugo | Extended 0.160.1 or newer |
| Module path | github.com/pgsty/oink |
| Consumer frontend toolchain | None |
| Default browser dependencies | Local-first |
| Primary content model | Docsy-compatible Markdown and front matter |
Verification
The final 0.1.0 candidate was swept against the theme fixture and the Oink project site across seven viewport widths. The recorded run reported no console errors, failed requests, horizontal overflow, or axe violations. Separate fixtures cover minimum and current Hugo versions, LTR and RTL languages, subpaths, print output, repeated component instances, and network-isolated consumer builds.
Full change set
See the v0.1.0 source snapshot.