Peios Learn
Products
PePeios pkpekit PvProvium UDUniversal Directory TrTrail PrProject WiWispist
Using Peios Security Basics Technical Documentation Source
Using Peios Security Basics Technical Documentation Source
Trail

Reference

Single-page view · as markdown

Directory and file names

Trail / Reference

Trail derives the whole site from the tree, so every name in it is meaningful and every unrecognised name is a build error. This page is the exhaustive grammar. Anatomy of a site is the same material as a walkthrough.

The name shapes #

ShapeExampleWhat
<slug>.productpekit.productA product. Site root only; no order prefix.
<order>--<slug>.antho1--security.anthoAn anthology.
<order>--<slug>.topic4--reference.topicA topic.
<order>--<slug>.book2--pgss.bookA book.
<order>--<slug>.shelf3--tooling.shelfA shelf.
<order>--<slug>300--tokensA topic subfolder or a book chapter. No suffix.
<order>--<slug>.md100--sids.mdAn article.
<order>--<slug>.link3--faq.linkA link alias.
<slug>.<ext>logon-flow.svgA co-located image. No order prefix.
trail.tomlConfiguration, at every level.

<order> #

A non-negative integer, parsed as written. 1, 100 and 007 are all valid and all mean their numeric value.

  • It is a sort key only — never shown, never part of a URL — except inside a book, where it is the published section number.
  • Gaps are fine and normal.
  • Two siblings sharing an order is an error, since their relative order would then depend on nothing visible.
  • Anything not parseable as an integer is an error: no 1.5, no -1, no first.

a<N> — appendix orders #

Inside a book only. An order written a1, a2, … marks an appendix entry, which is lettered rather than numbered and sorts after every numbered sibling at its level.

  • Letters are bijective base 26: a1 → A, a26 → Z, a27 → AA.
  • The letter comes from the number, not from position, so gaps show through here too.
  • a0 is an error: appendix orders start at a1.
  • Outside a book, an a<N>-- prefix is just a non-numeric order prefix, and therefore an error.

<slug> #

Lowercase kebab-case:

ASCII lowercase letters, digits and hyphens
not empty
not starting or ending with a hyphen

getting-started, pgss, rfc-2119 are fine. Getting_Started, -draft, tokens/, café are not.

The slug is the URL segment, and it is what you write in a ~link.

<kind> #

Exactly one of antho, topic, book, shelf. Any other suffix is an error, as is a grouping directory with no suffix at all in a place where one is expected.

What each directory may contain #

Anything not listed is a build error. Entries whose name starts with . are skipped everywhere, so .git, .DS_Store and editor droppings are invisible to trail.

DirectorySubdirectoriesFiles
Site root*.product, the output dirtrail.toml; whatever favicon, custom_css, head_html and passthrough name
Product.antho, .topic, .book, .shelftrail.toml
Anthology.antho, .topic, .book, .shelftrail.toml
Shelf.antho, .topic, .book — never another .shelftrail.toml
Topic<order>--<slug> subfolderstrail.toml, .md, .link, images
Topic subfolder(none — one level only)trail.toml, .md, .link, images
Book<order>--<slug> chapterstrail.toml, .md, .link, images
Book chapter<order>--<slug> chapters, to any depthtrail.toml, .md, .link, images

The dividing line: directories that hold groupings hold no files but trail.toml; directories that hold articles hold no grouping directories.

trail.toml by level #

LevelRequired?Keys
Site rootrequiredsee the trail.toml reference
Productrequiredtitle, monogram, color, description, inline_ref
Anthologyrequiredtitle, description, inline_ref
Bookrequiredtitle, description, short, inline_ref
Topicoptionaltitle, inline_ref
Topic subfolderoptionaltitle, inline_ref
Book chapteroptionaltitle, inline_ref
Shelfoptionaltitle only — inline_ref is an error

Unknown keys are errors at every level. Where title is optional and absent, it is derived from the slug: hyphens to spaces, each word capitalised (the-two-gates → "The Two Gates").

Reserved names #

NameWhereWhy
printarticle slugsevery container publishes a /print page
assetsproduct slugscollides with /assets in the output
pagefindproduct slugscollides with /pagefind in the output

