SoulSync 2.6.4 — Merge dev → main
Patch release on top of 2.5.2 (the last tag on main). The 2.6.x line was bumped on dev but never tagged / published, so this is the first time all of it reaches users. Big patch — bug fixes across downloads / wishlist / library, a chunk of internal hardening, and some UI consistency work.
Version constant: 2.6.4 (web_server.py:_SOULSYNC_BASE_VERSION).
Headline bug fixes
#745 — Every download saved with the first 4 chars of the album name where $year should be.
The $year template variable was a blind release_date[:4] slice. When something upstream put the album NAME into release_date, that slice emitted garbage — "Mantras (Deluxe)"[:4] → "Mant", so files landed in Mantras (Deluxe) (Mant) [Album]/ instead of (2026). Added _extract_year_from_release_date(): returns the leading 4 chars only when they're a plausible year (isdigit, 1900 < y ≤ 2100), else empty — matching the guard soulid_worker._extract_year already uses. A non-year now resolves to empty and the template's bracket-cleanup drops the empty (). Fixed at the shared post-process path builder (core/imports/paths.build_final_path_for_track) that downloads, reorganize, AND imports all route through, so it's covered everywhere at once. Defensive layer — stops the symptom regardless of which upstream poisons release_date; the exact upstream (Soulseek + AcoustID + a future-dated album) is still being chased separately.
#746 — Library Reorganize pulling files back out of the /deleted folder.
The Duplicate Cleaner quarantines de-duped files into <transfer>/deleted/. If a user's media server scans the transfer folder (e.g. a /music root holding both the library and the transfer dir), those quarantined files get real DB rows — and Reorganize, being purely DB-driven, would dutifully move them back OUT of /deleted to the template location. We can't stop the rows existing (they come from the server), so the fix is bounded to Reorganize as the reporter asked: skip any track whose resolved path is under <transfer>/deleted. Anchored to the prefix (not a substring) so a real album like "Deleted Scenes" is kept; surfaced as a skip in the preview, mirrored on apply.
#740 — Wishlist album-bundle downloads jamming the whole queue.
Per-album wishlist bundles were blocking the shared 3-worker missing-tracks pool (a 2.6.3 regression). Moved album-bundle downloads onto a dedicated album_bundle_executor so a big bundle can't starve the per-track pool. User-validated on a 73-track run.
#747 — Soulseek album poll hangs on a stalled/dead peer.
A peer that stalls (stuck InProgress/Queued, dropped transfers, or "Completed, Aborted" at 0 bytes) used to spin the bundle poll to its full ~6h timeout. Three fixes: a 180s bundle-level stall guard (resolves with whatever completed when nothing progresses); "Aborted"/"Cancelled" correctly classified as failed (the "Completed, Aborted" string was misread as a completed-but-missing file); and when the chosen folder yields nothing usable, fall back to the proven per-track flow instead of hard-failing the batch. failed batches now also age out of the stuck-batch cleanup.
#732 — Search results disappear when interacting with the media player.
The outside-click allow-lists didn't include the player popup, so clicking into the mini-player counted as a click-away and tore down the results. Added the player to the allow-lists.
#735 — Import overwriting the album-artist tag to "Unknown Artist".
#736 — Spotify playlist sync showing only the first 100 tracks (pagination cutoff).
#722 — Duplicate tracks in albums with Japanese / CJK titles (similarity normalisation didn't fold CJK width/forms).
#721 — Usenet album bundles stuck on "downloading release" / 99–100%. Two-stage SAB History handoff: 2.6.4 covers the gap where SAB flips status to Completed before its post-processing writes the final storage field, plus writable staging dir + client→local path resolution.
#728 — Usenet album progress fetch from SAB (contributor PR, @IamGroot60).
Also: enhanced artist view no longer 404s for library artists opened via source ID; MusicBrainz artist discography no longer capped at 25 releases; album art now embeds at highest available resolution across all art paths (HiFi/MB cover art uses the CAA 1200px thumbnail instead of the flaky /front original).
Internal hardening (no user-facing change, lots of safety)
Wishlist orchestration unified. Manual wishlist runs now route through the same shared engine as auto (_run_wishlist_cycle), with batch-row construction centralized in make_wishlist_batch_row. Auto behavior proven unchanged; manual is now identical to auto instead of a parallel reimplementation.
DB schema hardening. Added a schema_migrations ledger + PRAGMA user_version backstop; stopped watchlist_artists rebuilds from silently dropping amazon_artist_id; normalized legacy comma-separated genres to canonical JSON; introduced a canonical source-ID registry (core/source_ids.py) and adopted it at the highest-value sites.
Discovery endpoints lift (10 commits). Collapsed ~10 families of near-duplicate per-source discovery endpoints (get_*_sync_status, reset_*_playlist, start_*_sync, etc.) into shared helpers.
Exception surfacing. ~70 silent except: pass sites across remaining modules now log instead of swallowing.
UI consistency
- New shared
.btnprimitive +.btn--sm/.btn--block/.btn--warningtiers; migrated config-modal, wishlist, watchlist, and sync-history button families onto it. - Shared
.page-shellcard primitive extracted; automations, playlist-explorer, settings, sync, and exception pages adopted it..tab/.cardprimitives added. - Downloads moved above Automations in the sidebar.
- Tools → Database Updater: added a Deep Scan option.
- Basic search visual overhaul + per-source picker in hybrid mode; standardized artist-detail hero action buttons.
Test plan
- Full suite green on
dev(4400+ tests) - #745: import-path tests — differential-verified the poisoned
release_dateproduces the exact(Mant)folder without the fix, clean folder with it; real years preserved - #746: 8 unit + 2 integration reorganize tests, differential-verified; 128 adjacent reorganize tests green
- #747: 6 poll-stall + 2 fallback tests (fake-clock), differential-verified; live Slipknot run no longer wedges the queue
- #740: dedicated-pool fix user-validated on a 73-track run