This repository was archived by the owner on May 25, 2026. It is now read-only.
feat: filter unshuffleable rows from picker - #61
Merged
Conversation
The picker was returning every playlist from /v1/me/playlists,
including ones the caller follows but doesn't own. Post-Feb-2026
those rows can't be shuffled — /v1/playlists/{id}/items returns
403 Forbidden against non-owned, non-collaborative playlists (see
the smoke test in #55).
/api/me/playlists now fetches /v1/me in parallel with the playlist
list and Liked Songs count, then filters the playlist list to keep
only items where the caller is the owner or a collaborator. The
parallel fetch keeps the picker's first paint at one round-trip's
worth of latency. Liked Songs sits outside the filter — the caller
owns their Saved Tracks library by definition.
New `fetchCurrentUserId` helper in src/lib/spotify/me.ts follows
the same boundary pattern as the other Spotify fetches: hard-coded
origin, bounded timeout, status-coded error mapping, schema
validation. Reuses the existing error classes from playlists.ts;
the route's existing catch branches cover every failure mode the
new fetch can throw.
The defense-in-depth from #58 (`SpotifyPlaylistInaccessibleError`
→ 400 unshuffleable_playlist) remains in /api/shuffle so a race
where the user loses access between picker render and shuffle
still produces a coherent envelope.
Closes #55.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
fetchCurrentUserId(accessToken)helper atsrc/lib/spotify/me.ts— calls/v1/me, projects outid, same boundary pattern as the other Spotify fetches (origin, bounded timeout, status-coded error mapping, schema validation). Reuses the existing error vocabulary fromplaylists.ts./api/me/playlistsroute fetches/v1/mein parallel with the existing playlist list + Liked Songs count, then filtersplaylistsResult.itemsto keep onlyownerId === currentUserId || isCollaborative. Liked Songs sits outside the filter.Why
Per the smoke test in #55 (comment), post-Feb-2026
/v1/playlists/{id}/itemsreturns 403 Forbidden for any playlist the caller doesn't own and isn't a collaborator on. Without this filter, the picker shows rows the user can't shuffle — and even with #58's defense-in-depth, clicking one is a click that goes nowhere.The 1st bullet of #55's product recommendation closes the loop: the picker only ever surfaces shuffleable playlists. #58's
SpotifyPlaylistInaccessibleError→ 400unshuffleable_playlistis now a true safety net (covers picker-render → shuffle-time races) rather than the primary defense.Collaborative branch is kept in based on Spotify's documented behaviour; the smoke test didn't exercise it (test account had no collaboratives). The defense-in-depth catches the case if a collaborative playlist also 403s in practice.
Test plan
lint,typecheck,test(127/127),buildall green locally/api/me/playlists; verify only owned/collab playlists + Liked Songs come back, none of the followed onesCloses #55.