@ -3413,9 +3413,9 @@ function closeHelperSearch() {
// projects that span multiple commits before shipping. Strip the flag at
// release time and add a real `date:` line at the top of the version block.
constWHATS_NEW={
'2.4.3':[
// --- post-release patch work on the 2.4.3 line — entries hidden by _getLatestWhatsNewVersion until the build version bumps ---
{date:'Unreleased — 2.4.3 patch work' },
'2.5.0':[
// --- May 10, 2026 — 2.5.0 release ---
{date:'May 10, 2026 — 2.5.0 release' },
{title:'Tidal: Favorite Tracks Now Show Up As A Playlist (Same As Spotify Liked Songs)',desc:'github issue #502 (yug1900): tidal users wanted their favorited tracks ("my collection" in the tidal app) to appear alongside their normal playlists in the sync tab — same treatment spotify gets for "liked songs". prior attempt at this surfaced empty data because the wrong endpoint was being hit (`/v2/favorites?filter[type]=TRACKS` returns nothing for personal favorites — that endpoint is scoped to collections the third-party app created itself, not the user\'s app-level favorites). reporter located the working endpoint: `GET /v2/userCollectionTracks/me/relationships/items?countryCode=US&locale=en-US&include=items`. cursor-paginated (20 per page, follow `links.next` with `page[cursor]=...` until exhausted), responses only carry track-level attributes — artist + album NAMES come back as relationship-link stubs, not embedded data. fix: two-phase fetch. phase one walks the cursor chain to enumerate every track id (cheap, IDs only). phase two batch-hydrates 20 IDs at a time through the existing `_get_tracks_batch` helper which already knows how to `include=artists,albums` and produce fully-populated `Track` objects matching the rest of the codebase — no duplication of the JSON:API artist/album parse, no new dataclass shape. virtual playlist `tidal-favorites` appended to the end of `/api/tidal/playlists` (mirrors spotify\'s liked-songs placement). id intentionally has NO colon — sync-services.js renderer interpolates ids into css selectors via template literals (`#tidal-card-${p.id} .foo`) and a colon would parse as a css pseudo-class operator. `tidal_client.get_playlist("tidal-favorites")` recognizes the virtual id and dispatches to the collection path internally, so every existing per-id consumer gets it for free: per-playlist detail endpoint, mirror auto-refresh automation, "build spotify discovery from tidal playlist" flow. needs token reconnect to grant the new `collection.read` oauth scope (added to the auth flow). existing tokens hit a 401 — the client now sets a `_collection_needs_reconnect` flag and the listing endpoint surfaces a placeholder card titled "Favorite Tracks (reconnect Tidal to enable)" with a description pointing at settings, so the user has something visible to act on instead of a silently missing row. 22 new tests pin the cursor walk (full chain, max-ids cap mid-page + at page boundary), auth gates (no token / 401 / 403 all bail clean), reconnect-flag lifecycle (set on 401/403, cleared on next successful walk, NOT set on 5xx so transient server errors don\'t falsely tell the user to reconnect), forward-compat type filter (non-track entries skipped), count helper, batch hydration delegation + chunking at the 20-per-batch cap, partial-batch failure containment, and the virtual-id dispatch (real playlist ids still flow through the normal path).',page:'sync'},
{title:'Library Reorganize: Stop Leaving Orphan Audio Files Behind + Hint For Unknown-Artist Rows',desc:'discord report (foxxify): library reorganize wasn\'t organizing everything. two distinct gaps. (A) lossy-copy users have `track.flac` AND `track.opus` side-by-side at the source; the db only knows about ONE of those (whichever is the canonical library entry). reorganize moved the canonical, left the other format orphaned at the old location, and the empty-folder cleanup never fired because the source dir still had audio in it. fix: at the per-track finalize step the reorganize code now scans the source dir for sibling-stem audio files (same filename stem, audio extension, different format), moves them to the same destination dir as the canonical with the renamed stem + their original extension, then proceeds with the existing source removal + cleanup. preserves both formats post-move so users keep their flac archive AND their opus library copy. (B) old "Unknown Artist / album_id / 0 tracks" rows left over from the pre-#524 manual-import bug couldn\'t be relocated because the album row has no usable metadata source id — reorganize emitted a generic "run enrichment first" message that doesn\'t apply (enrichment can\'t fix these rows; they need their real metadata recovered from file tags). these are the existing `Fix Unknown Artists` repair job\'s domain — reads file tags, re-resolves artist/album/track via configured metadata source, re-tags + moves. reorganize now detects the bad-metadata shape (Unknown Artist OR album.title that\'s a 6+ digit numeric id) and emits a clear "run the Fix Unknown Artists repair job to recover real artist/album from file tags first" hint instead, pointing the user at the right tool. fixer was already implemented and handles the case end-to-end — discoverability gap, not a logic gap. 31 new tests pin: orphan-format detection (canonical-vs-sibling, multi-format, defensive on missing source dir, sidecar exclusion), sibling-move with renamed-stem propagation + dst-dir creation + idempotent re-runs + os-failure handling, and the unknown-artist-hint detection helpers (placeholder names, numeric-id title detection at 6+ digit cutoff, real-album-with-no-source-id keeps the generic enrichment hint, strict-source mode preserved when artist/title look fine).',page:'library'},
{title:'AcoustID Scanner: Compilation Albums No Longer Flag Every Track',desc:'discord report (skowl): downloaded a compilation album like "high tea music: vol 1" where every track has a different artist (eclypse, andromedik, t & sugah, gourski, himmes, sektor, lexurus, etc.) and the acoustid scanner flagged every single track as wrong song — the file tag had the correct per-track artist (e.g. "eclypse" for "city lights") but the scanner compared against the album-level artist ("andromedik", the curator). raw similarity 12% → wrong song flag. the multi-value-credit fix from the prior pr (foxxify) didn\'t help because both sides were single-value but DIFFERENT artists. cause: scanner sql joined `artists` table via `tracks.artist_id` which points at the ALBUM artist, not the per-track artist. but `tracks.track_artist` column was already populated with the correct per-track value by every server scan + auto-import path that handles compilations. scanner just wasn\'t reading it. fix: changed the scanner select to `COALESCE(NULLIF(t.track_artist, \'\'), ar.name)` — prefers per-track artist when populated, falls back to album artist for legacy rows / single-artist albums where track_artist is null. NULLIF handles the empty-string-instead-of-null case for legacy data. composes with foxxify\'s multi-value fix — for the rare compilation track where acoustid ALSO returns a multi-value credit, both paths work together. 2 new tests pin: compilation track uses per-track artist (reporter\'s exact case), null/empty track_artist falls back to album artist via coalesce.',page:'library'},
@ -4311,7 +4311,7 @@ function _getLatestWhatsNewVersion() {