Files
bookhoard/docs/developer/writing-ui-docs.md
T
John O'Keefe ab5c52c709 docs(dev): developer portal rewritten; tier definition closed in the standard
development.md was a bare link list whose 'implementation guides'
blurb still taught the pre-RLS visibility pattern. Now it's the
developer-tier portal: contributing/development.md as the start-here
(architecture/setup/testing — fresh and maintained), the gated API
reference with its per-area entry points and the openapi.yaml mirror,
the four protocol docs, the under-the-hood guides (RLS visibility,
scanner sidecars, calibre OPF, tag-normalization contract, Android
design), the standards (writing-ui-docs, glossary, contract pins),
and the gates enumerated — routes<->docs, Bruno parity, links, labels,
RLS invariant. writing-ui-docs.md's Developer-tier line closes the
open tone question: how-it-works, source-pinned, external software
linked not paraphrased.
2026-11-02 14:57:18 -05:00

7.3 KiB
Raw Permalink Blame History

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

  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).

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 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 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.