fix(player): persist player prefs outside quota-bound queue blob (#958)

* fix(player): persist volume/repeat in dedicated localStorage key

Player prefs were bundled with the full queue blob in psysonic-player; after
thin-state #872 a large queue can exceed the quota and safeStorage silently
drops every write, so volume and repeat mode stopped surviving restarts.
Move them to psysonic_player_prefs, gate main-blob writes until rehydrate,
and sync volume to the Rust engine on startup.

* fix(player): split queue visibility and Last.fm cache from main persist blob

Move isQueueVisible and lastfmLovedCache to dedicated localStorage keys so
they keep saving when psysonic-player hits the quota on large queues. Include
the new keys in settings backup export.

* docs: note player prefs persist fix in CHANGELOG and credits (PR #958)
This commit is contained in:
cucadmuh
2026-06-03 21:55:20 +03:00
committed by GitHub
parent 40932d28e2
commit 75e2c7f9c6
10 changed files with 334 additions and 10 deletions
+59
View File
@@ -0,0 +1,59 @@
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import {
persistLastfmLovedCache,
readInitialLastfmLovedCache,
} from './lastfmLovedCacheStorage';
const CACHE_KEY = 'psysonic_lastfm_loved_cache';
const LEGACY_KEY = 'psysonic-player';
beforeEach(() => {
window.localStorage.clear();
});
afterEach(() => {
window.localStorage.clear();
});
describe('readInitialLastfmLovedCache', () => {
it('defaults to an empty object when nothing is stored', () => {
expect(readInitialLastfmLovedCache()).toEqual({});
});
it('reads the dedicated cache key and drops non-boolean entries', () => {
window.localStorage.setItem(
CACHE_KEY,
JSON.stringify({ 'Hello::Adele': true, 'Bad::': false, '': true, n: 1 }),
);
expect(readInitialLastfmLovedCache()).toEqual({ 'Hello::Adele': true, 'Bad::': false });
});
it('falls back to the legacy psysonic-player blob', () => {
window.localStorage.setItem(
LEGACY_KEY,
JSON.stringify({
state: {
lastfmLovedCache: { 'T::A': true },
queueItems: [],
},
}),
);
expect(readInitialLastfmLovedCache()).toEqual({ 'T::A': true });
});
it('prefers the dedicated key over the legacy blob', () => {
window.localStorage.setItem(CACHE_KEY, JSON.stringify({ 'A::B': false }));
window.localStorage.setItem(
LEGACY_KEY,
JSON.stringify({ state: { lastfmLovedCache: { 'A::B': true } } }),
);
expect(readInitialLastfmLovedCache()).toEqual({ 'A::B': false });
});
});
describe('persistLastfmLovedCache', () => {
it('round-trips through readInitialLastfmLovedCache', () => {
persistLastfmLovedCache({ 'Song::Artist': true });
expect(readInitialLastfmLovedCache()).toEqual({ 'Song::Artist': true });
});
});