Oink 0.3.0 — Authoring, navigation, and a lighter page

Oink 0.3.0 adds enhanced code blocks and code groups, everyday content primitives, nested navigation with a Command Palette, semantic typography presets, and removes jQuery from every page.

Release date: 2026-08-12 · Theme tag: v0.3.0 · Repository: pgsty/oink

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.

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:

HTML
<!-- layouts/_partials/hooks/head-end.html -->
<script src="{{ (resources.Get "js/jquery.min.js").RelPermalink }}"></script>

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:

SCSS
// Before
body {
  font-family: 'My Sans', sans-serif;
}

// Oink 0.3.0
:root {
  --td-body-font-family: 'My Sans', sans-serif;
}

Upgrade to 0.3.0

  1. Check whether any project JavaScript depends on the global $, and bundle jQuery yourself if so.
  2. Remove any direct reference to static/js/tabpane-persist.js; authored tabpane content itself does not change.
  3. Move raw body or heading font overrides to the typography roles.
  4. Decide whether to opt into assistant handoff links. If enabled, review URLs for sensitive query or fragment data and disclose the third-party boundary.
  5. Update the Hugo Module and tidy the module graph.
  6. Build and inspect representative documentation, blog, mobile, print, and color-mode pages.
BASH
hugo mod get github.com/pgsty/[email protected]
hugo mod tidy
hugo --gc --minify

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.