A day of debugging Linux native video produced four defects (DR-238 … DR-241) and one regression from fixing them in the wrong place. None of them were mpv bugs. All four trace to the same missing seam. `PlayerBackend` abstracts a *device* — load, then seek — rather than an *intent*. A start position is therefore not expressible, so every caller sequences load-then-seek itself and each races the engine's asynchronous load independently. That is why resume worked through the adapter, which seeks after "file loaded", and silently failed through the command, which seeks immediately: two callers, one intent, two behaviours. The same gap put transport rules above the engines. Whether a stream can be seeked in place was decided by a truth table in a command handler, on behalf of engines it does not own, which is how `use_html5` came to mean both "who renders" and "how do I seek". And nothing in the contract obliged an engine to report its own state, so a handler for mpv's `pause` property sat unreachable while the UI waited for an event that never came. Supporting evidence for the diagnosis: commands/player/mod.rs is 3,561 lines and is where "stop → rebuild URL → update queue → load → seek" lives; player_play_item needed a cfg(not(linux)) guard; and the frontend carries didStartNativePlayback, didStopBackendEarly and hasPerformedInitialSeek — playback state in the UI, which contradicts the one-directional rule. The proposal is a MediaPlayer contract whose `open` carries the start position, whose `seek` states a destination and leaves in-place-versus-re-open to the engine, whose `snapshot` is one coherent read, and whose `Phase` includes `Opening` — the state the previous design could not express and the window a seek was lost in. Testability is the half that makes it worth doing: one conformance suite run against every engine, and a FakePlayer that lets the controller, queue, autoplay and session logic be tested with no engine at all. The suite is written before the second engine on purpose, so it cannot encode whatever the first happened to do. Migration is a strangler in eight steps; the first three are pure addition.
Specs index
Feature specs for JellyTau. Start a new one from SPEC-TEMPLATE.md and run it past SPEC-REVIEW-CHECKLIST.md before accepting it.
What lives here
Only work that has not shipped. Once a spec is fully implemented its design is folded into the architecture docs — which are the maintained description of the build — and the spec file is deleted. Git history keeps the original, including its rejected alternatives and acceptance criteria; the architecture docs keep the reasoning that a future change still needs.
So: a file in this directory is a promise, not a description. If you want to know how something works, read docs/architecture/. If you want to know what is planned, read here.
Status vocabulary
| Status | Meaning |
|---|---|
| Proposed | Written, not accepted. Nothing built. |
| Accepted | Agreed as the design; implementation not started or not finished. |
| Partially implemented | Some parts shipped; the spec names what is left. |
| Design authority | No code of its own — it records a decision later specs act on. |
Next free requirement ids (always re-check requirements.md before allocating): UR-079, IR-033, DR-232. Three specs below suggested ids that have since been taken by other work; each carries a ⚠️ note at the top.
Partially implemented
| Spec | What landed | What is left |
|---|---|---|
| frontend-domain-model.md | Catalog surface: MediaKind, from_jellyfin isolated, ticks → ms |
primaryImageTag → imageId (~30 sites); player/session/reporting tick math; stream.type |
| libmpv2-migration.md | LICENSE |
The libmpv → libmpv2 crate swap |
| read-through-media-cache.md | DR-126…128, DR-133…138 — cache entries are download rows; local playback of downloads | DR-122/124/125 — the read-through capture. DR-121 shipped as backend-owned stream selection and left this spec |
| scoped-search-boundary-implementation.md | Stage 1: SearchScope owned by Rust (DR-063…067) |
Stage 2: result-side grouping (GROUP_ITEM_TYPES still in searchScope.ts) |
Not started
| Spec | Blocked on / note |
|---|---|
| desktop-native-video.md | mpv draws video on every desktop platform, then the webview <video> path and hls.js are deleted. Converts a measured 7% direct-play rate toward Android's 85%. Stacked on backend-owned stream selection. |
| build-provenance.md | build.rs is still bare. ⚠️ suggested id DR-093 is taken. |
| player-facade-enforcement.md | ~60 commands.player* sites still outside the facade; no lint rule. ⚠️ suggested id DR-095 is taken. |
| windows-native-audio-backend.md | Blocked on the libmpv2 swap. ⚠️ suggested id IR-030 is taken. |
| linux-native-video-spike.md | Spike run 2026-08-21: compositing works on Linux, X11 and Wayland. G1-G6 green bar the Tauri default_vbox() half of G1. The adaptive-bitrate question it was waiting on is answered: the server publishes one EXT-X-STREAM-INF, so there is no ladder for mpv to lose (DR-229). StreamSelection (DR-225) is the contract to consume. |
Design authority
| Spec | Role |
|---|---|
| playback-backend-unification.md | Why video cannot unify onto one native engine and audio can. The audio half has since shipped on Android; Windows has not. |
| scoped-search-boundary.md | The boundary design the check:boundary rule came from. Stage 1 built. |
| scoped-search.md | Superseded in part — its "frontend only, no Rust changes" decision is the leak the boundary spec reversed. UX still current. |
Where the shipped specs went
Sixteen specs were folded into the architecture docs and deleted (2026-08-21). Where to look for each:
| Shipped work | Now documented in |
|---|---|
| Account menu & global chrome | 02-svelte-frontend.md — App Shell and Chrome |
| Library mosaic | 02-svelte-frontend.md — Library Mosaic |
| Series current-episode navigation | 02-svelte-frontend.md — Series and Episode Navigation |
| Downloads as an offline library | 02-svelte-frontend.md — Downloaded Browse |
| Favourites browsing | 01-rust-backend.md — Favorites System |
| Streaming bitrate cap | 01-rust-backend.md — Streaming quality ladder |
| Locally-indexed search | 03-data-flow.md — Search Flow; 01-rust-backend.md — Background workers |
| Offline downloaded-only filter | 06-downloads-and-offline.md — Offline Catalog Visibility |
| Audio equalizer · Android audio settings parity | 05-platform-backends.md — Audio settings on ExoPlayer |
| Android native video spike | 05-platform-backends.md — Native Video Compositing |
| Video background audio | 05-platform-backends.md — Background Audio Handoff |
| Traceability gate repair | traceability-ci.md |
| Boundary tripwire hardening | scripts/check-frontend-boundary.sh (its header is the spec) |
| Playback docs corrections · req-coverage script removal | Nothing to document — both were corrections that have been applied |