Skip to content

Commit fe613b5

Browse files
feat(queue): backend queue state machine replacing frontend shuffle, navigation, and play-next logic (#42)
* feat(queue): backend queue state machine replacing frontend shuffle, navigation, and play-next logic Move ~500 lines of queue state machine from JS frontend to Rust backend. Frontend queue store becomes a thin reactive layer calling backend commands and applying returned state snapshots. Backend: - Extend queue_state table with play_next_offset, play_history_json, play_next_track_ids_json, repeat_one_pending columns - Add toggle_shuffle (Fisher-Yates with play-next pinning), add_play_next (move semantics + offset tracking), advance_to_next/previous (repeat-one two-phase, loop modes, history), skip_to_next/previous (override repeat-one), check_integrity - Register 6 new Tauri commands, enhance queue_set_shuffle to return QueueStateSnapshot - 773 Rust tests pass Frontend: - Remove _shuffleItems, _reshuffleForLoopRestart, _originalOrder, _playHistory, _playNextTrackIds, _playNextOffset, _repeatOnePending, _syncQueueToBackend, _validateQueueIntegrity - Add _applySnapshot and _applyNavigationResult - Update queue-builder and queue.props tests for backend-delegated pattern - 444 Vitest tests pass Closes TASK-328 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(queue): resolve deno lint errors in queue store Remove unnecessary async from _applyNavigationResult (no await inside). Prefix unused fromNavigation parameter with underscore. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(lint): collapse nested if in watcher scan completion Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(queue): optimistic shuffle toggle and aria-pressed for accessibility toggleShuffle now sets this.shuffle before the backend call (like cycleLoop does for loop mode), reverting on failure. Add aria-pressed to the shuffle button so the E2E accessibility test detects state change. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 980c9ba commit fe613b5

22 files changed

+2168
-1191
lines changed

app/frontend/__tests__/queue-builder.test.js

Lines changed: 4 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,6 @@ function createMockCtx(queueOverrides = {}, playerOverrides = {}) {
4646
currentIndex: -1,
4747
shuffle: false,
4848
_updating: false,
49-
_originalOrder: [],
50-
_playHistory: [],
51-
_playNextOffset: 0,
52-
_playNextTrackIds: new Set(),
53-
_pushToHistory: vi.fn(),
5449
...queueOverrides,
5550
},
5651
player: {
@@ -131,37 +126,17 @@ describe('handleDoubleClickPlay', () => {
131126
expect(ctx.queue.currentIndex).toBe(1);
132127
});
133128

134-
it('resets queue state fields after play context', async () => {
129+
it('applies shuffle_enabled from result', async () => {
135130
const tracks = makeTracks(['A', 'B']);
136131
const result = makePlayContextResult(tracks, 0);
132+
result.shuffle_enabled = true;
137133
queueApi.playContext.mockResolvedValue(result);
138134

139-
const existingHistory = [{ id: 99, title: 'Old' }];
140-
const ctx = createMockCtx({
141-
_playHistory: existingHistory,
142-
_playNextOffset: 5,
143-
_playNextTrackIds: new Set([99]),
144-
});
145-
146-
await handleDoubleClickPlay(ctx, tracks[0], tracks, 0, 'test');
147-
148-
expect(ctx.queue._playHistory).toEqual([]);
149-
expect(ctx.queue._playNextOffset).toBe(0);
150-
expect(ctx.queue._playNextTrackIds.size).toBe(0);
151-
});
152-
153-
it('sets _originalOrder to match items', async () => {
154-
const tracks = makeTracks(['A', 'B', 'C']);
155-
const result = makePlayContextResult(tracks, 0);
156-
queueApi.playContext.mockResolvedValue(result);
157-
158-
const ctx = createMockCtx();
135+
const ctx = createMockCtx({ shuffle: false });
159136

160137
await handleDoubleClickPlay(ctx, tracks[0], tracks, 0, 'test');
161138

162-
expect(ctx.queue._originalOrder).toEqual(tracks);
163-
// Should be a copy, not the same reference
164-
expect(ctx.queue._originalOrder).not.toBe(ctx.queue.items);
139+
expect(ctx.queue.shuffle).toBe(true);
165140
});
166141

167142
it('calls updateTrackState with track and duration_ms', async () => {

0 commit comments

Comments
 (0)