# Changelog All notable changes to JellyTau are documented here. 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.10.0 Two things you can see, and a great deal of work on how this project builds and ships itself. The app can now update itself, and it can tell you what it did when something goes wrong — both of which existed as gaps rather than as bugs, which is why they lasted so long. ### ✨ Changes - **JellyTau can update itself.** Anyone who installed an AppImage or ran the Windows installer was frozen on that version permanently: nothing in the app ever mentioned that a newer one existed, and the release page was the only announcement. Settings → Updates now checks, shows what changed, and installs and restarts on request. Each download is verified against JellyTau's signing key before anything is installed, so a substituted file is refused rather than run. Android is deliberately not wired to this — an app may not replace its own APK, that is the system installer's job — and is given a link to the releases page instead of a button that would fail. (UR-077 → DR-217) - **You can export a diagnostics bundle.** Until now the app forgot everything it had done the moment it closed. Logs went to standard output, which nobody sees when launching from a desktop icon, and on Android went nowhere at all — so the backend was invisible on the platform where the hardest playback bugs live. A crash left nothing behind. Logs are now kept in a size-capped file that survives a restart, a crash is recorded before the app dies, and Settings → Diagnostics exports the lot as one file to attach to a bug report. Access tokens and passwords are stripped before anything is written to disk, not merely before it is exported. Nothing is transmitted anywhere; you attach the file yourself. (UR-078 → DR-218) - **Linux gets an AppImage again.** The release notes have advertised one for months while the build never produced it — the packaging step looked for the file, found nothing, and said nothing. (DR-217) ### 🐛 Fixes - **Releases no longer ship every Windows installer ever built.** Every release from v0.1.0 to v0.8.2 carried its predecessors': sixteen installers on v0.8.2, thirteen of them stale, and a download list on v0.5.0 reaching back to 0.1.0. The build directory is never cleaned and the build machine reuses it, so each release collected whatever was left behind. It went unnoticed for eight months because nothing looked wrong — the files were real and the page merely looked busy. The stale files have been removed from the published releases, the build now clears that directory first, and a check refuses to publish a release containing an artifact from a different version. (DR-220) - **Release notes now say what changed.** All 35 previous releases published the same block of generic install instructions, whose "What's New" section was a link to a file that does not resolve from a release page. Every release page now carries its own entry from this changelog, and the past ones have been filled in. (DR-219) ### 🔒 Security and supply chain - **Dependencies are now checked against a vulnerability database on every build.** They never had been. The first run found eight vulnerabilities and one unsoundness in the Rust dependency graph — all of them fixed by an update nobody had a reason to run. Licences are checked against an allow-list too, so nothing gets redistributed inside a release that does not permit it. (DR-216) - **Every release publishes checksums and a bill of materials.** `SHA256SUMS` lets you verify a download (`sha256sum -c SHA256SUMS`); the SBOM lists what went into the build, so "does this release contain ?" has an answer that is not "rebuild it and find out". (DR-216) - **Builds are reproducible again.** Every CI job named a container image tag that was rewritten in place, so rebuilding an old release did not necessarily rebuild the same thing. Jobs now pin an immutable tag. The one dependency that comes from a git branch rather than a package registry is pinned to an exact revision, closing a path by which new upstream code could arrive unreviewed in a library linked into the player. (DR-216) ### 🧹 Under the hood - Formatting, linting and type-checking now run in CI. All three were configured and enforced by nothing: 199 files did not match the project's own formatter, a type error could sit on the main branch until somebody cut a release, and the test-coverage command had been broken for months by a dependency mismatch. Coverage now has a floor that only moves up. (DR-215) - The traceability matrix counts requirements implemented by configuration. Several carried the necessary annotations and were being counted as uncovered because the extraction tool only read source files. (DR-215) - The project now has a security policy, contribution guide, code of conduct, issue and pull-request templates, and an operations document covering the builder image, the release secrets, and what losing the signing key would mean. - The app framework moved from Tauri 2.9.5 to 2.11.5. Nothing about this is visible in use, but it is worth recording that it did not go quietly: the windowing layer beneath Tauri quietly stopped publishing the Android JavaVM and application handle that this app's credential storage had been reading for its whole life. Nothing here had changed; a side effect several dependencies down had simply gone away, and the app aborted on launch on every Android device. JellyTau now sets that handle itself rather than relying on someone else to do it. Caught by installing on a real tablet before release — no test suite runs the app. (UR-012 → DR-223) ## v0.9.1 A one-line fix to the home screen, released on its own because it is the kind of small wrongness you notice every time. ### 🐛 Fixes - **Swiping the hero banner now buys you a full six seconds.** The rotation timer was started once when the banner appeared and then left alone, so a swipe, arrow or dot tap inherited whatever was left of the running countdown — swipe five and a half seconds in and the banner moved on half a second later, before you had read the title. Any manual change now restarts the countdown from that moment. (UR-034 → DR-038) ## v0.9.0 An audit release. One new setting you asked for, two naming bugs that only ever showed in builds a developer never looks at, and a large amount of tidying that should be invisible in use. Note for anyone upgrading a Linux package: the Debian/RPM package is now called `jelly-tau` rather than `jellytau` (the packager derives it from the app name). It declares the rename, so `apt`/`dnf` will replace the old package rather than install a second copy. The command is still `jellytau`. ### ✨ Changes - **You can now hide library folders from music browsing.** Pick the folders to exclude in Settings; they disappear from albums, artists, genres, search and the home rows alike. This replaces a filter that dropped anything *named* "Podcasts" — one person's library layout compiled into the app, which meant an album genuinely called "Podcasts" vanished while a podcast folder named anything else stayed. Exclusion now matches on the folder itself, is decided in one place rather than at the six screens someone remembered to filter, and defaults to excluding nothing. (UR-076 → DR-209) - **The app is called JellyTau again.** The Android release build showed `jellytau` under its icon, and the Linux and Windows packages carried the same lowercase name. The debug build has always overridden the label to "JellyTau Debug", so the install a developer looks at every day was the only correctly cased one and nobody saw it. (DR-214) - **The RPM package is published.** It has been built by every release since Linux packaging was added, and never copied out of the build — so it existed, cost build time, and reached nobody. (DR-214) - **Linux and Windows packages carry their own metadata.** Publisher, copyright, category, description and licence were all absent, so the packages installed with no maintainer and no description. The hand-written Arch package had all of it; only the generated packaging was missing it. (DR-214) ### 🔒 Hardening None of these were reachable in normal use — the app refuses plain-`http` servers, Android blocks cleartext, and the webview runs under a CSP that bars inline script — so they are consistency fixes rather than incidents. Each one had the correct pattern already in the same file, a few lines away. - **Thumbnail cache writes stay inside the cache directory.** The filename was built from three values but only one was sanitised, and joining a path does not fold `..` or keep the base when handed an absolute path. (DR-210) - **Download paths stay inside the download directory.** A correct sanitiser already existed, but the command that queues a download accepted a raw path, so the guard could be routed around rather than being absent. (DR-211) - **Query and URL values are bound and encoded, not pasted in.** The offline item-type filter built SQL by string formatting while its sibling query used placeholders, and browse URLs left values unencoded while the genre parameter next to them was encoded properly. Volume is also range-checked at the command boundary instead of relying on each player backend. (DR-212) ### 🛠 Development Nothing here changes the app, but the previous release's audit found the tooling claiming more than it delivered, and this is the repair. - **The frontend has a real logger.** 484 `console` calls shipped to users and ran on every device; the Rust half has had levelled logging with a runtime override since the beginning. There is now a matching facade — quiet in release builds, verbose in debug ones, with warnings and errors never suppressed and `localStorage` able to turn the volume up in a shipped build to diagnose a problem. (DR-204) - **The traceability matrix is navigable.** Every one of its ~2,800 file links was broken: the generator wrote repo-root paths into a file that lives in `docs/`. The document the whole traceability system exists to produce could not be clicked through, and had no test. Both are fixed, and a link checker now fails the build on a dead documentation link. (DR-093, DR-208) - **The Rust toolchain is pinned.** Developer machines and CI were five releases apart, which meant a clean `cargo clippy` locally proved nothing about CI — the same tree measured zero warnings on one and three on the other. With both sides on the same compiler, clippy is now a hard gate instead of advisory. (DR-206) - **The frontend has a linter and formatter**, its first — the Rust half has had `cargo fmt --check` and clippy in CI for a while. A pre-commit hook runs the fast checks, so the "before committing" list is enforced rather than remembered. (DR-205, DR-207) - **Containerised builds no longer leave root-owned files** in the working tree, which had accumulated to the point of breaking `cargo clean` and, eventually, `cargo build` itself. (DR-213) - Removed: a webdriverio end-to-end suite that had not run in seven months and was wired into nothing, and a frontend validation module whose six exported functions had no caller outside their own tests — which made it read as covered input validation while guarding nothing. ## v0.8.2 A single fix, for Android background audio. ### 🐛 Fixes - **Listening to a video in the background no longer jumps back to where you started.** Handing a video off to background audio streams a live mp3 transcode, which is chunked — no length, and no duration the player can read. ExoPlayer resumes a failed load in place only when it knows one of those two things; with neither it assumes the source is live and re-requests the URL from the beginning. That URL starts at the moment you locked the screen, so a network blip left a retry armed, and when the buffer eventually ran dry — minutes later, with nothing in between — playback silently resumed from the handoff point and carried on. No error was raised and nothing ended, so none of the existing stream-recovery paths could see it; the only sign was a position that went backwards, which is why it looked random. The player is now refused its own retry for exactly that kind of stream, so the failure surfaces and the backend re-opens the stream at the position playback actually reached, keeping your selected audio track. Music and video are untouched: both declare their timeline, and the player resumes them where the load stopped. (UR-040, UR-004 → DR-203) ## v0.8.1 A single fix, for Android. ### 🐛 Fixes - **The screen no longer sleeps while you are watching something.** Android counts its display timeout from the last time you touched the phone, and watching a film is exactly when you do not — so the picture dimmed and the screen went out mid-playback unless you kept tapping it. Nothing in the app ever asked the display to stay on, and neither video renderer does so by itself: ExoPlayer's wake mode keeps the CPU and wifi alive but says nothing about the screen, and an embedded WebView does not take the display wake lock that a browser takes for `