— Design Spec (#)
Milestone:
Manual: docs/user_manual/guide/Part . / / .md Copy this file to
docs/contributor_manual/specs/<surface>.md(orspecs/<area>/<surface>.md) to start a surface under design-led testing. Delete this quote block and fill every section._-prefixed files are scaffolds — the guardrail ignores them.Design-led (Testing Constitution). Creative director owns intent; tests enforce it; code makes them pass. Status: DRAFT — awaiting approval before tests/code. Flip to
Status: APPROVEDonly after the creative director approves the intent; an APPROVED spec MUST: carry a filled Test matrix; be cited by ≥1 test; declare aMilestone: <name>matching a GitHub milestone of the SAME name (spec name == milestone name == test tag — guardrailscheck_specs_have_tests.py+check_spec_milestones.pyenforce); and declare aManual: <path>pointing at the user-manual section that tells a USER about this surface (guardrailcheck_spec_manual_refs.py). Create/rename the milestone when you approve the spec, and point its description back at this file (the link is bidirectional). Tag the tests to match: Swift@Taginfichero/Tests/Unit/general/TestTags.swift, pytest markers infichero-server/pyproject.toml.THREE ANCHORS, no orphans (creative-director, 2026-09-17). Every spec ties to: 1. a GitHub milestone — where its issues live (the work), 2. a user-manual section — how a user is told about it (the promise), 3. ≥1 test citing its behavior ids (the proof). A surface with no manual section is not finished: if the manual does not cover it yet, say so here (
Manual: TBD — <what the section must explain>) and file an issue on the manual milestone. “TBD” is allowed on a DRAFT and blocks APPROVED, so the gap is visible, never silent. Tags: [OK] built · [MISSING] not built · [PARTIAL] exists elsewhere / not wired.
Intent (the design)
One paragraph: what the user should be able to OBSERVE on this surface, and why. Name the concrete files/types the surface lives in.
Prior art / best practices (don’t invent from scratch)
Before proposing a novel design, survey how the field already solves this and cite what we build
on. Fichero is a scholarly/archival + AI tool, so look to: digital humanities (DH methods,
standards — IIIF, W3C Web Annotation, CIDOC-CRM, TEI, the factoid model), NLP/NLG (established
pipelines — e.g. Reiter & Dale; realisers), Hugging Face (models, datasets, tokenizers,
transformers/datasets APIs) and their conventions, and the relevant standards/libraries.
State: what established approach we adopt, what we deliberately do differently and why, and what we
reuse rather than rebuild. A spec that invents where the field has a solution is sent back.
Behaviors
One line per behavior, each with a stable id and a tag. The id is what a test cites.
- <surface>.<behavior> [MISSING] — what the user observes.
- <surface>.<behavior-2> [PARTIAL] — …
Test matrix (paste from ../TEST-TEMPLATE.md, tick the legs this surface touches)
| Leg | This surface? | Pins | File |
|---|---|---|---|
| Pure rule (Swift) | y/n | the rule off-main | fichero/Tests/Unit/…/<Surface>Tests.swift |
| Availability (Swift) | y/n | capability reachable in the surface | same, AppSource.root() |
| Backend (pytest) | y/n | endpoint contract + delivery | fichero-server/tests/… |
| MCP | y/n | tool maps + routes | fichero-mcp/tests/test_mcp_full.py |
| CLI | y/n | command wires endpoint | fichero-cli/tests/test_*.py |
| Click-around (XCUITest, Mac) | y/n | click → effect, end-to-end | fichero/Tests/UI/… (subclass FicheroUISessionTests) |
| iPhone (iOS) | y/n | the touch path on iPhone | fichero/Tests/UI/ios + fichero-ui-ios plan |
| iPad | y/n | the touch path on iPad | fichero/Tests/UI/ipad + fichero-ui-ipad plan |
| Load (#4634) | y/n | bounded, no peg, timed | fichero-server/tests/perf/… |
The surfaces a spec can touch: server (pytest) · MCP · CLI · Swift (unit/ availability/snapshot) · UI-Mac (XCUITest) · iPhone · iPad. Tick every one the feature reaches — a capability that ships on a surface but has no test there is unproven on it.
Hard-gate: the cross-surface invariant (same result backend/MCP/CLI/UX) + capability availability. Rest is tracked debt — but listed here so it isn’t forgotten.
Documentation matrix (paste from ../DOC-TEMPLATE.md, tick the audiences this reaches)
| Audience | Doc leg | This feature? | Lives in |
|---|---|---|---|
| User | user manual + screenshot | y/n | docs/user_manual/guide/<area>.md |
| Contributor | developer docs | y/n | docs/contributor_manual/<area>.md + this spec |
| AI / agent | MCP tool description | y/n | fichero-mcp/** (description=) |
| Scripter | CLI --help |
y/n | fichero-cli/** (help=) |
| Reference | capability/endpoint reference | y/n | generated (check_capability_reference_current) |
A feature is met by readers who never read each other’s docs — cover the ones it reaches.
Authorship (who owns which folder).
- docs/contributor_manual/ + docs/reference_manual/ + MCP/CLI help — AI-authored, as part of the spec.
The agent writes and maintains these from the code + this spec; keeping them current is part of
finishing the surface, not a separate task.
- docs/user_manual/ — the maintainer’s own. The final user-facing manual is authored in Tinderbox
and exported to the docs/user_manual GitHub folder; the agent does NOT write or draft it. The spec’s
job for this audience is only to keep the facts the maintainer documents accurate to what
shipped (behaviors, a11y ids, screenshots) — not to author the prose.
Preview harness (fast visual verification + doc screenshots)
Every UI surface ships a lightweight SwiftUI #Preview that renders the design from the spec’s own
DATA (not the live app), #if DEBUG-gated, so the composition can be verified and SCREENSHOTTED in
the Xcode canvas in seconds without booting the app. It:
- pins the design visually — the picture mirrors the shipping renderer’s layout rule, so a
glance (or a snapshot test) catches divergence the unit tests can’t see;
- renders instantly — no AppState/LibraryManager boot — so design iteration is a canvas
refresh, not a full app run (and it works even when the real surface needs a running backend);
- is the SOURCE of the manuals’ screenshots — capture the preview and drop it into
docs/assets/<surface>/; the reference + contributor manuals then illustrate the surface from the
same harness the design is verified against, so the docs stay in sync by construction.
Name it <Surface>LayoutPreview / <Surface>Preview beside the surface’s pure model. Example
(this pattern’s origin): WorkspaceLayoutPreview renders every BuiltInWorkspaceLayout as a
labelled mini window — the workspace spec’s verification surface and its manual screenshots.
Accessibility identifiers (required for the click-around leg)
List the stable a11y ids the UI test will drive — add them to the views AS YOU BUILD:
- <surface>.entry — the control that opens the surface
- <surface>.row.<id> — a row/item
- <surface>.menu.<verb> — each context-menu verb / button
UX completeness (required on EVERY surface)
Every user-facing control on this surface ships ALL of the following. Two levels of verification — be honest about which a given item has:
- Tooltip / help. Every control has a
.help("…")(menu items a help tag). Today:scripts/check_tooltips.pyscans for presence on icon-only toolbar controls (conservative, has aKNOWN_VIOLATIONSbacklog). Target: the spec table below names the intended text so a test can pin it. - Label. Every control has a visible or accessibility label — no icon-only control unreachable by name.
- Accessibility. An
.accessibilityLabel+ the stable.accessibilityIdentifier(above) on every interactive element. Today:scripts/check_accessibility.pyscans for presence of a label on icon-only controls (conservative, backlog). Target (Apple-first-party, NOT YET ADOPTED — [MISSING]): the click-around leg runstry app.performAccessibilityAudit(), which catches missing labels/identifiers and contrast at runtime. Adopting it is part of theui-testing-strategyspec — prefer it over growing the static scanner. - Localization-ready. No hardcoded user-facing strings — every string comes through
LocalizedStringKey/ the String Catalog (.xcstrings), so the surface is translatable without code changes. Today:scripts/check_localization.pyfails onText(verbatim:)and other escapes (real, Apple-aligned). (Shipping a locale is a later decision; being ready is required now.)
Honesty rule (design-led testing): a presence scan is not a behavioral test. Fill the table so
the intended text is on record; where a spec-pinned test or performAccessibilityAudit() isn’t
wired yet, tag the row [MISSING] rather than implying it’s proven.
| Control (a11y id) | Label | Tooltip/help text | Localized key | Verified by |
|---|---|---|---|---|
<surface>.entry |
… | … | <key> |
scan / test / audit / [MISSING] |
Open questions for the creative director
- …