Adding the queue move/remove/clear pass-throughs pushed the VM to 12 functions
(detekt cap 11). It's a thin transport facade forwarding to PlayerController, so
suppress with a rationale rather than splitting the delegating surface.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Queue screen gains: album-art thumbnails (ServerImage), drag-to-reorder via a
grip handle (offset->delta on release, mirroring the web), a remove button per
row, auto-follow of the now-playing track with a 'Jump to current' pill when
scrolled away, a clear-queue action, and a header count + total-time summary.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds moveInQueue/removeFromQueue/clearQueue, each keeping the domain queueRefs
snapshot in lock-step with the Media3 timeline (mirrors playNext/enqueue). Media3
onEvents rebuilds uiState so the queue view reflects reorder/removal/clear.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
QueueList now follows the now-playing row as the track auto-advances (only
while it's in view), centers it on open, and surfaces a 'Jump to current' pill
once the user scrolls it off-screen. Header gains a clear-queue action backed
by a new store clearQueue() that empties the queue and stops playback.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds a 40px cover thumbnail (coverUrl(album_id), FALLBACK_COVER on error) to
each queue row, matching the artwork every comparable player shows in its
up-next list.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The queue auto-scroll $effect calls scrollIntoView on render, and jsdom
doesn't implement it, so QueueDrawer.test.ts threw an unhandled TypeError that
failed the run even though every assertion passed. Polyfill it as a no-op in
the shared setup; tests never assert on scroll position.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
QueueList gains an `active` prop; when it flips true (drawer opens) or on mount
(now-playing panel) it centers the current row in view. Index/length are read
untracked so it positions once per open rather than following auto-advance,
matching the Android queue. QueueDrawer passes active={queueDrawerOpen} since
its aside is always mounted.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
QueueList used a plain LazyColumn with no hoisted state, so the queue always
opened at the top and the current track could be off-screen. Seed a
rememberLazyListState with the current index (coerced into bounds) so the list
renders already positioned on the now-playing row — no post-layout scroll flash.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The queue drawer's <aside> is always mounted, so QueueTrackRow's LikeButton
(added in #1596) instantiates the moment the queue populates on first play.
LikeButton calls useQueryClient() at init; with the drawer outside the
provider it threw 'No QueryClient was found in Svelte context', aborting the
reactive flush that starts playback — so play appeared to do nothing.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>