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 `