Commit graph

117 commits

Author SHA1 Message Date
Broque Thomas
b0a07f993a Clean up stale Artists-page references in helper.js + docs.js
helper.js click-for-help annotations had ~10 entries pointing at DOM
elements from the retired inline Artists page (#artists-search-input,
#artists-back-button, #artists-results-state, #artists-cards-container,
#artists-hero-section, .artists-hero-name/badges/genres/bio/stats,
#artist-detail-watchlist-btn/-settings-btn, .artist-detail-tabs,
#albums-tab, #singles-tab, #album-cards-container, #singles-cards-
container) plus #similar-artists-section pointing at the legacy id
(now #ad-similar-artists-section on the standalone page).

Replaced the dead Artists-page block with annotations that target
elements actually present on the standalone /artist-detail page:
.album-card, .completion-overlay, #ad-similar-artists-section,
.similar-artist-bubble, plus a new entry for .search-source-picker-
container on the unified Search page.

docs.js: 'How to: Set Up Auto-Downloads' step 1 used to read 'Search
for artists on the Artists page and click the Watch button on each
one'. Updated to 'Find artists via the Search page (or click an
artist anywhere in the app), then click the Watch button on the
artist detail page' — matches the post-unification flow.

Backend API endpoint references in docs.js (/library/artists, etc.)
are unrelated to the retired frontend page and stay as-is.
2026-04-22 20:29:32 -07:00
Broque Thomas
1a8071d6ec Revert "Retire artists.js and inline Artists page, ship unification at 2.40"
This reverts commit 71ff5cb5c3.
2026-04-22 15:52:53 -07:00
Broque Thomas
71ff5cb5c3 Retire artists.js and inline Artists page, ship unification at 2.40
Part D + E of the deferred cleanup + the final version bump that
publishes the whole Search/Artists unification project.

Deletions:
  - webui/static/artists.js (1903 lines) — removed entirely. The 2
    remaining externally-referenced helpers (lazyLoadArtistImages +
    showCompletionError) moved into shared-helpers.js first.
  - webui/index.html — 140-line #artists-page HTML block and the
    <script src="artists.js"> tag both removed.

init.js wiring:
  - 'case artists:' removed from loadPageData switch (no page to init).
  - navigateToPage top-level alias extended: 'artists' → 'search'
    (same pattern as the existing 'downloads' → 'search' alias).
    Legacy /artists bookmarks land on the unified Search page, the
    natural place to find an artist now.
  - _getPageFromPath now maps artist-detail → library as its parent
    (was artists). Matches the existing library-nav-highlight at
    init.js:2161.

Version bump:
  - _SOULSYNC_BASE_VERSION 2.39 → 2.40.
  - WHATS_NEW entries lose the 'unreleased' scaffolding and gain a
    new top entry summarizing the unified artist-detail page + the
    final artists.js retirement.
  - version-info modal gets a 'Search & Artists Unification' section
    at the top.
  - The _getLatestWhatsNewVersion filter added during the unreleased-
    tracking phase is rolled back — entries now display as soon as
    they land in WHATS_NEW, matching the pre-unification behaviour.

Test suite:
  - tests/test_script_split_integrity.py SPLIT_MODULES updated:
    'artists.js' dropped, 'shared-helpers.js' added. escapeHtml's
    cross-file dupe list entry updated to reference shared-helpers.
  - 354/354 tests pass.

User-visible result after this commit:
  - Sidebar: Search, Downloads, Discover, Library, Wishlist, etc. —
    no more Artists entry.
  - Click any artist anywhere: lands on the same /artist-detail page.
  - Search page has a source dropdown; Soulseek is just another option.
  - Legacy /downloads and /artists URLs alias to /search.
  - Version button shows v2.3 (Docker major); "What's New" panel
    opens to the unification summary.

Closes the project Cin requested in Discord. Future work: source-aware
/api/artist-detail could be extended to fall back through the whole
source priority chain when a specific source is given but returns no
discography. Not needed for the current flows.
2026-04-22 15:38:32 -07:00
Broque Thomas
3c7dc4de6e Artist detail back button falls back to Search, not Dashboard
When the user reached the inline Artists detail view from outside
the Artists page and no browser history is available (direct
bookmark), fall back to the Search page instead of Dashboard.
Search is the natural next step for finding a different artist.
2026-04-22 14:29:30 -07:00
Broque Thomas
78c14d3084 Hold version at 2.39 and fold unification changelog into one 2.40 entry
Reverts the 2.40→2.49 version spam from this session — every phase
commit was bumping the display version when the whole Search/Artists
unification project should really be a single release.

Changes:
  - _SOULSYNC_BASE_VERSION back to 2.39
  - All session-level version-info sections consolidated — the endpoint
    response is back to the pre-session 2.39 shape
  - helper.js WHATS_NEW entries for 2.40–2.49 collapsed into a single
    '2.40' block with one bullet per phase, marked unreleased
  - _getLatestWhatsNewVersion / _showOlderNotes filter out entries
    whose version is higher than the current build, so the 2.40 block
    won't fire the 'new' badge or appear in the What's New panel until
    we actually flip the build version
  - Picks up the artist-detail back-button fix from the previous turn
    (falls back to browser history when the user reached the inline
    detail from outside the Artists page)

When the unification project is done, a single commit that bumps
_SOULSYNC_BASE_VERSION to 2.40 will publish the whole folded entry.
2026-04-22 14:25:20 -07:00
Broque Thomas
19e9174866 Fix 404 on source-artist click — revert Phase 4a source migrations, bump to 2.48
Phase 4a (9361c29) mistakenly routed every artist click to
navigateToArtistDetail, which fetches /api/artist-detail/<id>. That
endpoint only knows how to look up local DB primary keys. For source
artists (Spotify/Deezer/iTunes/etc.) the id is a metadata-source id,
not a library PK — so clicks 404'd out.

Library artists (db_artists section in search results, library page
clicks, stats links, media player) continue to go to the standalone
/artist-detail page as before. Source artists now route back to the
Artists page's inline view via selectArtistForDetail, which calls
/api/artist/<id>/discography with a source param — the endpoint that
actually handles non-library IDs.

Reverted 7 migration points:
  - search.js: Enhanced Search source-artists onClick
  - downloads.js: global widget _gsClickArtist non-library branch
  - downloads.js: _navigateToArtistFromModal fallback
  - discover.js: viewRecommendedArtistDiscography
  - discover.js: viewDiscoverHeroDiscography
  - discover.js: 'Your Artists' card name-click inline HTML
  - discover.js: 'Your Artists' info-modal 'View All' button
  - discover.js: artist-map context menu
  - discover.js: genre-deep-dive artist click
  - api-monitor.js: watchlist artist discography view

Phase 4a's goal of "one artist page for everything" is deferred —
it needs backend work on /api/artist-detail to accept a source param
and fall back to metadata-source lookup when the local DB lookup
fails. Keeping the signature extension on navigateToArtistDetail
(source parameter) in place for when that lands.
2026-04-22 14:17:06 -07:00
Broque Thomas
d037643908 Clean up interactive help annotations for unified Search, bump to 2.47
Phase 4c of the Search/Artists unification — docs-only cleanup.

The click-for-help system and the 'Your First Download' guided tour
referenced elements that no longer exist (the Basic/Enhanced toggle,
the embedded download-manager toggle, the active/finished queue
panels). Updated annotations + tour steps to match the current UI.

  - New annotation for .search-source-picker-container (the dropdown)
  - Removed 6 annotations for deleted elements
  - 'first-download' tour now walks users through the source picker
    and uses page: 'search' (PAGE_TOUR_MAP accepts both 'search' and
    the legacy 'downloads' id so older bookmarks still match)
  - Retired the 'artists-browse' standalone tour — no sidebar entry
  - Dropped the dead #finished-queue detection in the setup milestone
    check (the dashboard stat card is the single source of truth)
2026-04-22 13:59:42 -07:00
Broque Thomas
09f15ce7d2 Retire Artists sidebar entry, redirect entry points to Search, bump to 2.46
Phase 4b of the Search/Artists unification. Cin flagged that 'Artists'
in the sidebar read like a library section but was actually a
dedicated artist-search page, duplicating what unified Search already
does. Removed the sidebar entry so users funnel through Search.

  - Sidebar Artists button gone
  - 'Browse Artists' on empty Watchlist now opens Search
  - 'View artist from Wishlist' opens Search pre-filled with the name
  - Profile Home Page + Page Access drop the Artists option

artists.js stays on disk: it defines ~30 shared helpers used across
the app (escapeHtml, openDownloadMissingModalForArtistAlbum, service
status, download bubbles, image helpers) that library/discover/etc.
depend on. Wholesale deletion would orphan too much. The inline
Artists page and its selectArtistForDetail flow are still there —
just unreachable from the sidebar — so /artists deep links keep
working for bookmarks.
2026-04-22 13:40:22 -07:00
Broque Thomas
9361c29965 Route all artist-detail callers to the standalone page, bump to 2.45
Phase 4a of the Search/Artists unification. The app had two artist-
detail implementations: the standalone page Library navigates to via
navigateToArtistDetail (its own route, deep-link support, highlights
Library in the sidebar), and an inline state inside the Artists page
reached via selectArtistForDetail. They rendered similar content but
were separate code paths and kept drifting apart (PR #356 just had
to fix source propagation in both).

Every external caller of selectArtistForDetail (9 sites across
api-monitor.js, discover.js, downloads.js, search.js) now calls
navigateToArtistDetail(id, name, source) directly. Removed ~63 lines
of the navigate-then-setTimeout-then-select dance. Source context
(Spotify/iTunes/Deezer/etc.) carries cleanly through via the new
third argument.

Artists sidebar entry, its inline search, and selectArtistForDetail
all still work — they just have no external callers. Phase 4b will
retire the sidebar entry and artists.js.
2026-04-22 13:36:36 -07:00
Broque Thomas
f203b3e46d Remove embedded Download Manager from Search page, bump to 2.44
Phase 3c of the Search/Artists unification. The Search page carried
a second copy of the Download Manager (active + finished queues,
clear/cancel-all buttons) that was hidden by default and duplicated
the dedicated Downloads page. That duplicate is now gone.

Removed:
  - Side-panel HTML block and the toggle button that showed/hid it
  - ~290 lines of polling + render infra in downloads.js: loadDownloads-
    Data, startDownloadPolling/stopDownloadPolling, updateDownload-
    Queues, renderQueue, updateTabCounts/updateDownloadStats,
    initializeDownloadTabs/switchDownloadTab, cancelDownloadItem,
    clearFinishedDownloads, cancelAllDownloads, and the
    activeDownloads/finishedDownloads globals
  - initializeDownloadManagerToggle and its call from init.js
  - Stopped hitting /api/downloads/status every second on the Search
    page (the dedicated Downloads page already polls its own view)

CSS grid for the Search page collapsed from '1fr 370px' to '1fr' now
that the right panel is gone. Unused .controls-panel__* / .download-
manager__* / .downloads-side-panel CSS rules kept in place — harmless,
can be pruned later.
2026-04-22 13:31:42 -07:00
Broque Thomas
6992e2e5b5 Rename Search page id from 'downloads' to 'search', bump to 2.43
Phase 3b of the Search/Artists unification. The Search page's
internal id was 'downloads', which clashed with the actual Downloads
page (id 'active-downloads') and confused anyone reading the code.
Renamed to 'search' across HTML, navigation, DOM selectors, and the
deep-link route list.

Backwards compat: navigateToPage('downloads') aliases to 'search'
at the top of the function; /downloads URL still serves index.html
and the client router resolves the page correctly; profile ACL
checks accept both 'search' and 'downloads' so existing profiles
with 'downloads' in allowed_pages keep working without migration.

Sidebar label unchanged. Zero visual change — pure internal tidy.
2026-04-22 13:22:49 -07:00
Broque Thomas
68d46c5aba Replace Enhanced/Basic toggle with source picker, bump to 2.42
Phase 3 of the Search/Artists unification. The Search page's two-mode
toggle is replaced by a single 'Search from' dropdown: All sources
(Auto), Spotify, Apple Music, Deezer, Discogs, Hydrabase, MusicBrainz,
or Soulseek (raw files). Auto keeps today's fan-out behavior for
backwards compatibility; picking a specific source hits only that
provider. 'Soulseek' routes to the raw-file basic section, so one
picker covers both old modes. Loading text and the enhanced fetch
now respect the selected source. Zero API changes — uses the source
param added in 2.40 and the shared fetch helper from 2.41.
2026-04-22 13:06:13 -07:00
Broque Thomas
377343326f Dedupe enhanced-search fetch in widget and page, bump to 2.41
Phase 2 of the Search/Artists unification: the Search page dropdown
and the global spotlight widget both POST to /api/enhanced-search
with identical boilerplate. Extracted into enhancedSearchFetch() in
search.js (loaded before downloads.js). Both callers migrated. Zero
UX change — purely sets up Phase 3 to wire a source picker in one
place instead of two.
2026-04-22 12:59:05 -07:00
Broque Thomas
952c35de39 Add source param to /api/enhanced-search, bump to 2.40
Phase 1 of the Search/Artists unification project: the endpoint now
accepts an optional `source` (spotify, itunes, deezer, discogs,
hydrabase, musicbrainz) so callers can target a single metadata source
instead of always fanning out. Omitted or `auto` preserves current
multi-source behavior — no existing callers break. Cache keys include
the source tag so per-source and fan-out results don't collide.
2026-04-22 12:49:49 -07:00
Broque Thomas
8f85b0c251 Fix silent wrong-artist track downloads (Maduk/Tom Walker bug)
User reported searching "Maduk - Leave A Light On" on Tidal silently
downloaded Tom Walker's completely different song of the same name, then
embedded Maduk's metadata into Tom Walker's audio. Three layers of
defense all failed permissively. Two of them are fixed here; the third
(score formula weights) was left alone since these two together cover it.

Layer 1 fix — candidate artist gate (web_server.py:27782)
  Old: `if _best_artist < 0.4 and confidence < 0.85: continue`
  New: `if _best_artist < 0.5 and confidence < 0.85: continue`

  SequenceMatcher returns exactly 0.400 for "maduk" vs "tom walker"
  (5-char vs 10-char strings with coincidental char matches), which
  slipped past the strict `< 0.4` check. The word-boundary containment
  check earlier in the function already short-circuits legitimate
  formatting variations to sim=1.0, so falling to SequenceMatcher means
  strings are genuinely different. 0.5 closes the fencepost AND gives
  a small safety buffer.

Layer 3 fix — AcoustID verification (acoustid_verification.py:316)
  When title matches but artist doesn't AND expected artist isn't found
  anywhere in AcoustID's returned recordings:
    Old: always SKIP (let file through, assume cover/collab)
    New: FAIL if artist_sim < 0.3 (clear mismatch)
         SKIP if artist_sim >= 0.3 (ambiguous — cover/collab/formatting)

  The 0.3 cutoff catches hard mismatches like Maduk/Tom Walker (sim ~0.2)
  while preserving benefit-of-the-doubt for borderline artist formatting
  differences. Legitimate covers and collabs where the expected artist
  appears anywhere in AcoustID's recordings still PASS via the existing
  secondary-match loop above.

Both fixes are defense-in-depth — either alone would have caught this
bug. Together they close the pre-download AND post-download gaps.

All 292 tests pass. Version bumped to 2.39 with changelog entries.
2026-04-22 10:32:55 -07:00
Broque Thomas
03b7230ac2 Preserve cover art in discovery fix-modal cache matched_data
Companion fix to the provider-hardcode bug (6ceedc8). The cache
matched_data built by the 5 update_match / fix endpoints was dropping
image_url and album.images when album came back as a bare string —
common for Deezer and iTunes search results. Cache hits on re-discovery
then produced downloads with no artwork.

Each save site now carries image info through:
- album_obj gets image_url + images:[{url}] populated from spotify_track.image_url
- matched_data adds top-level image_url for pipeline consumers that check there
- Works for both dict-shaped album (Spotify) and string-shaped album (Deezer/iTunes)

Mirrors the handling already present in _build_fix_modal_spotify_data for
the in-memory result['spotify_data'] — explains why the UI showed art fine
during the fix modal but the cached entry lost it after restart.

save_discovery_cache_match uses INSERT OR REPLACE, so existing bad cache
entries refresh when the user re-fixes the track. No manual cache clearing
needed.

Added to 2.38 changelog (same round of discovery-fix work).
2026-04-21 21:26:36 -07:00
Broque Thomas
6ceedc8fd4 Fix manual discovery fixes lost after restart for non-Spotify users
Five update_match endpoints hardcoded the provider as 'spotify' when
saving manual fixes to the discovery cache, but the re-discovery worker
queries the cache with _get_active_discovery_source() — the user's
actual primary. If the primary was Deezer/iTunes/Discogs/Hydrabase, the
provider column never matched, so every manual fix looked like it
vanished on restart.

Replaced 'spotify' with _get_active_discovery_source() at all 5 sites:
- Tidal update_match (web_server.py:34569)
- Deezer update_match (web_server.py:36235)
- Spotify Public update_match (web_server.py:37084)
- YouTube update_match (web_server.py:38037)
- Discovery Pool fix (web_server.py:49787)

Now symmetric with how the auto-discovery workers already save. Spotify-
primary users see no change (the hardcoded value matched their source).

Version bumped to 2.38 with changelog + version-info entries.
2026-04-21 21:12:22 -07:00
Broque Thomas
e5d4d61c0e Fix watchlist content filters: live false positives + auto-scan bypass
Two bugs reported in issue #320:

1. Auto-watchlist scan bypassed Global Override settings.
   scan_watchlist_profile applied _apply_global_watchlist_overrides, but
   the scheduled auto-scan called scan_watchlist_artists directly —
   bypassing the override. Users who unchecked "Albums" or "Live" under
   Watchlist → Global Override still saw full albums and live tracks
   added during nightly scans (per-artist defaults, which include
   everything, won).

   Moved override application into scan_watchlist_artists itself so
   every entry point respects it. scan_watchlist_profile now forwards
   the apply_global_overrides flag through to avoid double-application.

2. is_live_version (watchlist + discography backfill) and
   live_commentary_cleaner's content patterns used bare \blive\b, which
   matched verb uses like "What We Live For" by American Authors,
   "Live Forever" by Oasis, "Live and Let Die" by Wings.

   Tightened the live patterns to require clear recording context:
   (Live) / [Live Version] / - Live / Live at|from|in|on|version|
   session|recording|performance|album|show|tour|concert|edit|cut|take
   / In Concert / On Stage / Unplugged / Concert.

   Locked in 11 regression tests covering the reported false positives
   (What We Live For, Live Forever, Living on a Prayer, Live and Let Die)
   and the reported true positives (Dimension - Live at Big Day Out,
   MTV Unplugged, etc.).

Version bumped to 2.37 with changelog entries.
2026-04-21 19:16:25 -07:00
Broque Thomas
178719e443 Fix metadata cache health bar duplicating on findings dashboard
_loadCacheHealthStats ran async from loadRepairFindingsDashboard and
appended a new .repair-cache-health div each time. If the dashboard
refreshed while earlier fetches were still in flight, each resolving
fetch appended its own section — producing 2–6 stacked copies.

Now each fetch removes any existing .repair-cache-health inside the
dashboard before appending, so at most one bar is ever visible.
2026-04-21 18:47:15 -07:00
Broque Thomas
457763cbab Rebuild Discography Backfill: auto-wishlist, Fix All, section UI
Root-cause fix for "scanning 50 artists" then silence: when the master
repair worker was paused, force-run still kicked off _run_job but the
job's first wait_if_paused() blocked forever because is_paused was tied
to the master-enabled state. Force-run now bypasses master-pause —
scheduled runs still respect it.

Also fixes Fix All on discography findings doing nothing: the backend
bulk_fix_findings query had a fixable_types allowlist that excluded
missing_discography_track (and acoustid_mismatch). Added both.

Backfill job rebuild:
- auto_add_to_wishlist opt-in setting — creates findings AND pushes to
  wishlist during the scan
- 3-option fix dialog (Add to Wishlist / Just Clear / Cancel) on single
  Fix, Bulk Fix selection, and Fix All (page-level)
- Fix All "Just Clear" path uses the clear endpoint with job_id filter
  instead of the generic "may delete files" bulk-fix warning
- Batched in-memory matching using get_candidate_albums_for_artist +
  get_candidate_tracks_for_albums (same fast path the Library pages use)
- Rich album context per finding (id, name, album_type, release_date,
  images, artists, total_tracks) — flows through the wishlist pipeline
  so auto-processor classifies each track into the right cycle
  (albums vs singles) and post-processing gets correct folder/tags/art
- Per-artist progress logs [N/50] Scanning ArtistName
- Default interval 24h (was 168h); all release types default on; settings
  reordered with _section_* group headers (Core / Release Types /
  Content Filters)

Repair settings UI:
- Generic _section_<name> key convention renders as an uppercase group
  divider in the settings panel — any job can opt in
- .repair-setting-row gets a dashed bottom border so label↔toggle pairing
  is visually clear
- _prettifyRepairSettingKey fixes acronym capitalization (EPs, not Eps)

Version bumped to 2.36 with changelog entries.
2026-04-21 18:44:43 -07:00
Broque Thomas
cf143f70af Batch discography completion matching against pre-fetched candidates
Artist detail pages ran check_album_exists_with_editions and check_track_exists
per discography item, each firing 5+ title variations times 3 artist variations
of fuzzy LIKE searches plus fallback broad-artist queries. For a 30-album artist
that was ~450 SQL round-trips just to answer "which of these do I own."

Hoist the artist's library albums and tracks into memory once per request via
two new helpers — get_candidate_albums_for_artist and get_candidate_tracks_for_albums —
and thread them through as optional candidate_albums / candidate_tracks params on
check_album_exists_with_editions, check_album_exists_with_completeness,
check_track_exists, check_album_completion, and check_single_completion.

Batched path scores the same _calculate_album_confidence / _calculate_track_confidence
against the in-memory list, preserving Smart Edition Matching and accuracy.
Title-only cross-artist fallback still fires for collaborative-album edge cases.
None on either param preserves legacy per-item SQL behavior for unaffected callers.

Applied to both /api/library/completion-stream (library artist detail page) and
iter_artist_discography_completion_events (Artists search page). Timing logs
added to confirm the pre-fetch cost and loop elapsed time.

On a Kendrick page load, per-album resolution drops from ~8 seconds to under
the 50ms streaming sleep floor. Observed ~100x SQL reduction on the happy path.
2026-04-21 10:47:47 -07:00
Broque Thomas
739d2f67c1 Fix Reorganize All sending every album to Albums folder
The reorganize endpoints built a template context without albumtype,
so ${albumtype} silently fell through to the engine's hardcoded "Album"
default — EPs, singles, and compilations all landed in Albums/.

Wires albumtype through from the albums table's record_type column
(populated by Spotify/Deezer/iTunes enrichment workers) with track-count
fallback when record_type is missing. New helper mirrors the download
pipeline's classification logic so reorganize produces the same folders
as initial placement. Also handles Deezer's raw 'compile' value which
the Deezer worker writes directly without mapping.
2026-04-21 08:59:16 -07:00
Broque Thomas
95cf1eeeea Add Reorganize All Albums button, version bump to 2.35, changelogs
New "Reorganize All" button in enhanced library artist header processes
all albums sequentially using the configured path template.

Version bumped to 2.35. Updated What's New modal with major features
(Discography Backfill, Multi-Artist Tagging, Enriched Downloads,
Template Delimiters, Reorganize All). Updated helper.js changelog
with all April 20 fixes and features.
2026-04-20 23:46:37 -07:00
Broque Thomas
21ae328955 Fix track ownership false positives, wing-it wishlist leak, debug counts
Track ownership: check-tracks endpoint now filters by album context
when provided, preventing false "Found" when a track exists in a
different album by the same artist (e.g. Thriller on HIStory).

Wing-it wishlist: manual "Add to Wishlist" button now skips wing-it
fallback tracks (wing_it_ ID prefix), matching the behavior of
failed download and failed sync paths.

Debug info: watchlist/wishlist/automation counts were always 0
because get_db() doesn't exist — fixed to get_database().
2026-04-20 10:17:47 -07:00
Broque Thomas
2f2e5ddbd0 Fix album track lookup hardcoded to Spotify on Artists page
The /api/artist/{id}/album/{id}/tracks endpoint was hardcoded to use
spotify_client and returned 401 if Spotify wasn't authenticated,
even when the user's primary source was Deezer or iTunes. Now uses
the configured primary metadata source via _get_metadata_fallback_client
with Spotify as fallback. Also gives a clearer error message when
no metadata source is available at all.
2026-04-20 09:15:09 -07:00
Broque Thomas
908924d22c Fix wishlist splitting multi-artist albums by track artist
Adding a soundtrack or compilation album to wishlist was creating
separate wishlist entries per track artist (e.g. Persona 3 OST split
into ATLUS Sound Team/Lotus Juice/Azumi Takahashi). Now uses the
album-level artist when available so all tracks stay grouped as one
album. Per-track artist resolution only applies to playlists where
there's no album context.
2026-04-20 08:18:35 -07:00
Broque Thomas
c8fcb4626a Fix artist search case sensitivity with metadata APIs
Some metadata APIs return fewer or no results for all-lowercase
queries. Title-case the query when it's all lowercase before
sending to the API ("foreigner" → "Foreigner"). Mixed-case input
is left as-is. Confidence scoring still uses the original query.
2026-04-20 08:01:55 -07:00
Broque Thomas
6ca3f3b070 Enable Lidarr as production-ready download source
Fixed 5 critical gaps in the download orchestrator where lidarr was
missing from client loops: get_all_downloads, get_download_status,
cancel_download fallback, clear_all_completed_downloads, and
cancel_all_downloads. Without these, lidarr downloads were invisible
to the UI, couldn't be cancelled, and accumulated in memory.

Also: error messages now visible in download list (appended to
filename on error state), removed "(Development)" label from UI.
2026-04-20 07:24:04 -07:00
Broque Thomas
f8e24b8432 Regenerate M3U with real library paths after post-processing
M3U files were generated when the download batch completed but
before post-processing finished tagging and moving files. Paths
pointed to download locations instead of final library paths,
making every track show as missing. Now regenerates the M3U from
the backend batch completion handler after all post-processing
is guaranteed done, resolving real file paths from the library DB.
Skips overwrite if zero tracks resolve to avoid replacing a
partially-good M3U with an all-missing one.
2026-04-19 20:00:19 -07:00
Broque Thomas
036f284ee3 Fix AcoustID retag action not writing tags to audio file
The retag fix for AcoustID mismatches was only updating the DB
record (title, artist_id) without writing corrected tags to the
actual audio file. Users would click Fix, the finding disappeared,
but the file on disk stayed unchanged. Now writes title and artist
tags to the file via Mutagen after the DB update.

Also fixed artist INSERT missing server_source when creating a new
artist during retag — now uses the active media server value.
2026-04-19 19:14:49 -07:00
Broque Thomas
ef8cff3c69 Version bump to 2.34 2026-04-19 18:40:58 -07:00
Broque Thomas
35d6fff916 Fix wishlist albums cycle forced to 1 concurrent worker
Auto-wishlist albums cycle was passing is_album=True to
_get_batch_max_concurrent which returns 1 for soulseek mode.
This restriction is for folder-based album grabs from a single
peer, not individual track downloads. Wishlist always does
single-track downloads regardless of cycle, so it should use
the user's configured concurrency setting.
2026-04-19 17:29:46 -07:00
Broque Thomas
5fb1972361 Fix downloads nav badge dropping to 300 after page open
_adlFetch() fetches /api/downloads/all?limit=300 then _adlUpdateBadge()
was counting active statuses from that truncated array, overwriting
the real server-side count maintained by WebSocket. Removed the
badge update from _adlFetch — the WebSocket status push already
keeps it accurate.
2026-04-19 17:28:08 -07:00
Broque Thomas
dd3b71bc9a Fix discovery search quality and server playlist track positioning
Fix modal results: sort standard album versions above live, remix,
cover, soundtrack, remaster, and deluxe variants so users see the
original studio track first instead of obscure versions.

Plex Find & Add: tracks were always appended to the end of the
playlist because addItems ignores position. Now moves the track
to the correct slot after adding via moveItem.
2026-04-19 17:26:29 -07:00
Broque Thomas
5b9c9bc6fa Sort Fix modal results to prioritize standard album versions
Discovery Fix modal search results now sort standard album versions
above live recordings, remixes, covers, soundtracks, remasters,
deluxe editions, and other variants. Fixes cases where searching
"Mother Danzig" returned a live version first, or "Even Flow Pearl
Jam" returned a soundtrack instead of the original from Ten.
2026-04-19 17:21:02 -07:00
Broque Thomas
a3c8b9ecdd Add unmatch button, video naming template, and fix slskd log spam
Unmatch: found tracks in playlist discovery now have a red X button
to remove bad matches. Clears match data, sets back to Not Found,
persists in DB for mirrored playlists, and respects user choice on
re-discovery runs (won't re-match automatically).

Video naming: new path template in Settings with $artist, $title,
$artistletter, $year variables. Default unchanged ($artist/$title-video)
so existing Plex setups aren't affected.

slskd logs: Clean Search History automation skips when Soulseek is
not the active download source, eliminating connection error spam.
2026-04-19 17:16:22 -07:00
Broque Thomas
122a6999b3 Add customizable music video naming and fix slskd log spam
Video naming: new path template in Settings → Paths & Organization
with $artist, $artistletter, $title, $year variables. Default
unchanged ($artist/$title-video → Artist/Title-video.mp4) so
existing Plex setups aren't affected. Users can remove the -video
suffix or reorganize however they like.

slskd logs: the Clean Search History automation now skips when
Soulseek is not the active download source, eliminating noisy
connection error logs for users who don't use Soulseek.
2026-04-19 16:57:36 -07:00
Broque Thomas
a23bce5966 Auto Wing It fallback for failed playlist discovery tracks
When playlist discovery fails to match a track on any metadata API,
instead of marking it "Not Found" and excluding it from downloads,
automatically build stub metadata from the raw source title/artist
and include it in the download queue. Soulseek searches with the
raw data, post-processing enhances whatever it can find.

All 7 discovery workers updated: YouTube, ListenBrainz, Tidal,
Deezer, Spotify Public, Beatport, and automated mirrored playlists.
Amber "Wing It" badge distinguishes stubs from real API matches.
Fix button still available so users can manually find a proper match.
Wing It stubs persist in DB for mirrored playlists and are
re-attempted on future discovery runs. Failed wing-it downloads
skip wishlist per-track (checked by wing_it_ ID prefix) so real
matched failures in the same batch still go to wishlist normally.
2026-04-19 16:05:50 -07:00
Broque Thomas
f9de081bd5 Fix library page crash when All letter filter is used
soul_id.startsWith() threw TypeError for non-string values, crashing
the entire card rendering pipeline. Letter-specific filters worked
because the problematic artist wasn't in those filtered results.
Added String() wrapper on all 3 soul_id.startsWith calls and a
try-catch around individual card rendering so one bad card can't
take down the whole page.
2026-04-19 12:48:53 -07:00
Broque Thomas
2bd8d2ac7a Version bump to 2.33 2026-04-18 23:39:26 -07:00
Broque Thomas
533091f605 Update What's New — live log viewer now mentions search and smart filtering 2026-04-18 23:26:04 -07:00
Broque Thomas
8b0e619fa1 Add live log viewer on Settings → Logs tab
Terminal-style real-time log viewer with:
- Log file selector (app, post-processing, acoustid, source reuse)
- Color-coded log levels (DEBUG gray, INFO blue, WARNING yellow, ERROR red)
- Level filter buttons (All/Debug/Info/Warn/Error)
- Auto-scroll with toggle, copy and clear buttons
- Live updates via WebSocket (2s polling, pushes new lines)
- Initial load fetches last 200 lines via REST API
- 1000-line display cap with oldest lines trimmed

Also fixes Advanced tab settings (Discovery Pool, Security, etc.) being
hidden inside collapsed Library Preferences section body — misplaced
closing div caused them to be invisible.
2026-04-18 22:57:15 -07:00
Broque Thomas
288994e081 Update What's New with all today's features and fixes
Added: Genre Whitelist, Standalone Full Refresh, Folder Terminology
Rebrand, and 12 bug fixes (duplicate detector, single track downloads,
liked songs source label, metadata crash, scan button stuck, deep scan
logging, settings tab flash, standalone verify speed, and more).
2026-04-18 20:41:59 -07:00
Broque Thomas
efe8280e23 Rebrand folder terminology: Download→Input, Transfer→Output, Staging→Import
All user-facing labels, docs, help text, tooltips, error messages, and debug
info output updated. Backend config keys, variable names, actual path values,
and Docker volume mounts are completely unchanged — zero functional impact.
2026-04-18 17:35:20 -07:00
Broque Thomas
cef1162c5e Update What's New with today's features and fixes 2026-04-18 16:07:33 -07:00
Broque Thomas
4f5025d526 Add MusicBrainz search tab, wider global search, bump to v2.32
New MusicBrainz tab in Enhanced and Global search — finds tracks and
albums on MusicBrainz's community database with Cover Art Archive
images. Covers obscure tracks that Spotify/Deezer/iTunes miss.

- core/musicbrainz_search.py: search adapter with Track/Artist/Album
  dataclasses, Cover Art Archive integration, smart query parsing
- Albums deduplicated (keeps best version with date and art)
- No artist results shown (MusicBrainz has no artist images)
- Album detail with full tracklist for download modal
- Smart word-boundary splitting for queries without separators
- Global search results container widened from 620px to 920px
- UI version bumped to 2.32
2026-04-18 02:06:27 -07:00
Broque Thomas
70005968b6 Restructure version modal and What's New for v2.31
SoulSync Standalone Library is now the first section in both the
version modal and What's New popup. Auto-Import section updated with
all improvements (recursive scan, singles, tag preference, AcoustID).
New Downloads & Soulseek section groups download-related improvements.
Recent Fixes cleaned up — feature items moved to proper sections.
2026-04-18 01:28:19 -07:00
Broque Thomas
b4475152a9 Hide all sync buttons in standalone mode, bump UI to v2.31
All sync-related buttons hidden when active server is SoulSync
Standalone. Covers static buttons (querySelectorAll on status update)
and dynamic modal buttons (_isSoulsyncStandalone flag).
UI version bumped to 2.31 (Docker stays at 2.3).
2026-04-18 01:14:54 -07:00
Broque Thomas
d688e7fa15 Update What's New and version modal with standalone library and import fixes 2026-04-17 23:29:04 -07:00
Broque Thomas
619b7ab4be Update What's New and version modal with recent fixes 2026-04-17 15:05:57 -07:00