# Oink 0.6.0 — Immersive blogs, safer builds, smaller internals

> Oink 0.6.0 adds an immersive presentation to the existing Blog shell, completes Blog publishing with images, authors, series, three index forms and sharing, and replaces build-stopping template errors with safe warnings.

---

LLMS index: [llms.txt](/llms.txt)

---

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.

**v0\.6\.0 · 2026-08-20**
- [View release](https://github.com/pgsty/oink/releases/tag/v0.6.0)
- [Source · tar\.gz](https://github.com/pgsty/oink/archive/refs/tags/v0.6.0.tar.gz)
- [Source · zip](https://github.com/pgsty/oink/archive/refs/tags/v0.6.0.zip)
- [pgsty\/oink](https://github.com/pgsty/oink)

## At a glance {#at-a-glance}

- The Blog shell gains a full-bleed `hero` image 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 {#article-shell}

An immersive page uses four independent front matter keys:

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

Put the same keys in a section cascade to apply them to its posts. A section
index that should share the presentation also declares them on its own page,
because a cascade does not affect the page that owns it.

`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 {#blog}

### Featured images {#featured-image}

`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 {#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 {#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 {#blog-index}

| 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 {#share}

`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 {#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 {#warn-not-stop}

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:

1. warn with the bad value and the safe fallback or omission;
2. do not emit unsafe or misleading output;
3. let an ordinary `hugo server` continue;
4. let `--panicOnWarning` stop CI and publication.

This preserves strict gates without letting one typo take every preview URL
down.

## Outline rail {#outline}

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 {#fixed}

- Mounted content no longer publishes build-machine paths in Edit, History, or
  Create Child URLs.
- Generic `data-*` and `aria-*` 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 {#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 {#migration}

From 0.5:

1. Keep explicit prose date formats if ISO dates are not desired.
2. Ensure publishing commands use `--panicOnWarning`.
3. Replace the old `release` map with
   `release_url: https://github.com/<owner>/<repo>/releases/tag/<tag>`.
4. Replace `upstream_attribution` with `upstream_link`, and
   `downstream_modified` with `upstream_modified`.
5. 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 {#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 {#full-change-set}

[v0.5.0 to v0.6.0](https://github.com/pgsty/oink/compare/v0.5.0...v0.6.0)
