feat(queue): persist queue header duration mode (#724)

* feat(queue): persist header duration mode (#625)

The queue header chip cycles total / remaining / ETA, but the choice
lived in QueuePanel `useState` and reset to 'total' on every app launch.
Move it into `authStore` (persisted under `psysonic-auth`) so the chosen
mode survives restarts like other UI preferences.

- `DurationMode` consolidated in `authStoreTypes` (re-exported from
  `queuePanelHelpers` so existing component imports stay valid).
- New `queueDurationDisplayMode` field + `setQueueDurationDisplayMode`
  setter wired through `createUiAppearanceActions`, default `'total'`.
- `computeAuthStoreRehydration` validates the persisted value against
  the three known modes (pattern matches the existing seekbarStyle
  block) — garbage, null, undefined, or a missing key map back to
  `'total'` so the chip never receives an unknown mode.
- `QueuePanel` reads / writes via `useAuthStore` selectors instead of
  local `useState`.
- Tests: trivial-setter coverage in `authStore.settings.test.ts` and a
  new `authStoreRehydrate.test.ts` covering corrupt, missing, and
  valid rehydration cases.

Reuses kveld9's design from PR #625; not merged because the 1.46
refactor split locales and reshuffled queue-helper exports. Credited
via Co-Authored-By trailer.

Co-Authored-By: Kveld. <kveld912@proton.me>

* docs(changelog): queue header duration mode persistence (#724)

Adds the CHANGELOG entry + kveld9 credits line referencing the real PR
numbers (no #TBD placeholder) — feedback.md §2.7+§2.8 workflow: commit
docs as a second push onto the open PR.

---------

Co-authored-by: Kveld. <kveld912@proton.me>
This commit is contained in:
Frank Stellmacher
2026-05-15 19:14:06 +02:00
committed by GitHub
parent 187170057d
commit ccf4d5d8cb
10 changed files with 73 additions and 3 deletions
+12
View File
@@ -15,6 +15,7 @@ import {
import type {
AuthState,
DiscordCoverSource,
DurationMode,
LyricsSourceConfig,
SeekbarStyle,
} from './authStoreTypes';
@@ -79,6 +80,16 @@ export function computeAuthStoreRehydration(state: AuthState): Partial<AuthState
? {}
: { seekbarStyle: 'truewave' as SeekbarStyle };
// Garbage / null / undefined / missing key from a legacy or tampered persist
// payload maps back to 'total' so the duration chip never receives an
// unknown mode (would render an empty label).
const VALID_QUEUE_DURATION_MODES = new Set<string>(['total', 'remaining', 'eta']);
const queueDurationDisplayModeMigrated = VALID_QUEUE_DURATION_MODES.has(
(state as { queueDurationDisplayMode?: unknown }).queueDurationDisplayMode as string,
)
? {}
: { queueDurationDisplayMode: 'total' as DurationMode };
// The `animationMode` 3-state setting was removed; users on `'reduced'`
// or `'static'` collapse onto the former `'full'` path automatically as
// soon as the field is gone from the store. Strip the persisted field
@@ -130,6 +141,7 @@ export function computeAuthStoreRehydration(state: AuthState): Partial<AuthState
...lyricsSourcesMigrated,
...wheelSmoothOneTime,
...seekbarStyleMigrated,
...queueDurationDisplayModeMigrated,
...discordCoverSourceMigrated,
};
}