docs(standard): screenshot-free UI documentation standard + UI-label gate
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.
This commit is contained in:
1 parent
fbdd4ab9f9
commit
e07301f74a
3 files changed
+498
No files matched your search
@@ -0,0 +1,117 @@
|
||||
# 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.
|
||||
- **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](../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.
|
||||
|
||||
## 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.
|
||||
Reference in new issue
Block a user