# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). > **πŸ›‘οΈ A note on safety investments:** Making sure Psysonic is trusted on every OS takes real money out of my pocket β€” an Apple Developer Account and a Windows code-signing certificate. If you'd like to help cover those costs, you can chip in at [ko-fi.com/psychotoxic](https://ko-fi.com/psychotoxic) β€” completely voluntary, no pressure at all. Every bit helps keep Psysonic free and safe across Windows, macOS and Linux. > ## [1.48.0] ## Added ### Music Network β€” scrobble to more than just Last.fm **By [@Psychotoxical](https://github.com/Psychotoxical) and [@cucadmuh](https://github.com/cucadmuh), PR [#1066](https://github.com/Psychotoxical/psysonic/pull/1066)** * **Settings β†’ Integrations** now hosts a **Music Network** that scrobbles your plays to one or more services at once: **Last.fm**, **Libre.fm**, **Rocksky**, **ListenBrainz** (the public service or any compatible server), **Maloja** (native, Audioscrobbler or ListenBrainz API), **Koito**, and any **custom GNU FM** instance. * Choose a **primary** service β€” your loved tracks, similar artists and listening stats come from it β€” while scrobbles fan out to every connected service. A master switch turns the whole thing on or off. * **Last.fm works exactly as before**, including love, similar artists and the Statistics page; your existing connection is migrated automatically on first launch. * *Known limitation:* Rocksky currently rejects scrobbles whose title or artist contains non-standard (non-ASCII) characters β€” a Rocksky-side issue, not a Psysonic bug. ### Sidebar β€” pin Now Playing to the top **By [@Psychotoxical](https://github.com/Psychotoxical), PR [#1000](https://github.com/Psychotoxical/psysonic/pull/1000), suggested by [@PHLAK](https://github.com/PHLAK)** * New **Settings β†’ Sidebar** toggle moves the "Now Playing" entry to the top of the sidebar instead of the bottom (off by default). ### Fullscreen player β€” rebuilt for much lower CPU/RAM **By [@Psychotoxical](https://github.com/Psychotoxical), PR [#1001](https://github.com/Psychotoxical/psysonic/pull/1001)** * The previous fullscreen player was a heavy CPU and memory consumer β€” constant repaints from animated/blurred backgrounds and effects kept the GPU and a CPU core busy the whole time it was open. It has been **completely replaced** by a static, low-overhead screen: only the seekbar, elapsed time, and clock update live; everything else stays still. * Features: sharp high-res background, large album cover, true waveform seekbar, up-next queue popover, scrolling synced lyrics, clickable rating stars, and an on-screen clock. * The artist photo now always shows as the background (album cover as fallback); the old **Appearance β†’ "Fullscreen player"** settings were removed. ### Queue β€” Timeline display mode **By [@Psychotoxical](https://github.com/Psychotoxical), PR [#1004](https://github.com/Psychotoxical/psysonic/pull/1004), suggested by [@Legislate3030](https://github.com/Legislate3030)** * New third queue display mode (cycle the header button, or pick it in **Settings β†’ Personalisation β†’ Queue display**). Timeline keeps the current track centered with played history above and upcoming tracks below β€” both visible at once β€” so it's easy to follow playback and jump back to earlier songs. * The up-next order respects shuffle, and a "History" / "Up next" divider marks the boundary. ### Offline β€” unified local playback, library index join, and favorites sync **By [@cucadmuh](https://github.com/cucadmuh), PR [#1008](https://github.com/Psychotoxical/psysonic/pull/1008)** * All local audio bytes live under one **`media/`** tree: `cache/` (ephemeral hot-cache), `library/` (user-pinned offline), and `favorites/` (auto-synced stars). Paths use library-index metadata and the URL-derived server index key so two profiles on the same server share one bucket. * **`localPlaybackStore`** replaces the split hot-cache / offline metadata stores β€” one index drives prefetch, promotion, eviction, and `psysonic-local://` playback resolution. * **Offline Library** lists pinned and favorites-tier tracks by joining that index with the SQLite library catalog (no duplicate offline album cards). Pin album, playlist, or artist from browse; disk usage shown in the Offline Library header. * **Favorites auto-sync** keeps starred tracks on disk in `media/favorites/` with a compact toggle, cross-server reconcile, and cancel-on-unstar so orphaned files are not left behind. * **Cached offline pins stay in sync** β€” manually pinned **albums** and **artist discographies** reconcile after a library index sync (delta/full); **regular playlists** reconcile hourly and when edited in-app. Added tracks download and removed ones are pruned. **Smart playlists** (`psy-smart-…`) are excluded β€” their contents refresh from server rules automatically. * Mixed-server queues play offline with correct per-track server scope; network guards skip Subsonic when local bytes exist. * Startup migration from legacy `psysonic-offline/` layout; Settings β†’ Storage uses a single **media directory** picker and a live hot-cache track count. ### Themes β€” community Theme Store **By [@Psychotoxical](https://github.com/Psychotoxical), PR [#1009](https://github.com/Psychotoxical/psysonic/pull/1009), [#1011](https://github.com/Psychotoxical/psysonic/pull/1011), [#1012](https://github.com/Psychotoxical/psysonic/pull/1012), [#1013](https://github.com/Psychotoxical/psysonic/pull/1013), [#1014](https://github.com/Psychotoxical/psysonic/pull/1014), [#1015](https://github.com/Psychotoxical/psysonic/pull/1015), [#1016](https://github.com/Psychotoxical/psysonic/pull/1016), [#1018](https://github.com/Psychotoxical/psysonic/pull/1018), [#1020](https://github.com/Psychotoxical/psysonic/pull/1020), [#1036](https://github.com/Psychotoxical/psysonic/pull/1036), [#1038](https://github.com/Psychotoxical/psysonic/pull/1038), [#1041](https://github.com/Psychotoxical/psysonic/pull/1041)** * New **Settings β†’ Themes** tab: pick a theme, set the day/night scheduler, and browse a built-in **Theme Store** to install, update and uninstall community themes β€” with search, a dark/light filter, and full-size thumbnail previews. * The app now bundles six core themes (Catppuccin Mocha & Latte, Kanagawa Wave, Stark HUD, and the colour-blind-safe Vision Dark / Vision Navy); every other palette installs on demand from the [psysonic-themes](https://github.com/Psysonic/psysonic-themes) repo. Installed themes are saved locally and apply instantly at startup, even offline. * **Import a theme from a local `.zip`** (manifest.json + theme.css): the package is validated, you confirm its name and author, then it installs like any other community theme. * Themes are **free-form** β€” beyond recolouring, they can add their own styling and animations and react to playback / fullscreen / sidebar / lyrics state. A safety floor (no network, no scripts) is always enforced; store themes are reviewed, and imported themes install at your own risk. * The store paginates large catalogues, and refreshing the list no longer jumps back to the top. * Each store theme shows its **total downloads** and a **last-changed** date, and can be sorted by most popular, newest or name; the catalogue now has numbered page navigation. These stats refresh once a day. * The **Now Playing** page now follows the active theme end to end β€” light themes render it legibly instead of washed-out, with no per-theme tweaks needed. * The sidebar now shows a small notice when one of your installed themes has an update available β€” click it to jump straight to the Theme Store, or dismiss it until the next update. Themes with an update also show an update control right on their card under **Settings β†’ Themes** to update them in place with one click, and the store refreshes once on startup so new themes and updates show up without hitting refresh. * Upgrading from an older build: an active or scheduled theme that has moved to the store and isn't installed falls back to Mocha (dark) / Latte (light). ### Offline β€” local-bytes browse when the server is down **By [@cucadmuh](https://github.com/cucadmuh), PR [#1017](https://github.com/Psychotoxical/psysonic/pull/1017)** * When the active server is unreachable, browse and detail pages read from **local playback bytes** and the **library index** instead of Subsonic β€” albums, artists, tracks, cached playlists, and cross-server favorites. * Single integration contract: `offlineBrowseContext`, `offlineActionPolicy`, and `resolveAlbum` / `resolveArtist` / `resolvePlaylist` resolvers; context menus and detail toolbars block server mutations offline. * Disconnect navigation forks by offline capability (stay on page, stay-reload, or redirect); Home reuses the last cached feed snapshot; DEV offline toggle simulates full disconnect for testing. * PlayerBar hides star rating and favorite controls while offline browse is active. ### Startup β€” themed loading splash before the app bundle loads **By [@cucadmuh](https://github.com/cucadmuh), PR [#1030](https://github.com/Psychotoxical/psysonic/pull/1030)** * Inline splash in `index.html` (progress bar + P logo) shows while the Vite bundle loads in dev and production β€” no empty or black window on launch. * Splash colours follow the persisted theme (built-in palettes, day/night scheduler, and installed community themes); the logo uses each theme's accent gradient instead of a hardcoded white asset. * The native window stays hidden until the splash has painted (`visible: false` + deferred `show` from Rust/JS); window-state restore no longer overrides startup visibility. ### Servers β€” software and version on each server card **By [@Psychotoxical](https://github.com/Psychotoxical), PR [#1045](https://github.com/Psychotoxical/psysonic/pull/1045)** * Each server card under **Settings β†’ Servers** now shows the server software and version (e.g. `Navidrome 0.62.0`) under the server name. The value comes from the existing connection ping, so no extra request is made; it is hidden for servers that don't report it (plain Subsonic without OpenSubsonic). ### What's New β€” remote release notes **By [@cucadmuh](https://github.com/cucadmuh), PR [#1058](https://github.com/Psychotoxical/psysonic/pull/1058)** * The **What's New** page shows user-friendly highlights from `WHATS_NEW.md` instead of embedding the full technical changelog in the app bundle. * RC and stable builds prefetch `whats-new.md` from the GitHub release on startup and cache it locally; offline users see a thin embedded fallback. * **`tauri:dev`** reads markdown straight from the repo for easy editing; shipped bundles embed only the current release-line slice. A **Full changelog** tab on the page shows the technical list for the same version. * CI uploads `whats-new.md` on each `next` / `release` tag alongside platform artifacts. * Remote download uses the existing Rust `fetch_url_bytes` proxy so GitHub release assets work without browser CORS. ## Changed ### Settings β†’ Servers β€” compact server cards **By [@cucadmuh](https://github.com/cucadmuh), PR [#1054](https://github.com/Psychotoxical/psysonic/pull/1054)** * Two-line header: custom entry name plus `user@host`, HTTPS lock, and a clickable version info tooltip (hover or tap). * Navidrome **0.62+**: green **AudioMuse-AI** inline badge when the plugin is detected; older Navidrome keeps the manual toggle row below the card. * **Use** and **Active** share one rightmost slot (green badge vs primary button); card actions are edit β†’ test β†’ use/active. Delete lives in the edit form footer. * **TooltipPortal**: `data-tooltip-click` for click-pinned tooltips (touch and explicit open without the 1s hover delay). ### Dependencies β€” npm and Rust refresh **By [@cucadmuh](https://github.com/cucadmuh), PR [#997](https://github.com/Psychotoxical/psysonic/pull/997)** * Frontend and Tauri npm dependencies bumped (React, Vite, Vitest, i18next, axios, Tauri plugins); test stack upgraded to **jsdom** 29. * Rust workspace: `id3` 1.17, `reqwest` 0.13.4, `sysinfo` 0.39, **zip** 8 for library backups. Symphonia 0.6 and `mach2` 0.6 remain deferred (upstream constraints). ### Audio β€” Symphonia 0.6 upgrade **By [@cucadmuh](https://github.com/cucadmuh), PR [#999](https://github.com/Psychotoxical/psysonic/pull/999)** * Audio decode + analysis pipeline ported to **Symphonia 0.6** (new `AudioDecoder` API, `GenericAudioBufferRef`, `Time`/`Timestamp` units); `symphonia-adapter-libopus` bumped to **0.3** and the vendored `symphonia-format-isomp4` 0.5 patch dropped in favour of upstream ISO-BMFF fixes. * `rodio`'s `symphonia-all` feature dropped so the workspace no longer pulls a duplicate `symphonia-core`. ### Playback β€” Preload Next Track setting removed **By [@cucadmuh](https://github.com/cucadmuh), PR [#1007](https://github.com/Psychotoxical/psysonic/pull/1007)** * The **Preload Next Track** toggle and timing modes under **Settings β†’ Storage β†’ Buffering** are gone β€” ranged streaming now starts playback without that extra RAM prefetch. * Gapless and crossfade still prefetch the next track internally when Hot Cache is off; Hot Cache is unchanged. ### PsyLab β€” Performance Probe rename, Tuning tab, and log tools **By [@cucadmuh](https://github.com/cucadmuh), PR [#1027](https://github.com/Psychotoxical/psysonic/pull/1027)** * **Ctrl+Shift+D** opens **PsyLab** (formerly Performance Probe). Cover backfill thread tuning moved to a new **Tuning** tab. * **Logs** tab: selectable text, toolbar copy/export, and a context-menu **Copy** for the current selection. * Runtime log lines are sanitized before they enter the buffer β€” Subsonic/auth tokens and remote hostnames are redacted or partially masked; LAN and localhost addresses stay readable. ### PsyLab β€” Connections tab and Navidrome admin role **By [@cucadmuh](https://github.com/cucadmuh), PR [#1033](https://github.com/Psychotoxical/psysonic/pull/1033)** * New **Connections** tab: session/endpoint status, active-server capability readout (OpenSubsonic, AudioMuse detection, provider/strategy, detection trust, resolved call route, and AudioMuse mode), and queue-playback server when it differs from the active profile. * Navidrome **admin vs standard user** badge via native login probe β€” useful when diagnosing plugin/settings visibility. ### Servers β€” capability framework with AudioMuse sonic routing (Navidrome β‰₯ 0.62) **By [@cucadmuh](https://github.com/cucadmuh), PR [#1033](https://github.com/Psychotoxical/psysonic/pull/1033)** * New declarative **server-capability framework** (`src/serverCapabilities/`): a catalog picks a feature strategy per server generation, runs only the needed probes, and routes API calls β€” replacing scattered version checks in the UI and call sites. * Navidrome **0.62+**: detect the AudioMuse-AI plugin from `getOpenSubsonicExtensions` when `sonicSimilarity` is advertised β€” the first reliable signal. Settings shows an **auto-managed status indicator** (no manual toggle); older Navidrome keeps the manual toggle and the legacy `getSimilarSongs` Instant Mix probe. * **Path routing**: Instant Mix and Lucky Mix prefer the OpenSubsonic `getSonicSimilarTracks` endpoint when the plugin is present, falling back to legacy `getSimilarSongs`. ## Fixed ### Local index β€” multi-genre browse, filters, and counts **By [@cucadmuh](https://github.com/cucadmuh), reported by HiveMind on the Psysonic Discord, PR [#1059](https://github.com/Psychotoxical/psysonic/pull/1059)** * Tracks tagged with several genres in one metadata field (e.g. `Noise Metal/Dark Ambient/Experimental Black Metal`) again match **each atomic genre** in Genres browse, All Albums filters, genre detail, and Advanced Search β€” not only the first segment. * New `track_genre` index (OpenSubsonic `genres[]` when present, Navidrome-default split fallback), maintained on sync; one-time blocking startup backfill for existing libraries with progress. * Migration v12 repairs databases that recorded legacy schema versions 2–11; TS network fallback uses robust `genreTagsFor` parsing. ### Navidrome Now Playing and scrobble with local playback **By [@cucadmuh](https://github.com/cucadmuh), PR [#1055](https://github.com/Psychotoxical/psysonic/pull/1055)** * **Show in Now Playing** and Navidrome play-count scrobbles no longer silently skip when audio plays from hot cache, offline library pins, or favorites-auto bytes. * Presence and queue sync target the **playback server** reachability gate, so a queue on server A still reports to Navidrome while browsing server B. ### Now Playing β€” cards no longer blank out on track change **By [@Psychotoxical](https://github.com/Psychotoxical), PR [#1042](https://github.com/Psychotoxical/psysonic/pull/1042)** * The "from this album", "discography" and "most played" sections on the Now Playing page disappeared after a track change once the next track started playing from the local cache, and didn't come back. The page now keeps loading that info whenever the server is reachable, regardless of whether the audio plays from cached or offline bytes. ### Now Playing β€” metadata reads from the local library index first **By [@Psychotoxical](https://github.com/Psychotoxical), PR [#1049](https://github.com/Psychotoxical/psysonic/pull/1049)** * The "from this album", "discography", "most played" and song details on the Now Playing page now come from the local library index when it has them, only falling back to the server when the index can't serve a row. Cards and fields (genre, play count, contributors) stay populated during cached and offline playback, with fewer server requests. ### Library DB β€” named slow-write ops for stall diagnosis **By [@cucadmuh](https://github.com/cucadmuh), PR [#1043](https://github.com/Psychotoxical/psysonic/pull/1043)** * Production `library-db` write paths now log stable `module.action` op names instead of the generic `misc`, so the next `[library-db] SLOW write` line on macOS (or elsewhere) identifies the call site β€” diagnostic step for playback stalls under long write-lock holds ([#1040](https://github.com/Psychotoxical/psysonic/issues/1040)). ### Servers β€” complete border on the active server card **By [@Psychotoxical](https://github.com/Psychotoxical), PR [#998](https://github.com/Psychotoxical/psysonic/pull/998)** * The active server card under **Settings β†’ Servers** now draws its border on all four sides; previously only the left and right edges showed. ### Audio streaming β€” start latency and stall recovery **By [@cucadmuh](https://github.com/cucadmuh), PR [#999](https://github.com/Psychotoxical/psysonic/pull/999)** * Ranged-HTTP FLAC/MP3/OGG streams start playing as soon as enough data is buffered again, instead of waiting for the whole file to download (Symphonia 0.6's trailing-metadata probe scan is skipped for progressive non-MP4 streams). * The streaming format probe now runs under a 20s timeout on a worker thread, so a stalled stream (e.g. right after a server switch) no longer blocks playback start until a manual player restart. ### Playback β€” macOS stutter from background device checks **By [@Psychotoxical](https://github.com/Psychotoxical), PR [#1039](https://github.com/Psychotoxical/psysonic/pull/1039)** * On some macOS setups playback stuttered at a steady ~3-second cadence: a background check that scans every audio output device ran on each poll and briefly contended with playback. It now runs only when a specific output device is pinned; with the system default (the common case) a single lightweight check runs instead. ### Track preview β€” Symphonia 0.6 format hints and fast stream start **By [@cucadmuh](https://github.com/cucadmuh), PR [#1006](https://github.com/Psychotoxical/psysonic/pull/1006)** * Preview resolves container format from HTTP headers, Subsonic `suffix`, and magic-byte sniff so Symphonia 0.6 no longer fails with `.unknown` demuxer errors. * Preview opens via ranged HTTP when the server supports byte ranges β€” audio starts after ~384 KiB buffered instead of waiting for a full-file download; buffered fallback uses the same probe seek-gate as main playback. * Player bar cover guard while preview metadata loads; progress ring leaves the loading spinner once the engine emits `audio:preview-start`. ### Mainstage β€” hero backdrop stays in sync when skipping albums quickly **By [@cucadmuh](https://github.com/cucadmuh), reported by Asra on the Psysonic Discord, PR [#1021](https://github.com/Psychotoxical/psysonic/pull/1021)** * Rapid prev/next clicks on the Mainstage hero no longer leave the blurred cover-art background on the previous album while the foreground cover and metadata already show the next one. ### Song rails β€” multi-artist credits link to each artist **By [@cucadmuh](https://github.com/cucadmuh), reported by zunoz on Discord, PR [#1023](https://github.com/Psychotoxical/psysonic/pull/1023)** * **Random Picks**, **Discover Songs**, and other song cards now split OpenSubsonic `artists[]` into individually clickable names β€” the same behaviour as album track rows and the player bar, instead of one link for the whole joined credit string. * Album cards and the rest of the app share the same artist-ref helper, including when Subsonic returns a single ref object instead of a one-element array. ### Fullscreen player β€” corner clock follows Clock format setting **By [@cucadmuh](https://github.com/cucadmuh), reported by zunoz on Discord, PR [#1025](https://github.com/Psychotoxical/psysonic/pull/1025)** * The wall clock in the fullscreen player now honours **Settings β†’ System β†’ Clock format** (24-hour vs 12-hour), matching the queue ETA and sleep-timer preview instead of always showing AM/PM. ### All Albums β€” Only compilations filter returns results **By [@cucadmuh](https://github.com/cucadmuh), reported by zunoz on Discord, PR [#1026](https://github.com/Psychotoxical/psysonic/pull/1026)** * The **Only compilations** toggle on **All Albums** no longer returns an empty list when compilations are tagged via **Various Artists** as album artist or when genre is combined with other browse filters β€” local index SQL, track-grouped browse, and client-side detection now agree on the same compilation signals. ### Artist page β€” Top Tracks play button **By [@cucadmuh](https://github.com/cucadmuh), PR [#1031](https://github.com/Psychotoxical/psysonic/pull/1031)** * Play on **Top Tracks** rows no longer silently does nothing when the artist page has top songs but no albums loaded (e.g. lossless artist view); playback starts from the clicked track and continues into the catalog when albums are available. ### PsyLab β€” tab bar no longer collapses on the Logs tab **By [@cucadmuh](https://github.com/cucadmuh), PR [#1033](https://github.com/Psychotoxical/psysonic/pull/1033)** * The PsyLab tab row keeps its height when the Logs flex layout fills the modal β€” tabs were previously squashed to a thin strip. ### Themes β€” consistent focus borders on inputs and dropdowns **By [@Psychotoxical](https://github.com/Psychotoxical), PR [#1052](https://github.com/Psychotoxical/psysonic/pull/1052)** * Text fields no longer draw a double border when focused β€” they now show a single clean ring across every theme. The colour-blind-safe themes keep their stronger high-contrast focus ring on every field, including the header search. * Dropdown and popover borders now follow the active theme in all themes (a couple of themes previously rendered them with an unthemed colour). ### Home β€” Most Played no longer jumps the page when loading more **By [@Psychotoxical](https://github.com/Psychotoxical), reported by zunoz on Discord, PR [#1053](https://github.com/Psychotoxical/psysonic/pull/1053)** * Clicking the arrow to load more albums in the **Most Played** rail sometimes snapped the page up to an earlier section. Loading more now keeps the viewport in place. ### Album grids β€” album artist on compilation cards **By [@cucadmuh](https://github.com/cucadmuh), PR [#1057](https://github.com/Psychotoxical/psysonic/pull/1057), reported in [#1056](https://github.com/Psychotoxical/psysonic/issues/1056)** * Random Albums, New Releases, All Albums, and other album grids no longer show a track artist on compilation albums when the tags set a single album artist (e.g. **Underworld** on a various-artists mix); the card matches the album page. * Local index browse, live search, and FTS album dedupe prefer `album_artist` over per-track `artist`; Hero, Most Played, and offline pin labels use the same display helper. ### Dev startup β€” missing generated release-notes bundle **By [@cucadmuh](https://github.com/cucadmuh), PR [#1060](https://github.com/Psychotoxical/psysonic/pull/1060)** * Fresh clones no longer crash Vite on `tauri:dev` when `src/generated/releaseNotesBundle.ts` is missing β€” `dev` and `tauri:dev` now run `prebuild:release-notes` before launch (file stays gitignored). ### What's New β€” release-notes cache file on disk (RC/stable) **By [@cucadmuh](https://github.com/cucadmuh), PR [#1062](https://github.com/Psychotoxical/psysonic/pull/1062)** * RC and stable builds now persist the downloaded `whats-new.md` slice under AppData β€” `plugin-fs` had mkdir but lacked recursive write scope, so the `release-notes/` folder appeared empty and every launch re-fetched from GitHub. ### Favorites β€” player-bar star stays synced in track lists **By [@artplan1](https://github.com/artplan1), PR [#1063](https://github.com/Psychotoxical/psysonic/pull/1063)** * Liking a song from the **player bar**, fullscreen player, or global shortcuts now updates the star in album tracklists, playlists, Random Mix, and Favorites β€” the row no longer reverts the instant the server sync completes. * List views seed starred state from a one-shot fetch and merge session `starredOverrides`; clearing those overrides on sync success had only patched `currentTrack` and the queue cache, so rows fell back to stale fetched values. ## [1.47.0] > **πŸ™ Thank you to our amazing Discord community.** This release would not have been possible without your tireless support, quality checks, bug reports and all-round collaboration. Every report, every repro and every bit of feedback shaped what shipped here β€” thank you. Come join us: [discord.gg/AMnDRErm4u](https://discord.gg/AMnDRErm4u) ## Added ### Servers β€” edit existing profiles **By [@Psychotoxical](https://github.com/Psychotoxical), PR [#780](https://github.com/Psychotoxical/psysonic/pull/780)** * Pencil button opens an inline edit form prefilled with the existing profile. Card actions collapse to icon-only on narrow viewports so Edit/Delete stay reachable. ### Local library index + search (preview) **By [@Psychotoxical](https://github.com/Psychotoxical) + [@cucadmuh](https://github.com/cucadmuh), PR [#846](https://github.com/Psychotoxical/psysonic/pull/846)** * **Settings β†’ Library:** local SQLite track index per server β€” background initial and delta sync, full resync, integrity verify, and auto-reconcile when the server reports fewer tracks than expected. * **Live Search** and **Advanced Search** query the local index when it is ready (fast, offline-capable). * **Multi-server UI** (by [@cucadmuh](https://github.com/cucadmuh)): per-server exclude/include; indexing runs one server at a time so SQLite stays responsive; offline servers are retried automatically. * Local search results respect the sidebar music-library filter; parallel album fetch during initial sync. ### Player stats β€” local listening history **By [@cucadmuh](https://github.com/cucadmuh), PR [#849](https://github.com/Psychotoxical/psysonic/pull/849)** * **Statistics β†’ Player stats** tab (`/player-stats`): year summary (listening time, clustered sessions, track plays, unique tracks, listening days, full/partial counts), GitHub-style heatmap by track plays per day, recent-days accordion, and day drill-down with per-track completion. * Records finalized listens to `play_session` in `library.sqlite` when the playback server's local index is enabled and ready (not preview/radio; `listenedSec > 10`). * Live UI refresh after a persisted listen; partial-index notice when only some servers are indexed. * i18n across 9 locales. ### Playback speed β€” global tempo and pitch strategies **By [@cucadmuh](https://github.com/cucadmuh), PR [#852](https://github.com/Psychotoxical/psysonic/pull/852)** * **Settings β†’ Audio** and player bar: global speed **0.5–2.0Γ—** with three strategies β€” **Speed** (pitch-corrected, default), **Varispeed**, and **Pitch shift** (manual semitone offset). * Rust path: preserve-pitch worker (`pitch_shift`) for Speed / Pitch shift; varispeed via sample-rate scaling; seek-restart on strategy or enable changes. * Unified content timeline for seek bar, elapsed time, and seek; player bar popover anchored like volume (compact controls; full hints in Settings). * **Orbit:** passthrough **1.0Γ—** while a session is active. Not applied to radio or preview. ### Track enrichment β€” oximedia BPM/mood, mood search, queue display **By [@cucadmuh](https://github.com/cucadmuh), PR [#863](https://github.com/Psychotoxical/psysonic/pull/863)** * **Analysis:** client-side oximedia pass (60s center window) writes BPM, valence, arousal, moods JSON, and mood_tag facts to the local library index; unified playbackβ†’analysis dispatch covers stream, hot/offline files, preload, and gapless. * **Queue:** measured BPM and top mood labels when the playback server's index is enabled; `analysis:enrichment-updated` refreshes the UI without waiting for poll. * **Advanced Search:** virtual mood groups (joy, sadness, dance, work, romance, anger) filter via local index + mood_tag rows (migration 008). * Mood search requires persisted mood_tag facts; queue display may still show labels from valence/arousal fallback before tags land. ### Analytics strategy + migration safety for index-key rebuild **By [@cucadmuh](https://github.com/cucadmuh), PR [#864](https://github.com/Psychotoxical/psysonic/pull/864)** * Rebuilt server scoping around stable `indexKey` identifiers across Rust + frontend paths used by playback, analysis, and local index state. * Added per-server analysis strategy controls (lazy/aggressive), per-server parallelism tuning, queue progress visibility, and clear-analysis actions in **Settings β†’ Library**. * Added first-launch migration orchestration (inspect/run + progress events + blocking gate) with frontend persisted-key rewrites to the new `indexKey` scope. * Reworked playback/analysis handoff paths (play, preload, stream/ranged, queue restore) so analysis dispatch and queue-priority hints use the same server scope model. * Hardened startup/runtime migration checks so bootstrap waits for required migration phases before normal playback/index startup. ### Backup & Restore β€” library databases + full archive flow **By [@cucadmuh](https://github.com/cucadmuh), PR [#864](https://github.com/Psychotoxical/psysonic/pull/864)** * **Settings β†’ System β†’ Backup & Restore:** added two archive-backed modes β€” **Library databases** (library + analysis SQLite snapshots) and **Full** (settings + library databases). * Import auto-detects backup type from file contents (`.psybkp` / `.psylib` / `.psyfull`) from one entry point instead of per-mode import actions. * Restore switches active databases via runtime store swap/restore flow and keeps previous files as `.bak` for recovery on failed validation. ### Cover art β€” tier ladder, disk cache, and grid prefetch **By [@cucadmuh](https://github.com/cucadmuh), PR [#869](https://github.com/Psychotoxical/psysonic/pull/869)** * Album and artist grids load sharper repeat visits from a dedicated WebP disk cache (separate from the general image cache budget in **Settings β†’ Storage**). * Dense lists prefetch smaller tiers first, then steady-state grid quality capped at 512px for scroll performance; detail and player chrome still resolve up to 800px on demand. * One-time upgrade clears legacy multi-size IndexedDB cover keys; offline-first when the server is unreachable. ### Lossless β€” local index browse, filters, and drill-down **By [@cucadmuh](https://github.com/cucadmuh), PR [#871](https://github.com/Psychotoxical/psysonic/pull/871)** * **Local index:** `library_list_lossless_albums` queries indexed tracks by lossless suffix allowlist; `/lossless-albums` and Home rail use SQLite when the library index is ready, with Navidrome bit_depth walk as fallback. * **Advanced Search:** `lossless is true` on tracks, albums, and artists (local + network); artist/album links open detail with `?lossless=1` and a lossless-mode banner. * **All Albums:** lossless toggle (local index only β€” plain lossless, year, and genre combinations). * **Sidebar:** dedicated Lossless page route conserved; nav entry hidden by default and removed from visibility settings. ### Albums β€” combined browse filters and session restore **By [@cucadmuh](https://github.com/cucadmuh), PR [#876](https://github.com/Psychotoxical/psysonic/pull/876)** * **Albums** toolbar: sort, genre (with counts), year range, favorites, lossless, and compilations combine in one browse query when the local index is ready; returning from album detail restores the same filter state. * Favorites list reconciles from the server into the local index (no stub album rows); genre/year/lossless/compilation filters apply on the indexed catalog. * Year spinners use catalog min/max from the local index; compilation filter uses indexed OpenSubsonic flags (resync refreshes track metadata). ### Artist detail β€” album year sort **By [@cucadmuh](https://github.com/cucadmuh), PR [#877](https://github.com/Psychotoxical/psysonic/pull/877)** * **Albums by …** on the artist page: toggle year sort (newest/oldest) within each release-type block; preference persists for the session per server. ### Servers β€” second optional address per profile (LAN + public) **By [@Psychotoxical](https://github.com/Psychotoxical), PR [#880](https://github.com/Psychotoxical/psysonic/pull/880)** * **Settings β†’ Servers:** a profile can carry a second address β€” typically a LAN counterpart of a public URL or vice versa. The app probes LAN-first and uses whichever endpoint actually answers, so the same profile is fast at home and reachable away from home without manual switching. Single-address profiles behave exactly as before. * **Same-server verify on save** when both addresses are filled β€” mismatched or unreachable pairs are blocked with a clear message; two-LAN combinations are caught client-side before any network call. * **Share links** (Orbit invites, library / queue shares, magic invite v2) embed the **public** address by default so off-LAN guests can reach the host; a *Use local address in share links* checkbox flips that for LAN-only groups. Pasted invites match either address. * **Editing the primary URL** to a new host triggers a confirm-modal-gated data move: library + analysis databases, cover-cache files on disk, and player queue all re-tag to the new identifier in one go. Changing only the second address or `http`↔`https` skips the migration entirely. ### Album play β€” hold to shuffle **By [@ImAsra](https://github.com/ImAsra), PR [#888](https://github.com/Psychotoxical/psysonic/pull/888)** * Hold an album **Play** button (~1 s) for a filling wave animation, then the album starts in shuffled order; a short click still plays in track order. * Album cards, hero, Because-you-like rail, and Most Played; tooltip in all locales. ### Performance Probe β€” monitor UI, overlay pins, and live metrics **By [@cucadmuh](https://github.com/cucadmuh), PR [#890](https://github.com/Psychotoxical/psysonic/pull/890)** * **Ctrl+Shift+D** modal: **Monitor** tab (metric cards with pin-to-overlay) and **Toggles** tab (tree of probe flags/phases). * Live CPU/memory polling: process CPU, RSS by group, thread CPU groups (Linux `/proc`); **macOS** process CPU + RSS via `sysinfo`. * HUD overlay: FPS always on top; pinned live metrics with **1-minute sparklines**; Analysis/Cover pipeline blocks; corner + opacity controls. * Cover pipeline stats in the probe (per-server cache, ensure/peek queues). ### Performance Probe β€” thread-group CPU toggle **By [@cucadmuh](https://github.com/cucadmuh), PR [#891](https://github.com/Psychotoxical/psysonic/pull/891)** * Monitor live poll: explicit opt-in checkbox for Linux `/proc` thread-group CPU (off by default); fixes camelCase IPC so thread rows populate instead of staying on β€œCollecting…”. ### Queue β€” choose between Queue and Playlist view **By [@Psychotoxical](https://github.com/Psychotoxical), PR [#922](https://github.com/Psychotoxical/psysonic/pull/922)** * **Settings β†’ Personalisation β†’ Queue Display Mode:** *Queue* shows only upcoming tracks β€” the current one stays in the header and leaves the list once played; *Playlist* keeps the full queue with the current track highlighted at the top. A small icon in the queue header flips the mode, and the title follows it. * New default is *Queue* β€” switch to *Playlist* in settings if you prefer the full list with the playing track shown in place. ### Genre detail β€” play or shuffle a whole genre **By [@Psychotoxical](https://github.com/Psychotoxical), PR [#926](https://github.com/Psychotoxical/psysonic/pull/926)** * Play, Shuffle and Add-to-queue buttons on a genre page start the whole genre in one click β€” in order or randomized β€” instead of adding each album by hand. Suggested by Apollosport. ### Library browse β€” restore scroll, filters, and search when returning from detail **By [@cucadmuh](https://github.com/cucadmuh), PR [#936](https://github.com/Psychotoxical/psysonic/pull/936)** * **Artists, Search, Tracks, New Releases, Random Albums:** going back from album or artist detail keeps browse filters, list scroll, and search text instead of resetting the page. * **Search / Advanced / Tracks** share one browse page; the separate quick-search results route is removed. * In-app **Back** and the browser **mouse back** button on album/artist detail use the same return path to the browse session you left. ### Genres β€” local index browse with Subsonic fallback **By [@cucadmuh](https://github.com/cucadmuh), PR [#937](https://github.com/Psychotoxical/psysonic/pull/937)** * **Genre detail** and the **Genres** cloud load album lists and counts from the local library index when it is ready; new SQLite indexes speed genreβ†’album browse. * When the index is disabled or not ready, the album grid falls back to Subsonic **byGenre** as before. * Returning from an album restores genre-detail scroll; **Play** uses hold-to-shuffle like other browse pages. * **Advanced Search** grouped album totals count distinct albums, not raw matching track rows. ### Live Search β€” scoped browse on library pages **By [@cucadmuh](https://github.com/cucadmuh), PR [#938](https://github.com/Psychotoxical/psysonic/pull/938)** * **Artists, All Albums, New Releases, Tracks, and Composers** use the header Live Search field with a scope badge (sidebar icon) instead of a separate in-page filter input. * While scoped, typing filters **that page only** via the same local-vs-network browse search; the global Live Search dropdown stays closed. * **Ghost badge** on browse routes when scope is cleared β€” one click restores page-only mode; query text is preserved. * Album browse text search uses title-only FTS in the local index; Tracks hides discovery chrome while searching; session stash restores query and scroll after back from detail. ### Performance Probe β€” live runtime logs tab **By [@cucadmuh](https://github.com/cucadmuh), PR [#946](https://github.com/Psychotoxical/psysonic/pull/946)** * New **Logs** tab streams the backend runtime log buffer live inside the app, so the stdout/stderr console β€” unreachable on Windows without exporting a file β€” can be read online. The buffer tags each line with a monotonic seq and a new `tail_runtime_logs` command tails it incrementally. * Includes an off/normal/debug **depth switch** (mirrors app Settings), a 500–5000 **line cap**, pause/clear, auto-follow, and an ordered comma-separated **word filter** where a plain word includes and `-word` excludes, applied left to right as layers (sequence matters). ## Changed ### Settings + Queue polish **By [@kveld9](https://github.com/kveld9) + [@Psychotoxical](https://github.com/Psychotoxical), adopted from PR [#558](https://github.com/Psychotoxical/psysonic/pull/558), rewritten in PR [#778](https://github.com/Psychotoxical/psysonic/pull/778)** * Settings toggle rows dim non-toggle content to 0.6 opacity when their switch is off; mutex-disabled rows (Crossfade/Gapless) unchanged. * Queue toolbar `Clear` β†’ `Clear queue` across all 9 locales. ### Linux β€” session GDK, WebKitGTK mitigations, and Wayland text **By [@cucadmuh](https://github.com/cucadmuh), PR [#731](https://github.com/Psychotoxical/psysonic/pull/731)** * **Nix / AUR** default installs follow the session GDK backend instead of pinning `GDK_BACKEND=x11`; startup applies **`webkit2gtk-nvidia-quirk`** only (skip with **`PSYSONIC_WEBKIT_GPU_ACCEL`**). **`nix run .#psysonic-x11-legacy`** keeps the old explicit X11 launcher. * **AppImage stays on X11/XWayland**: unlike the `.deb` / `.rpm` / Nix packages it still pins `GDK_BACKEND=x11` (set by the bundle's AppRun hook), so it doubles as the legacy channel. Use `.deb`, `.rpm`, AUR, or the Nix default for a native-Wayland launch. * **NVIDIA + forced X11** on a Wayland user session no longer greys out the webview β€” the quirk uses the DMABUF renderer path instead of Wayland explicit-sync disable. * **Wayland + GPU compositing:** clearer UI text via on-demand hardware acceleration on main and mini webviews; **Settings β†’ System** adds **Wayland text rendering** presets (Balanced / Sharp / GPU / Minimal). Opt out with **`PSYSONIC_SKIP_WAYLAND_FONT_TUNING`**. ### Library browse β€” in-page overlay scroll **By [@cucadmuh](https://github.com/cucadmuh), PR [#731](https://github.com/Psychotoxical/psysonic/pull/731)** * **Artists**, **Albums**, **Composers**, **Lossless albums**, and **New releases** scroll inside the route on a locked in-page viewport β€” toolbars stay sticky, virtual grids use the matching scroll root. * Sidebar hover and album/artist card covers no longer jitter on WebKitGTK + Wayland during pointer moves. ### Interface Scale β€” covers the whole window **By [@Psychotoxical](https://github.com/Psychotoxical), PR [#781](https://github.com/Psychotoxical/psysonic/pull/781)** * Settings β†’ Appearance β†’ Interface Scale now scales sidebar, queue, player bar, modals/portals and the fullscreen player alongside the main content β€” same behaviour as browser Ctrl+/βˆ’. ### Radio β€” card control polish **By [@Psychotoxical](https://github.com/Psychotoxical), reported by zunoz on Discord, PR [#786](https://github.com/Psychotoxical/psysonic/pull/786)** * Repeat is disabled while a radio stream plays. * Deleting the playing station fades out instead of cutting hard. * Play / Stop tooltip on the cover-overlay button; stop uses a Square icon. ### Library browse β€” local index race and catalog paths **By [@cucadmuh](https://github.com/cucadmuh), PR [#847](https://github.com/Psychotoxical/psysonic/pull/847)** * **Artists**, **Composers**, **Tracks**, and **Search Results** text search races local FTS against network search3; a ready index still serves hits when remote is down. * **All Albums** paginated browse and genre filter, plus **Artists** catalog browse-all, read from the local index when ready (network fallback unchanged). * DevTools: `[psysonic][library] browse-race …` lines for race winner, timings, hit counts, and fallback reason. ### Lyrics β€” sources can be turned off entirely **By [@Psychotoxical](https://github.com/Psychotoxical), suggested by sddania, PR [#855](https://github.com/Psychotoxical/psysonic/pull/855)** * YouLyPlus is now an independent toggle instead of an either/or with the standard sources, so lyrics can be switched off completely β€” turn off YouLyPlus and every source under **Settings β†’ Lyrics**. With nothing selected, no lyrics are fetched or shown and the queue lyric tab says so. Fresh installs start with all sources off. ### Queue β€” smoother scrolling for very long queues **By [@Psychotoxical](https://github.com/Psychotoxical), PR [#857](https://github.com/Psychotoxical/psysonic/pull/857)** * The queue panel now renders only the rows in view, so very long queues (e.g. hours of Artist Radio) stay smooth instead of bogging down the interface. ### Queue β€” section dividers kept when restoring from the local index **By [@Psychotoxical](https://github.com/Psychotoxical), PR [#858](https://github.com/Psychotoxical/psysonic/pull/858)** * When the queue is rebuilt from the local library index on startup, the **Radio** and **Auto-added** section dividers are now preserved. Groundwork toward keeping very large queues fast and light. ### Queue β€” on-demand track loading for very large queues **By [@Psychotoxical](https://github.com/Psychotoxical), PR [#859](https://github.com/Psychotoxical/psysonic/pull/859)** * Continued groundwork for multi-thousand-track queues: track details are resolved on demand through a shared cache rather than all being held at once. No change to how the queue looks or behaves. ### Queue β€” panel now reads through the shared track cache **By [@Psychotoxical](https://github.com/Psychotoxical), PR [#860](https://github.com/Psychotoxical/psysonic/pull/860)** * The queue panel sources its row details through the on-demand track cache, another step toward keeping multi-thousand-track queues light on memory. No visible change. ### Backup UX β€” blocking progress gate for long operations **By [@cucadmuh](https://github.com/cucadmuh), PR [#864](https://github.com/Psychotoxical/psysonic/pull/864)** * Backup/export and restore operations now show a global blocking status modal after file selection, so the app no longer looks frozen while archive and SQLite work runs. ### Development β€” parallel `tauri dev` alongside release **By [@cucadmuh](https://github.com/cucadmuh), PR [#866](https://github.com/Psychotoxical/psysonic/pull/866)** * Debug builds skip `tauri-plugin-single-instance` so `./dev.sh` can run next to an installed release while sharing the same app data directory. * Debug-only chrome: window title `Psysonic (Dev)`, red sidebar brand, monochrome custom titlebar buttons, mobile `DEV` badge, horizontally flipped tray icon. * Debug builds do not register OS global shortcuts, MPRIS/media keys, or Windows taskbar media controls β€” release keeps system-wide input when both are open. ### Discord Rich Presence β€” track title in the member list **By [@Psychotoxical](https://github.com/Psychotoxical), PR [#885](https://github.com/Psychotoxical/psysonic/pull/885)** * The Discord member list and the collapsed presence card now show the playing track next to the music icon instead of the static "Psysonic" label β€” matches how comparable players appear in the user list. * New **User list line (name)** template under **Settings β†’ Integrations β†’ Discord Rich Presence**, default `{title}`. Same placeholders as the other templates: `{title}`, `{artist}`, `{album}`. Leaving it empty restores the previous "Psysonic" display. ### Library browse β€” chunked local catalogs and unified in-page scroll **By [@cucadmuh](https://github.com/cucadmuh), PR [#890](https://github.com/Psychotoxical/psysonic/pull/890)** * **Albums / Artists**: local index loaded in **200-row SQL chunks** instead of a single ~50k-row fetch; filters preserved. * **All Albums**: client-slice infinite scroll on the local index (Artists-style). * Shared in-page scroll hooks and sentinel UI across browse routes; album SQL pagination prioritized over cover ensures. ### CI β€” hot-path coverage gates block merges **By [@cucadmuh](https://github.com/cucadmuh), PR [#921](https://github.com/Psychotoxical/psysonic/pull/921)** * Frontend and Rust `coverage` jobs no longer carry `continue-on-error`; listed hot-path files must stay at β‰₯70% line coverage or the PR fails. ### Cover backfill β€” live-tunable parallelism and pipeline **By [@cucadmuh](https://github.com/cucadmuh), PR [#943](https://github.com/Psychotoxical/psysonic/pull/943)** * Cover backfill runs through a producer/consumer pipeline (bounded channel + fixed consumer pool) that stays saturated and bails promptly on a switch to **lazy** instead of draining the whole backlog. * **Performance Probe** gains a runtime cover-thread control (`library_cover_backfill_set_parallel`) that resizes the HTTP/encode pools live; "Run full pass now" forces a pass and clears fetch-failed backoff. * Clearing the active server's cover cache re-arms the idle gate and wakes the worker, and in-pass progress is emitted on a ticker so the offline & cache view keeps counting through the whole scan. ### Performance Probe β€” cover pipeline throughput (cpm) **By [@cucadmuh](https://github.com/cucadmuh), PR [#945](https://github.com/Psychotoxical/psysonic/pull/945)** * The cover pipeline now reports a covers-per-minute throughput (cpm), the analogue of the analysis pipeline's tpm: a rolling one-minute rate derived from the backfill `done` progress. Shown in the Monitor tab "Cover backfill" card (pinnable to the overlay) and in the Cover pipeline overlay block. ### Performance Probe β€” cover pipeline on-demand (ui) throughput **By [@cucadmuh](https://github.com/cucadmuh), PR [#947](https://github.com/Psychotoxical/psysonic/pull/947)** * Cover cpm previously measured only the native backfill (lib). On-demand UI cover ensures (grid / now-playing) now report their own covers-per-minute rate, shown as separate **Backfill (lib)** and **On-demand (ui)** cards in the Monitor tab (each pinnable) and as `lib`/`ui` rows in the Cover pipeline overlay block. ### Performance Probe β€” responsive throughput windows (tpm / cpm) **By [@cucadmuh](https://github.com/cucadmuh), PR [#948](https://github.com/Psychotoxical/psysonic/pull/948)** * Analysis **tpm** and cover **cpm** (lib + ui) now measure throughput over the trailing **5 seconds** instead of a full-minute rolling average. The figure is still extrapolated to per-minute, but reacts promptly to bursts/stalls and decays to 0 within the window when idle, instead of coasting on minute-long inertia. ### Track cards β€” distinct look + jump to album **By [@Psychotoxical](https://github.com/Psychotoxical), PR [#953](https://github.com/Psychotoxical/psysonic/pull/953)** * Single tracks in the discovery rails now show a round, vinyl-style cover so they read as songs rather than albums β€” clicking one still plays it instantly. * A new **To album** badge under the artist jumps to the track's album, available in all 9 languages. ### Tooltips β€” consistent hints on every button **By [@Psychotoxical](https://github.com/Psychotoxical), PR [#972](https://github.com/Psychotoxical/psysonic/pull/972)** * Buttons across the app now show a short tooltip describing what they do, appearing after a 1-second hover so they never flash during quick mouse passes. * Tooltips that were missing β€” on the artist, album, All Albums, track-list and playlist actions β€” are filled in, and tooltips that used to point different directions within the same toolbar now line up consistently. * Advanced Search gains a **Search in:** label so the All / Artists / Albums / Songs row reads clearly as a scope limiter. Available in all 9 languages. ### Mainstage β€” renamed to match the sidebar **By [@Psychotoxical](https://github.com/Psychotoxical), reported by zunoz on Discord, PR [#975](https://github.com/Psychotoxical/psysonic/pull/975)** * The **Settings β†’ Personalisation** section for customising the home page is renamed **Mainstage** so it matches the sidebar entry it controls. Localised across all 9 languages; settings search still finds it under the new name. ## Fixed ### In-page browse β€” virtual scroll and cover-art priority **By [@cucadmuh](https://github.com/cucadmuh), PR [#783](https://github.com/Psychotoxical/psysonic/pull/783)** * **In-page browse:** virtual artist/album/composer grids and lists no longer lose all rows after deep scroll β€” `scrollMargin` now targets the in-page overlay viewport, not the locked main route scrollport. * **Cover art on browse pages:** `CachedImage` priority scoring follows the real scrolling pane so visible thumbnails win network fetch slots; Artists infinite scroll loads one page per batch instead of re-entrantly queueing many pages during a fast fling. ### Lucky Mix after server switch **By [@cucadmuh](https://github.com/cucadmuh), PR [#785](https://github.com/Psychotoxical/psysonic/pull/785)** * Starting a mix on the browsed server no longer spams cross-server enqueue errors β€” unpinned or foreign queues hand off cleanly before batch enqueue. ### Radio β€” paused streams stay paused **By [@Psychotoxical](https://github.com/Psychotoxical), reported by drelabre on GitHub, PR [#786](https://github.com/Psychotoxical/psysonic/pull/786)** * Pausing a radio stream no longer auto-resumes after about a minute on macOS. ### Mainstage β€” album rail hover controls **By [@cucadmuh](https://github.com/cucadmuh), PR [#787](https://github.com/Psychotoxical/psysonic/pull/787)** * **Home horizontal album rails (Discover, etc.):** play/enqueue overlay no longer flickers on WebKitGTK + Wayland GPU; cover zoom stays smooth like the All Albums grid. * **Album grids and song rails:** overlay `pointer-events` so the dim layer does not steal hover from the card. ### Album view β€” bulk add to playlist selection **By [@Psychotoxical](https://github.com/Psychotoxical), PR [#844](https://github.com/Psychotoxical/psysonic/pull/844)** * Bulk "Add to playlist" no longer clears the track selection without opening the playlist picker. ### Playlists β€” column sorting keeps the viewport in place **By [@Psychotoxical](https://github.com/Psychotoxical), PR [#848](https://github.com/Psychotoxical/psysonic/pull/848)** * Sorting a playlist column no longer snaps the viewport down to the list when scrolled to the top. ### Settings β€” local library index exclude/include feedback **By [@cucadmuh](https://github.com/cucadmuh), PR [#850](https://github.com/Psychotoxical/psysonic/pull/850)** * **Settings β†’ Library:** **Exclude from sync** and **Include again** show immediate busy labels and block repeat clicks while bind/unbind runs; exclude cancels an in-flight sync first. ### Statistics β€” player stats tab without local index **By [@cucadmuh](https://github.com/cucadmuh), PR [#851](https://github.com/Psychotoxical/psysonic/pull/851)** * **Statistics β†’ Player stats** tab stays visible when the local library index is off; an info notice explains that player statistics require the index and links to **Settings β†’ Library**. ### Playlists & Favorites β€” column picker on short lists **By [@Psychotoxical](https://github.com/Psychotoxical), PR [#853](https://github.com/Psychotoxical/psysonic/pull/853)** * On a one-song playlist (or short favorites list) the column menu was clipped behind the list, added a stray scrollbar, and could hide the row when scrolled. The picker now sits outside the scroll area, so it opens fully on lists of any length. ### Browse all tracks β€” sticky header no longer overlapped **By [@Psychotoxical](https://github.com/Psychotoxical), PR [#854](https://github.com/Psychotoxical/psysonic/pull/854)** * Scrolling the full tracks list painted rows over the sticky column header. Browse now flows in the page like the search results, so the header stays put; it shares one list view with Search and Advanced Search. ### Local library index β€” full resync removes server-deleted tracks **By [@cucadmuh](https://github.com/cucadmuh), PR [#861](https://github.com/Psychotoxical/psysonic/pull/861)** * **Settings β†’ Library β†’ Full resync** now soft-deletes local rows that no longer exist on the server after a successful re-sync (mark-and-sweep via `resync_gen`), so **Ready (N tracks)** no longer stays inflated when tracks were removed on Navidrome/Subsonic. Delta tombstone reconcile is unchanged. ### Server index-key migration β€” unknown/legacy data handling **By [@cucadmuh](https://github.com/cucadmuh), PR [#864](https://github.com/Psychotoxical/psysonic/pull/864)** * Legacy destructive migration paths were replaced with a dual-DB import/switch flow that keeps old DBs as source until verification passes. * Rows belonging to removed servers are explicitly skipped/purged from the active migrated DB scope instead of being silently carried forward. * Legacy sqlite artifacts from old paths are now cleaned up after successful path migration (including WAL/SHM sidecars) to prevent stale old-version leftovers. ### Now Playing β€” stray zero metadata badges **By [@cucadmuh](https://github.com/cucadmuh), PR [#865](https://github.com/Psychotoxical/psysonic/pull/865)** * Hero track-info badges no longer render literal `0` when numeric metadata fields (bit depth, bitrate, sample rate, year, rating) are missing and arrive as zero from the server. ### Analysis β€” failed tracks no longer block completion forever **By [@cucadmuh](https://github.com/cucadmuh), PR [#867](https://github.com/Psychotoxical/psysonic/pull/867)** * **Settings β†’ Library:** problematic tracks that cannot complete loudness/enrichment are now persisted as **failed** and excluded from endless retry loops after restart. * Added failed-track controls in Analytics strategy (count, inspect list with title/path, export, and rescan) so users can review and requeue only when they want. * Aggressive analysis idle checks now run as a cheap startup + 5-minute recheck against live-track count instead of heavy frequent polling. ### Live Search β€” multi-server local index hits **By [@cucadmuh](https://github.com/cucadmuh), PR [#868](https://github.com/Psychotoxical/psysonic/pull/868)** * **Live Search** with a local index no longer returns empty or wrong-server hits on multi-server libraries β€” FTS is scoped to the active server instead of global bm25 across all indexed tracks. * Local artist/album rows dedupe correctly (one performer no longer fills the whole dropdown); Advanced Search text queries use the same server scope fix. ### Queue β€” mixed-server routing and quota-safe persist **By [@Psychotoxical](https://github.com/Psychotoxical) + [@cucadmuh](https://github.com/cucadmuh), PR [#872](https://github.com/Psychotoxical/psysonic/pull/872)** * Mixed-server queues with the same track ID on different servers now stay on their original server through track switches, undo, and radio top-ups. * Persisted queue is quota-safe β€” a full local storage no longer blocks playback on very large queues. ### Analytics β€” aggressive scan no longer eats memory on big libraries **By [@Psychotoxical](https://github.com/Psychotoxical), PR [#873](https://github.com/Psychotoxical/psysonic/pull/873)** * **Settings β†’ Library β†’ Analytics β†’ Aggressive** on multi-server or 100k+ track libraries no longer climbs in memory until the system swaps (Linux) or runs out (Windows OOM mid-scan): the HTTP download stage now waits for Symphonia decode + loudness to catch up instead of buffering tracks faster than they can be processed. * Now-playing prefetch still bypasses the cap, so starting a track during a background scan stays instant. ### Home β€” Discover Songs cover art with local index **By [@Psychotoxical](https://github.com/Psychotoxical) + [@cucadmuh](https://github.com/cucadmuh), PR [#874](https://github.com/Psychotoxical/psysonic/pull/874)** * **Mainstage β†’ Discover Songs** no longer shows disc placeholders when the local library index returns tracks without `coverArt` but with a valid `albumId` β€” cover resolution matches the Rust backfill rule (`COALESCE(cover_art_id, album_id)`). * Discover Songs row gets dedicated mainstage cover prefetch and warmup so song cards are not crowded out by album rails on cold caches. ### Cover art β€” Windows thumbnails, PNG decode, and Subsonic cover ids **By [@cucadmuh](https://github.com/cucadmuh), PR [#878](https://github.com/Psychotoxical/psysonic/pull/878)** * Small cover surfaces on Windows (player bar, queue, artist top tracks) no longer stay empty while large album art loads β€” tier ladder disk lookup, valid `asset://` URLs only, and broader Tauri asset scope. * Fixes a startup/UI freeze when disk paths were applied via `rememberGridDiskSrc` (notify loop); seeds cache without waking subscribers. * Resolves Subsonic `coverArt` when it equals the track id β€” prefers `albumId` and warmed album-grid art on playback and artist pages. * No broken-image flash on cover surfaces while disk tiers warm (placeholder until a loadable URL exists). * Rust cover pipeline decodes **PNG** bytes from the server (previously JPEG/WebP only); failed decode no longer leaves albums stuck with `.fetch-failed`. ### Analytics β€” advanced library backfill without webview jank **By [@cucadmuh](https://github.com/cucadmuh), PR [#881](https://github.com/Psychotoxical/psysonic/pull/881)** * **Settings β†’ Library β†’ Analytics β†’ Advanced** on large libraries no longer stalls the whole UI (~4 FPS): catalog scheduling runs in a native background worker like cover backfill, not a webview polling loop. * Partially analyzed tracks (hash + BPM but missing waveform/loudness) are picked up via a targeted second scan with a reset cursor so the library is not skipped mid-pass. * Performance Probe analysis stats still update during background backfill; waveform/enrichment refresh events stay quiet for low-priority work. ### Analytics β€” advanced backfill scan no longer replays the first chunk **By [@cucadmuh](https://github.com/cucadmuh), PR [#882](https://github.com/Psychotoxical/psysonic/pull/882)** * **Settings β†’ Library β†’ Analytics β†’ Advanced** on large libraries no longer stalls mid-pass when most early tracks are already analyzed: the native coordinator keeps hash/BPM gap scan phase and cursor across ticks instead of restarting from the first ids every cycle. ### Analytics β€” Opus waveform and loudness analysis **By [@cucadmuh](https://github.com/cucadmuh), PR [#883](https://github.com/Psychotoxical/psysonic/pull/883)** * **Opus tracks:** waveform, LUFS, and enrichment analysis now use the same `symphonia-adapter-libopus` registry as playback β€” previously Symphonia could demux Ogg Opus but failed at decoder creation, leaving `.opus` libraries without analysis data. ### Settings β€” Linux text-input freeze workaround **By [@Psychotoxical](https://github.com/Psychotoxical), PR [#884](https://github.com/Psychotoxical/psysonic/pull/884)** * **Settings β†’ System β†’ Behavior** (Linux only): optional toggle for users on WebKitGTK 2.50.x where text fields freeze when clicked (issues #342, #782) β€” turning it on forces the input to repaint on focus. Default off; enabling it adds a brief flicker on search icons. ### Artist page β€” top track thumbnails **By [@cucadmuh](https://github.com/cucadmuh), PR [#886](https://github.com/Psychotoxical/psysonic/pull/886)** * **Top Tracks** on the artist page now load cover art through the same album `id` + `coverArt` path and disk warm batch as the albums grid below β€” fixes slow or missing 32px thumbs that used a separate sparse resolver. * Warm/peek uses the album-grid tier (not 32px), top-track rows ensure at high priority, and the page registers the same dense prefetch as All Albums. ### Covers β€” load on Windows when the server URL has a `:port` **By [@Psychotoxical](https://github.com/Psychotoxical), PR [#889](https://github.com/Psychotoxical/psysonic/pull/889)** * Album, now-playing, mainstage, and lightbox covers no longer stay blank on Windows when the active server URL has a `:port` (typical Navidrome LAN setup on `:4533`). The colon used to land in a Windows filesystem segment, so the OS rejected the whole cache path with `ERROR_INVALID_NAME` and every cover load failed silently. * Existing cache buckets on disk are wiped once on the next launch (layout-stamp bump) and rebuild lazily as users browse. Library, offline, and hot caches are untouched. ### Library browse & covers β€” scroll stability and cover loading **By [@cucadmuh](https://github.com/cucadmuh), PR [#890](https://github.com/Psychotoxical/psysonic/pull/890)** * In-page infinite scroll stabilized; cover memory caches capped; covers keep loading during active grid scroll. * **New Releases** and **Lossless** grids use the correct in-page scroll root; cover ensure invoke pump no longer sticks; viewport priority tiers for ensure/peek. * Performance Probe overlay: fixed blank page from unstable sparkline history; synchronized poll clock and bar/sparkline tick jitter. ### Queue β€” new tracks no longer render as blank placeholders **By [@Psychotoxical](https://github.com/Psychotoxical), PR [#892](https://github.com/Psychotoxical/psysonic/pull/892)** * Adding tracks to the queue from Advanced Search results, song rows, or song cards right after launch could show every new entry as `…` / `0:00` instead of the real title and duration, until something else triggered a queue-replacing playback. * Root cause: the queue's owning server was not pinned yet, so the resolver cache skipped seeding the incoming tracks. Add-to-queue mutations now pin the active server up-front. ### Radio β€” track info in OS media controls **By [@Psychotoxical](https://github.com/Psychotoxical), reported by agriffit79 on GitHub, PR [#924](https://github.com/Psychotoxical/psysonic/pull/924)** * The Linux media overlay (MPRIS) now shows the current **radio track and artist** instead of just "Psysonic", and updates as the stream changes songs. Internet radio plays through the WebView audio element, which exposes its own OS media player β€” that player is now fed the live ICY/AzuraCast metadata. Streams that send no metadata still fall back to the station name. ### Advanced Search β€” centered button label **By [@Psychotoxical](https://github.com/Psychotoxical), PR [#925](https://github.com/Psychotoxical/psysonic/pull/925)** * The **Search** button's label is now centered. Buttons wider than their text (the Search button has a fixed minimum width) previously rendered the label left-aligned. ### CI β€” npmDepsHash on app-v* tags **By [@cucadmuh](https://github.com/cucadmuh), PR [#927](https://github.com/Psychotoxical/psysonic/pull/927)** * Channel publish now refreshes `nix/upstream-sources.json` and `flake.lock` on the channel branch **before** cutting `app-v*` tags, so Nix builds from release tags no longer fail with stale `npmDepsHash` (e.g. after promote finalizes `package-lock.json` version). ### Queue β€” Infinite Queue and Smart Radio top-ups no longer show `…` / `0:00` **By [@Psychotoxical](https://github.com/Psychotoxical), PR [#930](https://github.com/Psychotoxical/psysonic/pull/930)** * Tracks added automatically by **Infinite Queue** or by **Smart Radio** could render as `…` / `0:00` instead of their real title and duration when the queue was filled without a queue-replacing playback (single-track enqueue from a song row, search result, etc). * Same root cause as PR #892 β€” just on the auto-add paths the earlier fix did not cover. The owning server is now pinned before each auto-top-up so the resolver cache sees the fresh tracks. ### Performance β€” idle Rust CPU, probe overlay, and cover prefetch **By [@cucadmuh](https://github.com/cucadmuh), PR [#939](https://github.com/Psychotoxical/psysonic/pull/939)** * **Advanced analytics coordinator:** park on `Notify` when disabled β€” no idle 2s poll loop; wake on configure or library sync-idle. * **Performance Probe:** run CPU snapshot on the blocking pool; skip `/proc` poll on Windows; fix overlay flicker and sparkline clock jumps; hold previous CPU % until the first rate sample (no 0% flash). * **Background polls:** Settings β†’ Storage hot-cache poll 15s; cover registry full disk stats every 30s when idle instead of every 1.5s tick. * **Cover art:** restore lazy route prefetch; batch disk peek before ensure so cached WebP warms `diskSrcCache` without flooding invoke slots; yield when viewport ensures are queued. ### Player stats β€” paused time no longer counts as listening time **By [@cucadmuh](https://github.com/cucadmuh), PR [#942](https://github.com/Psychotoxical/psysonic/pull/942)** * Pausing a track and resuming later inflated the listening time in **Statistics β†’ Player stats** β€” the whole paused span was billed as if the track had been playing. * Root cause: the session's tick baseline froze on pause, so the first progress tick after resume measured against the pre-pause timestamp. It now settles the played segment on pause and rebaselines on resume. ### Cover backfill β€” idle CPU spin and offline & cache menu spikes **By [@cucadmuh](https://github.com/cucadmuh), PR [#943](https://github.com/Psychotoxical/psysonic/pull/943)** * **Aggressive** cover backfill could pin a `tokio-runtime-worker` near 100% CPU when effectively idle: it had no "nothing changed, don't rescan" gate. Added a cheap disk-free idle signature (`COUNT(DISTINCT)` covers) with a `sync-idle` cooldown; the gate settles on a completed pass even when some covers are unfetchable (404), so libraries that never reach 100% no longer trigger a wake storm. * Worklist is now built from a single DB `GROUP BY` plus one cover-dir snapshot and diffed in memory β€” no per-row `stat`, no per-batch rescan loop β€” so increasing parallelism actually saturates the pipeline. * **Settings β†’ offline & cache** caused periodic CPU spikes: the section re-walked each server's full cover directory every 15s. The per-server walk is now memoized with a short TTL and reused by the stats/progress commands; the menu recomputes on entry and via progress/cache-cleared events, with a 5-minute safety poll instead of a tight 15s loop. * Transient download failures (network / 5xx / 429) retry up to 3Γ— with exponential backoff; permanent 4xx settle without re-scanning. ### Cover art β€” per-song over-fetch on Navidrome (album/mf-* explosion) **By [@cucadmuh](https://github.com/cucadmuh), PR [#944](https://github.com/Psychotoxical/psysonic/pull/944)** * The per-disc cover detection treated each track's own `mf-` coverArt as "distinct disc art", so backfill warmed one cover per track (e.g. ~520k cached elements for ~170k tracks) and filled the `album/` bucket with `mf-*` directories. * It now treats a release as multi-disc only when each disc has a single consistent cover that differs across discs (a genuine box set); per-song ids collapse to one cover per album (β‰ˆ albums + artists). Fixed on both the Rust backfill path and the on-demand TS `albumHasDistinctDiscCovers`. * Failed cover downloads are now logged with the album/artist name and the server error (e.g. `fetch failed for album "X" β€” Artist (coverArtId=…): cover HTTP 503`). Backfill failures log at the normal level; incidental on-demand misses stay at the debug level. ### Cover backfill β€” follow the local/public endpoint switch **By [@cucadmuh](https://github.com/cucadmuh), PR [#952](https://github.com/Psychotoxical/psysonic/pull/952)** * On a dual-address server, library cover backfill was configured once with a snapshot of the connect URL and never followed the smart LAN↔public switch. Starting already off the LAN β€” or moving off it mid-session β€” (internet up, playback already on the public address) left backfill hammering the now-unreachable local address and flooding the log with `error sending request` failures. * The backfill worklist no longer carries a URL: each cover fetch now reads the current reachable address live, so a LAN↔public flip is honoured even by the pass already in flight (its remaining covers download against the new endpoint). The connect cache is observable and pushes the resolved URL to the native worker on every flip; a real change clears the stale `.fetch-failed` backoff and runs a forced pass so the handful of covers attempted against the old address retry on the reachable one. This also covers the boot case where the initial pass starts on the primary URL before the first reachability probe resolves. On-demand UI / playback covers already followed the switch. ### UI polish β€” focus rings, search fields, column menus, settings **By [@Psychotoxical](https://github.com/Psychotoxical), PR [#954](https://github.com/Psychotoxical/psysonic/pull/954)** * A pass of UI/CSS fixes: keyboard focus rings now sit inside the focused element, so they're no longer clipped at the edge of cards, rails, the player bar, queue tabs or search fields; the page, Help and Settings search fields share one consistent shape and focus highlight; the column-visibility dropdown on track tables no longer gets cut off on short lists (e.g. a single favorited song); and the Theme settings list rounds its corners to match its section. ### Player β€” prefs survive restart when queue persist hits quota **By [@cucadmuh](https://github.com/cucadmuh), reported by norp on the Psysonic Discord, PR [#958](https://github.com/Psychotoxical/psysonic/pull/958)** * Volume, repeat mode, queue panel visibility, and the Last.fm loved-track cache no longer depend on the quota-bound `psysonic-player` blob (full `queueItems` since thin-state #872). Each pref now has its own small localStorage key with legacy migration from the old blob. * Startup no longer overwrites saved prefs before Zustand rehydration finishes; persisted volume is pushed to the Rust engine on boot. ### All Albums β€” genre filter respects sidebar library scope **By [@cucadmuh](https://github.com/cucadmuh), reported by zunoz on the Psysonic Discord, PR [#959](https://github.com/Psychotoxical/psysonic/pull/959)** * With multiple music libraries, narrowing the sidebar to one library no longer leaves the Genre filter showing server-wide genres β€” options now come from the scoped local index catalog (same scope as the album grid). ### Now Playing β€” multi-artist links and About the Artist tabs **By [@cucadmuh](https://github.com/cucadmuh), reported by zunoz on the Psysonic Discord, PR [#960](https://github.com/Psychotoxical/psysonic/pull/960)** * Tracks with OpenSubsonic `artists[]` (e.g. Navidrome `feat.` splits) now expose per-artist links on the Now Playing hero and in the queue current-track row β€” same interaction as player bar and album track lists. * About the Artist loads bio for each performer; when multiple artist ids are present, tabs switch between their bios, images, and similar artists instead of showing one joined name with a single profile. ### Composers β€” page search keeps role-split credits **By [@cucadmuh](https://github.com/cucadmuh), reported by zunoz on the Psysonic Discord, PR [#961](https://github.com/Psychotoxical/psysonic/pull/961)** * Scoped search on Composers no longer replaces the Navidrome role-scoped catalog with generic artist index/search3 hits that merge split composer credits into one joined name and id β€” results stay split like the scroll overview. ### Browse grids β€” multi-select ring no longer clips (WebKitGTK) **By [@cucadmuh](https://github.com/cucadmuh), reported by zunoz on the Psysonic Discord, PR [#962](https://github.com/Psychotoxical/psysonic/pull/962)** * Multi-select rings on Artists, All Albums, Playlists, and related card grids use an inset `::after` overlay (same approach as card focus rings) instead of `outline` on `overflow: hidden` tiles β€” fixes top-row clipping and the ~1px gap vs the inner border on Wayland/WebKitGTK. ### Composers β€” hide performer-only artists from role catalog **By [@cucadmuh](https://github.com/cucadmuh), reported by zunoz on the Psysonic Discord, PR [#963](https://github.com/Psychotoxical/psysonic/pull/963)** * Navidrome's composer role list can include artists with zero composer album credits (e.g. Apollo 440 with performer albums only). Composers browse/search now drops rows where `stats.composer.albumCount` is zero so ghost composer cards no longer appear. ### Mainstage β€” Because you listened respects sidebar library **By [@cucadmuh](https://github.com/cucadmuh), reported by zunoz on the Psysonic Discord, PR [#964](https://github.com/Psychotoxical/psysonic/pull/964)** * The recommendation rail picks albums from Last.fm similar artists via `getArtist`, which can ignore `musicFolderId` β€” picks are now filtered to the scoped library album set, and the rail cache invalidates when the sidebar library filter changes. ### Build a Mix β€” keyword blocks and scoped genre list **By [@cucadmuh](https://github.com/cucadmuh), reported by zunoz on the Psysonic Discord, PR [#965](https://github.com/Psychotoxical/psysonic/pull/965)** * Random Mix keyword filter (click-to-block artist/genre) now applies even when "Exclude audiobooks" is off β€” blocking the only track in a library shows an empty state after Remix instead of the excluded song. * Genre Mix loads genres through the scoped catalog (`fetchGenreCatalog` / local index) instead of server-wide `getGenres`, matching the sidebar library filter. ### Artist detail β€” external link buttons keep border on hover **By [@cucadmuh](https://github.com/cucadmuh), reported by zunoz on the Psysonic Discord, PR [#966](https://github.com/Psychotoxical/psysonic/pull/966)** * Last.fm, Wikipedia, and Favorite used a hover border color that matched the card background β€” the rim disappeared instead of highlighting the inner fill like Play/Shuffle/Radio (`btn-surface`). * Playlist detail β€” Play and Add Songs now show tooltips like the other header actions; track count uses proper pluralization (`1 song` vs `N songs`) with standard `count` interpolation. * Suggested Songs rows now render BPM (and other optional columns like genre, play count, last played) β€” the column switch was missing those cases. ### Player transport β€” custom delay input validation **By [@cucadmuh](https://github.com/cucadmuh), reported by zunoz on the Psysonic Discord, PR [#967](https://github.com/Psychotoxical/psysonic/pull/967)** * Absurd custom minute values (e.g. eleven nines) no longer arm an immediate timer while the preview still shows a far-future start time β€” input is capped to the browser delay limit and Apply stays disabled when out of range. * Fractional custom minutes (e.g. `0.1`, `0.01`) now share the same delay math between the modal preview, armed timer, and play-button countdown so the displayed remaining time matches when playback starts or pauses. ### Settings β€” in-page search coverage and junk-query filtering **By [@cucadmuh](https://github.com/cucadmuh), reported by zunoz on the Psysonic Discord, PR [#968](https://github.com/Psychotoxical/psysonic/pull/968)** * AudioMuse-AI (Navidrome) is indexed and selectable from settings search; choosing it opens Servers and scrolls to the plugin toggle when shown. * In-app and global shortcut labels (e.g. Volume up / Volume down) appear as search hits and focus the parent shortcuts subsection. * Nonsense queries no longer return unrelated fuzzy matches (e.g. long repeated letters). ### Floating player bar β€” remove full-width background strip **By [@cucadmuh](https://github.com/cucadmuh), reported by Asra on the Psysonic Discord, PR [#969](https://github.com/Psychotoxical/psysonic/pull/969)** * Floating mode no longer stretches the player bar between sidebar and queue with fixed `left`/`right` β€” only the centered pill is painted over the page instead of a full-width black band behind the rounded corners. ### Smart Playlist editor β€” themed sort, stable toggles, exclude-all genres **By [@cucadmuh](https://github.com/cucadmuh), reported by zunoz on the Psysonic Discord, PR [#970](https://github.com/Psychotoxical/psysonic/pull/970)** * Sort dropdown uses the themed `CustomSelect` instead of a native `` boxes. - Supports option groups (EQ: Built-in Presets / Custom Presets), keyboard navigation, and click-outside-to-close. ### Changed #### Fullscreen Player / Now Playing β€” Background - **Ken Burns animation improved**: background image now has significantly more movement (Β±8% translate, `inset: -30%`) with a 90-second cycle β€” more cinematic without being distracting. - **Color orbs removed** from both the Fullscreen Player and the Now Playing page. They caused noticeable GPU load especially on integrated graphics. ### Fixed - **Live dropdown (Now Playing)**: Own playback was no longer reported to Navidrome after the Last.fm implementation removed the `reportNowPlaying` call. Both are now called independently on track start. - **Sidebar: Now Playing button position when collapsed**: The button was appearing in the middle of the nav instead of just above the System section. Caused by a leftover `margin-top: auto` on the Statistics link that split the remaining flex space. --- ## [1.6.0] - 2026-03-19 > ⚠️ **Wichtiger Hinweis / Important Notice** > > **DE:** Der Bundle-Identifier der App wurde von `dev.psysonic.app` auf `dev.psysonic.player` geΓ€ndert. **Alle gespeicherten Einstellungen (Server-Profile, Theme, EQ, Sprache usw.) gehen beim Update auf diese Version einmalig verloren** und mΓΌssen neu eingetragen werden. ZukΓΌnftige Updates sind davon nicht betroffen. > > **EN:** The app's bundle identifier has changed from `dev.psysonic.app` to `dev.psysonic.player`. **All saved settings (server profiles, theme, EQ, language, etc.) will be reset once when updating to this version** and need to be re-entered. Future updates are not affected. ### Added #### Replay Gain - **Replay Gain support** in the Rust audio engine. Gain and peak values from the Subsonic API are applied per-track at playback time, keeping loudness consistent across your library. - Two modes selectable in Settings β†’ Playback: **Track** (default) and **Album** gain. - Peak limiting applied to prevent clipping: effective gain is capped at `1 / peak`. - Volume slider preserves the gain ratio β€” `audio_set_volume` multiplies `base_volume Γ— replay_gain_linear`. #### Crossfade - **Crossfade between tracks** (0.5 – 12 s, configurable in Settings β†’ Playback). - Old sink is volume-ramped to zero in 30 steps while the new track starts playing; old sink stored in `fading_out_sink` so a subsequent skip cancels the fade-out immediately. - `audio_set_crossfade` Tauri command; synced to Rust on startup and on toggle. #### Gapless Preloading *(Experimental β€” Alpha)* - **Gapless playback**: when ≀ 30 s remain in the current track, the next track's audio is preloaded via `audio_preload` in the background. - `audio_play` checks the preload cache first β€” if there is a URL match the download is skipped entirely, eliminating the gap between tracks. - The old Sink is kept alive during the new track's download and decode phase; the Sink swap happens atomically after decoding is complete, fixing a subtle **start-of-track audio cut** that occurred regardless of gapless state. - ⚠️ **This feature is experimental and still in active development.** It may not work correctly in all scenarios. Enable it in Settings β†’ Playback at your own discretion. #### Settings β€” Tab Navigation - Settings reorganised into **5 horizontal tabs**: Playback, Library, Appearance, Server, About. - Each tab groups related settings with a matching icon. #### Artist Pages β€” "Also Featured On" - Artist detail pages now show an **"Also Featured On"** section listing albums where the artist appears as a guest or featured performer (but is not the primary album artist). - Implemented via `search3` filtered by `song.artistId`, excluding the artist's own albums. #### Download Folder Modal - When no download folder is configured and the user initiates a download (album or track), a **folder picker modal** now appears asking where to save. - Includes a "Remember this folder" checkbox that writes the choice to Settings. - Clear button added in Settings β†’ Server to reset the saved download folder. #### Changelog in Settings - The full **Changelog** is now readable inside the app under Settings β†’ About. - Rendered as collapsible version entries; the current version is expanded by default. - Inline Markdown (`**bold**`, `*italic*`, `` `code` ``) is rendered natively. #### EQ as Player Bar Popup - The Equalizer is now accessible directly from the **player bar** via a small EQ button, opening as a centred popup overlay β€” no need to navigate to Settings. ### Fixed - **Bundle identifier warning**: changed `identifier` from `dev.psysonic.app` to `dev.psysonic.player` to avoid the macOS `.app` extension conflict warned by Tauri. - **Version mismatch in releases**: `tauri.conf.json` version was out of sync with `package.json` and `Cargo.toml`, causing GitHub Actions to build release artefacts with the wrong version number. All four version sources (`package.json`, `Cargo.toml`, `tauri.conf.json`, `packages/aur/PKGBUILD`) are now kept in sync. ### Known Issues - **FLAC seeking**: jumping to a position in a FLAC file via the waveform seekbar currently does not work. Seeking in MP3, OGG, and other formats is unaffected. --- ## [1.5.0] - 2026-03-18 ### Added #### 10-Band Graphic Equalizer - Full **10-band graphic EQ** implemented entirely in the Rust audio engine using biquad peak filters (31 Hz – 16 kHz). Gains adjustable Β±12 dB per band. - EQ is processed in the audio pipeline via `EqSource` β€” a custom `rodio::Source` wrapper that applies cascaded biquad filters in real-time. - Filter coefficients update smoothly on every 1024-sample block without audio interruption. - **Seek support**: `EqSource::try_seek()` implemented β€” filter state is reset on seek to prevent clicks/artefacts. This also **fixes waveform seek**, which had silently broken when the EQ was introduced (rodio returned `SeekError::NotSupported` without the impl). - **10 built-in presets**: Flat, Bass Boost, Treble Boost, Rock, Pop, Jazz, Classical, Electronic, Vocal, Acoustic. - Custom presets: save, name, and delete your own presets. - EQ state persisted via `psysonic-eq` localStorage key (gains, enabled, active preset, custom presets). - New `audio_set_eq` Tauri command; settings synced to Rust on startup via `eqStore.syncToRust()`. #### Connection Indicator - **LED indicator** in the header bar (green = connected, red = disconnected, pulsing = checking). Sits between the search bar and the Now Playing dropdown. - Shows server name and LAN/WAN status next to the LED. - **Offline overlay**: when the server is unreachable, a full-content-area overlay appears with a retry button. - `useConnectionStatus` hook pings the active server periodically and exposes `status`, `isRetrying`, `retry`, `isLan`, and `serverName`. #### Now Playing Page - New `/now-playing` route and `NowPlayingPage` component β€” accessible from the sidebar. ### Fixed #### Waveform Seek (Player Bar) - **Drag out of canvas no longer breaks seeking**: `mousemove` and `mouseup` events are now registered on `window` (not the canvas element), so dragging fast across other elements still updates playback position correctly. - **Stale closure fix**: `trackId` and `seek` function are kept in refs so the window-level handlers always see the current values. ### Changed #### App Icon - New app icon (`public/logo-psysonic.png`) across all platforms β€” Login page, Sidebar, Settings About section, README header, and all generated Tauri platform icons (Windows ICO, macOS ICNS, Linux PNGs, Android, iOS). ## [1.4.5] - 2026-03-17 ### Changed #### Artist Pages β€” External Links - Last.fm and Wikipedia buttons now open in the **system browser** instead of an in-app window. The button label temporarily changes to "Opened in browser" / "Im Browser geΓΆffnet" for 2.5 seconds as visual confirmation. #### Queue Panel - **Release year** added to the now-playing meta box, shown below the album name (when available). - **Cover art enlarged** from 72 Γ— 72 px to 90 Γ— 90 px, aligned to the top of the meta block so it lines up with the song title. - **Default width increased** from 300 px to 340 px. ## [1.4.4] - 2026-03-17 ### Added #### AUR Package - Psysonic is now available on the **Arch User Repository** β€” Arch and CachyOS users can install via `yay -S psysonic` or `paru -S psysonic`. Builds from source using the system's own WebKitGTK, avoiding the EGL/Mesa compatibility issues that affected the AppImage on modern distros. ### Changed #### App Icon - New app icon across all platforms (Windows, macOS, Linux, Android, iOS). #### Linux Distribution - **AppImage removed**: The AppImage was fundamentally incompatible with non-Ubuntu distros (Arch, Fedora) due to bundled WebKitGTK conflicting with the system's Mesa/EGL. Linux users should use the `.deb` (Ubuntu/Debian), `.rpm` (Fedora/RHEL), or the new AUR package (Arch/CachyOS). ## [1.4.3] - 2026-03-16 ### Fixed #### Random Mix β€” Genre Mix - **Second "Play All" button removed**: The genre mix section had a redundant play button below the super-genre selector. The top-right button is now context-aware β€” it plays the genre mix when one is active, otherwise the regular mix. - **"Play All" disabled during genre mix loading**: The button now stays grayed out with a live progress counter (`n / 50`) until all songs are fully loaded. Clicking while the list was still building sent only the songs loaded so far. - **Over-fetching fixed**: Genre mix previously fetched up to 100+ songs and sliced to 50 at the end. Now the matched genre list is capped at 50 (randomly sampled when more match) so the total fetch stays close to 50 with no wasted server I/O. - **Regular mix cache-busting**: `getRandomSongs` requests now include a timestamp parameter, preventing browser/axios from returning a cached response and showing the same list on every remix. - **Display/state mismatch on remix**: Clicking "Mischen" now clears the current list immediately, ensuring the spinner is shown and the displayed songs always match what "Play All" would send. #### Queue Panel - **Hover highlight lost on right-click**: Queue items now retain their hover highlight while a context menu is open for them (`.context-active` CSS class). - **Song count and total duration**: The queue header now shows the number of tracks and total runtime below the title (e.g. `12 tracks Β· 47:32`). #### Context Menu - **"Favorite" option added for queue items**: Right-clicking a queue item now includes a "Favorite" option, consistent with the song context menu. ## [1.4.2] - 2026-03-16 ### Fixed #### Linux AppImage β€” Modern Distro Compatibility - **Build upgraded to Ubuntu 24.04**: The AppImage was previously built on Ubuntu 22.04 with WebKitGTK 2.36. On modern distros (CachyOS, Arch, etc.) with Mesa 25.x, `eglGetDisplay(EGL_DEFAULT_DISPLAY)` returns `EGL_BAD_PARAMETER` and aborts immediately because newer Mesa no longer accepts implicit platform detection. Building on Ubuntu 24.04 bundles WebKitGTK 2.44 which uses the correct `eglGetPlatformDisplay` API. - **`EGL_PLATFORM=x11` added to AppRun**: Additional safeguard that explicitly tells Mesa's EGL loader to use the X11 platform when the app is running under XWayland. #### Shell β€” Update Link - `shell:allow-open` capability now includes a URL scope (`https://**`), fixing the update toast link that silently did nothing in Tauri v2 without an explicit allow-list. ## [1.4.1] - 2026-03-16 ### Fixed #### Random Albums β€” Performance & Memory - **Auto-refresh removed**: The 30-second auto-cycle timer caused 10 React state updates/second (progress bar interval) and a burst of 30 concurrent image fetches on every tick, eventually making the whole app unresponsive. The page now loads once on mount; use the manual refresh button to get a new selection. - **Concurrent fetch limit**: Image fetches are now capped at 5 simultaneous network requests (was unlimited β€” 30 at once on every refresh). - **Object URL memory leak**: The in-memory image cache now caps at 150 entries and revokes old object URLs via `URL.revokeObjectURL()` when evicting. Previously, object URLs accumulated without bound across the entire session. - **Dangling state updates**: `useCachedUrl` now uses a cancellation flag β€” if a component unmounts while a fetch is in flight (e.g. during a grid refresh), the resolved URL is discarded instead of calling `setState` on an unmounted component. #### i18n - Page title "Neueste" on the New Releases page was hardcoded German. Now uses `t('sidebar.newReleases')`. ## [1.4.0] - 2026-03-16 ### Added #### Statistics Page β€” Upgraded - **Library overview**: Four stat cards at the top showing total Artists, Albums, Songs, and Genres β€” counts derived from the library in parallel. - **Recently Played**: Horizontal scroll row showing the last played albums with cover art. - **Most Played**: Ranked list of the most frequently played tracks. - **Highest Rated**: List of top-rated tracks by user star rating. - **Genre Chart**: Visual bar chart of the top genres by song and album count. #### Playlists Page β€” Redesigned - Replaced the card grid with a clean list layout. - **Sort buttons**: Sort by Name, Tracks, or Duration β€” click again to toggle ascending/descending. - **Filter input**: Live search across playlist names. - Play and delete buttons appear on row hover. #### Favorites β€” Songs Section Upgraded - Tracks now display in a full tracklist layout matching Album Detail: separate `#`, Title, Artist, and Duration columns with a header row. - Artist name is clickable and navigates to the artist page. - Right-click context menu on any track (Go to Album, Add to Queue, etc.). - **"Add all to queue"** button (`btn btn-surface`) next to the section title. #### Context Menu β€” Go to Album - New **Go to Album** option (`Disc3` icon) added for `song` and `queue-item` context menu types. - Only shown when the song has a known `albumId`. #### Queue Panel β€” Meta Box - Now shows: **Title** (no link) β†’ **Artist** (linked to artist page) β†’ **Album** (linked to album page). - Removed year display and the old titleβ†’album link. #### Random Mix β€” Hover Persistence - Track row stays highlighted while its context menu is open via `.context-active` CSS class. - Highlight is cleared automatically when the context menu closes. #### Artist Cards β€” Redesigned - `ArtistCardLocal` now matches `AlbumCard` exactly: no padding, full-width square cover via `aspect-ratio: 1`, name and meta below. - Uses `CachedImage` with `coverArtCacheKey` for proper IndexedDB caching. - Same `flex: 0 0 clamp(140px, 15vw, 180px)` sizing as album cards β€” artist cards are no longer oversized. ### Fixed #### Random Albums β€” Cover Loading & Manual Refresh - **Removed `renderKey`**: The album grid was fully remounted on every refresh, restarting all 30 IndexedDB image lookups from scratch. Grid is now stable β€” only data changes, images stay cached. - **`loadingRef` guard**: Prevents concurrent fetch calls if the auto-cycle timer fires during a manual refresh. - **Timer race condition**: Manual refresh now calls `clearTimers()` before `load()`, eliminating the race where the auto-cycle timer fired mid-load. #### Favorites β€” Artist Navigation - Arrow nav buttons in the Artists section now use the same CSS classes as the Albums section (`album-row-section`, `album-row-header`, `album-row-nav`) β€” consistent styling across both rows. ### Changed - **AlbumDetail** refactored into a thin orchestrator. Logic extracted into `AlbumHeader` (`src/components/AlbumHeader.tsx`) and `AlbumTrackList` (`src/components/AlbumTrackList.tsx`). - **German i18n**: "Queue" consistently translated as "Warteschlange" throughout β€” `queue.shuffle`, `favorites.enqueueAll`. ## [1.3.0] - 2026-03-15 ### Added #### Player Bar β€” Complete Redesign - **Waveform seekbar**: Replaces the classic thin slider. A canvas-based waveform with 500 deterministic bars (seeded by `trackId`) fills the full available width. Played portion renders as a blue β†’ mauve gradient with a soft glow; buffered range is slightly brighter; unplayed bars are dimmed to 28% opacity. Click or drag anywhere to seek. - **New layout**: Single flex row β€” `[Cover + Track Info] [Transport Controls] [Waveform + Times] [Volume]`. More breathing room for the waveform; controls feel lighter and better proportioned. - **Queue toggle relocated**: Moved from the bottom player bar to the top-right of the content header β€” consistent with the sidebar collapse button pattern. Uses `PanelRightClose` / `PanelRight` icons (same family as `PanelLeftClose` / `PanelLeft` in the sidebar). #### Ambient Stage β€” MilkDrop Visualizer - **Butterchurn integration**: Clicking the waveform icon (top-right of the fullscreen player) activates the MilkDrop visualizer powered by [butterchurn](https://github.com/jberg/butterchurn) + `butterchurn-presets`. - A hidden `