# Oink 0.8.0 — A whole section in one fetch, the sidebar as data, and who links here

> Oink 0.8.0 adds two opt-in output formats for readers that arrive as programs — a full-text bundle holding an entire section in one file, and a navigation tree published as JSON — plus static backlinks, which list the pages that link to a page in its right rail. All three stay off until you ask.

---

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

---

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.

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

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

- `LLMSFULL` publishes `llms-full.txt` per top-level section: every page of
  the section, in sidebar reading order, in one file.
- `NAVJSON` publishes `navigation.json` per language: the sidebar tree as
  data, versioned by a JSON Schema.
- `params.ui.backlinks` lists 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.txt` lists whichever ones you enabled, so discovery stays in the file
  an agent already fetches.
- A `data/docs_nav.json` node without a `children` key no longer crashes the
  build.

## The full-text bundle {#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:

```yaml {title="content/docs/_index.md"}
---
title: Docs
outputs: [HTML, print, RSS, markdown, LLMSFULL]
---
```

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](/docs/customize/agents/#full-text-bundle).

## Navigation JSON {#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:

```yaml {title="hugo.yml"}
outputs:
  home: [HTML, LLMS, NAVJSON]
```

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
  `weight` is never serialized — a consumer re-sorting the array would
  disagree with the sidebar the array came from.
- **The format is versioned.** `schemaVersion` is `1`, and the contract ships
  in the theme repository as
  [`schema/nav.v1.schema.json`](https://github.com/pgsty/oink/blob/main/schema/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](/docs/customize/agents/#navigation-json).

## Who links here {#backlinks}

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:

```yaml {title="hugo.yml"}
params:
  ui:
    backlinks: true
```

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](/docs/customize/config/) — lists
more than forty inbound links. Details:
[Backlinks](/docs/customize/navigation/#backlinks).

## Discovery stays in `llms.txt` {#discovery}

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 {#docs-nav-leaf}

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

```bash
hugo mod get github.com/pgsty/oink@v0.8.0
hugo mod tidy
```

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](/docs/customize/agents/); the backlink switch is on
[Navigation and menus](/docs/customize/navigation/#backlinks).

The complete list is in [CHANGELOG.md](https://github.com/pgsty/oink/blob/main/CHANGELOG.md).
