* feat(playback): stream buffering UI, ranged M4A tail prefetch, demuxer fix
Defer seekbar/progress until HTTP stream is armed for both legacy and
RangedHttpSource; show buffering overlay on cover art. Add MP4 tail
prefetch and Symphonia isomp4 bounded-mdat/moov-at-EOF probing so
moov-at-end M4A can start without reading the full mdat.
* feat(hot-cache): spill large ranged streams to disk for promote
When a ranged HTTP download completes above the 64 MiB RAM promote cap,
write the existing buffer once to app-data stream-spill/ and register it
for hot-cache promote (rename) and replay via fetch_data. Analysis seeds
from the spill file up to the local-file cap (512 MiB).
* fix(ui): stream buffering — grayscale cover and static clock icon
Desaturate player and queue cover art while isPlaybackBuffering; keep a
non-animated clock overlay for visibility without the spinning animation.
* fix(playback): review follow-up — tests, i18n, spill cleanup, changelog
Clippy and test layout fixes; stream spill orphan cleanup on startup;
buffering flag guard in progress handler; bufferingStream in all player
locales; CHANGELOG and contributor credits for stream/M4A work.
* docs: attribute stream buffering and M4A streaming to PR #737
* test(audio): avoid create_engine in stream spill unit test
CI runners have no audio output device; test spill take/consume via
the Mutex slot only, matching install_stream_completed_spill tests.
* 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>
Adds a new sub-section under Settings → Personalisation (Advanced) that
hides individual controls in the player bar: Star rating, Favorite
(heart), Last.fm love, Equalizer, Mini player. Last.fm love still only
renders when a Last.fm session exists; the overflow row in the player
collapses when both Equalizer and Mini player are hidden.
- New `playerBarLayoutStore` (Zustand + persist, items[{id, visible}] +
rehydrate sanitize) following the queueToolbar / playlistLayout
pattern; defaults to all visible.
- New `PlayerBarLayoutCustomizer` reuses the same row + toggle pattern
as the other personalisation customisers.
- Gates threaded through `PlayerTrackInfo` (3 controls), `PlayerBar`
(EQ + Mini buttons), and `PlayerOverflowMenu` (EQ + Mini in the
overflow row, with row-level conditional).
- `PersonalisationTab`: added as the last advanced sub-section so it
only appears when the global Advanced Mode toggle is on.
- Settings search index gets entries for both Playlist page layout and
Player bar (playlist row was missing).
- New i18n keys `settings.playerBar*` in all 9 locales.
Reuses kveld9's design from PR #627; not merged because the locale
split and the Advanced Mode refactor landed afterwards. Credited via
Co-Authored-By trailer + a new line in settingsCredits.ts under the
existing kveld9 entry.
Co-authored-by: Kveld. <kveld912@proton.me>
Adds a per-element visibility toggle for the playlist detail page (Add
Songs, Import CSV, Download ZIP, Cache Offline, Suggestions) and reworks
the way uncommon options are surfaced: instead of a per-tab collapsible
group, a global "Advanced" toggle in the Settings header reveals all
`advanced` sub-sections across every tab and marks each one with a small
badge. Sets the pattern up so any future advanced option lives in its
natural tab, gated by the same switch.
- New `advancedSettingsEnabled` boolean on `authStore`
(UiAppearance slice, persisted with the rest of the store).
- `SettingsSubSection` gains an `advanced?: boolean` prop. Hidden when
the toggle is off; renders an "Advanced" pill in the header when on.
- Settings header gets a Toggle-Switch next to the search lupe.
- `PersonalisationTab` flattens — Sidebar + Home stay always visible;
Artist sections, Queue Toolbar, and the new Playlist layout get
`advanced` and disappear by default. `PersonalisationAdvancedGroup`
component + CSS removed.
- New `playlistLayoutStore` (Zustand + persist, items[{id,visible}] +
rehydrate sanitize) following the queueToolbarStore pattern.
- `PlaylistHero` and `PlaylistSuggestions` gate the four toolbar buttons
and the suggestions rail on the store directly.
- One-time migration in MainApp on mount: if the user had opened the
old per-tab Advanced group (`psysonic_personalisation_advanced_open
=== 'true'`) OR already customised any of the three sub-sections,
Advanced Mode auto-enables on first launch. Idempotent via a
localStorage flag; legacy key removed afterwards.
- New i18n keys `settings.advancedMode`, `settings.advancedModeTooltip`,
`settings.advancedBadge`, `settings.playlistLayout*` in all 9 locales.
Reuses kveld9's design from PR #556; not merged because the locale split
landed afterwards. Credited under the existing kveld9 entry in
settingsCredits.ts.
Co-authored-by: Kveld. <kveld912@proton.me>
* fix(playback): pin queue streams, cover art, and library links to queue server
When the active server changes while a queue from another server is playing,
keep streams and UI on queueServerId; switch back for artist/album links and
queue or player-bar context menus.
* fix(playback): switch to queue server when opening Now Playing
Ensure active server matches queueServerId before Subsonic fetches on the
Now Playing page, mobile player route, and queue info panel; scope caches
by server id.
* docs(credits): mention Now Playing in PR #717 contribution line
* fix(playback): route scrobble and queue sync to queue server
Address PR review: apiForServer for scrobble/now-playing/savePlayQueue,
clear queueServerId on server removal, mini-player queueServerId sync,
block cross-server enqueue with toast, and regression tests.
Global queue paste opens the preview modal before play; overlay scrollbar,
context-menu suppression, changelog/credits for PR #716 and DanielWTE (#551).
* fix(ui): use stable list keys on Now Playing dashboard cards
Subsonic payloads can repeat the same id in similar artists, album
track rows, and top songs. Keys now combine id with list index so
React reconciliation stays stable and duplicate-key warnings stop.
* docs: changelog and credits for Now Playing list keys (PR #703)
Document the dashboard list key fix in CHANGELOG and Settings contributors.
* fix(settings): sort the contributors list chronologically
The Settings → System contributors list rendered the array in raw
insertion order, so Psychotoxical (since v1.0.0) showed up last and
hand-maintained ordering drifted over time.
Sort on export instead: ascending by the `since` app version (reusing
isNewer), tie-broken by the first-contribution PR number. The list
stays correctly ordered regardless of where new entries are inserted.
* docs(changelog): add entry for contributors list sort fix (#700)
* fix(ui): split OpenSubsonic album and track artists in header and player
Album detail header uses albumArtists from album or child songs; player bar,
mobile player, and mini player use structured track artists with per-id links.
Adds deriveAlbumHeaderArtistRefs helper and OpenArtistRefInline.
Fixes#552
* docs: changelog and credits for OpenSubsonic artist links (PR #696)
* fix(player): align cached cover URL with cacheKey on track change
Prevents a one-frame stale blob src (and broken image in the player bar)
when switching tracks; reset CachedImage load state in useLayoutEffect.
* docs: changelog + credits for cover-art track-switch fix (PR #695)
Pull pure helpers, the contributors/maintainers data list and the
tab type/search index out of `Settings.tsx` so the main component
only has tab-section render logic + the orchestrating state left:
- `utils/audioDeviceLabels.ts` — five ALSA-device label helpers
(formatAudioDeviceLabel + duplicate-disambiguation + sort +
select-option builder).
- `utils/formatBytes.ts` — formatBytes + snapHotCacheMb.
- `components/settings/LoudnessLufsButtonGroup.tsx` — small chip
group used by the audio tab.
- `components/settings/settingsTabs.ts` — `Tab` type + legacy alias
map + `resolveTab` + `SearchIndexEntry` + `SETTINGS_INDEX` + the
`matchScore` substring-with-fuzzy-fallback scorer.
- `config/settingsCredits.ts` — `CONTRIBUTORS` (~270 LOC of static
contributor history) + `MAINTAINERS`.
Pure code-move. Settings.tsx: 3552 → 3037 LOC (−515). Settings/G
journey now 5298 → 3037 LOC (~43% reduction).