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.
