diff --git a/core/sync/wishlist_readd.py b/core/sync/wishlist_readd.py index 6658352e..7414c943 100644 --- a/core/sync/wishlist_readd.py +++ b/core/sync/wishlist_readd.py @@ -71,6 +71,11 @@ def reconstruct_sync_track_data( return None sid = str(tr.get('source_track_id') or '') + # Wing-it fallback stubs have no real metadata (no album/cover) — the live sync + # SKIPS them for the wishlist (services/sync_service.py), so the re-add must too, + # rather than store a coverless, mis-classified placeholder. + if sid.startswith('wing_it_'): + return None # Primary: the exact normalized track the auto-add used. if sid: diff --git a/tests/test_sync_wishlist_readd.py b/tests/test_sync_wishlist_readd.py index b794ed10..227e9784 100644 --- a/tests/test_sync_wishlist_readd.py +++ b/tests/test_sync_wishlist_readd.py @@ -100,6 +100,15 @@ def test_refuses_when_no_id_and_no_full_track(): assert reconstruct_sync_track_data([_tr(0, "")], [], 0) is None +def test_refuses_wing_it_stub(): + # Wing-it fallback stubs have no real metadata; the sync skips them, so must we — + # even if a full (stub) track happens to be in tracks_json. + trs = [_tr(0, "wing_it_abc123", name="Sami Matar", artist="X")] + assert reconstruct_sync_track_data(trs, [], 0) is None + stub = {"id": "wing_it_abc123", "name": "Sami Matar", "album": "Sami Matar", "artists": ["X"]} + assert reconstruct_sync_track_data(trs, [stub], 0) is None + + def test_build_map_skips_idless_and_non_dicts(): m = build_original_tracks_map([_full("a"), {"name": "no id"}, "garbage", None]) assert set(m.keys()) == {"a"} diff --git a/webui/static/pages-extra.js b/webui/static/pages-extra.js index 2a02993a..b82dac7a 100644 --- a/webui/static/pages-extra.js +++ b/webui/static/pages-extra.js @@ -2271,11 +2271,18 @@ async function openSyncDetailModal(entryId) { let dlDisplay = dlIcon; if (!dlDisplay && t.download_status === 'wishlist') { - // Clickable: re-add this exact track to the wishlist with the - // same context the sync originally used. - dlDisplay = ``; + // Wing-it fallback stubs (no real metadata) were never actually + // wishlisted by the sync — show them as plain, non-clickable. + const isWingIt = String(t.source_track_id || '').startsWith('wing_it_'); + if (isWingIt) { + dlDisplay = `→ Wishlist`; + } else { + // Clickable: re-add this exact track to the wishlist with the + // same context the sync originally used. + dlDisplay = ``; + } } return `