Skip to content

feat: fetch playlists via Tidal API with favorites fallback#12

Merged
dantraynor merged 1 commit into
mainfrom
issue-11
Mar 26, 2026
Merged

feat: fetch playlists via Tidal API with favorites fallback#12
dantraynor merged 1 commit into
mainfrom
issue-11

Conversation

@dantraynor
Copy link
Copy Markdown
Owner

Replaces Redux store reads with direct Tidal API calls for fetching user playlists, improving reliability when store data is stale or incomplete. Includes a favorites-based fallback using Playlist.fromId(). Simplifies duplicate song detection by replacing the cache+timeout system with local session tracking and server-side onDupes: SKIP. Net reduction of 33 lines.

Replace stale Redux store reads with direct Tidal API calls for playlist
fetching, with a favorites-based fallback. Simplify duplicate detection
using local session tracking and server-side onDupes: SKIP.
Copilot AI review requested due to automatic review settings March 26, 2026 08:57
@dantraynor dantraynor merged commit e656794 into main Mar 26, 2026
7 checks passed
@dantraynor
Copy link
Copy Markdown
Owner Author

fixes issue #11

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the MultiplePlaylists plugin to fetch the user’s playlists directly from the Tidal API (with a favorites-based fallback), and simplifies duplicate handling by adding server-side duplicate skipping plus local session tracking.

Changes:

  • Fetch playlists via TidalApi instead of relying on Redux content.playlists, with a fallback that resolves favorites via Playlist.fromId().
  • Replace the cache+timeout duplicate-check system with local session tracking (addedSongs) and server-side onDupes: "SKIP".
  • Refactor playlist rendering flow to show a loading state and process duplicate checks in small batches.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

async function fetchUserPlaylists(): Promise<Array<{uuid: string, title: string, numberOfTracks: number}>> {
const state = redux.store.getState();
const userId = state.session?.userId;
if (!userId) throw new Error("Not logged in");
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fetchUserPlaylists() throws when state.session?.userId is missing, but the caller treats any failure the same and may end up showing "No playlists found". Consider detecting the "not logged in" case early in populatePlaylistList and showing a more accurate UI message (or skipping the favorites fallback if adding to playlists isn't possible).

Suggested change
if (!userId) throw new Error("Not logged in");
if (!userId) {
trace("fetchUserPlaylists: no userId in session; returning empty playlist list");
return [];
}

Copilot uses AI. Check for mistakes.
Comment on lines +324 to +328
// Server check
const trackIds = await getPlaylistTrackIds(playlistId);
const found = trackIds.has(songIdStr);
console.log(`[MultiplePlaylists] Song ${songIdStr} ${found ? 'IS' : 'NOT'} in playlist ${playlistId} (server)`);
return found;
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isSongInPlaylist() always calls getPlaylistTrackIds() which iterates the entire playlist. This is invoked during UI rendering (per playlist) and again in addToSelectedPlaylists, so the same playlist can be fully scanned twice per modal open, and again on subsequent opens. Consider caching trackIds per playlist for the lifetime of the modal/session (or reusing the earlier duplicate-check result) to avoid repeated full playlist loads and reduce API load/latency.

Copilot uses AI. Check for mistakes.
Comment on lines +373 to +375
// Track locally so we detect it immediately next time
if (!addedSongs.has(playlistId)) addedSongs.set(playlistId, new Set());
addedSongs.get(playlistId)!.add(String(song.id));
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Local duplicate tracking is updated immediately after dispatch(...) without any confirmation that the add actually succeeded. If the underlying async add fails, the song will be treated as "already added" for the rest of the session and the user may be unable to retry. Consider only updating addedSongs after a confirmed success signal (e.g., observing the relevant store update / awaited API result), or make the tracking explicitly optimistic and roll it back if failure can be detected.

Copilot uses AI. Check for mistakes.

// Local tracking of songs added to playlists (server data can be stale)
const addedSongs = new Map<string, Set<string>>(); // playlistId -> Set<songId>

Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addedSongs is a long-lived global Map that never gets cleared. If the plugin is used heavily across many playlists/songs, this can grow unbounded over the app lifetime. Consider clearing it on plugin unload (e.g., add an unload callback) and/or pruning per-playlist sets after some reasonable size/age.

Suggested change
// Ensure local tracking map is cleared when the plugin is unloaded to avoid unbounded growth.
unloads.add(() => {
addedSongs.clear();
});

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants