The planned Playwright screenshot docs (SCREENSHOT_AUTOMATION.md) are replaced by a text standard: screenshots rot silently and cannot be gate-checked, while this UI's labels are server-rendered templ literals that can. Layers: pattern-first prose (concepts survive redesigns) > capability inventory > label->outcome flows > quoted labels > generated references where a registry exists (filters/fields.md precedent). The filter docs stay hands-off exemplars; user-doc tone is plain language by contract, dev/admin tone explicitly deferred. writing-ui-docs.md is the standard (page template, findability notes, tone-by-audience, process rule: a UI change is not done until the docs audit runs). ui-controls.md is the canonical control-label glossary the docs quote from; TestUIControlLabels greps every entry against templates/*.templ (source: ts override; also:-fragments for Alpine-composed labels like the Match/Group header) so renames fail make test in the same commit as the rename. Gate proven both ways: a planted bogus label failed; restored set passes.
5.6 KiB
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: one system taught once, the places it appears listed as a pattern, samples for every field in a generated reference 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
- The pattern first. The concept, its invariants, and everywhere it appears — "one system, three places". Never page cartography.
- Function inventory. Capabilities without control names: "you can pin any narrowing to a named filter you can reopen later".
- 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.
- 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").
- Current labels. Quoted exactly as rendered, one canonical spelling per control (arbitrated by the glossary).
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. - Developer and Administration sections — tone is a separate discussion, not yet decided. Write them plainly and hand-off-rich as well; do not import opinions about their voice until that pass happens.
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 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 must exist as a literal intemplates/*.templ(orweb/srcfor TS-rendered surfaces, markedsource: ts). Renames failmake testin 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.
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.