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

Getting started

Single-page view · as markdown

What is Trail

Trail / Getting started

Trail is a static site generator for documentation. You give it a directory of markdown files arranged into named folders; it gives you a complete website — every page rendered, cross-linked, indexed for search, mirrored as markdown for machines, and bundled into printable single-page views.

This site is built with it. Everything you are reading, including this page, comes out of one trail build.

The one idea: the tree is the site #

Trail has no table-of-contents file, no sidebar.json, no list of pages to keep in sync with the pages themselves. The directory tree is the navigation. A folder named 2--writing.topic is a topic called "Writing" that sorts second; the .md files inside it are its articles, in the order their own numeric prefixes give them.

learn/
├── trail.toml                        # the site
└── trail.product/                    # a product
    ├── trail.toml
    ├── 1--getting-started.topic/     # a topic, sorted first
    │   ├── 100--what-is-trail.md     # an article, sorted first
    │   └── 200--quick-start.md
    └── 2--structuring-a-site.topic/
        └── 100--products.md

Add a file, and the page exists, appears in the sidebar, joins the search index, and gets a markdown mirror. Rename a folder, and the section moves. Nothing else has to be told.

The trade is that names carry meaning. 100--quick-start.md is not a decorative filename: 100 is the sort key and quick-start is the URL segment. Directory and file names is the exhaustive grammar; Anatomy of a site is the guided tour.

Strict by default #

The second principle is that mistakes are build failures, not silent surprises. A documentation site that quietly drops a page is worse than one that refuses to build.

  • A key that trail.toml does not define is an error, not an ignored line.
  • A file trail does not recognise — a stray notes.txt in a topic, a README.md without an order prefix — is an error, not a file left out of the site.
  • A ~link whose target no longer exists fails the build. Every broken link in the site is reported at once, so one run tells you the whole story.
  • An ambiguous ~link — one matching two pages — always fails, even under --allow-dangling-links. Trail will not guess which page you meant.
  • A frontmatter updated: that is not an ISO date, a colour that is not #rrggbb, a .link alias pointing at another alias: all errors.

The escape hatches are deliberate and few. --allow-dangling-links downgrades missing targets to warnings while you are mid-refactor; that is the whole list.

One binary, no runtime #

Trail is a single Rust binary with everything compiled in: the templates, the stylesheet, the fonts and their licences, the search engine, the syntax highlighter, the diagram renderer. There is no theme directory to install beside it, no Node toolchain, no npm install step, and nothing to fetch at build time.

The output is equally plain. dist/ is a directory of static files — HTML, CSS, WOFF2, JSON, a WASM search core — that any static host will serve as-is. There is no server component and no build step at request time.

What one build does #

trail.toml + the tree
  → load          parse every trail.toml and every frontmatter block, validate names
  → resolve       .link aliases, ~link targets, inline-reference phrases, images
  → render        one HTML page per article, product, anthology and book cover
  → export        .md mirrors, /print bundles, llms.txt, site.json, sitemap, robots
  → index         the Pagefind search bundle, built from the rendered pages
  → prune         delete anything in dist/ this build did not write

Loading is where nearly all the errors live: by the time rendering starts, the site model is known to be well-formed. Pruning is why dist/ belongs entirely to trail — a page you deleted from the source disappears from the output on the next successful build, along with superseded search index fragments and stale images.

Builds are fast enough not to think about. This site — over twelve hundred pages — builds from scratch in a little over three seconds.

What a site is made of #

Four kinds of thing, from the outside in:

What it is
SiteOne trail.toml at the root, the front page, and the products.
ProductA documented thing (trail.product/) with its own colour, monogram and landing page.
GroupingHow a product's pages are organised: anthologies, topics, books and shelves.
ArticleOne .md file: YAML frontmatter, then markdown.

The distinction that matters most is topic versus book. A topic is a set of articles you can read in any order — a how-to section, a concept section. A book is a formal, ordered document — a specification, a reference manual — with a cover page, dotted section numbers (2.1.3), chapters that nest arbitrarily deep, and lettered appendices. They render differently because they are read differently.

What trail gives readers #