Images #

A co-located image is <slug>.<ext> where the stem is a valid slug and the extension is lowercase and one of:

png   jpg   jpeg   gif   svg   webp   avif

Anything else in an article directory falls through to the "unexpected file" error.

Sorting #

Siblings sort by order, ascending. Inside a book, appendix entries sort after every numbered entry at the same level, whatever their numbers.

Products are the exception: no order prefix, so they sort by the root config's featured list first (in that list's order), then the rest by title.

Duplicates #

Within one container, both order and slug must be unique:

items 'concepts' and 'guides' share order 1
duplicate article slug 'overview'

The noun is item for a container's mixed children (topics, books, anthologies, shelves), and article inside a topic subfolder or where only articles can sit. Slugs are reported without their order prefixes.

Shelf contents are checked flattened into their parent, because a shelf adds no URL segment — two shelves in one product cannot both hold a getting-started topic, since both would want the same URL.

URLs #

A page's URL is the product slug, then the slug of every container between it and the page, with two exceptions: shelves contribute nothing, and order prefixes contribute nothing.

peios.product/1--security.antho/2--tokens.topic/300--issuing/100--flow.md
  →  /peios/security/tokens/issuing/flow

Pages are written to disk as <path>/index.html.

trail.toml reference

Trail / Reference

trail.toml appears at every level of the tree, with a different schema at each. Unknown keys are build errors everywhere, so a typo fails loudly instead of doing nothing.

The site root #

Required at the site root. Four keys are mandatory.

KeyTypeDefault
sitenamestringrequiredThe site's identity: header wordmark (last word accented), <title> suffix, search placeholder, og:site_name.
titlestringrequiredThe front page's headline; its og:title and the base for its meta description.
descriptionstringrequiredThe front page's standfirst and meta description; the first line of llms.txt.
footerstringrequiredThe line at the foot of every page.
urlstringnoneThe site's public base URL, e.g. https://learn.peios.org. Trailing slashes are trimmed.
featuredarray of strings[]Product slugs, in order. Sorts products, and selects which appear on the front page.
accent#rrggbbtheme defaultThe site accent colour.
accent_dark#rrggbbderivedThe dark-mode accent. Derived by mixing accent 75% toward white when absent.
custom_csspathnoneA stylesheet shipped at /assets/custom.css, loaded after the built-in one.
faviconpathnoneShipped at the output root under its own name, linked from every page.
head_htmlpathnoneA file injected verbatim at the end of every page's <head>.
edit_urlstringnone"Edit this page" URL template. Must contain {path}.
navarray of tables[]Header links. See below.
passthrougharray of paths[]Root entries copied into the output verbatim. See below.
product_themingbooltrueWhether each product tints its own pages.
built_by_trailbooltrueWhether "Built with Trail." appears under the footer.
sitename = "Peios Learn"
title = "Documentation for everything Peios"
description = "Concepts, guides, and reference for the Peios operating system…"
url = "https://learn.peios.org"
accent = "#3b82f6"
featured = ["peios", "pekit", "provium", "universal-directory", "trail"]
footer = "Peios Learn — documentation for the Peios project."

[[nav]]
label = "Specs"
url = "~peios/pgss"

Validation #

  • accent and accent_dark must be #rrggbb. accent_dark without accent is an error.
  • custom_css, favicon and head_html are paths relative to the site root, and the file must exist. Naming one is also what makes it legal to keep in the site root; the first path component is what is tolerated there.
  • edit_url must contain the literal {path}, which is replaced with the article's source path relative to the site root.
  • Every slug in featured must name an existing product.

passthrough #

Root entries trail neither builds nor understands, copied into the output as they are:

passthrough = ["CNAME", ".well-known", "ads.txt"]

Each entry is a plain relative path naming a file or a directory that exists in the site root; directories are copied whole, nesting included. Naming an entry is also what makes it legal to keep in the site root, exactly as favicon and friends do — so this is how a repository that is also a trail site keeps its CNAME beside its trail.toml.

Errors: an entry that is empty, absolute, or contains ..; one naming something that does not exist; one naming the build output directory.

