Fix Spotify Playlist Discovery: Fix button, header readability, [object Object]

Three separate issues reported on the Spotify Playlist Discovery modal:

1. Fix button fails with "Track data not found"
   openDiscoveryFixModal() branched on platform name to locate the discovery
   state but had no case for 'spotify_public'. Row rendering passes that
   platform value when the source is a Spotify public playlist, so state
   lookup failed and the toast fired. Added the spotify_public branch —
   state lives in youtubePlaylistStates alongside the other reused platforms.

2. Table header too transparent to read
   .youtube-discovery-modal .discovery-table th used rgba(255,255,255,0.1)
   as background (10% white) with white text, which lost contrast when the
   orange progress bar or varied row content scrolled underneath. Switched
   to near-solid dark rgba(17,17,20,0.96) with a brighter border-bottom
   and z-index:5 so the sticky header stacks cleanly above table content.

3. "[object Object]" in the matched-artist column for Wing-It tracks
   The Spotify Public Playlist row-transform joined result.spotify_data.artists
   directly with .join(', '). Wing-It stub metadata (built server-side by
   _build_discovery_wing_it_stub) returns artists as [{name: "..."}] —
   array of objects. .join() stringified each object to "[object Object]".
   Same pattern existed in three places in script.js; all now map objects
   to .name and filter empties before joining. Graceful fallback to "-"
   if the result is empty.

No existing tests touched. Full suite stays at 263 passed. Ruff clean.
This commit is contained in:
Broque Thomas 2026-04-21 15:38:50 -07:00
parent be2d425972
commit 891b18540b
2 changed files with 27 additions and 6 deletions

View file

@ -19429,6 +19429,8 @@ function openDiscoveryFixModal(platform, identifier, trackIndex) {
state = youtubePlaylistStates[identifier]; // Deezer uses YouTube state infrastructure
} else if (platform === 'mirrored') {
state = youtubePlaylistStates[identifier]; // Mirrored playlists use YouTube state infrastructure
} else if (platform === 'spotify_public') {
state = youtubePlaylistStates[identifier]; // Spotify public playlists use YouTube state infrastructure
}
// Support both camelCase and snake_case for discovery results
@ -27121,7 +27123,13 @@ async function openTidalDiscoveryModal(playlistId, playlistData) {
status_class: isFound ? 'found' : 'not-found',
spotify_track: result.spotify_data ? result.spotify_data.name : (result.spotify_track || '-'),
spotify_artist: result.spotify_data && result.spotify_data.artists ?
(Array.isArray(result.spotify_data.artists) ? result.spotify_data.artists.join(', ') : result.spotify_data.artists) : (result.spotify_artist || '-'),
(Array.isArray(result.spotify_data.artists)
? result.spotify_data.artists
.map(a => (typeof a === 'object' && a !== null) ? (a.name || '') : a)
.filter(Boolean)
.join(', ') || '-'
: result.spotify_data.artists)
: (result.spotify_artist || '-'),
spotify_album: result.spotify_data ? (typeof result.spotify_data.album === 'object' ? result.spotify_data.album.name : result.spotify_data.album) : (result.spotify_album || '-'),
spotify_data: result.spotify_data, // Pass through spotify_data
spotify_id: result.spotify_id, // Pass through spotify_id
@ -28582,7 +28590,13 @@ async function openDeezerDiscoveryModal(playlistId, playlistData) {
status_class: isFound ? 'found' : 'not-found',
spotify_track: result.spotify_data ? result.spotify_data.name : (result.spotify_track || '-'),
spotify_artist: result.spotify_data && result.spotify_data.artists ?
(Array.isArray(result.spotify_data.artists) ? result.spotify_data.artists.join(', ') : result.spotify_data.artists) : (result.spotify_artist || '-'),
(Array.isArray(result.spotify_data.artists)
? result.spotify_data.artists
.map(a => (typeof a === 'object' && a !== null) ? (a.name || '') : a)
.filter(Boolean)
.join(', ') || '-'
: result.spotify_data.artists)
: (result.spotify_artist || '-'),
spotify_album: result.spotify_data ? (typeof result.spotify_data.album === 'object' ? result.spotify_data.album.name : result.spotify_data.album) : (result.spotify_album || '-'),
spotify_data: result.spotify_data,
spotify_id: result.spotify_id,
@ -32461,7 +32475,13 @@ async function openSpotifyPublicDiscoveryModal(urlHash, playlistData) {
status_class: isFound ? 'found' : 'not-found',
spotify_track: result.spotify_data ? result.spotify_data.name : (result.spotify_track || '-'),
spotify_artist: result.spotify_data && result.spotify_data.artists ?
(Array.isArray(result.spotify_data.artists) ? result.spotify_data.artists.join(', ') : result.spotify_data.artists) : (result.spotify_artist || '-'),
(Array.isArray(result.spotify_data.artists)
? result.spotify_data.artists
.map(a => (typeof a === 'object' && a !== null) ? (a.name || '') : a)
.filter(Boolean)
.join(', ') || '-'
: result.spotify_data.artists)
: (result.spotify_artist || '-'),
spotify_album: result.spotify_data ? (typeof result.spotify_data.album === 'object' ? result.spotify_data.album.name : result.spotify_data.album) : (result.spotify_album || '-'),
spotify_data: result.spotify_data,
spotify_id: result.spotify_id,

View file

@ -14808,16 +14808,17 @@ body.helper-mode-active #dashboard-activity-feed:hover {
}
.youtube-discovery-modal .discovery-table th {
background: rgba(255, 255, 255, 0.1);
background: rgba(17, 17, 20, 0.96);
color: #ffffff;
font-weight: 600;
padding: 12px 8px;
text-align: left;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
border-top: 1px solid rgba(255, 255, 255, 0.08);
border-bottom: 1px solid rgba(255, 255, 255, 0.18);
font-size: 14px;
position: sticky;
top: 0;
z-index: 1;
z-index: 5;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;