Beyond the pages themselves, every build produces:

  • Search — a ⌘K modal over a Pagefind index, loaded lazily so pages carry no search cost until someone searches. Results carry the query through to the page, which highlights every match.
  • Three-state theming — light, dark, or follow the system, remembered per reader. Syntax highlighting, diagrams and images all follow it.
  • Print and machine views — every topic, book, anthology and product is also one long page at /print, one markdown file at /print.md, and every individual page is mirrored at its own URL with .md appended.
  • The reading furniture — a scroll-spying "On this page" list, copy buttons on code blocks, heading permalinks, adjustable text size, previous/next links, a mobile drawer, and a back-to-top button.

The reading experience covers all of it; The output surface covers exactly what lands in dist/.

What trail is not #

  • Not a general-purpose SSG. There is no plugin system, no shortcode API, no way to add a page type. The templates are compiled in and are not user-replaceable; theming happens through CSS custom properties and an optional stylesheet.
  • Not versioned. One build produces one version of the site. Hosting several versions means several builds at several paths.
  • Not incremental. Every build renders every page. It is fast enough that this has never mattered.
  • Not a subpath host. Sites are served from the root of a domain; internal links are root-relative.

Where to start #

If you want a working site in five minutes, go to the Quick start.

If you want to understand the layout before writing anything, read Anatomy of a site, then the Structuring a site section in order.

If you are writing pages in a site that already exists, Writing is the section you want — frontmatter, links, images, code blocks and the rest.

Quick start

Trail / Getting started

This page takes you from an empty directory to a served, searchable documentation site in about five minutes. You will write four files, run trail build, look at what it produced, then switch to trail serve and add a second page while it is running.

Prerequisites #

  • A trail binary. Trail is a single Rust binary; build it from its source directory with cargo build --release and the result is target/release/trail. Put it on your PATH, or call it by path — the examples below assume trail is on your PATH.
  • Nothing else. The templates, stylesheet, fonts, syntax highlighter, diagram renderer and search engine are all compiled into the binary.

Create the site #

A trail site is a directory with a trail.toml in it. Make one:

$ mkdir acme-docs && cd acme-docs
# acme-docs/trail.toml
sitename = "Acme Docs"
title = "Documentation for Acme"
description = "Guides and reference for the Acme toolchain."
footer = "Acme — docs."

Those four keys are the only required ones. sitename is the wordmark in the header (its last word is accented); title and description are the front page's headline and standfirst, and feed the site's <meta> tags. Everything else — the accent colour, the base URL, header links, a favicon — is optional and covered in Configuring the site.

Note

The site root may contain only trail.toml, *.product directories, the build output directory, and any files named by the config (a favicon, a custom stylesheet). Anything else is a build error. This is deliberate: a stray file in the root is far more likely to be a mistake than a plan.

Add a product #

Every page belongs to a product — a documented thing with its own landing page, colour and monogram. Products are directories named <slug>.product:

$ mkdir -p docs.product
# acme-docs/docs.product/trail.toml
title = "Acme CLI"
monogram = "ac"
color = "#3b82f6"
description = "The command-line interface to Acme."

All four keys are required. The directory name gives the URL (/docs) and the slug you link by; title is what readers see. The monogram is the two-or-so characters shown on the product's tile on the front page, and color tints the product's cards, sidebar and headings.

Add a topic and an article #

A topic is a set of articles read in any order. Topics are directories named <order>--<slug>.topic:

$ mkdir -p docs.product/1--guides.topic
<!-- acme-docs/docs.product/1--guides.topic/100--hello.md -->
---
title: Hello
type: how-to
description: The first page.
---

Hello from **Acme**.

Three things are going on in that filename and that frontmatter block:

  • 1--guides.topic — the 1 sorts this topic among its siblings and never appears in a URL; guides is the URL segment; .topic is the kind. With no trail.toml of its own the topic's title is derived from the slug ("Guides").
  • 100--hello.md — same grammar for articles. Numbering in hundreds is a convention, not a rule: it leaves room to insert 150-- later without renumbering.
  • title: and type: are the two required frontmatter keys. description: is optional but worth writing — it becomes the page's meta description and its social-preview text.

Build it #

$ trail build
built 6 pages (1 products) → ./dist

Six pages from one article, because trail writes more than the pages you authored:

$ find dist -type f -not -path 'dist/assets/*' -not -path 'dist/pagefind/*'
dist/index.html                     the front page
dist/404.html                       the not-found page
dist/docs/index.html                the product landing page
dist/docs/guides/hello/index.html   your article
dist/docs/print/index.html          all of Acme CLI on one page
dist/docs/guides/print/index.html   all of Guides on one page
dist/docs.md                        the product, as markdown
dist/docs/guides/hello.md           your article, as markdown
dist/docs/print.md                  all of Acme CLI, as one markdown file
dist/docs/guides/print.md           all of Guides, as one markdown file
dist/llms.txt                       the site's map for language models
dist/site.json                      the site's structure, machine-readable
dist/robots.txt

Plus dist/assets/ (the stylesheet and fonts) and dist/pagefind/ (the search index and its WASM core). Note that there is no page at /docs/guides — a topic has no page of its own. It shows up as a card on the product page, and links to it land on its first article.

dist/ belongs to trail: at the end of every successful build it deletes anything in there that the build did not write.

Serve it #

$ trail serve
serving ./dist at http://127.0.0.1:8724 (watching . for changes)

trail serve builds the site, serves it, and watches the source tree. Save a file and the browser reloads itself; break something and the error prints in the terminal while the last good output keeps being served.

Open the site and try it: press ⌘K (or Ctrl K) to search, and use the theme button in the header to switch between light, dark and system.

Add a second page #

Leave the server running and add another article beside the first:

<!-- acme-docs/docs.product/1--guides.topic/200--installing.md -->
---
title: Installing
type: how-to
description: How to install Acme.
related:
  - docs/guides/hello
---

See [Hello](~docs/guides/hello) first.

Two new things:

  • ~docs/guides/hello is a page reference, not a URL. The first segment names a product; the rest matches the end of a page's path. Move or rename the target and the link still resolves as long as its slug is unchanged; delete it and the build fails rather than shipping a dead link. See Links and references.
  • related: takes the same references (without the ~) and renders a "Related content" list at the foot of the page.

Save, and the page appears in the sidebar next to Hello.

Where to go next #

  • Anatomy of a site — the whole tree, level by level, with a real example.
  • Products — the structuring section in order: products, anthologies, topics, books.
  • Articles and frontmatter — every frontmatter key and what it does.
  • Configuring the site — the base URL, accent colour, navigation links, edit links and the rest of trail.toml.

Anatomy of a site

Trail / Getting started

A trail site is a directory tree in which every name means something. This page walks that tree from the root down, and explains the naming grammar all of it shares. Directory and file names is the same material as an exhaustive table; Structuring a site covers each kind of container in depth.

The whole shape #

learn/                                  the site root
├── trail.toml                           the site config — required
├── dist/                                the build output — trail's to manage
│
├── peios.product/                       a product  →  /peios
│   ├── trail.toml                       required: title, monogram, color, description
│   ├── 1--security-fundamentals.antho/  an anthology  →  /peios/security-fundamentals
│   │   ├── trail.toml                   required: title, description
│   │   ├── 1--identity.topic/           a topic  →  /peios/security-fundamentals/identity
│   │   │   ├── trail.toml               optional: title, inline_ref
│   │   │   ├── 100--sids.md             an article  →  …/identity/sids
│   │   │   ├── 200--principals.md
│   │   │   ├── 300--tokens/             a subfolder  →  …/identity/tokens/…
│   │   │   │   ├── trail.toml           optional
│   │   │   │   └── 100--issuing.md      →  …/identity/tokens/issuing
│   │   │   └── diagram.svg              an image, published beside its article
│   │   └── 2--pgss.book/                a book  →  /peios/security-fundamentals/pgss
│   │       ├── trail.toml               required: title, description; optional: short
│   │       ├── 1--scope.md              §1  →  …/pgss/scope
│   │       ├── 2--model/                a chapter, §2
│   │       │   ├── trail.toml           optional
│   │       │   └── 1--objects.md        §2.1
│   │       └── a1--glossary.md          Appendix A
│   └── 2--tooling.shelf/                a shelf — a heading, not a URL segment
│       └── 1--pekit.topic/              →  /peios/pekit   (the shelf adds nothing)
│
└── pekit.product/                       another product  →  /pekit

Everything below the root is optional. A site with one product, one topic and one article is a complete site; this site's trail.product is exactly that shape, four levels shallower than peios.product.

The naming grammar #

Three name shapes appear throughout the tree.

