Files
jellytau/docs/specs/SPEC-TEMPLATE.md
T
dtourolle 96abc3afef docs(specs): make "a spec becomes an architecture doc" the written rule
The sixteen specs folded in last commit were folded because someone noticed
they had gone stale, not because anything said they should be. Without the rule
written down the directory drifts straight back to a mix of promises and
descriptions, and neither can be trusted: you cannot tell from a file whether it
describes the build or proposes a change to it.

So: docs/specs/ holds only unshipped work, there is no "Implemented" resting
state, and the fold-in and the deletion happen in the same commit.

The template now asks for the destination architecture doc **up front**, which
is a design check rather than bookkeeping — a feature that fits no existing doc
usually has an unclear layer assignment, and it is cheaper to find that out at
spec time. It also tells the author which half of what they are writing is
durable (invariants, rejected alternatives, the defect a decision prevents) and
which half dies with the file (phases, migration steps, acceptance criteria).

The review checklist gains a Lifecycle section, including the case that gets
lost otherwise: out-of-scope work worth doing has to be written where it will
still be found after the spec is gone.
2026-08-21 18:29:09 +02:00

121 lines
5.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Spec: <feature name>
<!--
Copy this file to docs/specs/<kebab-name>.md and fill it in. Delete the HTML
comments as you go. The section that matters most for this project is
"Layer assignment" — read its comment before writing it.
Before merging a spec, run it past docs/specs/SPEC-REVIEW-CHECKLIST.md.
LIFECYCLE: this file is temporary. docs/specs/ holds only unshipped work — when
the last acceptance criterion is met, the design is folded into
docs/architecture/ and this file is deleted in the same commit. Write it
knowing that: the durable half is the reasoning (invariants, rejected
alternatives, the defect a decision prevents), and the disposable half is the
plan (phases, migration steps, acceptance criteria).
-->
**Status:** Proposed <!-- Proposed | Accepted | Partially implemented | Superseded.
NOT "Implemented" — a fully shipped spec is folded into docs/architecture/
and deleted. See "Destination on completion" below. -->
**Requirements:** <!-- UR-xxx → DR-yyy; allocate new DRs in requirements.md. -->
**UX spec:** <!-- link to the relevant ux-flows.md section, or "n/a". -->
**Supersedes / revises:** <!-- link any spec this changes, or delete this line. -->
**Destination on completion:** <!--
Which architecture doc absorbs this design when it ships, and roughly which
section. e.g. "05-platform-backends.md — a new section beside
ExoPlayerBackend". Name it NOW: a feature that fits no existing doc usually
has an unclear layer assignment, which is worth finding out at spec time.
This spec file is deleted in the same commit that folds it in. -->
## Summary
<!-- 24 sentences. What changes for the user, in plain terms. -->
## Motivation
<!-- Why now. The problem being solved. -->
## Layer assignment
<!--
🔴 THIS IS THE SECTION THAT KEEPS THE ARCHITECTURE HONEST. Do not skip it, and
do NOT reframe it as "how little backend work can we get away with."
The project rule (CLAUDE.md, architecture/02-svelte-frontend.md): the Rust
backend owns ALL business logic — auth, catalog, sessions, downloads, offline,
playback, AND domain vocabulary (e.g. what Jellyfin item types the category
"Music" means). The Svelte frontend is PRESENTATION ONLY: rendering, layout,
navigation, view/order preferences, input handling.
For each distinct piece of *logic* this feature introduces, put it in the table
and name the layer it belongs to and WHY. "It's less work in the frontend" and
"the backend already accepts this parameter" are NOT reasons to place logic in
the frontend — the backend accepting a parameter does not make deciding that
parameter's value a presentation concern.
Litmus test for "does this belong in Rust?": Would this logic have to change if
Jellyfin changed its API, added an item type, or altered a business rule? If
yes, it is domain logic → Rust. Would it change if we redesigned the UI? If
yes (and only yes), it is presentation → frontend.
A past incident: scoped-search.md placed the item-type taxonomy (what "Music"
means as a set of Jellyfin types) in the frontend because the backend already
accepted an includeItemTypes filter. That was a boundary leak; see
scoped-search-boundary.md. This section exists to catch that class of mistake
at spec time, not in review three features later.
-->
| Logic / responsibility | Layer | Why it belongs there |
|------------------------|-------|----------------------|
| <!-- e.g. scope → item-types --> | Rust | <!-- domain vocabulary; changes with Jellyfin's API --> |
| <!-- e.g. group display order --> | Frontend | <!-- pure presentation; changes only if UI is redesigned --> |
<!--
If a row is genuinely borderline, say so and give the tie-breaker you used.
Borderline defaults to Rust for anything touching domain data or vocabulary.
-->
## Design
<!--
How it works. Wire shapes for anything crossing the IPC boundary. Remember:
- Command NAME must match the Rust fn name exactly.
- Top-level params auto-convert snake_case → camelCase (Tauri v2).
- Nested struct fields need #[serde(rename_all = "camelCase")].
- Events are kebab-case.
(See CLAUDE.md §IPC and architecture/04-type-sync-and-threading.md.)
Regenerate bindings.ts from Rust types; never hand-edit it.
-->
## Out of scope
<!-- What this spec deliberately does NOT do. -->
## Acceptance criteria
<!-- Checkable statements. Include the standard gates: -->
- [ ] `bun run check` and `bun run test` pass.
- [ ] `cargo fmt` clean, `cargo clippy` clean, `bun run test:rust` passes (if Rust changed).
- [ ] `bun run check:boundary` passes (no taxonomy leak into the frontend).
- [ ] New requirement-implementing code carries `// TRACES:` comments.
- [ ] `bindings.ts` regenerated if Rust types changed.
## Testing
<!-- Rust: cargo test. Frontend: vitest, src/lib/**/*.test.ts. What to cover. -->
## TRACES
<!-- Suggested tags per new/changed piece: UR-xxx | DR-yyy | tests. -->
## Notes for the implementer
<!--
- A parallel Claude session may be active in this repo — `git diff` before
"repairing" unexpected changes (see project memory / CLAUDE.md gotchas).
- Anything else non-obvious.
-->