Passthrough entries are copied last, after everything trail generates, so an entry deliberately named after a generated file replaces it. Naming robots.txt is a supported way to ship your own; naming assets would bury the stylesheet, and trail will not stop you.

[[nav]] #

KeyType
labelstringrequired. The link text.
urlstringrequired. An external URL, a root-relative path, or a ~ page reference.

References here are resolved strictly at load time — a broken nav link fails the build even under --allow-dangling-links, because site chrome appears on every page.

Important

[[nav]] blocks must come last in the file. TOML gives every key after a table header to that table, so a footer = written below them is read as a nav item's field and fails with unknown field 'footer', expected 'label' or 'url'. Put every plain key above the first [[nav]].

A shelf is not a link target, so a nav entry cannot point at one — aim at the anthology above it, or a page inside it.

What url unlocks #

Without it: no sitemap.xml, no Sitemap: line in robots.txt, no og:url, relative canonical URLs, and relative outward links in /print bundles. Set it before deploying.

A product #

Required in every <slug>.product directory.

KeyType
titlestringrequired. The product's display name.
monogramstringrequired. The short string on the product's tile.
color#rrggbbrequired. The product accent.
descriptionstringrequired. One sentence: the card text, the page standfirst, the llms.txt entry.
inline_refarray of stringsPhrases linking to the product's landing page.
title = "pekit"
monogram = "pk"
color = "#22c55e"
description = "The recipe-driven build and packaging tool."
inline_ref = ["pekit"]

An anthology #

Required in every .antho directory.

KeyType
titlestringrequired.
descriptionstringrequired. The card text, the page standfirst, the llms.txt entry.
inline_refarray of stringsPhrases linking to the anthology's landing page.

A book #

Required in every .book directory.

KeyType
titlestringrequired.
descriptionstringrequired.
shortstringAn abbreviation used where the full title will not fit: the sidebar heading, the card pill, the line above the cover title.
inline_refarray of stringsPhrases linking to the book's cover. These may carry a § section suffix in prose.
title = "Peios Generic System Standards"
short = "PGSS"
description = "Cross-platform system standards from the Peios Project"
inline_ref = ["PGSS", "Peios Generic System Standards"]

A topic, subfolder or chapter #

Optional. Both keys are optional.

KeyType
titlestringOverrides the slug-derived title.
inline_refarray of stringsPhrases linking to the container's first article. Declaring them on a container with no articles is an error.
title = "Reference"

A shelf #

Optional, and takes title only.

KeyType
titlestringOverrides the slug-derived title.

inline_ref on a shelf is an error. A shelf has no page and no articles of its own, so the phrase would have nowhere to land — declare it on the shelf's items instead.

Derived titles #

Wherever title is optional and absent, it comes from the slug: hyphens become spaces and each word is capitalised. 2--writing-tests.topic becomes "Writing tests"; the-two-gates becomes "The Two Gates". Write the key when that is wrong — acronyms, proper nouns, unusual casing.

inline_ref everywhere #

The key means the same thing at every level: a list of phrases that should become links to this thing wherever they appear in the site's prose. Phrases are exclusive site-wide — two declarers claiming one phrase is an error — and must not be empty, carry leading or trailing whitespace, or contain §. See Inline references.

Frontmatter reference

Trail / Reference

Every article begins with a YAML frontmatter block: a line of exactly ---, the keys, and a closing ---. It must be the first thing in the file. Unknown keys are build errors.

---
title: Command-line reference
type: reference
description: Every pekit flag with its value form, the capability matrix, and exit codes.
related:
  - pekit/running/invocation
  - pekit/reference/recipe-format
inline_ref:
  - pekit CLI
updated: 2026-05-01
reading_minutes: 25
---

The keys #

KeyTypeTopic articleBook article
titlestringrequiredrequired
typestringrequirederror
descriptionstringoptionaloptional
relatedarray of stringsoptionaloptional
inline_refarray of stringsoptionaloptional
updatedISO dateoptionaloptional
reading_minutesintegeroptionaloptional

title #

The page's name: its h1, breadcrumb, sidebar entry, search-result heading and og:title. Never derived — an article always states its own.

type #

