The plinth engine — a guided tour

The documentation as the working set: what plinth is, how a build runs, and where the document mode fits.

What plinth is

plinth is a presentation-first static site generator for portfolios, promotions, events, decks — and, with this mode, long-form documents. It is deliberately not a CMS: content lives as EDN and Markdown in a git repository, a build is a pure function of that data plus a build instant, and the output is static files that make zero third-party requests at runtime.1

The engine's requirements live in one SPEC, and every requirement carries an id that at least one test must reference — a coverage gate CI enforces, so the SPEC and the code cannot quietly drift apart. This handbook is itself a demonstration: it is a :mode :document portfolio, and the engine rendering it is the subject it documents.

  1. The privacy posture is structural, not a setting: self-hosted fonts, no CDN scripts, no trackers unless the owner explicitly enables a provider from an audited registry — and then a generated privacy page states exactly what loads.↩

The data model

Everything the engine publishes is declared in a small family of EDN files: one portfolio.edn for the site, one file per item, one file per classification scheme. There is no database and no admin panel — the data repo is the single source of truth, and git is its history.

Items and media

An item is the unit of presentation: media elements and/or Markdown text, plus the facts a page needs — title, date at the precision the owner actually has, categories and tags. Media are pinned by content hash, so a silently substituted master fails the build instead of shipping:

{:kind :image
 :path "harbour.jpg"
 :hash "sha256:2f7a…"
 :alt "Fishing boats at low tide"}

The masters themselves live outside git and are verified against these hashes before anything is rendered.

Diagram: the data repo (EDN and Markdown) and the hash-verified masters directory both feed bb build, a pure function of data and instant, which emits a static self-contained dist directory.
One build, one arrow each way: everything the site becomes is a function of the data repo, the masters and the build instant.

Derivatives

Published pages never serve a master. The pipeline derives, per image:

  • responsive width rungs, the largest capped by :max-width
  • modern formats first (AVIF, WebP), an honest fallback last
  • metadata stripped unconditionally — capture details reach a page only from EDN, promoted deliberately

The figures in this handbook rode exactly that pipeline: they are lazy-loaded rungs with intrinsic dimensions, not copied files.

Rights attestation

Every media element belongs to a rights block naming the holder, the licence, the provenance and who attested it, when. A build refuses media without one.1 Documents follow the same rule: this page's figures are covered by the document's own rights declaration.

  1. The attestation is the chain's first link — demo data is marked as such, and the production publisher refuses anything synthetic or demo-marked.↩

Text is Markdown

Everything long-form is Markdown referenced from EDN — item texts, scheme statements, category definitions, and every node of this document. In document mode the Markdown gains footnotes as content markup, rendered as backlinked end-notes. Documents may also link each other: the tasks a data repo drives are catalogued in the build tasks, a second document composed beside this one — and because it links back here, each of the two carries a build-generated "Referenced by" list.

The build

The build is a pure function:

output = f(data, configuration, instant)

No ambient clock, zone or locale is ever consulted; the instant is always an argument, so a page can be built as of any moment — a promotion's before, during and after are three builds of the same data. Identical inputs give identical bytes, which is what makes golden fixtures, blessing workflows and deploy verification possible. The command surface behind this is bb build.

Presentation modes

One data model, several presentations. A portfolio declares its :mode and the engine renders accordingly:

  • gallery — the full-viewport browsing site
  • single-item — the home page IS the item page
  • deck — slides with 2D navigation and a printable handout
  • links — one page of outbound buttons
  • document — the long-form tree this page demonstrates

Modes change presentation, never data: the same items could back a gallery today and a deck tomorrow.

The document mode

The mode you are reading: one vertically scrolling page, no page-length limit by design, navigation as traversal of a hierarchy rather than pagination. Headings follow the tree's depth with an h2–h6 clamp; every node carries a stable ancestry-derived anchor; nodes past the contents rail's coverage render inside <details open> — foldable by the reader, never folded by default, so print and crawlers always see everything.1

The prose on this page is justified by a per-document option — the engine's PRES-17 machinery, settable for one document rather than a whole site. Beside the page the build emits a Markdown rendition and an llms.txt index, so the document is consumable by language models as comfortably as by browsers. And for print there is bb doc-pdf: real typesetting through a pinned Typst toolchain, not a browser page printed to PDF.

  1. Folding is an offer, not a state: the attribute is open in the shipped HTML, and only a reader's click changes it.↩
Diagram: a document node tree descending from the document root — a section rendered as h2, a subsection as h3, a deeper node as h4 inside an open details element — with the contents rail listing the top level plus one sublevel.
The tree is the mode: headings follow depth (h2–h6), deep nodes fold, the rail is a projection of the top two levels.