fix(player): play-next must use insertedAt, not size-1, when idle

Final review of caeef65 caught a stale-currentIndex bug in
/play-next-song and cmdPlayNext: when the player is idle but
queue.currentIndex >= 0 (natural end-of-track, or playNext gave
up after retries without queue.clear), addNext splices mid-queue
and playAt(size-1) jumps PAST the inserted song to whatever
was last. Capture the insertion slot before calling addNext and
promote that exact index instead.

Add a regression test covering the [a,b,c,d] queue with
currentIndex=1 case -- splice at 2 yields x at index 2, but
size-1 would point at d (index 4).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
saopig1andClaude Opus 4.7 committed 2026-05-06 17:00:05 +08:00
1 parent caeef65cdb
commit fab8c194e3
3 files changed
+43 -5

No files matched your search

+24
View File
@@ -459,5 +459,29 @@ describe("PlayQueue", () => {
// prev again → pop 0 → song at index 0 = a
expect(queue.prev()?.id).toBe("a");
});
it("idle player + stale currentIndex: insertion target is currentIndex+1, not size-1", () => {
// Reproduces the scenario where the player has gone idle but the
// queue still has a non-negative currentIndex (e.g., after natural
// track end without queue.clear()).
queue.add(makeSong("a"));
queue.add(makeSong("b"));
queue.add(makeSong("c"));
queue.add(makeSong("d"));
queue.play(); // current = 0 (a)
queue.next(); // current = 1 (b)
// Simulate idle-with-stale-currentIndex: the player has gone idle
// but queue still points at b.
// Caller pre-captures insertedAt:
const insertedAt = queue.getCurrentIndex() + 1; // = 2
queue.addNext(makeSong("x"));
// queue is now [a, b, x, c, d]
// size-1 would be 4 (d) — WRONG.
// insertedAt is 2 (x) — RIGHT.
expect(queue.list().map((s) => s.id)).toEqual(["a", "b", "x", "c", "d"]);
expect(queue.size() - 1).toBe(4); // proves size-1 strategy would pick d
const promoted = queue.playAt(insertedAt);
expect(promoted?.id).toBe("x");
});
});
});