Added
Square corners — sharp-edged cards and covers
By @Psychotoxical, PR #1215
- New Square Corners toggle under Settings → Appearance → Visual Options → Display overrides the active theme to render cards and cover art with square, non-rounded corners. Covers album, playlist, artist and song cards, detail-page cover art, the Now Playing / Radio and fullscreen views, the cover lightbox, the queue cover, and the mini player. Off by default; buttons, inputs and dialogs keep the theme's corners.
Discord community banner
- A dismissible banner inviting you to join the Psysonic community on Discord appears after 20 hours of accumulated app use. Join opens the invite; dismiss it for the session, or choose Never show again to hide it permanently.
Bulgarian translation
By @akirichev, PR #1228
- Full Bulgarian (Български) UI translation — selectable from the language picker on the Settings and Login screens.
Artists browse — album vs track credit mode
- Toggle Album artists vs Track artists on the Artists page — album mode lists indexed album artists; track mode includes performers from the local artist index (featured/guest credits). Star filter works in both modes; the choice persists across app restarts like Show artist images.
- Letter bucket filter (
A–Z,#,OTHER) runs in local SQL instead of scanning catalog chunks client-side, so late-alphabet picks load promptly on large libraries.
CLI — relative volume and quieter scripting output
psysonic --player volume +5/volume -10adjust the current level by that many percent;volume 80still sets an absolute level (use-qbefore--playerwhen the delta is negative so it is not parsed as a flag).- CLI invocations no longer print WebKit/NVIDIA workaround notes on stderr; on Linux, remote
--playerforwarding runs before WebKit startup so helper processes exit with less noise.
Theme Store — per-theme changelogs and pinned updates
By @Psychotoxical, PR #1240
- Each theme card now has an expandable What's new with per-version release notes, so you can see what a theme update changed — including non-visual fixes. Provided by theme authors; themes without notes just don't show the section.
- Installed themes with an available update now appear at the top of the store list instead of wherever the sort placed them, so you don't have to hunt for them.
Multi-library filter — browse and search across selected libraries
- The sidebar library picker now supports multi-select with priority ordering: browse, search, genre and album/artist detail views aggregate across the chosen libraries and de-duplicate shared items by priority. Built for large libraries — scoped SQL uses the hot
library_idcolumn with covering indexes and FTS-first matching. - Identity matching that powers cross-library de-duplication now normalises names per shipped locale (folds German ß, Norwegian æ, French œ, Romanian ș/ț, and Cyrillic ё/й); CJK titles are matched as-is.
Theme contributors credited in Settings
By @Psychotoxical, PR #1248
- Settings → System → Contributors now lists community theme authors in a Themes sub-section alongside the App contributors, pulled from the theme store so it stays current as new themes are published.
- The theme card What's new now shows just the latest version's notes instead of the full version history.
Fullscreen player — Minimal and Immersive styles
By @Psychotoxical, PR #1249
- Settings → Appearance → Fullscreen player style lets you choose Minimal (the current view) or Immersive — the earlier fullscreen player, with the artist photo/backdrop, a cover-derived accent colour, and rail or Apple-style scrolling lyrics.
- In Immersive, Show artist photo and Photo dimming are configurable; Apple-style lyrics show the artist image as a dimmed full-screen backdrop.
Italian translation
By @daquino94, PR #1250
- Full Italian (Italiano) UI translation — selectable from the language picker on the Settings and Login screens.
Fullscreen player — Prism style
By @Psychotoxical, PR #1251
- A third Fullscreen player style, Prism — a full-bleed artist backdrop with a floating glass lyrics panel on the right and a single glass control bar at the bottom (transport, a centred now-playing pill with an integrated progress line, and utilities). The cover-derived accent colour drives the progress fill and the active lyric line, and upcoming lyric lines fade out with a progressive blur.
Lyrics — word-by-word highlighting straight from your server
By @Psychotoxical, PR #1265
- The Server lyrics source now highlights lyrics word by word, so karaoke sync no longer depends on the third-party YouLyPlus backend. Requires Navidrome 0.63 or newer and lyrics that carry word timing (TTML or Enhanced LRC); anything else keeps highlighting line by line.
- Settings → Lyrics → Lyrics Sources spells out those requirements, and the block now follows the standard settings sub-card layout.
Start minimized to tray
- New Start Minimized to Tray toggle under Settings → System → Behavior. When enabled, the next cold start keeps the main window hidden and Psysonic runs from the system tray until you show it from the tray icon.
- Requires Show Tray Icon (turning this on enables the tray automatically; hiding the tray clears the setting). The choice applies on the next launch only — toggling it in Settings does not hide the window immediately.
Navidrome public share links — open and play without logging in
- Paste or search a Navidrome public share URL (
/share/{id}) to preview the shared track list in a modal, then play the full queue with no server account — direct stream and cover URLs are resolved anonymously from the share page. - Share playback uses a dedicated scope so an idle server play-queue pull cannot replace the share queue while you are also logged into Navidrome. Share sessions are not restored after an app restart — the server play queue applies as usual.
Track lists — optional album cover thumbnails
- Browse and queue track rows can show the track's album cover (per-disc art when the album has distinct disc covers). Covers load through the standard cover cache pipeline — library resolve, viewport ensure, Rust resize to disk tiers — not a separate warm path.
- Settings → Appearance adds separate toggles for queue vs browse tracklists. Favorites, playlist, and album-detail track grids gain a flex-resize handle on the title column when covers are shown.
Changed
Frontend restructure — feature-folder architecture and hardening
By @Psychotoxical, with additional architecture by @cucadmuh, PR #1225
- Reorganised the frontend into a feature-folder architecture with a CI-enforced layering guard, added unit + behavior-scenario + boot-smoke test coverage, and introduced a compile-time frontend/backend IPC contract via tauri-specta. Internal only — no change to how the app looks or behaves.
Typed-IPC contract — completed the tauri-specta cutover
By @Psychotoxical, with additional architecture by @cucadmuh, PR #1230
- Completed the frontend/backend typed-IPC contract: the frontend now calls the generated tauri-specta command surface, with CI guards keeping the bindings fresh and every command registered in the handler. Internal only — no change to how the app looks or behaves.
Equalizer — per-device profiles follow the active system default
By @cucadmuh, PR #1274, suggested by @JustBuddy
- With Remember EQ per device enabled and System Default selected, the equalizer now keys profiles to the active OS default output and switches when that default changes externally (Windows sound settings, Stream Deck, etc.), instead of using one shared profile for all system-default outputs.
- On Linux/PipeWire, the active default is resolved from WirePlumber (
wpctl) first — including Hyprpanel, pavucontrol, andwpctl set-default— not cpal, which can keep a stale card name even after the default sink changes. When PipeWire has already moved the playback stream to the new default, the device watcher skips a redundant stream reopen (avoids a post-switch stutter).
Queue toolbar — Navidrome public share sessions
- While a Navidrome public share queue is active, Save Playlist is hidden in the queue toolbar (share tracks cannot be saved to the server). Load Playlist stays available.
- The queue Share button copies the original Navidrome
/share/{id}page URL instead of a Psysonic magic-string queue payload.
Fixed
Per-track covers when playing from a playlist
By @Psychotoxical, PR #1218, reported by The Cup Slammer on Discord
- Playing a song from a playlist could show the song's own cover art in Now Playing instead of its album cover, while playing the same song from its album page showed the album cover. Now Playing now consistently uses the album cover, matching album-page playback. Albums with genuine per-disc artwork are unaffected.
Playback — ReplayGain prefetch, gapless playbar sync, and library peak index
By @cucadmuh, reported by Asra on the Psysonic Discord, PR #1231
- ReplayGain applies when stream or queue metadata resolves late — index-first prefetch before bind, reactive sync when resolver tags land, and live refresh from the library index after sync when tags differ on the playing track.
- Gapless auto-advance no longer leaves the playbar on the previous track; missed
audio:track_switchedis reconciled from engine position with seek guards so backward seek is not treated as a gapless switch. - Local library index stores
replayGainPeak(migration 015) so anti-clipping peak is available on index-first paths without an extra network round-trip.
Connection — ignore spurious offline hint on desktop
By @cucadmuh, reported by mikmik on the Psysonic Discord, PR #1234
- Desktop builds no longer get stuck showing "offline" when WebKitGTK leaves
navigator.onLinestuck atfalsewhile the server is actually reachable — the app now confirms with a real server probe instead of trusting that hint, so browse and playback keep working. Web builds are unchanged. - Pending favorite/rating sync now flushes when the server actually becomes reachable again, rather than relying on a browser
onlineevent that may never fire on desktop.
Playlists — add more than ~341 tracks; faster large-playlist edits
- Adding tracks to a playlist no longer fails past ~341 songs — writes are sent to the server in batches instead of one oversized request, so playlists of any size build correctly.
- Adding and merging into large playlists is faster: playlist membership is cached in memory for de-duplication instead of re-fetching the whole playlist on every add.
Queue — rows no longer stuck showing "…"
- Queue rows that were far from the currently playing track (e.g. after starting a large playlist from the middle, or scrolling the queue) no longer stay stuck on a "…" placeholder — the queue now loads track details for whatever you scroll to, in the desktop queue panel, the mobile queue drawer, and the fullscreen "up next" overlay.
Artists browse — case-insensitive search for Cyrillic names
- Artist name search on the Artists page no longer depends on query letter case for Cyrillic (and other non-ASCII) names when the local library index is enabled — matching uses the indexed
name_sortkey with Unicode case folding instead of SQLiteLIKEon the display name alone.
Genres — full catalog on large libraries with All libraries selected
- The Genres page and album browse genre filter no longer miss genres on large libraries when All libraries is selected — both now use the indexed
track_genreSQL aggregate instead of sampling the first page of albums (regression from multi-library scope routing in PR #1241).
Offline browse — on-disk-only Artists, Albums, Tracks, and Genres
- When browsing offline, Artists, Albums, Tracks, and Genres now list only content with on-disk bytes — library pins, favorites-auto saves, and hot-cache playback — instead of the full server or local index catalog.
- Sidebar and shell gates react when hot-cache rows appear; browse pages reload after hot-cache growth and library sync without leaving the page.
- Album vs track artist credit mode, starred artists, genre filters, and Tracks discovery rails respect the on-disk scope; album artist grouping follows indexed
album_artistparity.
All Albums — year filter keyboard entry
- The year filter on All Albums no longer clamps on every keystroke while typing a four-digit year — drafts commit on blur, Enter, or outside click; incomplete input reverts to the last applied value. Wheel and spinner controls are unchanged.
Discord — "Server" cover art no longer exposes your login
By @Psychotoxical, PR #1246
- The Settings → Integrations → Discord → Cover art source → Server option sent your server's cover URL to Discord, which republishes external image links, so anyone viewing your Rich Presence could read your username and login token. That option has been removed — cover art now comes only from None (app icon) or Apple Music, neither of which carries your credentials. Any saved Server setting is switched to None. Reported by lavioso on Discord.
Album detail — favorite heart and album-level stars
By @cucadmuh, reported by HiveMind on the Psysonic Discord, PR #1247
- Starring an album on the detail page now fills the heart immediately and keeps it filled after reload or returning from Favorites — the local index stores album favorites in
album.starred_atinstead of inferring from track stars. - When only a track is starred, the album heart stays empty unless the album itself is in Favorites; unfavorite no longer requires a double click on the detail page.
- Album user rating on detail reconciles from the server in the background; multi-library browse and Favorites filters use album-level stars consistently.
Library — renamed artists no longer linger as ghosts after resync
By @cucadmuh, reported by Seraphim on the Psysonic Discord, PR #1253
- Renaming an artist on the server no longer leaves a stale entry in the local Artists list that opened to "Artist not found" — a sync now prunes artist rows the latest server listing no longer confirms that also have no remaining tracks. Cleanup runs on both full and delta syncs (only after a confirmed artist listing, so a transient empty response can't drop valid entries), plus a one-time pass at startup that clears ghosts already accumulated in existing libraries.
- Newly added or renamed entries now show up right after a resync instead of only after an app restart: the Artists and Albums pages refresh their cached catalog when a library sync finishes.
Library — album artist links no longer dead-end at "Artist not found"
By @cucadmuh, reported by tummydummy, PR #1254
- Clicking the artist beneath an album (most visibly in Random Albums) no longer shows "Artist not found" when the server's
getArtistdoesn't recognise that album-artist id — the artist page now falls back to the local library index, which shares the id the card was built from. Artist pages also stay reachable on a brief network hiccup when the library is indexed.
Library — album tiles no longer miss cover art in Random Albums
By @cucadmuh, reported by tummydummy, PR #1254
- Album tiles for rows that synced without a cover id (surfacing most in Random Albums) no longer show a blank cover while the detail page has one — local browse now falls back to the album's first track cover id, so tile and detail resolve the same artwork. The same fallback applies to the detail header's library cover resolution, so the two stay consistent instead of flickering between art and placeholder.
Library — multi-library dedup sidecar no longer accumulates dead identity keys
- The precomputed
library-cluster.dbidentity keys used for cross-library dedup are now pruned on rebuild when their track no longer exists (removed, or dropped when a server mints a fresh id on rename). Previously the rebuild only refreshed live tracks and never deleted stale rows, so the sidecar grew with library churn until it was recreated wholesale (server switch / restore / import). The rows were inert (reads only ever join live tracks), so dedup and browse results are unchanged — this just stops the sidecar from bloating.
Library — renamed album artist links now heal on resync
- When an artist is renamed on the server (minting a new artist id), the album's stored artist link no longer stays stuck on the old id and dead-ending at "Artist not found". Album metadata now follows the server's
getAlbumfor the artist reference, so a resync updates it instead of keeping the pre-rename id indefinitely. Complements the earlier ghost-row prune (#1253) and the local-index fallback (#1254), which did not clear the stale reference itself.
Sync — large play queues no longer revert after pausing
- Pausing a large queue behind a reverse proxy (e.g. Nginx) could snap the player back to an earlier track — the save was one long URL that hit the HTTP 414 limit, failed silently, and idle auto-pull restored the stale server queue.
- Servers advertising the OpenSubsonic
formPostextension (Navidrome) now save via POST; others retry once as POST on 414. A failed save no longer lets auto-pull overwrite playback — it resumes only after a successful save.
Lyrics — timing codes showing up in the text
By @Psychotoxical, PR #1266
- Tracks whose embedded lyrics use Enhanced LRC displayed the raw word timing codes (
<00:12.34>) inside each line. The codes are now read as word timing instead of printed, so those lyrics also highlight word by word.
Lyrics — synced lyrics in FLAC and Ogg files
By @Psychotoxical, PR #1267
- FLAC, Ogg Vorbis, Opus and Speex files that store their synced lyrics in the
SYNCEDLYRICStag showed no embedded lyrics at all. That tag is now read, and it takes priority over the plainLYRICStag as intended.
Servers — connecting to servers behind a header gate
- Adding a server that needs a custom HTTP header (Cloudflare Access, Pangolin service tokens) failed with "Connection failed" even though streaming and covers would have worked. Root cause: the connection test ran in the WebView, which sends a header-less CORS preflight the gate rejects before the real request. The test now runs natively for header-carrying servers, so the header rides on the request and the server connects.
- A failed "Add server" used to close the form and leave only a tiny status dot, so it was unclear what went wrong. The form now stays open on failure and shows the reason — the server's own message (e.g. "Wrong username or password"), an HTTP status like
HTTP 403 Forbidden, or a transport error — and empty server address / username are caught up front with a clear message. - Even after a gated server connected, browse and detail views that run in the WebView — Main stage, New Releases, Random Albums, Statistics, search, genres, and more — stayed empty, because those
axiosrequests still tripped the gate's CORS preflight. Every Subsonic REST call that would carry a gate header now runs natively (the same reqwest path streaming and covers use), so the whole app works behind a header gate, not just playback. - Playback itself and background prefetch still returned
403on a gated server: the audio/preload path attached the gate header only when its playback server id happened to match the header registry key, and silently sent nothing otherwise. Header lookup now falls back to matching the request URL against the server's own endpoints (the same fallback covers and Navidrome browse already used), so streaming, prefetch and artist-info fetches carry the header reliably. Endpoint matching only ever hits a configured gated server and still honours the apply to LAN/public rule, so non-gated servers are untouched. - Under the extra native-proxy traffic a gated server now generates, browse calls opened a brand-new connection pool per request, which starved connections and made fast endpoints time out. Proxied requests now reuse a pooled HTTP client, keeping keep-alive across the burst of browse calls.
- On app startup the per-server gate headers were registered with the native layer only after a successful reachability probe and bind — so if that probe was slow or the server looked briefly offline, the registry stayed empty and every native request (streaming, covers, prefetch, artist art) 403'd behind the gate even though the header was configured. Headers are now registered up front, before any probe or bind, so the native layer always has them.
- Covers that hit the gate during the brief window before headers were registered got a
403, which was treated as "cover missing" and written a 30-minute do-not-retry marker — so on a gated server most covers stayed blank long after the gate started answering. A gate-style403/401on cover art is now treated as a recoverable hiccup (retried) rather than a permanent miss, and reconnecting a gated server clears those stale markers and re-runs the cover fill so the artwork loads. - For a server with both a LAN and a public address, whichever answered first after launch stuck for the whole session: if the app started off the LAN it pinned to the public address and never switched back to LAN once you got home (the public address kept answering, so the LAN address was never re-checked). The reachability tick now re-checks the LAN address first with a single attempt, so returning to the LAN upgrades the connection back to local automatically, while staying remote costs just that one probe rather than the full retry wait. The LAN/public badge also refreshes immediately when you switch the active server, instead of staying on the previous server's classification until the next poll.
Windows — startup hang and audio output after per-device EQ (#1274)
- Windows release builds no longer freeze on the loading splash after the per-device EQ changes in #1274 — boot-time imports no longer pull lucide into the auth-store chunk (circular init left
createLucideIconundefined). - Restores the lightweight cpal default-output path on Windows so startup no longer blocks on full device enumeration.
- Audio output devices on Windows and macOS now use stable backend IDs (
Wasapi:…) with clearer labels; duplicate friendly names are disambiguated, device-change detection works again, and legacy pinned device / per-device EQ keys stored as plain names are matched after upgrade.
Windows — MSI bundle on dev and RC versions
- Windows
.msibuilds no longer fail on channel versions like1.50.0-dev— WiX requires a numeric fourth version field, so the bundler maps-dev/-rc.Nto numeric semver inbundle.windows.wix.versionwhile Settings → About still shows the real package version. - Release builds no longer warn that the album feature barrel defeats a lazy import in the new-albums easter egg (direct import of the export helper).
Internet Radio — equalizer presets now apply to radio playback
- Internet Radio playback stayed on HTML5 after v1.32, but EQ changes only reached the Rust engine used for library tracks — toggling EQ or switching presets had no effect on a live station. Radio now routes through a Web Audio 10-band graph on the same
<audio>element when EQ is enabled; preset and slider changes update filters in place without restarting the stream.