# Writing UI documentation without screenshots Bookhoard's UI documentation uses no screenshots. This page is the standard for writing them. The flagship example of the style is the [filter system documentation](../user/filters/index.md): one system taught once, the places it appears listed as a *pattern*, samples for every field in a [generated reference](../user/filters/fields.md) that cannot drift from the UI. ## Why no screenshots Screenshots fail in every way the rest of this repo's rot is already gated against: - **They rot silently.** No gate can diff pixels against the running UI. A renamed button makes the screenshot wrong and nothing fails. - **They cannot be verified.** Shown text can: this app's UI is server-rendered from `templates/*.templ`, so every stable label and heading is a literal in the source — checkable, like routes and API examples are. - **They die with a redesign.** Text keyed to *function* survives a re-layout. Text keyed to a layout dies with its redesign. - **They exclude** (screen readers, text search) and bloat diffs. The one thing screenshots do better — "which button is it?" — text does via **exact quoted labels** plus a semantic path, and the label gate then holds that text against the source. ## The layering — what is load-bearing Write every UI page in this order. The top layers are why users don't get lost when the UI changes: | # | Layer | Content | Lifespan | | --- | --- | --- | --- | | 1 | **The pattern** | What the thing *is*, what is invariant, every place the same pattern appears | Redesign-proof | | 2 | **Function inventory** | What the user *can do*, phrased without control names | Redesign-proof | | 3 | **How-to with examples** | Numbered flows: current label → the outcome it produces | Survives most changes | | 4 | **Current labels** | Exactly quoted, bold | Gate-checked today | | 5 | **Generated references** | Enumerables regenerated from source with a currency test | Cannot drift | Labels are **tips, not the map**. Layer 3 may quote a label; layer 1 must make the page followable even if that label is renamed next week. ## The page template 1. **The pattern first.** The concept, its invariants, and everywhere it appears — "one system, three places". Never page cartography. 2. **Function inventory.** Capabilities without control names: "you can pin any narrowing to a named filter you can reopen later". 3. **How-to with examples.** Each step: the label of what you interact with, then the outcome ("the count at the bottom shows how many books will match"). An example beats an adjective. 4. **Findability note.** One line telling the reader how to re-locate the feature *by function again* after any UI change — deliberately label-independent ("it's the drawer opened from any books or podcasts list — the only place whose header can read Match ALL"). 5. **Current labels.** Quoted exactly as rendered, one canonical spelling per control ([arbitrated by the glossary](ui-controls.md)). ## Writing rules - **Semantic placement, never spatial.** No "top-right" or "at the bottom of the screen" talk. "Devices page → the **Register Device** button" survives any re-layout. - **One canonical label per control.** Two spellings of the same control is a defect, not a synonym. - **Status updates get a what-it-means table**, not a color description. - **Screenshots are never load-bearing.** Default is none. ## Tone by audience - **User Guide (`docs/user/`) — plain language, deliberately.** Short sentences. Everyday words. No developer jargon; any term the reader genuinely needs (say, *rule*) is explained once, in place, then used consistently. The reader is assumed to know books, while the app's own words are the only technical vocabulary allowed. - **Administration (`docs/admin/`) — plain language, one layer deeper.** Same bones as the user docs: task-first, what to click for what, exact quoted labels. Added: every control also states what the click *actually does* — semantics, cost, and consequences — before the reader clicks it. The canonical example: **Force Rescan All Libraries** re-reads and re-extracts every file and recomputes hashes, while the other scan buttons are shallow scans that skip unchanged files — an admin should know that before clicking, not after. Every setting states what it is, what it does, what it means when that is confusing, and its valid range. No API-client detail: request and response schemas live in the developer docs. The calibration in one line: **user = what you see, admin = what it does, developer = how it works.** - **Developer section — source-pinned and wire-accurate.** The developer tier is *how it works*: wire formats, data access, pipeline mechanics, and enforced rules, every claim grounded in source the way admin docs ground behavior. External software (device installs, third-party tooling) is linked to its project's documentation, not paraphrased. (Decided 2026-11-02, closing the earlier open question.) ## Generate what has a registry Anything enumerable gets generated from source with a currency test, not hand-typed. The precedent: every filter field is [generated](../user/filters/fields.md) by `TestFieldReferenceDocIsCurrent` from the same registry the menus render — the doc cannot disagree with the UI. Hand-typed lists drift fastest; when no registry exists yet, say so in the page and lean on the pattern layers. ## The gates - `TestUIControlLabels` (`internal/docs/ui_labels_test.go`): every control in the [glossary](ui-controls.md) must exist as a literal in `templates/*.templ` (or `web/src` for TS-rendered surfaces, marked `source: ts`). Renames fail `make test` in the same commit as the rename. - `TestDocsRelativeLinks`: every relative link in the docs tree resolves to a real file. Naming a new control in prose? Add its glossary entry in the same commit. Form labels inside one modal (Name, Description…) are cited inline and not glossary-tracked; the glossary carries the controls people navigate by — buttons, tabs, badges, page titles. ## The process rule **A UI change is not finished until the docs audit runs.** Moved card, renamed button, restructured page — all of it triggers: labels still resolve, links still resolve, affected pages re-read against the new surface. The pattern layers keep users oriented between audits; the audit keeps layers 3–5 honest. ## Promises need approval Docs never promise future behavior on the owner's behalf. No "coming soon", "under consideration", "in a future release", or any other forward-looking commitment gets written without the owner explicitly approving that exact promise — and anything speculative found in the docs gets reported, not quietly kept and not quietly deleted. (Rule origin, 2026-11-02: the owner ruled out "Kindle and reMarkable are under consideration" and a "Custom Calibre Columns (Future)" section — no such plans existed — while the Operations guide's pre-existing "Coming Soon" placeholders were reported and knowingly kept for the future operations-docs pass.) ## What this does not replace The filter documentation (and the older filtering overview) is hands-off exemplar territory — read it as the model, never restyle it here. New UI surfaces (scanning, devices, settings, podcasts, the reader, collections) are written this way from the start.