Commit Graph

78 Commits

Author SHA1 Message Date
‮Artem cb9445eaad feat(favorites): virtualize songs tracklist + memoize rows (#805)
* feat(favorites): virtualize songs tracklist + memoize rows

10k+ starred songs no longer mount every row into the DOM. Same fix
shape as playlist virtualization: @tanstack/react-virtual windowing,
memoized FavoriteSongRow with stable callback bundle, scrollMargin
ResizeObserver anchored to .content-body. visibleTracks memoized once
per visibleSongs ref to avoid O(n) songToTrack on every click.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(favorites): review follow-ups for virtualization PR #805

Add maintainer comments for scrollMargin layout coupling and bulk-bar
useLayoutEffect dep. Record PR #805 in CHANGELOG and settings credits.

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 11:00:13 +03:00
cucadmuh 64a8a0ed8a fix(playback): Lucky Mix after switching Subsonic servers (#785)
* fix(playback): Lucky Mix after switching Subsonic servers

Hand off unpinned legacy queues to the browsed server, start the mix lock
before async work, skip cross-server enqueue toasts while rolling, bind
queue server before batch enqueues, and restore queueServerId on failed builds.

* chore(release): CHANGELOG and credits for PR #785
2026-05-18 23:38:47 +03:00
cucadmuh d7b23b3c08 fix(ui): stable play/enqueue hover on album rails and mainstage cards (#787)
* fix(ui): stable play/enqueue hover on album rails and mainstage cards

Horizontal Home rails (Discover): drop content-visibility:auto and scroll-snap,
pin controls to cover hover, dim overlay via background. Grids and song cards:
pointer-events on overlays so WebKitGTK/Wayland GPU does not lose :hover.

* chore: CHANGELOG and credits for PR #787
2026-05-18 23:35:08 +03:00
cucadmuh f6fe1484a9 fix(ui): in-page browse virtual lists and cover load priority (#783)
* fix(ui): in-page browse virtual lists and cover load priority

Align virtualizer scrollMargin with the in-page scroll viewport so
artist/album rows do not vanish on deep scroll after #731. Resolve
CachedImage IntersectionObserver root to the nearest scrolling pane so
network fetch slots favor visible covers; throttle Artists infinite scroll
to one page per visibleCount update.

* chore(release): CHANGELOG and credits for PR #783
2026-05-18 21:30:36 +03:00
cucadmuh 70c2fdfbf9 Linux: session-native GDK/WebKit mitigations and in-page browse scroll (#731)
* feat(linux): session GDK defaults, nvidia-quirk, optional x11-legacy wrap

Ship PSYSONIC_ALLOW_NATIVE_GDK from Nix/AUR instead of pinning WEBKIT_DISABLE_*
and GDK x11. Add flake psysonic-x11-legacy for the old wrap; alias gdk-session
to psysonic. Startup uses webkit2gtk-nvidia-quirk and Wayland-aware compositing;
refresh Help (a45) and nixos-install docs.

* fix(linux): session GDK and nvidia-quirk only; drop wrapper env heuristics

Remove PSYSONIC_ALLOW_NATIVE_GDK and devShell GDK/WEBKIT exports; stop
synthesizing GDK/WebKit vars in main.rs. Update Nix/AUR wrappers, install
docs, CHANGELOG, and help FAQ with practical user-facing workarounds.

* fix(linux): X11-pinned GDK uses DMABUF quirk path, not Wayland explicit-sync

When GDK_BACKEND is forced to x11 on a wayland user session, webkit2gtk-nvidia-quirk
would still apply __NV_DISABLE_EXPLICIT_SYNC and gray out the webview. Map that case
to WEBKIT_DISABLE_DMABUF_RENDERER like native X11.

* fix(ui): stabilize WebKitGTK/Wayland hover paint for nav and media cards

Sidebar nav links avoid transition:all and promote icons with translateZ(0).
Artist rows and album/artist/song cards use compositing hints; card shadows
and borders no longer interpolate so cover zoom can stay smooth without jitter.

* fix(ui): isolate artist/album card text and cover paint on WebKitGTK

Promote cover blocks with contain/paint and text stacks with translateZ(0);
use artist-card-info on the artists grid for the same layout as other cards.

* feat(artists): in-page overlay scroll and locked main viewport

Move list/grid into an inner OverlayScrollArea, stop sticky toolbar from
owning the route scroll, align the rail with the main panel edge, and skip
the main-route overlay thumb when the viewport cannot scroll vertically.

* feat(browse): extend in-page overlay scroll to more library routes

Reuse the locked main viewport pattern from Artists for Albums, Composers,
Lossless albums, and New releases; wire VirtualCardGrid and scroll chrome
to the matching in-page viewport ids.

* fix(linux): improve Wayland GPU compositing text clarity in WebKitGTK

Use on-demand hardware acceleration on main and mini webviews when the
session is Wayland and compositing stays on; gate subpixel body AA on the
same conditions via new Tauri probes. Document PSYSONIC_SKIP_WAYLAND_FONT_TUNING
for opt-out and changelog.

* fix(rust): satisfy clippy needless_return in Linux webkit helpers

* fix(linux): tune Wayland text rendering with HW policy env and CSS

Allow PSYSONIC_WEBKIT_WAYLAND_HW_POLICY to select WebKit hardware
acceleration policy (never/always vs default on-demand). Extend Wayland
font CSS to #root with geometricPrecision and text-size-adjust on html.

* feat(linux): Wayland text presets in settings, safe WebKit apply, CPU default

Persist profile to app config; apply WebKit policy at startup/mini only to
avoid WebKitGTK hangs on live toggles. UI + CSS preview stays live; default
preset is sharp (CPU-friendly).

* fix(linux): map Wayland sharp preset to OnDemand WebKit policy

HardwareAccelerationPolicy::Never at startup broke main-viewport wheel
scrolling on WebKitGTK+Wayland; sharp vs balanced remains a CSS AA path.
Use PSYSONIC_WEBKIT_WAYLAND_HW_POLICY for a true Never policy.

* fix(rust): gate Linux-only Wayland WebKit helpers for Windows builds

Re-export startup helpers only under cfg(linux) and drop non-Linux stubs so
Windows compiles without unused-import and dead-code warnings.

* chore(release): CHANGELOG + credits for Linux session/WebKit work (PR #731)

Consolidate scattered incremental changelog notes into two [1.47.0]
entries with PR link; remove duplicate Linux blocks from [1.46.0] Fixed.
Append settings credit line for cucadmuh.
2026-05-18 21:00:46 +03:00
Frank Stellmacher b4782aeedb feat(ui): scale the whole window with Interface Scale (#781)
* experiment(zoom): allow setting webview zoom via core capability

* experiment(zoom): drive uiScale through Tauri's native webview zoom

Replace the CSS `zoom: uiScale` on `.main-content-zoom` (which only
scaled the main content column, leaving the sidebar, queue panel,
player bar and portaled overlays at 1.0) with a `setZoom` call on the
current webview. That scales everything inside the window the same way
Ctrl+/− does in a browser, including portals and the queue panel.

Effect runs whenever `uiScale` changes and once on mount, so the
persisted setting is reapplied on launch.

* docs: changelog + credits for interface scale (#781)
2026-05-18 17:52:27 +02:00
Frank Stellmacher bca45d5a80 feat(servers): scan actions + edit existing server profiles (#780)
* feat(server-scan): plumbing for triggering library scans

Adds `startScan` / `getScanStatus` against the Subsonic API
(`fullScan=true` is Navidrome's extension), a small per-server scan
store, and a global polling hook (2 s cadence) that emits a toast when
each scan finishes. Scans can run on any configured server, including
inactive ones, by reusing `apiForServer`.

UI surfaces follow in the next commit.

* feat(server-scan): expose Quick / Full Scan in switcher + settings cards

Adds a `ServerScanActions` component with two variants (compact for the
server-switcher dropdown, card for the Settings server cards) backed by
the scan store from the previous commit. Full Scan requires a second
click within 3 s to confirm, matching the playlist-delete pattern.
Status slot shows a spinner with running track count while scanning, a
green check when finished, and a red icon on error.

The switcher row is converted from a single button to a flex container
so per-server scan controls don't hijack the server-switch click.
i18n added across all 9 locales.

* fix(server-scan): reorder switcher row to check / name / scan actions

Moves the check / spinner slot from the right edge to the left so the
spinner pop-in on server switch doesn't sit next to the scan icons.
Removes the layout shift that briefly hovered the Quick scan button
when the row re-rendered.

* feat(servers): edit existing server profiles in Settings → Servers

* Pencil-button on each server card opens an inline edit form that
  replaces the card (prefilled name / URL / username / password).
* `AddServerForm` reused with an `editingServer` prop — title flips to
  "Edit Server", submit label to "Save", magic-string field hidden (the
  edit scope is manual fields; magic-string remains an add-time invite
  shortcut).
* Edit saves unconditionally — ping runs post-save as a status indicator
  (analog to the existing Test button) instead of gating the save. Lets
  users update a profile when the server is currently unreachable.
* Translations across all 9 locales (`editServer`, `editServerTitle`).

* fix(servers): submit Add/Edit Server form on Enter

Wrapped the form body in a real <form>, made the submit button
type="submit", marked Cancel as type="button" so Enter no longer
cancels. Add-Mode now also responds to Enter — same flow, consistent
across both modes.

* fix(servers): collapse card action buttons to icon-only on narrow screens

* Quick-Scan / Full-Scan / Test buttons in each server card hide their
  text label below 1100px viewport via the .server-card-btn-label class
  and a single media query in connection-indicator.css.
* Labels remain accessible via data-tooltip and aria-label so screen
  readers + hover both keep working in the collapsed state.
* No content reflow above the breakpoint — pure additive CSS.

* fix(servers): include Use button in icon-only narrow-screen collapse

The Use ("Verwenden") button on inactive server cards lacked the
.server-card-btn-label wrapper, so its text stayed visible at narrow
viewports and pushed Edit/Delete off-screen. Added a Power icon and
wrapped the label so it collapses alongside the other action buttons.

* docs(changelog,credits): #780 server scan + edit
2026-05-18 16:32:40 +02:00
Frank Stellmacher 562218f447 feat(settings): dim disabled toggle rows + rename Clear → Clear queue (#778)
* feat(settings): dim disabled toggle rows + rename Clear → Clear queue

* Settings toggle rows (`.settings-toggle-row`, `.sidebar-customizer-row`) dim
  their non-toggle content to 0.6 opacity when the switch is off. Driven by a
  single `:has(.toggle-switch input:not(:checked):not(:disabled))` rule so it
  applies across every Settings tab without per-row markup. Mutex-disabled
  toggles (Crossfade/Gapless) are excluded to avoid stacking with the existing
  inline 0.45 dim on their row.
* Queue toolbar "Clear" button now reads "Clear queue" in all 9 locales for
  parity with "Shuffle queue" and to distinguish from playlist clear/delete.

Adopted from @kveld9's PR #558 — cherry-picked the spirit, rewrote the dim as
a single global selector instead of three inline-styled customizer rows, and
extended the rename to the Romanian locale that landed after #558 was opened.

Co-Authored-By: kveld9 <179108235+kveld9@users.noreply.github.com>

* docs(changelog,credits): credit @kveld9 for #778

Co-Authored-By: kveld9 <179108235+kveld9@users.noreply.github.com>

---------

Co-authored-by: kveld9 <179108235+kveld9@users.noreply.github.com>
2026-05-18 15:21:45 +02:00
‮Artem a090ad2832 feat(playlist): virtualize tracklist + memoize rows (#755)
* feat(playlist): virtualize tracklist + memoize rows

* fix(playlist): separator in virtual row key to avoid id/index collision

* chore(release): CHANGELOG + credits for playlist virtualization (PR #755)
2026-05-18 13:18:49 +03:00
cucadmuh db98d30a78 fix(playback): cross-server browse, Lucky Mix, and Now Playing (#768)
* fix(playback): keep browsed server when queue plays elsewhere

Lucky Mix on a non-playback server now clears the old queue and pins
the active server before building. Now Playing metadata uses
apiForServer against the queue server instead of forcing
ensurePlaybackServerActive on every activeServerId change.

* docs: note PR #768 in CHANGELOG and settings credits

* fix(now-playing): gate AudioMuse similar artists on playback server

Match getArtistInfoForServer credentials: when browsing another server
while the queue plays elsewhere, similar-artist count follows the queue
server's AudioMuse flag, not the browsed server.
2026-05-18 11:40:46 +03:00
Frank Stellmacher 8b92e85321 docs: credit zz5zz for ongoing bug & quirk reports (#761)
Add a second entry to zz5zz's contributor block in settingsCredits.ts
that covers the steady stream of Discord bug reports alongside the
existing Norwegian (Bokmål) translation credit. Also add a "Special
thanks" banner at the top of the 1.46.0 CHANGELOG section linking to
the Psysonic Discord, since several of this release's polish fixes
landed directly off the back of those reports.
2026-05-17 22:05:50 +02:00
‮Artem be9722149b fix(player): cap persisted queue window (#756)
Playing/shuffling a big playlist set `queue` to ~10k `Track` objects.
zustand `persist` then `JSON.stringify`'d the whole queue into `localStorage`
on every persisted `set` → `QuotaExceededError` storm: playback never started
and the main thread stalled on each write.

`partialize` now persists only a ±250-track window around `queueIndex`
(remapping the index into the slice) instead of the entire queue.

The authoritative full queue lives server-side — synced via Subsonic
`savePlayQueue` and restored through `getPlayQueue`. localStorage is only a
fast local cache, so bounding it doesn't lose the queue; it just restores a
±250 window instantly while the full queue rehydrates from the server.

Controlled before/after on a 10,509-track playlist:
- before: QuotaExceededError ×9, play/shuffle dead
- after: QuotaExceeded = 0, playback works
2026-05-17 22:06:41 +03:00
cucadmuh 33ffb94083 fix(isomp4): fix M4A moov-at-end probe failures and streaming fallback (#757)
* fix(stream): defer M4A probe until moov tail or fast-start prefix

Ranged moov-at-end M4A started Symphonia format probe as soon as ~384 KiB
linear data arrived, before the parallel tail prefetch filled the moov
atom — probe hit end of stream and skipped the track. Wait for tail_ready
or detect fast-start moov in the prefix; do not arm playback from linear
bytes alone when tail prefetch is active.

* fix(isomp4): skip EOF-spanning mdat after moov-at-end is parsed

Second-pass header scan with moov already loaded still tried to read
through mdat→EOF on RangedHttpSource holes, causing format probe
end of stream. Re-check play generation after moov wait.

* fix(isomp4): fix AtomIterator overread after seek in patched demuxer

`AtomIterator::new_root(reader, len)` treats `len` as bytes available
from the current `reader.pos()`, not the absolute file length. After
`mss.seek(resume_at)` we were passing the absolute `total_len`, so the
iterator thought there were `total_len` bytes left and tried to read
past EOF on the next iteration, returning "end of stream".

Fix: pass `total_len.map(|tl| tl.saturating_sub(resume_at))` (remaining
bytes from the new position) in both branches:
- `moov.is_none()` (moov-at-end layout, seek to moov offset)
- `moov.is_some()` (fast-start layout, skip bounded mdat body)

This caused Symphonia to fail probing moov-at-end M4A files read from
local disk (hot-cache) and from in-memory buffers — every decode attempt
returned "end of stream", analysis fell back to `byte_envelope_no_ebu`
(no EBU R128 loudness), and rodio produced distorted audio.

Also in this commit:
- `resolve_playback_format_hint()` helper to resolve hint from URL,
  stream suffix, Content-Disposition, or byte sniff
- ISO-BMFF diagnostic helpers (`isobmff_buffer_looks_complete`,
  `log_isobmff_buffer_diagnostic`, `mp4_suspect_zero_holes`)
- Probe-fallback path for ranged-stream failures now uses these helpers
  to decide whether to refetch or wait for the in-flight download

* chore(audio): remove redundant hint recomputation and add missing blank line

`bytes_hint_for_wait` in the ranged-stream fallback path was an exact
duplicate of `effective_hint` already in scope — reuse the existing binding.
Also add missing blank line after `wait_for_ranged_mp4_probe_ready` in mod.rs.

* chore(release): CHANGELOG and credits for PR #757

Add Fixed entry for M4A moov-at-end probe fix and credits line in
settingsCredits.ts under existing cucadmuh contributions.

* fix(audio): extract BuildSourceArgs to fix clippy::too_many_arguments

`build_playback_source_with_probe_fallback` had 12 parameters, exceeding
the clippy limit of 7. Group url/gen/hints/fade/hi-res/duration into
`BuildSourceArgs` so the function signature stays at 4 arguments.
2026-05-17 19:54:38 +03:00
Frank Stellmacher 606a150e01 feat(settings): clock format setting (Auto / 24h / 12h) (#742)
* feat(settings): clock format setting (Auto / 24h / 12h)

Reported on the Psysonic Discord — the Queue side panel's ETA label
and the sleep-timer preview both render via `formatClockTime`, which
just calls `toLocaleTimeString` and so follows the user's system
locale. On en-US that means AM/PM, with no in-app way out.

Add a tri-state **Clock Format** setting under
**Settings → System → App Behavior**:

* `auto` (default) — keep the existing locale-driven behaviour, so
  bestehende installs are unaffected on first launch.
* `24h` — force 24-hour wall-clock output everywhere
  `formatClockTime` is used.
* `12h` — force AM/PM output.

Wired through `authStore` (`clockFormat`, `setClockFormat`), exposed
via `CustomSelect` in `SystemTab`, and threaded into the two
consumers (`QueueHeader`, `PlaybackDelayModal`) so they re-render on
change. `formatClockTime` itself stays a pure helper — it accepts the
setting as an optional second argument and maps it to `hour12`.

Locale coverage: all nine bundled locales (en, de, es, fr, nl, nb,
ru, zh, ro) get the four new settings strings. Pin tests added for
the `setClockFormat` setter and the `hour12` mapping in
`formatClockTime`.

* docs(changelog): clock format setting + contributors (PR #742)
2026-05-17 01:26:40 +02:00
cucadmuh 6ea0acede5 feat(playback): stream buffering UI, M4A moov-at-end streaming, hot-cache spill (#737)
* 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.
2026-05-16 22:56:47 +03:00
Frank Stellmacher ccf4d5d8cb 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>
2026-05-15 19:14:06 +02:00
Frank Stellmacher 2d27428056 feat(settings): player bar layout — per-control visibility toggles (#627) (#721)
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>
2026-05-15 17:48:32 +02:00
Frank Stellmacher 651a3f276a feat(settings): global Advanced Mode toggle + playlist page layout (#556) (#720)
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>
2026-05-15 15:55:11 +02:00
cucadmuh 45e0e1206f fix(playback): pin queue playback to source server when browsing another library (#717)
* 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.
2026-05-15 16:08:41 +03:00
Maxim Isaev 33b0206ea2 feat(search): queue preview on Ctrl+V paste and modal polish
Global queue paste opens the preview modal before play; overlay scrollbar,
context-menu suppression, changelog/credits for PR #716 and DanielWTE (#551).
2026-05-15 13:52:07 +03:00
cucadmuh 5f0803e98a fix(ui): stable React list keys on Now Playing cards (#703)
* 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.
2026-05-14 23:12:55 +03:00
Frank Stellmacher 77f6933410 fix(settings): sort the contributors list chronologically (#700)
* 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)
2026-05-14 21:45:34 +02:00
cucadmuh f054fe425c fix(radio): portal add/edit station modal to avoid clipping (#699)
* fix(radio): portal add/edit station modal to document.body

* docs: changelog and credits for radio edit modal portal (PR #699)
2026-05-14 22:26:44 +03:00
cucadmuh 5d53a63553 fix(search): hide search3 artists with zero albums (#697)
* fix(search): hide search3 artists with zero albums

* docs: changelog and credits for search zero-album filter (PR #697)
2026-05-14 22:17:26 +03:00
cucadmuh 3cc172723d fix(ui): split album and track artists (OpenSubsonic) (#696)
* 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)
2026-05-14 21:56:39 +03:00
cucadmuh ecdbe0cf2a fix(player): stale cover blob and load state on track change (#695)
* 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)
2026-05-14 21:38:56 +03:00
cucadmuh 34cc311b4d docs(i18n): Romanian ro in 1.46.0 notes and README; chronological contributor credits (#666)
Settings System tab now follows CONTRIBUTORS array order instead of sorting by entry size.
2026-05-13 23:17:17 +03:00
Frank Stellmacher 306e56dc2b refactor(settings): G.55 — extract helpers + credits + tab index (#620)
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).
2026-05-13 01:48:16 +02:00