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
@@ -0,0 +1,60 @@
import { useAuthStore } from '@/store/authStore';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import type { QueueItemRef } from '@/features/playback/store/playerStoreTypes';
import { canonicalQueueServerKey } from '@/utils/server/serverIndexKey';
import { resolveBatch } from './queueTrackResolver';
/**
* Full-queue restore (thin-state, decision B). The player store rehydrates the
* whole thin `queueItems` ref list from localStorage on startup; this eagerly
* resolves every ref into the resolver cache so the queue UI / playback paths
* have real `Track` metadata. `resolveBatch` does the index batch
* (`library_get_tracks_batch`, ≤100 refs/call) → `getSong` network fallback (P8)
* internally, so the queue is never empty even with the index off (the P6
* default — every ref still resolves via getSong).
*
* Clears the restore-pending sentinel (`queueItemsIndex`) once the eager resolve
* is dispatched so it runs at most once; `queueItems` stays canonical. Legacy
* pre-thin-state blobs that only carried `queueRefs` were normalised into
* `queueItems` by the store's persist `merge`, so this reads `queueItems` only.
*/
export async function hydrateQueueFromIndex(): Promise<void> {
const player = usePlayerStore.getState();
// Restore-pending sentinel: `partialize` writes `queueItemsIndex` alongside
// the full `queueItems` on every persist, so a fresh rehydrate carries it
// back. Normal in-memory mutations keep `queueItems` canonical but never set
// the index, so its presence — not a non-empty `queueItems` — marks "this
// restored queue still needs an eager resolve". Without it (steady state /
// later server switch) there is nothing to do.
const restorePending =
player.queueItemsIndex !== undefined || (player.queueRefs?.length ?? 0) > 0;
if (!restorePending) return;
let refs: QueueItemRef[] = player.queueItems ?? [];
if (refs.length === 0 && player.queueRefs?.length) {
const rawSid = player.queueServerId ?? useAuthStore.getState().activeServerId ?? '';
const sid = canonicalQueueServerKey(rawSid);
refs = player.queueRefs.map(trackId => ({ serverId: sid, trackId }));
}
// Clear the restore-pending sentinel + any legacy refs; `queueItems` stays the
// canonical mirror. Done up front so a later resolve never re-triggers.
usePlayerStore.setState({
queueItemsIndex: undefined,
queueRefs: undefined,
queueRefsIndex: undefined,
});
if (refs.length === 0) return;
// Eager resolve of the whole queue into the resolver cache (best-effort —
// index batch when ready, else getSong window fallback so the queue plays
// even with the index off). Failures leave refs as placeholders until a row
// scrolls into view and the resolver bridge fetches them.
try {
await resolveBatch(refs);
} catch {
/* best-effort */
}
}