Drop legacy lineup system: tanquery-first + new playback slice across all pages#14178
Conversation
Replaces the legacy lineup/queue/player-coupled flow for the trending page
with a simpler two-concept model: tanquery hooks own track ordering, and a
single playback slice owns the queue + transport. On tile tap the trending
page dispatches playbackActions.playFrom({ tracks, startIndex, querySource })
and a new saga drives the existing audio pipeline.
Added:
- packages/common/src/store/playback/ (slice, selectors, saga, tests)
- packages/web/src/components/lineup/TrackLineup.tsx — tanquery-first
replacement for <Lineup>/<TanQueryLineup>, takes trackIds + source
- packages/mobile/src/components/lineup/TrackLineup.tsx — RN equivalent
Simplified:
- useTrending now returns { trackIds, hasNextPage, loadNextPage, queryKey,
... }; dropped the useLineupQuery wrapper + legacy lineup dispatches.
Migrated:
- Trending web (desktop + mobile) and mobile trending screens to use the
new hook + TrackLineup.
Deleted:
- packages/common/src/store/pages/trending/lineup/ (actions, reducer,
selectors)
- packages/web/src/common/store/pages/trending/ (lineup sagas)
- packages/web/src/pages/trending-page/hooks/ (6 hooks replaced by inlined
query logic) + selectors.ts + providerTypes.ts
- Legacy lineup-reset calls from mobile trending filter drawers and header
- Registry, reducer, pages/index, and both web/mobile store sagas.ts entries
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Same pattern as the trending PR: simplify useTrendingUnderground to return clean tanquery state, point web desktop + mobile trending pages and the mobile TrendingUndergroundLineup at <TrackLineup>, delete the legacy per-page lineup/slice/saga + registry/reducer/index entries. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… history, search, pick-winners, contest, exclusive-tracks
…, exclusive-tracks) + stripdead dispatches from feed/profile/search
…eupQuery, useLineupTable, per-page lineup actions/reducers/sagas + rewire library, collection, track, history, remixes, search, profile, feed, dashboard consumers to tanquery + playback slice
|
Conflict resolutions: - useLineupQuery.ts: kept delete (HEAD) \u2014 helper removed in lineup migration - ProfilePage.tsx: merged imports to include both useMemo (HEAD) and useRef (main)
Both pre-existing on main (PR #14163) but blocking CI on this branch.
🌐 Web preview readyPreview URL: https://audius-web-preview-pr-14178.audius.workers.dev Unique preview for this PR (deployed from this branch). |
Mirror queueActions.shuffle / queueActions.repeat into the playback slice in the playback saga so that when the audio element's onEnd callback fires playbackActions.next (set during the initial playFrom), the playback slice's reducer correctly honors the user's shuffle/repeat selection. Before this fix only the next/previous buttons honored shuffle (because they dispatch queueActions.next which goes through the legacy queue saga). Natural track-end silently advanced linearly because the playback slice's own shuffle state was never set — the PlayBar UI dispatches queueActions.shuffle, not playbackActions.setShuffle. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…aded The ShuffleButton restores shuffleState=ON from localStorage on mount, which dispatches queue/shuffle against an empty queue. The legacy queue's shuffle reducer only generates shuffleOrder when state.order.length > 0, so shuffleOrder remained empty even after tracks were later loaded via queue.add (which by design does not regenerate shuffleOrder). Result: clicking next caused the legacy queue's next reducer to overshoot immediately (shuffleIndex+1 >= shuffleOrder.length=0), the watchNext saga saw overshot=true and reset+paused the player. Fix: in shadowToLegacyQueue, after adding entries, re-toggle shuffle if the queue is currently in shuffle mode so the legacy queue regenerates its shuffleOrder against the now-populated order. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The playback slice's next reducer didn't honor RepeatMode.SINGLE, so
when the audio element ended and fired playbackActions.next (the onEnd
set by the playback saga's playCurrent), playback advanced linearly
instead of replaying the same track.
Also: with repeat=OFF at end of queue, the saga's watchNext
unconditionally called playCurrent, which restarted the last track
instead of stopping playback.
Fix:
- Honor RepeatMode.SINGLE && !skip in the playback next reducer (skip=true
from the next button still advances, matching legacy queue semantics).
- Add overshot/undershot flags to PlaybackState, set by next/previous
when at end/start of queue without a wrap. Cleared on
playFrom / playTrackAt / any successful advance.
- Saga's watchNext / watchPrevious read overshot/undershot and dispatch
playerActions.reset({shouldAutoplay: false}) instead of replaying.
Verified on localhost:3002:
- repeat=SINGLE natural end → same track replays (was: advanced).
- repeat=ALL at end → wraps to index 0 (already worked, confirmed).
- repeat=OFF at end → player resets and pauses (was: last track restarted).
- Next button (skip=true) with repeat=SINGLE still advances.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Caution Review the following alerts detected in dependencies. According to your organization's Security Policy, you must resolve all "Block" alerts before proceeding. It is recommended to resolve "Warn" alerts too. Learn more about Socket for GitHub.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
## Summary - Deletes the legacy `queue` and `player` slices and folds everything into a single `playback` slice - Combines the old `web/common/store/queue/sagas.ts` (494 lines) + `web/common/store/player/sagas.ts` (~570 lines) into one `web/common/store/playback/sagas.ts` - Rewires ~70 consumers from `queueActions/queueSelectors/playerActions/playerSelectors` → `playbackActions/playbackSelectors` The previous PR (#14178) introduced `playback` that *shadowed* `queue` and `player` for compat. This finishes the job — `playback` becomes the single source of truth for queue, transport, and audio-engine state. Two slices for one concern was exactly the legacy complexity we wanted gone; the new design replaces a saga whose only job was keeping them in sync. ## What's gone - `packages/common/src/store/queue/` (slice + selectors + types + tests) - `packages/common/src/store/player/` (slice + selectors + types + utils + sagas) - `packages/web/src/common/store/queue/sagas.ts` - `packages/web/src/common/store/player/sagas.ts` + `errorSagas.ts` - `state.queue` and `state.player` from the root reducer + types ## What's new — `packages/common/src/store/playback/` - **`slice.ts`** — owns the queue (`PlaybackTrack[]`), index, `playingUid`/`playingTrackId`, `playing`/`buffering`/`previewing`, `seek`/`seekCounter`/`counter`, `playbackRate`, `repeat`/`shuffle`/`shuffleOrder`/`shuffleIndex`, `querySource`, `retries`, `overshot`/`undershot`. New actions: `set`, `setIndex`, `play({uid})`-style, `addToQueue`/`removeByUid`/`reorder`, `setPlayingState`, `playSucceeded`, `reset`/`resetSucceeded`, `incrementCounter`, `error`, `queueAutoplay`. - **`selectors.ts`** — `getCurrentPlaybackTrack`, `getCurrentTrackId`, `getCurrentSource`, `getCurrentEntryUid`, `getCurrentPlayerBehavior`, `getCollectionId`, `makeGetCurrent`, `getOrder` (Queueable adapter for the mobile `AudioPlayer`), `getUid`/`getTrackId`/`getPlaying`/`getBuffering`/`getCounter`/`getPlaybackRate`/`getSeek`/etc. - **`types.ts`** — owns `PlaybackTrack`, `PlaybackQuerySource`, `PlaybackState`, plus the formerly-queue/player types (`QueueSource`, `Queueable`, `QueueItem`, `RepeatMode`, `PlayerBehavior`, `PlaybackRate`). ## Web-side engine — `packages/web/src/common/store/playback/sagas.ts` Combines the old player + queue sagas into one. Handles audio loading via the `audioPlayer` service (HTML on web, RNTrackPlayer shim on mobile), cascading mirror retries, audio-element listener bridge, buffering / error channels, listen counting, queue-driven `playCurrent` on `playFrom`/`playTrackAt`/`next`/`previous`/`togglePlay`, skip-deleted/owner-deactivated/locked tracks, `queueAutoplay` (genre-based recommendation append), `PLAYBACK_PLAY` analytics, cache subscribe on enqueue, and pagination via `querySource.queryKey`. ## Migration ~70 consumer files updated. Action-name renames the new shape requires: | Legacy | New | |---|---| | `queueActions.add({entries})` | `playbackActions.addToQueue({tracks: PlaybackTrack[]})` | | `queueActions.clear` | `playbackActions.clearQueue` | | `queueActions.remove({uid})` | `playbackActions.removeByUid({uid})` | | `queueActions.repeat`/`shuffle` | `playbackActions.setRepeat`/`setShuffle` | | `queueActions.updateIndex` | `playbackActions.setIndex` | | `playerActions.seek` | `playbackActions.seekTo` | | `playerActions.incrementCount` | `playbackActions.incrementCounter` | | `playerSelectors.getUid` | `playbackSelectors.getUid` | | `queueSelectors.getSource` | `playbackSelectors.getCurrentSource` | | `queueSelectors.getPlayerBehavior` | `playbackSelectors.getCurrentPlayerBehavior` | `PlaybackTrack.legacyUid` field renamed to `uid`. ## Compat preserved `PlaybackTrack.uid` matches what the rendered tile uses, so tile-highlight comparisons (`playingUid === entry.uid`) and the desktop `PlaylistLibrary` "currently playing source" highlight keep working. The mobile `AudioPlayer` reads from a `getOrder` Queueable adapter on top of `playback.queue`, so RNTrackPlayer keeps driving exactly as before. ## Test plan - [x] `packages/common`, `packages/web`, `packages/mobile` typecheck clean - [x] **Web (localhost:3002, @DuranDylan)**: Trending tile play → audio loads, PlayBar populates, tile speaker icon lights up. Next button advances, previous button returns. Library/SAVED_TRACKS row click loads + plays with the correct source. Pause / resume flips engine state. No console errors. - [x] **iOS Simulator (iPhone 17 Pro)**: Trending tile play → mini-player + NowPlayingDrawer populate, position counter advances (0:13 → 0:37 → 1:01 with real audio). Next loads new track. Pause freezes position; resume advances. Shuffle button toggled to active (purple). Repeat button cycled state. - [ ] Reviewer: smoke test on your own stage/prod to confirm end-to-end playback and that nothing else regressed. ## Scope Touched: **100 files, +4014 / −3280.** 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
lineupForRoute.js destructured selectors (getDiscoverFeedLineup, getHistoryTracksLineup, etc.) that the legacy-lineup migration in #14178 deleted, so every branch returned undefined — and nothing read the resulting context field anyway. The build only stayed green because feedPageSelectors still existed as an empty namespace; this PR's removal of the feed-page redux slice made rollup catch the missing export. - delete packages/web/src/store/lineup/lineupForRoute.js - remove the import + assignment in packages/web/src/store/storeContext.ts - drop the optional getLineupSelectorForRoute field (and now-unused Location/LineupState/Track/CommonState imports) from packages/common/src/store/storeContext.ts Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
Replaces the legacy redux lineup stack (lineup reducers, sagas,
<Lineup>/<TanQueryLineup>/<LineupProvider>,useLineupQuery,useLineupTable,LineupSagasclass,lineupRegistry) with a simpler two-concept model across every page in the web + mobile apps:trackIds: ID[].playbackslice with a single saga driving the existing audio pipeline.Click-to-play collapses from
tile → lineup.togglePlay → lineup.play → queue.play → player.play(3 sagas, 4 dispatches) down totile → playbackActions.playFrom → saga → audio.This PR combines the trending-only rewrite (previously #14168) with the full migration of the remaining pages into one big cleanup.
What's new
packages/common/src/store/playback/— slice + selectors + saga (shadows to the legacyqueue+playerslices so PlayBar / NowPlaying / mobile audio screens stay working unchanged).packages/web/src/components/lineup/TrackLineup.tsx+packages/mobile/src/components/lineup/TrackLineup.tsx— tanquery-first lineup components. TaketrackIds: ID[]+source: string+ optionalquerySource: { queryKey }for auto-pagination. DispatchplaybackActions.playFromon tile tap. No redux lineup state.playbackSourceprop onCommentSectionProviderreplaces the oldlineupActions+uidpair for play-from-comment.Pages migrated
Trending, trending-underground, trending-winners, feed, profile tracks + reposts, track page (hero + more-by + remixes + recommended), remixes page, history, search, library, collection (playlist + album), pick-winners, contest submissions, exclusive-tracks, premium-tracks (FanClub), deleted-page 'more by'. Every page-owned lineup is now tanquery-driven.
Full legacy deletion
Components deleted:
Lineup.tsx,LineupProvider.tsx,TanQueryLineup.tsx,hooks.tscomponents/lineup/Lineup.tsx,TanQueryLineup.tsx,index.ts,useFavoritesLineup.ts,useFetchCollectionLineup.tsHelpers deleted:
common/api/tan-query/lineups/useLineupQuery.tscommon/store/lineup/useLineupTable.tsweb/common/store/lineup/sagas.ts(LineupSagasclass)web/common/store/cache/collections/utils/updateCollectionPageLineup.ts+refreshCollectionPageLineup.tsPer-page lineup state + sagas deleted for: collection, track, search-results, remixes, profile (tracks + feed), feed, history-page, library-page. All
pages/*/lineup[s]/directories in bothcommonandwebare gone.lineupRegistryis now empty.Saga registrations removed from web + mobile root sagas:
historySagas,remixesSagas,searchTracksLineupSagas, profile's feed + tracks lineup sagas, track's lineup saga, library's lineup saga, feed's lineup saga, collection's tracks lineup saga. Dashboard'sretrieveUserTrackshelper was inlined into its saga (was the only surviving caller).Rewires
tracks.entriesfromuseCollection+useTracks+useUsers. Sort/reorder is now UI-owned local state (sortedOrder/customOrder). No more legacy lineup state needed for the sortable/reorderable TracksTable.trackIds: ID[]+source: string; dispatches playback slice directly (desktop history + library).playbackSourcestring;playTrackdispatchesplaybackActions.playFromfor the current track. Both web + mobile comment drawers updated.Compat preserved
Source tags match legacy lineup prefixes (
SAVED_TRACKS,COLLECTION_TRACKS,TRACK_TRACKS,DISCOVER_FEED,PROFILE_TRACKS, etc.) so downstream consumers keying onqueueSourcekeep working — most notably mobileAudioPlayer's offline-download check and desktopPlaylistLibrarycurrent-track highlight.The playback saga's shadow into legacy
queue+playerslices keeps PlayBar, NowPlayingDrawer, and all mobile audio screens functioning unchanged.Test plan
packages/common,packages/web,packages/mobileall typecheck cleanplayFrom,playback+playerslices stay synced, real artist names render (no "Unknown")useCollectionPage+useLibraryPagewere missinguseUsers()+userattachment on each entry (TQTrack omits the user field). Both fixed via paralleluseUsers(ownerIds)+ skipping entries whose user hasn't loaded yet.Scope
Touched: 251 files, +4724 / -11612.
🤖 Generated with Claude Code