ShapeExampleWhere
<slug>.productpekit.productOnly at the site root.
<order>--<slug>.<kind>2--writing.topicAnthologies, topics, books, shelves.
<order>--<slug>300--tokens, 2--modelTopic subfolders and book chapters — no suffix.
<order>--<slug>.md100--sids.mdArticles.
<order>--<slug>.link3--faq.linkAliases; see Link aliases.

The parts:

  • <order> is a non-negative integer used only for sorting among siblings. It never appears in a URL or a title. Gaps are fine and normal — numbering articles 100, 200, 300 leaves room to insert. Two siblings sharing an order is an error, because the resulting page order would depend on nothing you can see.
  • <slug> is lowercase kebab-case: ASCII lowercase letters, digits and hyphens, not starting or ending with a hyphen. It becomes the URL segment, and it is what you write in a ~link.
  • <kind> is antho, topic, book or shelf.

Inside books, an order may also be written a<N>-- (a1--glossary.md). Those are appendices: lettered A, B, C instead of numbered, and sorted after every numbered sibling at their level. See Books.

URLs #

A page's URL is its product slug followed by the slug of every container between the product and the page — with two exceptions.

peios.product/1--security-fundamentals.antho/1--identity.topic/100--sids.md
  →  /peios/security-fundamentals/identity/sids

The exceptions:

  • A shelf contributes no segment. It is a heading on its parent's page, nothing more. A topic inside 2--tooling.shelf/ lives at /peios/<topic>, exactly as if the shelf were not there.
  • Order prefixes contribute nothing. 1--identity.topic gives the segment identity.

Which containers have a page of their own is worth knowing, because it decides what you can link to:

Has a pageLinks to it land on
Site/—
Product/pekitits own landing page
Anthology/peios/security-fundamentalsits own landing page
Book/peios/…/pgss (the cover)the cover
Articleits own URLitself
Topicno page(topics are not link targets)
Topic subfolderno page(not a link target, except from a .link)
Book chapterno page(not a link target)
Shelfno page(not a link target)

Topics, subfolders and chapters are real structure — they appear in sidebars, cards and contents lists, and links from those surfaces open their first article — but they have no page you can address, so a ~link cannot point at one. Point at the article you actually mean.

Titles #

Titles come from three places, in order of precedence:

  1. An article's title: frontmatter, which is required.
  2. A container's trail.toml, where the title key is required for products, anthologies and books, and optional for topics, subfolders, chapters and shelves.
  3. The slug, hyphens to spaces and each word capitalised: the-two-gates becomes "The Two Gates".

The derived form is good enough surprisingly often; write a trail.toml when it is not — acronyms and casing it cannot guess ("Peiso", "The Console"), or when the container needs inline_ref phrases.

What may sit where #

Trail rejects anything it does not recognise, so it is worth knowing what each level tolerates.

DirectoryMay contain
The site roottrail.toml, *.product/, the output directory, files named by the config
A producttrail.toml, .antho/, .topic/, .book/, .shelf/
An anthologytrail.toml, .antho/, .topic/, .book/, .shelf/
A shelftrail.toml, the same as its parent minus shelves — a shelf never holds a shelf
A topictrail.toml, .md articles, <order>--<slug>/ subfolders, .link files, images
A topic subfoldertrail.toml, .md articles, .link files, images
A booktrail.toml, .md articles, <order>--<slug>/ chapters, .link files, images
A book chaptertrail.toml, .md articles, <order>--<slug>/ chapters, .link files, images

Only the article-holding levels take files: .md articles, .link aliases and images may sit in a topic, a subfolder, a book or a chapter, and nowhere else. A product, anthology or shelf directory holds trail.toml and subdirectories, full stop.

Images sit beside the articles that use them and are published at the URL of the directory they were found in; see Images.

Two slugs are reserved: print (every container publishes a /print page) may not be an article slug, and assets and pagefind may not be product slugs.

Ordering #

Siblings sort by their <order> prefix, ascending. Within a book, appendix entries (a<N>--) sort after every numbered entry at the same level, and they are lettered rather than numbered.

Products are the exception: they have no order prefix. They sort by the root config's featured list first, in that list's order, then everything else by title.

Where to go next #

  • Products — the product level in depth, and when to add another one.
  • Anthologies and shelves — grouping a large product.
  • Topics and folders — the everyday container.
  • Books — numbered, ordered documents with chapters and appendices.

Peios Learn — documentation for the Peios project.

Built with Trail.