Skip to content

fix(mobile): queue the track-page lineup from the hero Play button#14447

Merged
dylanjeffers merged 1 commit into
mainfrom
feat/mobile-track-page-queue-lineup
Jun 5, 2026
Merged

fix(mobile): queue the track-page lineup from the hero Play button#14447
dylanjeffers merged 1 commit into
mainfrom
feat/mobile-track-page-queue-lineup

Conversation

@dylanjeffers
Copy link
Copy Markdown
Contributor

The bug

When you press the big Play button at the top of a track-detail screen on mobile, it should queue up the related-tracks lineup that the same screen renders below — Remixes / More By <artist> / You Might Also Like — so playback continues past the hero track. Today it doesn't: the queue is exactly one item and playback stops at the end of the hero.

Every other lineup-driven screen on mobile (feed, profile tracks/reposts, library) does this correctly because its play interactions go through TrackLineup, which builds a multi-track playFrom queue with a querySource. The track screen's hero Play button bypasses TrackLineup entirely and dispatches its own playFrom.

Before

packages/mobile/src/screens/track-screen/TrackScreenDetailsTile.tsx:

dispatch(
  playbackActions.playFrom({
    tracks: [{ trackId, source: 'TRACK_TRACKS' }],
    startIndex: 0,
    querySource: null
  })
)

After

Subscribe to the same useTrackPageLineup hook that TrackScreenLineup already uses to render the sections below the track, and build the queue from trackIds:

const { trackIds: lineupTrackIds } = useTrackPageLineup(
  { trackId },
  { enabled: !!isReachable }   // mirror the gate on <TrackScreenLineup> in TrackScreen.tsx
)

// inside play():
const hasLineup =
  !isPreview &&
  lineupTrackIds.length > 1 &&
  lineupTrackIds[0] === trackId

const tracks: PlaybackTrack[] = hasLineup
  ? [
      { trackId, source: 'TRACK_TRACKS' },
      ...lineupTrackIds.slice(1).map((id) => ({
        trackId: id,
        source: 'TRACK_PAGE_MORE_BY'
      }))
    ]
  : [{ trackId, source: 'TRACK_TRACKS' }]

dispatch(
  playbackActions.playFrom({
    tracks,
    startIndex: 0,
    querySource: hasLineup
      ? { queryKey: [...getTrackPageLineupQueryKey(trackId)] }
      : null
  })
)

Why these specific source strings

  • Hero stays 'TRACK_TRACKS' so its uid (makeStableUid(Kind.TRACKS, track_id, 'TRACK_TRACKS') from TrackScreen.tsx) still matches the queue entry, and the hero's own play-button state continues to derive correctly from the playback selectors.
  • Related tracks use 'TRACK_PAGE_MORE_BY' because that is the source TrackScreenLineup already hands TrackLineup for all four sections (renderRemixParentSection, renderRemixesSection, renderMoreBySection, renderRecommendedSection). When auto-advance reaches a related track, the corresponding tile down the page highlights as currently-playing.

Why useTrackPageLineup here doesn't double-fetch

tanquery shares query data by key. TrackScreenLineup already subscribes via useTrackPageLineup({ trackId }); adding a second subscription in TrackScreenDetailsTile for the same trackId hits the same cache entry. The enabled: !!isReachable matches the truthiness check {isReachable ? <TrackScreenLineup>... in TrackScreen.tsx so we don't fire the network fetch when offline.

Edge cases

Case Behavior
Preview button (isPreview === true) Falls back to single-track queue. Preview is intentionally a one-track, no-auto-advance gesture.
Fast tap before lineup loads lineupTrackIds.length <= 1 → falls back to single-track queue (today's behavior).
Offline enabled: !!isReachable keeps the hook from firing → lineupTrackIds stays empty → single-track queue (today's behavior). The lineup section also doesn't render below in this state, so there's nothing to queue anyway.
lineupTrackIds[0] somehow isn't the hero Defensive bail-out → single-track queue. Shouldn't happen — useTrackPageLineup always pushes the hero at index 0 and sets indices.mainTrackIndex = 0 — but the guard costs nothing.

Verification

  • tsc --noEmit clean in packages/mobile.
  • Manual: open a track, press Play → hero plays. Let it end → next track plays from More By or You Might Also Like, and the matching tile down the page highlights as playing.
  • Manual: tap a track tile in the More By / You Might Also Like sections → still plays from that point in the same queue (unchanged from before).
  • Manual: tap Preview on a gated track → plays the preview only, no auto-advance (unchanged from before).
  • Manual: airplane-mode → tap Play → still plays the hero track as a single-track queue, no error, no extra fetch (lineup section is also hidden by isReachable).
  • Manual: NowPlayingDrawer → Up Next list shows the related tracks queued after the hero.

🤖 Generated with Claude Code

When you press the big Play button on a mobile track-detail screen, it
should hand the related-tracks lineup that the same screen already
fetches (Remixes / More By <artist> / You Might Also Like) to the
player as the queue — so playback flows past the hero track into those
related sections, the same way pressing play on a tile in any other
lineup-driven screen (feed, profile, library) does.

Before this fix, `TrackScreenDetailsTile`'s play handler dispatched
`playFrom({ tracks: [{ trackId, source: 'TRACK_TRACKS' }], startIndex: 0,
querySource: null })` — a queue of exactly one item — so playback
stopped at the end of the hero track.

Wire the hero button into the same `useTrackPageLineup` hook
`TrackScreenLineup` uses to render the sections below the track:

- Subscribe with `enabled: !!isReachable` to mirror the visibility gate
  on `TrackScreenLineup` in `TrackScreen.tsx`. tanquery shares the cache
  by query key with the existing subscription in `TrackScreenLineup`,
  so this does not double-fetch.
- In the `play` callback, build the queue as
  `[{ trackId, source: 'TRACK_TRACKS' }, ...rest.map(id => ({ trackId: id,
  source: 'TRACK_PAGE_MORE_BY' }))]`. The hero keeps the legacy
  `'TRACK_TRACKS'` source so its uid
  (`makeStableUid(Kind.TRACKS, track_id, 'TRACK_TRACKS')` from
  `TrackScreen.tsx`) still matches. The remainder uses
  `'TRACK_PAGE_MORE_BY'` so when auto-advance reaches a related track,
  the matching tile in `TrackScreenLineup` (which also renders with
  `source='TRACK_PAGE_MORE_BY'`) highlights as playing.
- Pass `querySource: { queryKey: [...getTrackPageLineupQueryKey(trackId)] }`
  to match what `TrackScreenLineup` already hands `TrackLineup`, so the
  playback saga can paginate the lineup near the queue's end.
- Skip the lineup queue when the Preview button is pressed
  (`isPreview === true`) — preview is intentionally a single-track,
  no-auto-advance gesture.
- Fall back to the original single-track queue when the lineup isn't
  loaded yet (offline, or a fast tap before the hook resolves) — so
  the worst case matches today's behavior.

Files:
- packages/mobile/src/screens/track-screen/TrackScreenDetailsTile.tsx

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Jun 4, 2026

⚠️ No Changeset found

Latest commit: c39554a

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@dylanjeffers dylanjeffers merged commit 030b33a into main Jun 5, 2026
3 checks passed
@dylanjeffers dylanjeffers deleted the feat/mobile-track-page-queue-lineup branch June 5, 2026 17:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant