fix(queue): clicking a track respects shuffle order (#327)

The frontend was resolving queue-panel clicks via
`actualIndex = current_index + trackIndex`, mixing the unshuffled
canonical position with the shuffle-aware display position. On shuffle
mode, the resulting index pointed at a different track than the one
the user clicked, which is exactly what issue #327 reports.

Solution:
- New `QueueManager::play_upcoming_at(upcoming_index)` resolves the
  click position through `shuffle_order` + `shuffle_position` when
  shuffle is on, and falls back to `current_index + upcoming_index + 1`
  otherwise.
- Exposed through `QbzCore::play_upcoming_at` (emits QueueUpdated) and
  `CoreBridge::play_upcoming_at`.
- New `v2_play_queue_upcoming_at` tauri command.
- `QueuePanel.onPlayTrack` now carries the `originalIndex` so the
  caller knows the exact upcoming-list position the user clicked.
- `handleQueueTrackPlay` calls the new command directly — no more
  client-side index arithmetic.

Also deletes the two dead `From<crate::queue::QueueTrack>` impls in
commands_v2/queue.rs (canonical queue lives in qbz-player now), per
the zero-legacy V2 policy.

Tests: two new unit tests cover the linear and shuffle-aware paths.
Session persistence is untouched — the new method only consults
existing state.

cc
This commit is contained in:
vicrodh committed 2026-04-16 20:04:24 -06:00
1 parent 6775d3a474
commit 5656bbe2ab
8 files changed
+135 -77

No files matched your search

+13
View File
@@ -327,6 +327,19 @@ impl<A: FrontendAdapter + Send + Sync + 'static> QbzCore<A> {
track
}
/// Jump to a track by its position in the upcoming list (as shown in the
/// Queue sidebar). Shuffle-aware: resolves through `shuffle_order` when
/// shuffle is active.
pub async fn play_upcoming_at(&self, upcoming_index: usize) -> Option<QueueTrack> {
let queue = self.queue.write().await;
let track = queue.play_upcoming_at(upcoming_index);
self.emit(CoreEvent::QueueUpdated {
state: queue.get_state(),
})
.await;
track
}
/// Advance to next track in queue
pub async fn next_track(&self) -> Option<QueueTrack> {
let queue = self.queue.write().await;