The learn taxonomy. Required on articles in topics; an error on articles in books, where the question it answers is already settled by the document's order.

Trail does not constrain the value and does not display it on the page. It is carried into site.json as the article's type. This site uses concept, how-to and reference.

description #

One sentence. It feeds:

  • <meta name="description"> and og:description;
  • the standfirst line of the page's markdown mirror;
  • the description field in site.json.

It is not shown on the page, and it is not the in-site search snippet — Pagefind builds those from body text.

--strict fails the build on any article without one. Alias pages are exempt.

related #

Page references in the ~ grammar, without the leading ~:

related:
  - pekit/running/invocation
  - peios/package-management/overview

They render as a "Related Content" list under the article, appear in the markdown mirror, and become a list of resolved paths in site.json.

Resolution is as strict as a body link: ambiguous references always fail the build; missing ones fail unless --allow-dangling-links downgrades them to a warning, in which case they are dropped from the list.

inline_ref #

Phrases that should link to this article wherever they appear in the site's prose:

inline_ref:
  - PCDS GUID
  - PCDS GUIDs

Matching is literal, whole-word and longest-first, and skips headings, links, image alt text, code, and the claiming page itself. A phrase belongs to exactly one declarer site-wide; a collision is a build error, as is an empty phrase, one with leading or trailing whitespace, or one containing §. Alias pages claim nothing. See Inline references.

updated #

An ISO YYYY-MM-DD date, validated as such:

updated: '2026/05/01' is not an ISO date (expected YYYY-MM-DD)

It appears in the page's meta line and becomes the page's <lastmod> in sitemap.xml. Containers take the most recent date beneath them.

Deliberately manual: file modification times are noise, and trail does not read git history.

Tip

Both components must be zero-padded to two digits: 2026-05-01, never 2026-5-1. No quoting is needed — an unquoted date works.

reading_minutes #

An integer overriding the estimate, which is otherwise the body's word count at 200 words a minute, rounded up, with a floor of 1. Code blocks count toward the word count.

Containers sum their children's values, so a book cover reports the whole book.

Errors #

MessageCause
… has no frontmatter (must start with '---')The file does not open with ---.
… has unterminated frontmatterNo closing ---.
parsing frontmatter of …Invalid YAML, a missing required key, or an unknown key.
updated: '…' is not an ISO date (expected YYYY-MM-DD)A malformed date.

Inside a book, type: produces an unknown-key error, since type is not part of the book article schema.

Frontmatter and .link aliases #

An alias page has no frontmatter of its own — it renders the target's body. It takes its title from the .link file (or from its own slug), its canonical URL from the original, and it claims no inline_ref phrases. Inside a book it also loses type and description, as every book entry does.

Command-line reference

Trail / Reference

Trail has two commands, build and serve, plus clap's own help. Everything below is the complete flag surface; there are no others.

Synopsis #

trail build [OPTIONS] [ROOT]
trail serve [OPTIONS] [ROOT]
trail help  [COMMAND]

trail --version prints the version; trail --help and trail <command> --help print usage.

trail build #

Builds the site into the output directory.

Argument
[ROOT]pathThe site root — the directory holding trail.toml. Default .
FlagValue
--out <DIR>pathWhere the site is written. Default ROOT/dist. Excluded from the site scan, so a build into a directory inside the root is safe.
--allow-dangling-links—Downgrade missing ~link and related: targets, missing images, and unresolvable § citations from errors to warnings. Ambiguous references stay fatal.
--strict—Fail the build when any article has no description:. Alias pages are exempt.
--render-llms-full—Also write each unit's print.md as llms-full.txt beside it.
--cbpath <PATH>one path segmentAlso publish a cache-busting copy of the whole site under PATH, with every link inside it rewritten to stay there.

On success:

built 1227 pages (8 products) → ./dist

With --cbpath, a second line names the copy:

built 1227 pages (8 products) → ./dist
cache-busting copy → ./dist/8f3a91c/

The page count is the site's; the copy holds the same number again and is not added in.

The count is HTML pages: articles, product and anthology landings, book covers, /print pages, the front page and 404.html. Markdown mirrors, llms.txt, site.json, the sitemap and the search bundle are not counted.

trail serve #

Builds the site, serves the output over HTTP, and rebuilds on change. Takes every build flag, plus:

FlagValue
--addr <ADDR>socket addressDefault 127.0.0.1:8724.
serving ./dist at http://127.0.0.1:8724 (watching . for changes)
rebuilt 1227 pages

Behaviour:

  • Changes are debounced for 200 ms; changes inside the output directory, and paths with a dot-prefixed component, are ignored.
  • Pages are served with a live-reload script that reloads them over server-sent events after each successful rebuild. trail build output never contains it.
  • Misses serve the built 404.html with a real 404 status.
  • A failed initial build or rebuild prints the error and keeps serving the last good output — the process does not exit.

Exit status and streams #

Exit 0The build succeeded.
Exit non-zeroThe build failed; the error is on stderr.
stdoutThe build summary line, and serve's serving/rebuild lines.
stderrErrors and warnings.

trail serve runs until interrupted.

Warnings #

These print on stderr and do not fail the build:

Warning
image '…' is not referenced by any article and was not publishedThe file stays out of the output.
in article '…': link '~…' matches no page in product '…'Only with --allow-dangling-links.
in article '…' (related): …A related: reference that did not resolve, with --allow-dangling-links.
in article '…': image '…' not found …With --allow-dangling-links.
initial build failed (serving previous output): …serve only.
rebuild failed (still serving the last good build): …serve only.

Errors #

Loading errors stop at the first problem, with the chain of contexts:

Error: loading product 'pekit'

Caused by:
    0: loading topic in '2--recipes.topic'
    1: in article '100--anatomy.md'
    2: updated: '2026-5-1' is not an ISO date (expected YYYY-MM-DD)

Link errors are collected across the whole site and reported together:

Error: 3 broken links:
in article '/pekit/recipes/anatomy': link '~pekit/running/invokation' matches no page in product 'pekit'
…

The common ones #

Message
unexpected file '…' in site root: only trail.toml is allowedThe root takes trail.toml, *.product/, the output dir, and files named by the config.
unexpected directory '…' in site root: only *.product directories are allowed
unexpected grouping type '.…' on '…' in a product (expected .antho, .book, .topic or .shelf)
a shelf cannot contain another shelf ('…')
grouping directory '…' is missing its '<order>--' prefix
'…' has a non-numeric order prefix '…'
'…' is not a valid slug (lowercase kebab-case)
items '…' and '…' share order NTwo siblings with the same order prefix.
duplicate article slug '…'Also raised for shelf contents flattened into their parent.
chapter '…' contains no articlesA book chapter must lead somewhere.
'print' is a reserved slug (every section has a /print page)
'…' is a reserved product slug (it collides with the '/…' output directory)assets and pagefind.
… has no frontmatter (must start with '---')
… has unterminated frontmatter
parsing frontmatter of …Bad YAML, a missing required key, or an unknown key.
updated: '…' is not an ISO date (expected YYYY-MM-DD)
color '…' is not a #rrggbb hex color
accent_dark without accent: set the base accent color too
edit_url must contain a {path} placeholder …
featured product '…' does not exist (no ….product directory)
favicon names '…', which does not exist in the site rootLikewise custom_css and head_html.
link '~…' is ambiguous; candidates: …Never downgradable.
unknown admonition type '[!…]' (expected NOTE, TIP, IMPORTANT, WARNING or CAUTION)
unclosed ':::tabs' block (close it with ':::')And the other tab-group errors.
inline_ref phrase '…' is claimed by both … and …
'… §…' does not match any section of '…'Downgradable.
image '…': ~references name pages, not files — use a path relative to the article
--cbpath '…' is already a path in the site; the copy would bury it …Checked before anything is built.
--cbpath '…' must be a single path segment, with no '/'Likewise the empty, dot-leading and bad-character forms.

Installing #

Trail is a single Rust binary. Build it from its source directory:

$ cargo build --release

The result is target/release/trail. It carries the templates, stylesheet, fonts, syntax definitions, search engine and diagram renderer inside it, so the binary alone is the whole tool — copy it onto your PATH and nothing else needs installing.

Peios Learn — documentation for the Peios project.

Built with Trail.