From 886cbcb29a25a50c79e42f772d0939e25e66698b Mon Sep 17 00:00:00 2001 From: Duncan Tourolle Date: Sun, 16 Aug 2026 10:40:33 +0200 Subject: [PATCH] docs(changelog): backfill every release, and date each fixed defect CHANGELOG.md stopped at v0.5.0 and had gaps below it. Every tag from v0.0.1 to v0.5.5 now has an entry, written from the commit bodies rather than the subjects. Entries before v0.1.2 are shorter and marked as reconstructed after the fact -- the commit messages of that era ("many changes", "Playback fix") do not record causes. docs/defect-windows.md is new: for each fixed defect, the releases it was actually present in, with the evidence for the dating recorded per row so a row can be disputed. Dated with `git log -S` on the defective token, not by blaming the lines a fix removed -- that reliably lands on whatever last touched the adjacent lines rather than on the defect's origin, and was used only to shortlist. Twelve defects date to the v0.0.1 proof of concept and shipped for seven to eight weeks. They are not regressions but original assumptions nothing exercised, four of them outright latent: the videoBitrate casing was harmless until a quality picker existed to select against, and the unconditional Range header was inert until that fix made transcoded downloads actually transcode -- so DR-170's code dates to v0.0.1 while its corruption window is the single release v0.5.1. Three others are plumbing built and never connected: get_next_up_episodes accepted a series_id with no caller until v0.3.0, the sync queue ran with neither producer wired, and both watched-state backend halves sat unused. No automated check sees these; the code is present, tested and reachable in principle. Also corrects the v0.5.5 entry. fa7cb6e9 and dcf08f30 are the same diff off the same parent -- a local commit and its Gitea PR-merge twin -- and a merge chain pulled the local one into master during v0.5.5. git log v0.5.4..v0.5.5 therefore lists an autoplay fix that changed no file in the release; nextEpisodeService.ts is byte-identical across the tag boundary. That fix shipped in v0.0.2 and has not regressed. It is the one case where reading the changelog off commit subjects would have produced a false entry. scripts/build-android.sh and src-tauri/src/repository/online.rs are also modified in this tree by a concurrent session and are deliberately left uncommitted. --- CHANGELOG.md | 758 ++++++++++++++++++++++++++++++++++++++++- docs/defect-windows.md | 150 ++++++++ 2 files changed, 901 insertions(+), 7 deletions(-) create mode 100644 docs/defect-windows.md diff --git a/CHANGELOG.md b/CHANGELOG.md index fbeb0c9e..33872dcc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,295 @@ Entries are grouped by the capability they change, not by commit. Requirement IDs in parentheses point at [docs/requirements.md](docs/requirements.md); the generated trace matrix lives in [docs/traceability.md](docs/traceability.md). +For how long each fixed defect had been shipping before it was found, see +[docs/defect-windows.md](docs/defect-windows.md). + +## v0.5.5 + +### ✨ Features + +- **Library artwork is laid out as a mosaic instead of cropped to one box.** The + library overview and the home shortcut strip showed three different artwork + shapes — square music covers, 16:9 backdrops, 2:3 posters — in grids that pick + one box and crop everything to it; the home strip lined its row up by cutting + the music covers down. Both surfaces now justify rows to a shared height with + each tile as wide as its own artwork, packing from the *decoded* aspect ratio + and committing one debounced batch so the grid does not reshuffle as artwork + lands. The last row is deliberately left unstretched, so one leftover tile does + not inflate into a banner. Favourites also gain a tile per category beside the + library it belongs to — which collection type maps to which category is + Jellyfin vocabulary, so it is derived in Rust rather than rebuilding the exact + leak `SearchScope::item_types` was extracted to close. + (UR-075, UR-067 → DR-163, DR-164) + +### 🐛 Fixes + +- **The server no longer burns subtitles into the picture.** Reported as + "subtitles are shown even when off", with no toggle in the app clearing them — + because they were never the app's subtitles. `PlaybackInfo` omitted + `SubtitleStreamIndex`, which does not mean "none": the server then honours the + source's own default flag, and on the reported episode that default was a PGS + bitmap track, which cannot go out as a sidecar. So it composited the track onto + every frame. The cost landed on the *video*: burn-in rules out remuxing, so an + HEVC stream that needed only its audio transcoded was re-encoded frame by + frame, which the server could not sustain — playback stalled every few seconds + and seeks took five to nine seconds to draw a frame. The negotiation and the + stream URL now both ask for `-1` and advertise every text format the app can + render as `External`, and the picker offers only subtitles the app can actually + draw, with the codec verdict decided in Rust and carried across the boundary. + Nothing is lost: the app already fetches text tracks and draws them itself. + (UR-020, UR-004 → DR-176) + +- **Switching bitrate mid-film no longer stalls playback.** Jellyfin keys a + transcode job by device and play session, but every stream URL carried the same + hardcoded `DeviceId` and no `PlaySessionId` at all — so a second stream for an + item was indistinguishable from the first and nothing ever stopped the old + ffmpeg. The server served the new playlist and then answered 400 for its + segments. Re-opening a stream is not rare: a quality switch, a transcoded seek + and an audio-track switch all do it. Each open now mints a session id and stops + the job it supersedes, in the URL builder so every re-open path is covered by + construction. Two client faults that made the same incident worse go with it: + the fatal-HLS-error handler double-counted the transcode seek offset, so past + roughly halfway through a film any transient network error read as + end-of-stream and autoplay skipped to the next item; and the HTML5 reload + primitive resolved on its own timeout, reporting success for a reload the + server never served. (UR-074, UR-004 → DR-177) + +- **Downloading an album gets the whole album.** `download_album` read its track + list from the local catalog cache, but Jellyfin does not return `AlbumId` on + every listing endpoint, so tracks cached from one of those were invisible to + the query — three albums in the reported database had it NULL on every track. + The frontend then resolved stream URLs from its *own* list and paired them with + the returned rows by position, so a row could be handed another track's URL and + anything past the end of the shorter list never started. The same missing link + hid downloaded tracks under their album offline. The operation now belongs to + Rust end to end — the server is asked what the album contains, the album link + is written onto every track queued, URLs resolve in the backend scoped to the + rows just queued, and each track gets its own file so a title repeated across + two discs stops overwriting itself. Re-tapping download on a broken album heals + it. (DR-173) + +- **Playback positions reported to Jellyfin are real ones.** Returning to the + foreground before the background-audio stream had started playing handed the + frontend 0.0s, so the episode restarted from the beginning and the stop report + wrote that zero to the server as the resume point. The same blind spot covered + webview-rendered media, whose native position is a permanent 0 — 14 of 14 stop + reports in a 35-minute trace were zeroes, one landing 40s after the frontend + had correctly reported 15:22 for the same episode. Position is now the maximum + of the backend's reading, the last position webview media reported and the + handoff base (at most one is ever meaningful); zero-position stop reports are + withheld, since a zero is never information and only ever destroys a real + resume point; and progress is reported from the controller's own ticks — + `/Sessions/Playing/Progress` had previously been requested zero times in those + 35 minutes. A finished audio-only episode is also reported stopped at its + runtime so Jellyfin's 90% rule marks it played, which nothing else could do + once the webview was suspended. (UR-005, UR-025, UR-040, UR-071 → DR-178, + DR-179, DR-180) + +- **The streaming quality button uses a speedometer icon**, not the + cloud-download glyph that read as a download action. + + + + +## v0.5.4 + +### 🐛 Fixes + +- **Native Android video is opt-in again — enabling it by default shipped sound + with a blank screen.** The decode path was never at fault: ExoPlayer ran and + fed a live SurfaceView the whole time, behind an opaque page. The step that + clears the layers above it never took effect — the WebView was logged going + transparent `= false` and never `= true`. This is precisely what the flag + existed to contain, and v0.5.3 had turned it on so picture-in-picture would + have a real surface to shrink. Reverting costs nothing that matters: PiP drives + from the WebView `