refactor(playback): move thin-state queue resolver out of utils/library into the feature

The queue-resolver family (queueTrackResolver, queueRestore, queueItemRef,
queueTrackView) runtime-imports usePlayerStore / playerStoreTypes — it is the
playback engine's thin-state queue subsystem, not shared library infra. It sat
in utils/library only because it queries the local index. Co-locate into
features/playback/store so those edges become intra-feature; its remaining deps
(advancedSearchLocal, libraryReady, serverLookup, serverIndexKey, authStore) are
plain core/infra (feature -> infra, no inversion).

Removes 4 of the 6 store/utils -> feature runtime inversions that blocked the
utils/library -> lib move. 8 files moved, 46 consumers rewritten. tsc 0, lint 0,
targeted suites green.
This commit is contained in:
Psychotoxical
2026-06-30 16:50:29 +02:00
parent cb1a110afb
commit f41005682d
50 changed files with 73 additions and 73 deletions
+1 -1
View File
@@ -6,7 +6,7 @@ import { resetAllStores } from '../test/helpers/storeReset';
import { invokeMock, onInvoke } from '../test/mocks/tauri'; import { invokeMock, onInvoke } from '../test/mocks/tauri';
import { coverArtRef } from '../cover/ref'; import { coverArtRef } from '../cover/ref';
import { coverCacheEnsure, coverCacheRestHost, librarySqlServerId } from './coverCache'; import { coverCacheEnsure, coverCacheRestHost, librarySqlServerId } from './coverCache';
import { toQueueItemRefs } from '../utils/library/queueItemRef'; import { toQueueItemRefs } from '@/features/playback/store/queueItemRef';
describe('librarySqlServerId', () => { describe('librarySqlServerId', () => {
beforeEach(() => { beforeEach(() => {
+1 -1
View File
@@ -23,7 +23,7 @@ import { reconcileLibraryTierForServer } from '@/features/offline';
import { initMiniPlayerBridgeOnMain } from '@/features/miniPlayer'; import { initMiniPlayerBridgeOnMain } from '@/features/miniPlayer';
import { runAdvancedModeMigration } from '../utils/migrations/advancedModeMigration'; import { runAdvancedModeMigration } from '../utils/migrations/advancedModeMigration';
import { bootstrapAllIndexedServers } from '../utils/library/librarySession'; import { bootstrapAllIndexedServers } from '../utils/library/librarySession';
import { hydrateQueueFromIndex } from '../utils/library/queueRestore'; import { hydrateQueueFromIndex } from '@/features/playback/store/queueRestore';
import { useLibraryAnalysisBackfill } from '../hooks/useLibraryAnalysisBackfill'; import { useLibraryAnalysisBackfill } from '../hooks/useLibraryAnalysisBackfill';
import { useCoverArtPrefetch } from '../cover/useCoverArtPrefetch'; import { useCoverArtPrefetch } from '../cover/useCoverArtPrefetch';
import { useLibraryCoverBackfill } from '../hooks/useLibraryCoverBackfill'; import { useLibraryCoverBackfill } from '../hooks/useLibraryCoverBackfill';
+2 -2
View File
@@ -17,11 +17,11 @@ import { usePlayerStore } from '@/features/playback/store/playerStore';
import { useCachedUrl } from '@/ui/CachedImage'; import { useCachedUrl } from '@/ui/CachedImage';
import { OpenArtistRefInline } from '@/features/artist'; import { OpenArtistRefInline } from '@/features/artist';
import { formatTrackTime } from '@/lib/format/formatDuration'; import { formatTrackTime } from '@/lib/format/formatDuration';
import { resolveQueueTrack } from '../utils/library/queueTrackView'; import { resolveQueueTrack } from '@/features/playback/store/queueTrackView';
import { import {
getQueueResolverVersion, getQueueResolverVersion,
subscribeQueueResolver, subscribeQueueResolver,
} from '../utils/library/queueTrackResolver'; } from '@/features/playback/store/queueTrackResolver';
import LyricsPane from './LyricsPane'; import LyricsPane from './LyricsPane';
import { usePlaybackDelayPress } from '../hooks/usePlaybackDelayPress'; import { usePlaybackDelayPress } from '../hooks/usePlaybackDelayPress';
import PlaybackDelayModal from './PlaybackDelayModal'; import PlaybackDelayModal from './PlaybackDelayModal';
+1 -1
View File
@@ -7,7 +7,7 @@ import { useAuthStore } from '../store/authStore';
import { usePlayerStore } from '@/features/playback/store/playerStore'; import { usePlayerStore } from '@/features/playback/store/playerStore';
import { makeTrack } from '../test/helpers/factories'; import { makeTrack } from '../test/helpers/factories';
import { resetAllStores } from '../test/helpers/storeReset'; import { resetAllStores } from '../test/helpers/storeReset';
import { toQueueItemRefs } from '../utils/library/queueItemRef'; import { toQueueItemRefs } from '@/features/playback/store/queueItemRef';
const hoisted = vi.hoisted(() => ({ const hoisted = vi.hoisted(() => ({
useCoverArtMock: vi.fn( useCoverArtMock: vi.fn(
@@ -3,11 +3,11 @@ import { useTranslation } from 'react-i18next';
import { X } from 'lucide-react'; import { X } from 'lucide-react';
import { usePlayerStore } from '@/features/playback/store/playerStore'; import { usePlayerStore } from '@/features/playback/store/playerStore';
import type { Track } from '@/features/playback/store/playerStoreTypes'; import type { Track } from '@/features/playback/store/playerStoreTypes';
import { resolveQueueTrack } from '@/utils/library/queueTrackView'; import { resolveQueueTrack } from '@/features/playback/store/queueTrackView';
import { import {
getQueueResolverVersion, getQueueResolverVersion,
subscribeQueueResolver, subscribeQueueResolver,
} from '@/utils/library/queueTrackResolver'; } from '@/features/playback/store/queueTrackResolver';
import { formatTrackTime } from '@/lib/format/formatDuration'; import { formatTrackTime } from '@/lib/format/formatDuration';
interface Props { interface Props {
@@ -3,7 +3,7 @@ import { listen, emitTo } from '@tauri-apps/api/event';
import { usePlayerStore } from '@/features/playback/store/playerStore'; import { usePlayerStore } from '@/features/playback/store/playerStore';
import { useAuthStore } from '@/store/authStore'; import { useAuthStore } from '@/store/authStore';
import { setTransitionMode, type TransitionMode } from '@/features/playback/utils/playback/playbackTransition'; import { setTransitionMode, type TransitionMode } from '@/features/playback/utils/playback/playbackTransition';
import { resolveQueueTrack } from '@/utils/library/queueTrackView'; import { resolveQueueTrack } from '@/features/playback/store/queueTrackView';
import type { SubsonicOpenArtistRef } from '@/lib/api/subsonicTypes'; import type { SubsonicOpenArtistRef } from '@/lib/api/subsonicTypes';
import type { Track } from '@/features/playback/store/playerStoreTypes'; import type { Track } from '@/features/playback/store/playerStoreTypes';
@@ -1,5 +1,5 @@
import { usePlayerStore } from '@/features/playback/store/playerStore'; import { usePlayerStore } from '@/features/playback/store/playerStore';
import { resolveQueueTrack } from '@/utils/library/queueTrackView'; import { resolveQueueTrack } from '@/features/playback/store/queueTrackView';
import type { MiniSyncPayload, MiniTrackInfo } from '@/features/miniPlayer/utils/miniPlayerBridge'; import type { MiniSyncPayload, MiniTrackInfo } from '@/features/miniPlayer/utils/miniPlayerBridge';
import type { Track } from '@/features/playback/store/playerStoreTypes'; import type { Track } from '@/features/playback/store/playerStoreTypes';
@@ -8,7 +8,7 @@ import { useAuthStore } from '@/store/authStore';
import { usePlayerStore } from '@/features/playback/store/playerStore'; import { usePlayerStore } from '@/features/playback/store/playerStore';
import { makeTrack } from '@/test/helpers/factories'; import { makeTrack } from '@/test/helpers/factories';
import { resetAllStores } from '@/test/helpers/storeReset'; import { resetAllStores } from '@/test/helpers/storeReset';
import { toQueueItemRefs } from '@/utils/library/queueItemRef'; import { toQueueItemRefs } from '@/features/playback/store/queueItemRef';
vi.mock('@/api/coverCache', async importOriginal => { vi.mock('@/api/coverCache', async importOriginal => {
const actual = await importOriginal<typeof import('@/api/coverCache')>(); const actual = await importOriginal<typeof import('@/api/coverCache')>();
@@ -10,8 +10,8 @@ import {
import { clearPreloadingIds } from '@/features/playback/store/gaplessPreloadState'; import { clearPreloadingIds } from '@/features/playback/store/gaplessPreloadState';
import { deriveNormalizationSnapshot } from '@/features/playback/store/normalizationSnapshot'; import { deriveNormalizationSnapshot } from '@/features/playback/store/normalizationSnapshot';
import type { PlayerState, QueueItemRef } from '@/features/playback/store/playerStoreTypes'; import type { PlayerState, QueueItemRef } from '@/features/playback/store/playerStoreTypes';
import { resolveQueueTrack } from '@/utils/library/queueTrackView'; import { resolveQueueTrack } from '@/features/playback/store/queueTrackView';
import { seedQueueResolver } from '@/utils/library/queueTrackResolver'; import { seedQueueResolver } from '@/features/playback/store/queueTrackResolver';
import { canonicalQueueServerKey } from '@/utils/server/serverIndexKey'; import { canonicalQueueServerKey } from '@/utils/server/serverIndexKey';
import { sameQueueTrackId } from '@/features/playback/utils/playback/queueIdentity'; import { sameQueueTrackId } from '@/features/playback/utils/playback/queueIdentity';
import { queueUndoRestoreAudioEngine } from '@/features/playback/store/queueUndoAudioRestore'; import { queueUndoRestoreAudioEngine } from '@/features/playback/store/queueUndoAudioRestore';
@@ -2,8 +2,8 @@ import { getPlayQueueForServer, type PlayQueueResult } from '@/lib/api/subsonicP
import { songToTrack } from '@/features/playback/utils/playback/songToTrack'; import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
import { bindQueueServerId } from '@/features/playback/utils/playback/playbackServer'; import { bindQueueServerId } from '@/features/playback/utils/playback/playbackServer';
import { resolveServerIdForIndexKey } from '@/utils/server/serverLookup'; import { resolveServerIdForIndexKey } from '@/utils/server/serverLookup';
import { toQueueItemRefs } from '@/utils/library/queueItemRef'; import { toQueueItemRefs } from '@/features/playback/store/queueItemRef';
import { seedQueueResolver } from '@/utils/library/queueTrackResolver'; import { seedQueueResolver } from '@/features/playback/store/queueTrackResolver';
import type { Track } from '@/features/playback/store/playerStoreTypes'; import type { Track } from '@/features/playback/store/playerStoreTypes';
import { useAuthStore } from '@/store/authStore'; import { useAuthStore } from '@/store/authStore';
import { usePlayerStore } from '@/features/playback/store/playerStore'; import { usePlayerStore } from '@/features/playback/store/playerStore';
@@ -5,7 +5,7 @@ import {
playbackReportStart, playbackReportStart,
playbackReportStopped, playbackReportStopped,
} from '@/features/playback/store/playbackReportSession'; } from '@/features/playback/store/playbackReportSession';
import { resolveQueueTrack } from '@/utils/library/queueTrackView'; import { resolveQueueTrack } from '@/features/playback/store/queueTrackView';
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '@tauri-apps/api/core';
import { getMusicNetworkRuntimeOrNull } from '@/music-network'; import { getMusicNetworkRuntimeOrNull } from '@/music-network';
import { setDeferHotCachePrefetch } from '@/utils/cache/hotCacheGate'; import { setDeferHotCachePrefetch } from '@/utils/cache/hotCacheGate';
@@ -16,13 +16,13 @@ import { useAuthStore } from '@/store/authStore';
import { usePlayerStore } from '@/features/playback/store/playerStore'; import { usePlayerStore } from '@/features/playback/store/playerStore';
import { useLibraryIndexStore } from '@/store/libraryIndexStore'; import { useLibraryIndexStore } from '@/store/libraryIndexStore';
import { resetAuthStore, resetPlayerStore } from '@/test/helpers/storeReset'; import { resetAuthStore, resetPlayerStore } from '@/test/helpers/storeReset';
import { toQueueItemRefs } from '@/utils/library/queueItemRef'; import { toQueueItemRefs } from '@/features/playback/store/queueItemRef';
import { bindQueueServerForPlayback } from '@/features/playback/utils/playback/playbackServer'; import { bindQueueServerForPlayback } from '@/features/playback/utils/playback/playbackServer';
import { import {
_resetQueueResolverForTest, _resetQueueResolverForTest,
getCachedTrack, getCachedTrack,
seedQueueResolver, seedQueueResolver,
} from '@/utils/library/queueTrackResolver'; } from '@/features/playback/store/queueTrackResolver';
import { applyQueueHistorySnapshot } from '@/features/playback/store/applyQueueHistorySnapshot'; import { applyQueueHistorySnapshot } from '@/features/playback/store/applyQueueHistorySnapshot';
import { import {
pushQueueUndoSnapshot, pushQueueUndoSnapshot,
@@ -5,7 +5,7 @@ import { computeWaveformSilence, planCrossfadeTransition } from '@/utils/wavefor
import { findLocalPlaybackUrl } from '@/store/localPlaybackResolve'; import { findLocalPlaybackUrl } from '@/store/localPlaybackResolve';
import { playbackCacheKeyForRef } from '@/features/playback/utils/playback/playbackServer'; import { playbackCacheKeyForRef } from '@/features/playback/utils/playback/playbackServer';
import { resolvePlaybackUrl } from '@/features/playback/utils/playback/resolvePlaybackUrl'; import { resolvePlaybackUrl } from '@/features/playback/utils/playback/resolvePlaybackUrl';
import { resolveQueueTrack } from '@/utils/library/queueTrackView'; import { resolveQueueTrack } from '@/features/playback/store/queueTrackView';
import type { Track } from '@/features/playback/store/playerStoreTypes'; import type { Track } from '@/features/playback/store/playerStoreTypes';
import { import {
hasPlannedCrossfade, hasPlannedCrossfade,
+3 -3
View File
@@ -12,9 +12,9 @@ import { reseedLoudnessForTrackId } from '@/features/playback/store/loudnessRese
import { getPlaybackProgressSnapshot } from '@/features/playback/store/playbackProgress'; import { getPlaybackProgressSnapshot } from '@/features/playback/store/playbackProgress';
import { shouldRebindPlaybackToHotCache } from '@/features/playback/store/playbackUrlRouting'; import { shouldRebindPlaybackToHotCache } from '@/features/playback/store/playbackUrlRouting';
import type { PlayerState } from '@/features/playback/store/playerStoreTypes'; import type { PlayerState } from '@/features/playback/store/playerStoreTypes';
import { toQueueItemRefs } from '@/utils/library/queueItemRef'; import { toQueueItemRefs } from '@/features/playback/store/queueItemRef';
import { resolveQueueTrack } from '@/utils/library/queueTrackView'; import { resolveQueueTrack } from '@/features/playback/store/queueTrackView';
import { seedQueueResolver } from '@/utils/library/queueTrackResolver'; import { seedQueueResolver } from '@/features/playback/store/queueTrackResolver';
import { pushQueueUndoFromGetter } from '@/features/playback/store/queueUndo'; import { pushQueueUndoFromGetter } from '@/features/playback/store/queueUndo';
import { syncUserQueueMutationToServer } from '@/features/playback/store/queueSync'; import { syncUserQueueMutationToServer } from '@/features/playback/store/queueSync';
import { import {
+3 -3
View File
@@ -11,9 +11,9 @@ import {
} from '@/features/playback/store/infiniteQueueState'; } from '@/features/playback/store/infiniteQueueState';
import { isInOrbitSession } from '@/store/orbitRuntime'; import { isInOrbitSession } from '@/store/orbitRuntime';
import type { PlayerState, QueueItemRef, Track } from '@/features/playback/store/playerStoreTypes'; import type { PlayerState, QueueItemRef, Track } from '@/features/playback/store/playerStoreTypes';
import { toQueueItemRefs } from '@/utils/library/queueItemRef'; import { toQueueItemRefs } from '@/features/playback/store/queueItemRef';
import { resolveQueueTrack } from '@/utils/library/queueTrackView'; import { resolveQueueTrack } from '@/features/playback/store/queueTrackView';
import { seedQueueResolver } from '@/utils/library/queueTrackResolver'; import { seedQueueResolver } from '@/features/playback/store/queueTrackResolver';
import { import {
addRadioSessionSeen, addRadioSessionSeen,
getCurrentRadioArtistId, getCurrentRadioArtistId,
@@ -29,7 +29,7 @@ vi.mock('@/features/playback/store/infiniteQueueState', () => ({
})); }));
vi.mock('@/features/playback/store/engineState', () => ({ setIsAudioPaused: vi.fn() })); vi.mock('@/features/playback/store/engineState', () => ({ setIsAudioPaused: vi.fn() }));
vi.mock('@/features/playback/store/skipStarRating', () => ({ applySkipStarOnManualNext: vi.fn() })); vi.mock('@/features/playback/store/skipStarRating', () => ({ applySkipStarOnManualNext: vi.fn() }));
vi.mock('@/utils/library/queueTrackView', () => ({ vi.mock('@/features/playback/store/queueTrackView', () => ({
resolveQueueTrack: (ref: { trackId: string }) => ({ resolveQueueTrack: (ref: { trackId: string }) => ({
id: ref.trackId, id: ref.trackId,
artistId: 'a1', artistId: 'a1',
@@ -41,8 +41,8 @@ vi.mock('@/features/playback/utils/playback/buildInfiniteQueueCandidates', () =>
})); }));
vi.mock('@/features/playback/utils/playback/songToTrack', () => ({ songToTrack: (s: unknown) => s })); vi.mock('@/features/playback/utils/playback/songToTrack', () => ({ songToTrack: (s: unknown) => s }));
vi.mock('@/features/playback/utils/playback/playbackServer', () => ({ ensureQueueServerPinned: () => null })); vi.mock('@/features/playback/utils/playback/playbackServer', () => ({ ensureQueueServerPinned: () => null }));
vi.mock('@/utils/library/queueTrackResolver', () => ({ seedQueueResolver: vi.fn() })); vi.mock('@/features/playback/store/queueTrackResolver', () => ({ seedQueueResolver: vi.fn() }));
vi.mock('@/utils/library/queueItemRef', () => ({ toQueueItemRefs: () => [] })); vi.mock('@/features/playback/store/queueItemRef', () => ({ toQueueItemRefs: () => [] }));
import { runNext } from '@/features/playback/store/nextAction'; import { runNext } from '@/features/playback/store/nextAction';
@@ -40,7 +40,7 @@ vi.mock('@/features/playback/store/playerStore', () => ({
}), }),
}, },
})); }));
vi.mock('@/utils/library/queueTrackView', () => ({ vi.mock('@/features/playback/store/queueTrackView', () => ({
getQueueTracksView: vi.fn((refs: { trackId: string }[]) => getQueueTracksView: vi.fn((refs: { trackId: string }[]) =>
refs.map(r => ({ refs.map(r => ({
id: r.trackId, id: r.trackId,
@@ -1,5 +1,5 @@
import type { QueueItemRef, Track } from '@/features/playback/store/playerStoreTypes'; import type { QueueItemRef, Track } from '@/features/playback/store/playerStoreTypes';
import { getQueueTracksView } from '@/utils/library/queueTrackView'; import { getQueueTracksView } from '@/features/playback/store/queueTrackView';
import { scheduleHotCachePrefetchForTrack } from '@/hotCachePrefetch'; import { scheduleHotCachePrefetchForTrack } from '@/hotCachePrefetch';
import { getPlaybackCacheServerKey } from '@/features/playback/utils/playback/playbackServer'; import { getPlaybackCacheServerKey } from '@/features/playback/utils/playback/playbackServer';
import { useAuthStore } from '@/store/authStore'; import { useAuthStore } from '@/store/authStore';
@@ -16,8 +16,8 @@ import {
getCachedTrack, getCachedTrack,
seedQueueResolver, seedQueueResolver,
_resetQueueResolverForTest, _resetQueueResolverForTest,
} from '@/utils/library/queueTrackResolver'; } from '@/features/playback/store/queueTrackResolver';
import { toQueueItemRefs } from '@/utils/library/queueItemRef'; import { toQueueItemRefs } from '@/features/playback/store/queueItemRef';
const track = (id: string): Track => ({ const track = (id: string): Track => ({
id, title: id, artist: '', album: 'A', albumId: 'A', duration: 1, id, title: id, artist: '', album: 'A', albumId: 'A', duration: 1,
@@ -1,6 +1,6 @@
import { setRating, star, unstar } from '@/lib/api/subsonicStarRating'; import { setRating, star, unstar } from '@/lib/api/subsonicStarRating';
import { usePlayerStore } from '@/features/playback/store/playerStore'; import { usePlayerStore } from '@/features/playback/store/playerStore';
import { patchCachedTrack } from '@/utils/library/queueTrackResolver'; import { patchCachedTrack } from '@/features/playback/store/queueTrackResolver';
/** /**
* F4 pending-sync for **song** star + rating (spec §6.5 / R7-18). * F4 pending-sync for **song** star + rating (spec §6.5 / R7-18).
@@ -58,9 +58,9 @@ import {
recordEnginePlayUrl, recordEnginePlayUrl,
} from '@/features/playback/store/playbackUrlRouting'; } from '@/features/playback/store/playbackUrlRouting';
import type { PlayerState, Track } from '@/features/playback/store/playerStoreTypes'; import type { PlayerState, Track } from '@/features/playback/store/playerStoreTypes';
import { toQueueItemRefs } from '@/utils/library/queueItemRef'; import { toQueueItemRefs } from '@/features/playback/store/queueItemRef';
import { getQueueTracksView, resolveQueueTrack } from '@/utils/library/queueTrackView'; import { getQueueTracksView, resolveQueueTrack } from '@/features/playback/store/queueTrackView';
import { seedQueueResolver } from '@/utils/library/queueTrackResolver'; import { seedQueueResolver } from '@/features/playback/store/queueTrackResolver';
import { promoteCompletedStreamToHotCache } from '@/features/playback/store/promoteStreamCache'; import { promoteCompletedStreamToHotCache } from '@/features/playback/store/promoteStreamCache';
import { pushQueueOnPlaybackStart } from '@/features/playback/store/queueSync'; import { pushQueueOnPlaybackStart } from '@/features/playback/store/queueSync';
import { playListenSessionFinalize } from '@/features/playback/store/playListenSession'; import { playListenSessionFinalize } from '@/features/playback/store/playListenSession';
+1 -1
View File
@@ -5,7 +5,7 @@ import { readInitialPlayerPrefs, persistPlayerPrefs } from '@/features/playback/
import { createHydrationGatedStorage, createSafeJSONStorage } from '@/lib/util/safeStorage'; import { createHydrationGatedStorage, createSafeJSONStorage } from '@/lib/util/safeStorage';
import { emitPlaybackProgress } from '@/features/playback/store/playbackProgress'; import { emitPlaybackProgress } from '@/features/playback/store/playbackProgress';
import type { PlayerState, QueueItemRef, Track } from '@/features/playback/store/playerStoreTypes'; import type { PlayerState, QueueItemRef, Track } from '@/features/playback/store/playerStoreTypes';
import { toQueueItemRefs } from '@/utils/library/queueItemRef'; import { toQueueItemRefs } from '@/features/playback/store/queueItemRef';
import { canonicalQueueServerKey } from '@/utils/server/serverIndexKey'; import { canonicalQueueServerKey } from '@/utils/server/serverIndexKey';
import { readInitialQueueVisibility } from '@/features/playback/store/queueVisibilityStorage'; import { readInitialQueueVisibility } from '@/features/playback/store/queueVisibilityStorage';
import { createNetworkLoveActions } from '@/features/playback/store/networkLoveActions'; import { createNetworkLoveActions } from '@/features/playback/store/networkLoveActions';
@@ -1,6 +1,6 @@
import type { QueueItemRef, Track } from '@/features/playback/store/playerStoreTypes'; import type { QueueItemRef, Track } from '@/features/playback/store/playerStoreTypes';
import { stampTrackServerId } from '@/features/playback/utils/playback/trackServerScope'; import { stampTrackServerId } from '@/features/playback/utils/playback/trackServerScope';
import { canonicalQueueServerKey } from '../server/serverIndexKey'; import { canonicalQueueServerKey } from '@/utils/server/serverIndexKey';
/** /**
* Derive thin `QueueItemRef`s from a `Track[]` queue (thin-state). Per-item * Derive thin `QueueItemRef`s from a `Track[]` queue (thin-state). Per-item
@@ -4,8 +4,8 @@ import { useAuthStore } from '@/store/authStore';
import { setIsAudioPaused } from '@/features/playback/store/engineState'; import { setIsAudioPaused } from '@/features/playback/store/engineState';
import { prefetchLoudnessForEnqueuedTracks } from '@/features/playback/store/loudnessPrefetch'; import { prefetchLoudnessForEnqueuedTracks } from '@/features/playback/store/loudnessPrefetch';
import type { PlayerState, QueueItemRef, Track } from '@/features/playback/store/playerStoreTypes'; import type { PlayerState, QueueItemRef, Track } from '@/features/playback/store/playerStoreTypes';
import { toQueueItemRefs } from '@/utils/library/queueItemRef'; import { toQueueItemRefs } from '@/features/playback/store/queueItemRef';
import { seedQueueResolver } from '@/utils/library/queueTrackResolver'; import { seedQueueResolver } from '@/features/playback/store/queueTrackResolver';
import { pushQueueUndoFromGetter } from '@/features/playback/store/queueUndo'; import { pushQueueUndoFromGetter } from '@/features/playback/store/queueUndo';
import { syncUserQueueMutationToServer } from '@/features/playback/store/queueSync'; import { syncUserQueueMutationToServer } from '@/features/playback/store/queueSync';
import { import {
@@ -8,7 +8,7 @@
* track here, off the render path. * track here, off the render path.
*/ */
import { usePlayerStore } from '@/features/playback/store/playerStore'; import { usePlayerStore } from '@/features/playback/store/playerStore';
import { resolveVisibleRange } from '@/utils/library/queueTrackResolver'; import { resolveVisibleRange } from '@/features/playback/store/queueTrackResolver';
usePlayerStore.subscribe((state, prev) => { usePlayerStore.subscribe((state, prev) => {
// Re-seed when the queue refs or the current index change — the prefetch // Re-seed when the queue refs or the current index change — the prefetch
@@ -1,7 +1,7 @@
import { useAuthStore } from '../../store/authStore'; import { useAuthStore } from '@/store/authStore';
import { usePlayerStore } from '@/features/playback/store/playerStore'; import { usePlayerStore } from '@/features/playback/store/playerStore';
import type { QueueItemRef } from '@/features/playback/store/playerStoreTypes'; import type { QueueItemRef } from '@/features/playback/store/playerStoreTypes';
import { canonicalQueueServerKey } from '../server/serverIndexKey'; import { canonicalQueueServerKey } from '@/utils/server/serverIndexKey';
import { resolveBatch } from './queueTrackResolver'; import { resolveBatch } from './queueTrackResolver';
/** /**
@@ -3,10 +3,10 @@ import { getSongForServer } from '@/lib/api/subsonicLibrary';
import { usePlayerStore } from '@/features/playback/store/playerStore'; import { usePlayerStore } from '@/features/playback/store/playerStore';
import type { QueueItemRef, Track } from '@/features/playback/store/playerStoreTypes'; import type { QueueItemRef, Track } from '@/features/playback/store/playerStoreTypes';
import { songToTrack } from '@/features/playback/utils/playback/songToTrack'; import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
import { resolveServerIdForIndexKey } from '../server/serverLookup'; import { resolveServerIdForIndexKey } from '@/utils/server/serverLookup';
import { canonicalQueueServerKey } from '../server/serverIndexKey'; import { canonicalQueueServerKey } from '@/utils/server/serverIndexKey';
import { trackToSong } from './advancedSearchLocal'; import { trackToSong } from '@/utils/library/advancedSearchLocal';
import { libraryIsReady } from './libraryReady'; import { libraryIsReady } from '@/utils/library/libraryReady';
/** /**
* Queue track resolver (thin-state phase 2). Resolves `QueueItemRef`s to full * Queue track resolver (thin-state phase 2). Resolves `QueueItemRef`s to full
@@ -1,7 +1,7 @@
import { describe, it, expect, beforeEach } from 'vitest'; import { describe, it, expect, beforeEach } from 'vitest';
import { usePlayerStore } from '@/features/playback/store/playerStore'; import { usePlayerStore } from '@/features/playback/store/playerStore';
import type { QueueItemRef, Track } from '@/features/playback/store/playerStoreTypes'; import type { QueueItemRef, Track } from '@/features/playback/store/playerStoreTypes';
import { seedQueueResolver, _resetQueueResolverForTest } from '@/utils/library/queueTrackResolver'; import { seedQueueResolver, _resetQueueResolverForTest } from '@/features/playback/store/queueTrackResolver';
import { resolveQueueTrack, getQueueTracksView } from './queueTrackView'; import { resolveQueueTrack, getQueueTracksView } from './queueTrackView';
const track = (id: string, over: Partial<Track> = {}): Track => const track = (id: string, over: Partial<Track> = {}): Track =>
@@ -5,7 +5,7 @@
* to restore list scroll position after an undo/redo commit. * to restore list scroll position after an undo/redo commit.
*/ */
import type { PlayerState, Track } from '@/features/playback/store/playerStoreTypes'; import type { PlayerState, Track } from '@/features/playback/store/playerStoreTypes';
import { toQueueItemRefs } from '@/utils/library/queueItemRef'; import { toQueueItemRefs } from '@/features/playback/store/queueItemRef';
import { beforeEach, describe, expect, it } from 'vitest'; import { beforeEach, describe, expect, it } from 'vitest';
import { import {
QUEUE_UNDO_MAX, QUEUE_UNDO_MAX,
+1 -1
View File
@@ -27,7 +27,7 @@ import {
recordEnginePlayUrl, recordEnginePlayUrl,
} from '@/features/playback/store/playbackUrlRouting'; } from '@/features/playback/store/playbackUrlRouting';
import type { PlayerState } from '@/features/playback/store/playerStoreTypes'; import type { PlayerState } from '@/features/playback/store/playerStoreTypes';
import { resolveQueueTrack } from '@/utils/library/queueTrackView'; import { resolveQueueTrack } from '@/features/playback/store/queueTrackView';
import { promoteCompletedStreamToHotCache } from '@/features/playback/store/promoteStreamCache'; import { promoteCompletedStreamToHotCache } from '@/features/playback/store/promoteStreamCache';
import { pushQueueOnPlaybackStart, flushLocalQueueWhenTakingPlayback } from '@/features/playback/store/queueSync'; import { pushQueueOnPlaybackStart, flushLocalQueueWhenTakingPlayback } from '@/features/playback/store/queueSync';
import { markPlaybackActive } from '@/features/playback/store/queuePlaybackIdle'; import { markPlaybackActive } from '@/features/playback/store/queuePlaybackIdle';
@@ -28,7 +28,7 @@ vi.mock('@/features/playback/store/playerStore', () => ({
})); }));
import { applySkipStarOnManualNext } from '@/features/playback/store/skipStarRating'; import { applySkipStarOnManualNext } from '@/features/playback/store/skipStarRating';
import { seedQueueResolver, _resetQueueResolverForTest } from '@/utils/library/queueTrackResolver'; import { seedQueueResolver, _resetQueueResolverForTest } from '@/features/playback/store/queueTrackResolver';
function track(id: string, overrides: Partial<Track> = {}): Track { function track(id: string, overrides: Partial<Track> = {}): Track {
return { return {
@@ -1,7 +1,7 @@
import type { Track } from '@/features/playback/store/playerStoreTypes'; import type { Track } from '@/features/playback/store/playerStoreTypes';
import { useAuthStore } from '@/store/authStore'; import { useAuthStore } from '@/store/authStore';
import { usePlayerStore } from '@/features/playback/store/playerStore'; import { usePlayerStore } from '@/features/playback/store/playerStore';
import { getCachedTrack } from '@/utils/library/queueTrackResolver'; import { getCachedTrack } from '@/features/playback/store/queueTrackResolver';
import { queueSongRating } from '@/features/playback/store/pendingStarSync'; import { queueSongRating } from '@/features/playback/store/pendingStarSync';
/** /**
* Skip 1 behaviour: every user-initiated `next()` on an unrated track * Skip 1 behaviour: every user-initiated `next()` on an unrated track
@@ -10,7 +10,7 @@ import {
import { deriveNormalizationSnapshot } from '@/features/playback/store/normalizationSnapshot'; import { deriveNormalizationSnapshot } from '@/features/playback/store/normalizationSnapshot';
import { invokeAudioUpdateReplayGainDeduped } from '@/features/playback/store/normalizationIpcDedupe'; import { invokeAudioUpdateReplayGainDeduped } from '@/features/playback/store/normalizationIpcDedupe';
import type { PlayerState } from '@/features/playback/store/playerStoreTypes'; import type { PlayerState } from '@/features/playback/store/playerStoreTypes';
import { resolveQueueTrack } from '@/utils/library/queueTrackView'; import { resolveQueueTrack } from '@/features/playback/store/queueTrackView';
type SetState = ( type SetState = (
partial: Partial<PlayerState> | ((state: PlayerState) => Partial<PlayerState>), partial: Partial<PlayerState> | ((state: PlayerState) => Partial<PlayerState>),
@@ -11,7 +11,7 @@ import { PlaylistArtistCell } from '@/features/playlist/components/PlaylistArtis
import { useThemeStore } from '@/store/themeStore'; import { useThemeStore } from '@/store/themeStore';
import { usePlaylistLayoutStore } from '@/features/playlist/store/playlistLayoutStore'; import { usePlaylistLayoutStore } from '@/features/playlist/store/playlistLayoutStore';
import { songToTrack } from '@/features/playback/utils/playback/songToTrack'; import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
import { getQueueTracksView } from '@/utils/library/queueTrackView'; import { getQueueTracksView } from '@/features/playback/store/queueTrackView';
import { codecLabel } from '@/utils/componentHelpers/playlistDetailHelpers'; import { codecLabel } from '@/utils/componentHelpers/playlistDetailHelpers';
import { formatLastSeen } from '@/utils/componentHelpers/userMgmtHelpers'; import { formatLastSeen } from '@/utils/componentHelpers/userMgmtHelpers';
import { formatTrackTime } from '@/lib/format/formatDuration'; import { formatTrackTime } from '@/lib/format/formatDuration';
@@ -9,11 +9,11 @@ import type { QueueDisplayMode } from '@/store/authStoreTypes';
import type { DurationMode } from '@/utils/componentHelpers/queuePanelHelpers'; import type { DurationMode } from '@/utils/componentHelpers/queuePanelHelpers';
import { formatLongDuration } from '@/lib/format/formatDuration'; import { formatLongDuration } from '@/lib/format/formatDuration';
import { formatClockTime } from '@/lib/format/formatClockTime'; import { formatClockTime } from '@/lib/format/formatClockTime';
import { resolveQueueTrack } from '@/utils/library/queueTrackView'; import { resolveQueueTrack } from '@/features/playback/store/queueTrackView';
import { import {
getQueueResolverVersion, getQueueResolverVersion,
subscribeQueueResolver, subscribeQueueResolver,
} from '@/utils/library/queueTrackResolver'; } from '@/features/playback/store/queueTrackResolver';
interface Props { interface Props {
queue: QueueItemRef[]; queue: QueueItemRef[];
+2 -2
View File
@@ -8,11 +8,11 @@ import { useLuckyMixStore } from '@/store/luckyMixStore';
import type { QueueItemRef, PlayerState } from '@/features/playback/store/playerStoreTypes'; import type { QueueItemRef, PlayerState } from '@/features/playback/store/playerStoreTypes';
import type { QueueDisplayMode } from '@/store/authStoreTypes'; import type { QueueDisplayMode } from '@/store/authStoreTypes';
import { formatTrackTime } from '@/lib/format/formatDuration'; import { formatTrackTime } from '@/lib/format/formatDuration';
import { resolveQueueTrack } from '@/utils/library/queueTrackView'; import { resolveQueueTrack } from '@/features/playback/store/queueTrackView';
import { import {
getQueueResolverVersion, getQueueResolverVersion,
subscribeQueueResolver, subscribeQueueResolver,
} from '@/utils/library/queueTrackResolver'; } from '@/features/playback/store/queueTrackResolver';
import { findQueueItemRefIndex } from '@/features/playback/utils/playback/queueIdentity'; import { findQueueItemRefIndex } from '@/features/playback/utils/playback/queueIdentity';
import type { TimelineDisplayRow } from '@/utils/queue/buildTimelineDisplayRows'; import type { TimelineDisplayRow } from '@/utils/queue/buildTimelineDisplayRows';
import { findTimelineScrollLocalIndex } from '@/utils/queue/buildTimelineDisplayRows'; import { findTimelineScrollLocalIndex } from '@/utils/queue/buildTimelineDisplayRows';
@@ -2,7 +2,7 @@ import { describe, it, expect, beforeEach } from 'vitest';
import { renderHook } from '@testing-library/react'; import { renderHook } from '@testing-library/react';
import { usePlayerStore } from '@/features/playback/store/playerStore'; import { usePlayerStore } from '@/features/playback/store/playerStore';
import type { Track } from '@/features/playback/store/playerStoreTypes'; import type { Track } from '@/features/playback/store/playerStoreTypes';
import { getCachedTrack, _resetQueueResolverForTest } from '@/utils/library/queueTrackResolver'; import { getCachedTrack, _resetQueueResolverForTest } from '@/features/playback/store/queueTrackResolver';
import { seedQueue } from '@/test/helpers/factories'; import { seedQueue } from '@/test/helpers/factories';
import { useQueueTrackAt, useCurrentTrack, useQueueItems } from '@/features/queue/hooks/useQueueTracks'; import { useQueueTrackAt, useCurrentTrack, useQueueItems } from '@/features/queue/hooks/useQueueTracks';
+2 -2
View File
@@ -1,11 +1,11 @@
import { useMemo, useSyncExternalStore } from 'react'; import { useMemo, useSyncExternalStore } from 'react';
import { usePlayerStore } from '@/features/playback/store/playerStore'; import { usePlayerStore } from '@/features/playback/store/playerStore';
import type { QueueItemRef, Track } from '@/features/playback/store/playerStoreTypes'; import type { QueueItemRef, Track } from '@/features/playback/store/playerStoreTypes';
import { resolveQueueTrack } from '@/utils/library/queueTrackView'; import { resolveQueueTrack } from '@/features/playback/store/queueTrackView';
import { import {
getQueueResolverVersion, getQueueResolverVersion,
subscribeQueueResolver, subscribeQueueResolver,
} from '@/utils/library/queueTrackResolver'; } from '@/features/playback/store/queueTrackResolver';
/** /**
* Stable queue selectors (queue thin-state). The store is refs-canonical now: * Stable queue selectors (queue thin-state). The store is refs-canonical now:
@@ -3,7 +3,7 @@ import { invoke } from '@tauri-apps/api/core';
import { getPlaybackProgressSnapshot } from '@/features/playback/store/playbackProgress'; import { getPlaybackProgressSnapshot } from '@/features/playback/store/playbackProgress';
import { usePlayerStore } from '@/features/playback/store/playerStore'; import { usePlayerStore } from '@/features/playback/store/playerStore';
import { useAuthStore } from '../../store/authStore'; import { useAuthStore } from '../../store/authStore';
import { resolveQueueTrack } from '../../utils/library/queueTrackView'; import { resolveQueueTrack } from '@/features/playback/store/queueTrackView';
/** Half-width of the CLI snapshot queue window (thin-state like the mini /** Half-width of the CLI snapshot queue window (thin-state like the mini
* bridge, the full 50k queue must not serialize over IPC on every change). */ * bridge, the full 50k queue must not serialize over IPC on every change). */
+2 -2
View File
@@ -17,8 +17,8 @@ vi.mock('../utils/queue/timelineBootstrapReady', () => ({
timelineBootstrapIndexReady: vi.fn(), timelineBootstrapIndexReady: vi.fn(),
})); }));
vi.mock('../utils/library/queueTrackResolver', async importOriginal => { vi.mock('@/features/playback/store/queueTrackResolver', async importOriginal => {
const actual = await importOriginal<typeof import('../utils/library/queueTrackResolver')>(); const actual = await importOriginal<typeof import('@/features/playback/store/queueTrackResolver')>();
return { ...actual, seedQueueResolver: vi.fn() }; return { ...actual, seedQueueResolver: vi.fn() };
}); });
+1 -1
View File
@@ -1,6 +1,6 @@
import { useEffect, useMemo, useSyncExternalStore } from 'react'; import { useEffect, useMemo, useSyncExternalStore } from 'react';
import { libraryGetRecentPlaySessions, type PlaySessionRecentTrack } from '@/lib/api/library'; import { libraryGetRecentPlaySessions, type PlaySessionRecentTrack } from '@/lib/api/library';
import { seedQueueResolver, resolveBatch } from '../utils/library/queueTrackResolver'; import { seedQueueResolver, resolveBatch } from '@/features/playback/store/queueTrackResolver';
import { import {
applyTimelineBootstrap, applyTimelineBootstrap,
getTimelineSessionHistorySnapshot, getTimelineSessionHistorySnapshot,
+1 -1
View File
@@ -1,7 +1,7 @@
import { buildStreamUrlForServer } from '@/lib/api/subsonicStreamUrl'; import { buildStreamUrlForServer } from '@/lib/api/subsonicStreamUrl';
import { getPlaybackCacheServerKey } from '@/features/playback/utils/playback/playbackServer'; import { getPlaybackCacheServerKey } from '@/features/playback/utils/playback/playbackServer';
import type { QueueItemRef } from '@/features/playback/store/playerStoreTypes'; import type { QueueItemRef } from '@/features/playback/store/playerStoreTypes';
import { resolveQueueTrack } from './utils/library/queueTrackView'; import { resolveQueueTrack } from '@/features/playback/store/queueTrackView';
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '@tauri-apps/api/core';
import { useAuthStore } from './store/authStore'; import { useAuthStore } from './store/authStore';
import { selectHotCacheEntries, useHotCacheStore } from '@/features/playback/store/hotCacheStore'; import { selectHotCacheEntries, useHotCacheStore } from '@/features/playback/store/hotCacheStore';
+2 -2
View File
@@ -9,8 +9,8 @@ import type { SubsonicSong } from '@/lib/api/subsonicTypes';
import type { ServerProfile } from '@/store/authStoreTypes'; import type { ServerProfile } from '@/store/authStoreTypes';
import type { Track } from '@/features/playback/store/playerStoreTypes'; import type { Track } from '@/features/playback/store/playerStoreTypes';
import { usePlayerStore } from '@/features/playback/store/playerStore'; import { usePlayerStore } from '@/features/playback/store/playerStore';
import { toQueueItemRefs } from '@/utils/library/queueItemRef'; import { toQueueItemRefs } from '@/features/playback/store/queueItemRef';
import { seedQueueResolver } from '@/utils/library/queueTrackResolver'; import { seedQueueResolver } from '@/features/playback/store/queueTrackResolver';
let trackCounter = 0; let trackCounter = 0;
let songCounter = 0; let songCounter = 0;
let serverCounter = 0; let serverCounter = 0;
@@ -6,7 +6,7 @@ import { buildDownloadUrl } from '@/lib/api/subsonicStreamUrl';
import { useAuthStore } from '../../store/authStore'; import { useAuthStore } from '../../store/authStore';
import { usePlayerStore } from '@/features/playback/store/playerStore'; import { usePlayerStore } from '@/features/playback/store/playerStore';
import type { Track } from '@/features/playback/store/playerStoreTypes'; import type { Track } from '@/features/playback/store/playerStoreTypes';
import { resolveQueueTrack } from '../library/queueTrackView'; import { resolveQueueTrack } from '@/features/playback/store/queueTrackView';
import { useZipDownloadStore } from '@/features/offline'; import { useZipDownloadStore } from '@/features/offline';
import { useDownloadModalStore } from '@/features/offline'; import { useDownloadModalStore } from '@/features/offline';
import type { EntityShareKind } from '../share/shareLink'; import type { EntityShareKind } from '../share/shareLink';
@@ -2,11 +2,11 @@ import { describe, it, expect, beforeEach, vi } from 'vitest';
import { usePlayerStore } from '@/features/playback/store/playerStore'; import { usePlayerStore } from '@/features/playback/store/playerStore';
import { resetPlayerStore } from '@/test/helpers/storeReset'; import { resetPlayerStore } from '@/test/helpers/storeReset';
import { makeTrack, seedQueue } from '@/test/helpers/factories'; import { makeTrack, seedQueue } from '@/test/helpers/factories';
import { seedQueueResolver } from '@/utils/library/queueTrackResolver'; import { seedQueueResolver } from '@/features/playback/store/queueTrackResolver';
import { playTimelineHistoryTrack } from './playTimelineHistoryTrack'; import { playTimelineHistoryTrack } from './playTimelineHistoryTrack';
vi.mock('@/utils/library/queueTrackResolver', async importOriginal => { vi.mock('@/features/playback/store/queueTrackResolver', async importOriginal => {
const actual = await importOriginal<typeof import('@/utils/library/queueTrackResolver')>(); const actual = await importOriginal<typeof import('@/features/playback/store/queueTrackResolver')>();
return { return {
...actual, ...actual,
resolveBatch: vi.fn(async () => undefined), resolveBatch: vi.fn(async () => undefined),
+2 -2
View File
@@ -1,7 +1,7 @@
import { usePlayerStore } from '@/features/playback/store/playerStore'; import { usePlayerStore } from '@/features/playback/store/playerStore';
import type { QueueItemRef } from '@/features/playback/store/playerStoreTypes'; import type { QueueItemRef } from '@/features/playback/store/playerStoreTypes';
import { getQueueTracksView, resolveQueueTrack } from '../library/queueTrackView'; import { getQueueTracksView, resolveQueueTrack } from '@/features/playback/store/queueTrackView';
import { resolveBatch } from '../library/queueTrackResolver'; import { resolveBatch } from '@/features/playback/store/queueTrackResolver';
import { findQueueItemRefIndex, sameQueueItemRef } from '@/features/playback/utils/playback/queueIdentity'; import { findQueueItemRefIndex, sameQueueItemRef } from '@/features/playback/utils/playback/queueIdentity';
/** /**