Accepted adr-015 · 2026-10-08

The Faces Are Rendered From minijinja Templates Styled by UnoCSS With daisyUI and Driven by htmx, All Carried in the Repository and Embedded in the Binary; One Set of Templates Serves Every Face — Service, Reader and Static Export — for an Anonymous Reader or a Person the Authority Identified

Context

Every page SpecDossier shows is built in Rust by string concatenation: code/crates/specdossier-view (about 1,600 lines, some 180 `format!`/`push_str`) writes the reader's view of a SOW, the dossier, the round, what is owed, intents, login, enrolment and refusals, with one inline CSS constant. htmx 2.0.4 is loaded at runtime from unpkg, pinned by version and integrity. The static viewer (code/scripts/viewer.nu) writes its own index with its own CSS and escaping, a second face that drifts from the first. Changing how anything looks means editing Rust and recompiling.

Projects of the same author serve their faces with the same means, chosen because nothing better has been found for the speed of changing a face and for what a page costs to serve: ontoref's daemon panel and opus-ontoref's editorial panel (tera, through stratumiops' shared `ui` crate), website-htmx-rustelo (minijinja); all with UnoCSS + unocss-preset-daisy (daisyUI themes, `data-theme`), htmx 2 vendored or served locally, fragments answered to `hx-request`, templates read from disk in development and embedded for release.

Two constraints of this repository bound how those means are taken: no Cargo manifest reaches outside it (code/.claude/CLAUDE.md), and a copy of the repository builds and verifies alone (dependencies-point-one-way) — so stratumiops' `ui` crate, a path dependency, cannot be used. And the faces are not single-user: a reader, the static export or a stand-alone deployment may be read by several people, and combine with signatio to know who reads.

The principal chose, 2026-10-08: UnoCSS + daisyUI for style, minijinja or tera for templates, htmx for interaction — for agility and performance, not because the pattern is known; every face, the static one included, may be multi-user and combine with signatio.

Decision

THE FACES ARE TEMPLATES. Every page and fragment SpecDossier renders as HTML is a minijinja template under code/crates/specdossier-view/templates/ — a base layout, pages, partials (the fragments htmx swaps) and macros — fed with serialized view data. No HTML is built by string concatenation in Rust. minijinja rather than tera: without the shared `ui` crate tera brings nothing of its own, and minijinja is the lighter dependency, faster to build and render, escaping by default, with the same Jinja syntax.

STYLE IS UTILITY CSS, BUILT AHEAD AND CARRIED IN THE REPOSITORY. UnoCSS with presetUno and unocss-preset-daisy (daisyUI themes; dark and light on `data-theme`, chosen before first paint and switchable) scans the templates and writes one minified stylesheet into the repository; a check recipe fails when it no longer matches the templates. Building the binary needs no Node; changing the style does.

EVERYTHING A PAGE LOADS IS SERVED BY SPECDOSSIER. htmx is vendored in the repository at a pinned version, with the stylesheet and the passkey module, embedded in the binary and served under `/assets/`; no page loads anything from a CDN at runtime.

EMBEDDED FOR RELEASE, READ FROM DISK TO CHANGE. A build embeds the templates and assets. When `SPECDOSSIER_TEMPLATES` names a directory, templates are read from it and reloaded when they change, so a face is changed without recompiling.

ONE SET OF TEMPLATES FOR EVERY FACE AND EVERY READER. The service, a reader (adr-014) and the CLI's static export render the same templates. Every page receives who reads it — nobody (an anonymous reader, the static export, the loopback's operator) or a person the authority identified, with the hats it answers — and the layout shows the session and the way to log in when an authority is declared. Proving who someone is stays the authority's own client's (one-path-many-faces): a template hosts signatio's web client, it never implements a proof. A static page the CLI writes for one SOW inlines its stylesheet, so the file a signer reads offline is whole and gives the same bytes for the same reading.

WHAT THIS DOES NOT DECIDE: whether a reader may declare an authority to know who reads and show each person what their hats are named in — an amendment of adr-014, decided on its own; desk's face.

Constraints

Alternatives considered