From 4bce81a800056821b77e0af5be66093a1d0cd4e3 Mon Sep 17 00:00:00 2001 From: Duncan Tourolle Date: Mon, 7 Sep 2026 22:29:40 +0200 Subject: [PATCH] chore(release): v0.11.6 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Seven fixes from an audit of the stack's most fragile seams, each with a test that fails without it. Two could take the app out entirely: an interrupted database migration left it unable to launch at all, and an unguarded panic at the Android JNI boundary aborted the process outright. Frontend gates (bun run test/check/lint/format:check) were not run for this release — node on the release machine is missing libada.so.3 and exits 127. All changes are under src-tauri/; CI runs those gates. --- CHANGELOG.md | 78 +++++++++++++++++++++++++++++++++++++++ package.json | 2 +- src-tauri/Cargo.lock | 2 +- src-tauri/Cargo.toml | 2 +- src-tauri/tauri.conf.json | 2 +- 5 files changed, 82 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e7dbb656..b438aac29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,84 @@ 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.11.6 + +Found by an audit of the stack's most fragile seams rather than by hitting them, +so most of these are faults that had not yet been reported — several could only +be reached on a bad day, and the worst of them only once. + +### 🐛 Fixes + +- **An interrupted update can no longer stop the app from ever opening again.** + Changes to the local database were applied one statement at a time with no way + to undo a half-finished one. If an update was interrupted partway — a full + disk, the phone reclaiming memory, the app being killed mid-launch — the + earlier statements stuck while nothing recorded that the change had happened. + On the next launch it started again from the beginning, immediately hit the + part that was already done, and gave up; and since the app treats a database + it cannot prepare as fatal, it stopped opening at all, on every launch, with + the only way out being to clear its data and lose downloads and sign-ins. Each + change is now all-or-nothing, so an interrupted one leaves no trace and the + next launch simply tries again. (UR-002 → DR-012) + +- **The app no longer vanishes without trace when the player hits trouble.** + The parts of the Android player that report back into the app — position, + state changes, errors, the end of a track — had no protection around them, and + a failure inside one killed the whole app instantly: no error, no message, not + even a crash report worth sending. One such failure was reachable in ordinary + use, on the position report that fires four times a second: under memory + pressure the app could fail to build the small worker it needs to send that + report, and that alone was enough to take everything down. A dropped position + report is now just a dropped position report. (UR-005 → DR-052) + +- **One internal failure no longer disables the whole app until it is restarted.** + Every part of the app that reads or writes local data shares a single gate to + it. If anything failed while holding that gate, the gate stayed jammed: from + then on every library page, download, setting and sign-in returned an error for + the rest of the session, and only quitting and reopening cleared it. The gate + now recovers instead of jamming. (UR-002 → DR-012) + +- **Browsing offline no longer reports a network error over content already on + the device.** A read of local data was given a tenth of a second to answer and + otherwise abandoned and treated as "nothing stored". That is easily exceeded + on phone storage whenever something else is writing — a sync catching up, a + batch of artwork being saved — and offline, where there is no server to fall + back to, the result was a network error shown over a library that was sitting + on disk. Worse, the abandoned read kept running and kept the storage busy, + making the next one slower still. A slow read is now waited for rather than + thrown away, and a fast one still answers immediately as before. (UR-002 → + DR-013) + +- **A download that arrived empty is no longer presented as ready to play.** If + the server answered a download with nothing at all — an error page, a + conversion that produced no output — the empty file was moved into place and + the item was marked available offline. Opening it then hung: the app's own + media server promised one byte of it and sent none, so the player waited + forever with nothing on screen to say why. An empty download is now treated as + the failure it is, keeping the partial file so it can resume, and a request for + an empty file gets an honest refusal instead of a promise. (UR-019, UR-071 → + DR-168, DR-137) + +- **Renaming your computer no longer signs you out.** On systems without a + password manager, sign-in tokens are kept in a file whose key was rebuilt from + the machine's name and the current username each time the app started. Rename + the machine, or launch it from somewhere the username is not set, and the key + came out different, the file could no longer be read, and the app treated that + as never having been signed in — with nothing shown to explain it. The key is + now made once, kept, and unaffected by what the machine is called. Existing + saved sign-ins are carried over automatically. This file has never been a + substitute for a real password manager, and the app now says so plainly rather + than implying otherwise. (UR-012 → IR-014) + +- **The player's internal locking is now checked rather than merely careful.** + The playback controller coordinates seventeen separate pieces of shared state + across the audio engine, the lock screen, timers and every screen in the app. + Nothing stopped two of them being taken in opposite orders by different parts + of the code, which freezes playback outright with no error anywhere — a fault + this part of the app has produced before. The correct order is now written down + and enforced automatically, so a future change cannot quietly reintroduce it. + No such fault existed; this keeps it that way. (UR-005 → DR-052) + ## v0.11.5 ### 🐛 Fixes diff --git a/package.json b/package.json index dc2ca3c6d..d6fdaec3d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jellytau", - "version": "0.11.5", + "version": "0.11.6", "description": "A cross-platform Jellyfin client built with Tauri, SvelteKit and Rust.", "author": "Duncan Tourolle ", "license": "MIT", diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 685e54de8..2befa4465 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -2209,7 +2209,7 @@ dependencies = [ [[package]] name = "jellytau" -version = "0.11.5" +version = "0.11.6" dependencies = [ "aes-gcm", "argon2", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 991a2c833..664d48bae 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -4,7 +4,7 @@ name = "jellytau" # `player-conformance`, and a second binary makes a bare `cargo run` — # which `tauri dev` issues — ambiguous. default-run = "jellytau" -version = "0.11.5" +version = "0.11.6" description = "A cross-platform Jellyfin client" authors = ["Duncan Tourolle "] license = "MIT" diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index e92459e68..822f2a616 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "JellyTau", - "version": "0.11.5", + "version": "0.11.6", "identifier": "com.dtourolle.jellytau", "build": { "beforeDevCommand": "bun run dev",