bun run check, bun run test, bun run format:check, bun run lint
cargo fmt, cargo clippy --all-targets -- -D warnings, cargo test
bun run check:boundary — no Jellyfin taxonomy in the frontend
New requirement-implementing code carries a TRACES: comment, and every
ID it names exists in docs/requirements.md (bun run traces:validate)
Bug fix: a test that reproduces it was written first and observed
failing before the fix
Android source edits were made in src-tauri/android/src and synced with scripts/sync-android-sources.sh (never edit gen/ directly)
## What and why
<!-- What changes, and the reason. The diff shows the what; the why is what
the commit log is for. -->
## How it was verified
<!-- What you actually ran or clicked. "Tests pass" on its own says little;
"played a transcoded episode on Android, seeked twice, backgrounded it"
says a lot. -->
## Checklist
- [x] `bun run check`, `bun run test`, `bun run format:check`, `bun run lint`
- [x] `cargo fmt`, `cargo clippy --all-targets -- -D warnings`, `cargo test`
- [x] `bun run check:boundary` — no Jellyfin taxonomy in the frontend
- [x] New requirement-implementing code carries a `TRACES:` comment, and every
ID it names exists in `docs/requirements.md` (`bun run traces:validate`)
- [x] **Bug fix:** a test that reproduces it was written *first* and observed
failing before the fix
- [x] Android source edits were made in `src-tauri/android/src` and synced with
`scripts/sync-android-sources.sh` (never edit `gen/` directly)
Three corrections to the Linux native-video spike, each of which reverses
something recorded earlier in the same session:
- The crash is unexplained. It was first blamed on hwdec=auto-safe's Vulkan
failures, on a misreading of the logs — those are two per run at start-up,
not per-frame, and every clean run has the same two. A 300s soak on
auto-safe survived. So did 240s of automated fullscreen toggling (~120
transitions) and 240s of continuous resizing (~2000 reallocations). Three
hypotheses, none reproduced. Recorded rather than dismissed: an
intermittent fault nobody can reproduce is worse to inherit than a
deterministic one.
- G5 drops to amber. It looked and felt smooth, but the only SIGSEGV observed
came from the only session in which fullscreen was exercised, and the spike
has no lifecycle handling at all — it never frees the render context. An
implementation must bind that to the GL context's lifetime regardless of
what caused this crash, because Android already paid for that lesson as
DR-184.
- Finding 3's premise is in doubt. "The webview path already has real ABR via
hls.js" was never checked against the URLs this app builds:
get_video_stream_url requests a single rendition, the frontend has no
level-handling code at all, and a quality switch is implemented by
re-opening the stream. If the playlist is single-variant there is no
adaptation to lose. The decisive test needs a live server and is recorded
as unrun.
Also records hardware decode working through the render API (nvdec-copy
engaged), and that hwdec=vaapi silently fell back to software on this box.
Rust becomes the single owner of which stream to play — direct play or
transcode, at what ceiling, over what transport — and hands every player
backend a self-describing StreamSelection instead of a bare URL. mpv,
ExoPlayer and the HTML5/hls.js path all consume one decision rather than
three places re-deriving it.
The motivating leak is concrete. VideoPlayer.svelte determines transport with
`currentStreamUrl.includes(".m3u8")`, in two places, for a URL Rust
constructed and therefore already knows the shape of. That is the boundary
rule in miniature: not item-type taxonomy, but the same error of
reconstructing a domain fact in the presentation layer because the wire shape
did not carry it. A tagged Transport enum deletes it.
The design line, which ExoPlayer forces: Rust decides *what stream*, the
player decides *how to deliver it*. ExoPlayer has genuine adaptive track
selection; this spec must not reimplement or fight it. Rust only adapts where
the player cannot (mpv) and the server actually offers a ladder.
Six phases, and phase 1 stands alone as pure ownership movement with no
behaviour change. Phase 4 (direct-play negotiation) is what removes the
transcode and unblocks the Linux native-video work. Phase 5 (adaptation) is
gated on counting EXT-X-STREAM-INF entries in a real playlist — the
acceptance criteria require that count be recorded before it is either
started or dropped.
Takes DR-121 from read-through-media-cache.md, which specced Rust-owned
quality reporting but never built it; that spec keeps its capture half.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
What and why
How it was verified
Checklist
bun run check,bun run test,bun run format:check,bun run lintcargo fmt,cargo clippy --all-targets -- -D warnings,cargo testbun run check:boundary— no Jellyfin taxonomy in the frontendTRACES:comment, and everyID it names exists in
docs/requirements.md(bun run traces:validate)failing before the fix
src-tauri/android/srcand synced withscripts/sync-android-sources.sh(never editgen/directly)Rust becomes the single owner of which stream to play — direct play or transcode, at what ceiling, over what transport — and hands every player backend a self-describing StreamSelection instead of a bare URL. mpv, ExoPlayer and the HTML5/hls.js path all consume one decision rather than three places re-deriving it. The motivating leak is concrete. VideoPlayer.svelte determines transport with `currentStreamUrl.includes(".m3u8")`, in two places, for a URL Rust constructed and therefore already knows the shape of. That is the boundary rule in miniature: not item-type taxonomy, but the same error of reconstructing a domain fact in the presentation layer because the wire shape did not carry it. A tagged Transport enum deletes it. The design line, which ExoPlayer forces: Rust decides *what stream*, the player decides *how to deliver it*. ExoPlayer has genuine adaptive track selection; this spec must not reimplement or fight it. Rust only adapts where the player cannot (mpv) and the server actually offers a ladder. Six phases, and phase 1 stands alone as pure ownership movement with no behaviour change. Phase 4 (direct-play negotiation) is what removes the transcode and unblocks the Linux native-video work. Phase 5 (adaptation) is gated on counting EXT-X-STREAM-INF entries in a real playlist — the acceptance criteria require that count be recorded before it is either started or dropped. Takes DR-121 from read-through-media-cache.md, which specced Rust-owned quality reporting but never built it; that spec keeps its capture half.