🙏 Special thanks to @zz5zz for his tireless quirk-spotting and bug reports on the Psysonic Discord — several of the polish fixes in this release landed directly off the back of his messages.
Under the Hood — Refactoring & Test Suite
By @cucadmuh + @Psychotoxical
Alongside the user-facing changes, this release closes out a large engineering effort across the codebase. None of it changes how Psysonic behaves — it changes how fast and how safely the next features can land.
- Backend → Cargo workspace — five focused Rust crates instead of one monolith. See Backend — Cargo workspace with 5 domain crates under Changed below.
- Frontend modularization — large components, stores, locales and CSS split into focused files. See Frontend — large modules split into focused files under Changed below.
- Automated test suite —
cargo testand Vitest with per-file coverage gates in CI on hot paths (playback, queue, auth, offline cache, API, core UI).
Foundational work: faster reviews, narrower diffs, and a safety net under the parts of the app that matter most.
Added
Discord — album cover art from your own server
- Discord Rich Presence can now show album artwork from your own server via the Subsonic
getAlbumInfo2endpoint (requires the server to be publicly reachable). - New cover-source picker under Discord Rich Presence settings: None (app icon only), Server, or Apple Music. Mutually exclusive.
- Fresh installs default to Server for opt-in-friendly cover art with no third-party data leak. Existing users keep their previous Apple-covers preference via migration.
Queue — preserve "Play Next" insertion order (toggle)
By @Psychotoxical, suggested by @Sayykii, PR #464
- New optional toggle in Settings → Audio → Playback ("Preserve Play Next order"). When on, multiple "Play Next" insertions queue up behind each other instead of the latest one bumping earlier picks down. Default off — existing behaviour unchanged.
- Side-benefit: single-song "Play Next" now goes through the unified
enqueueAtpath and gets undo + server-sync support that the album path already had.
Library — "favorites only" filter on Albums, Artists and Advanced Search
By @Psychotoxical, suggested by @lilgringo, PR #466
- New star-toggle button in the toolbars of Albums, Artists and Advanced Search that flips the visible list to favourites-only.
- Filter state is ephemeral per page (not persisted) so users don't come back to a half-empty library and wonder where their content went.
- Reads star state live from in-memory overrides — toggling a favourite from a context menu updates the visible list immediately, no refetch.
Search — artist photos in live and mobile results
- Live search and the mobile search overlay now show artist photos in the Artists section, using the same cover-art cache as albums, with a fallback icon when nothing is available.
- On mobile, artist rows use a round thumbnail next to square album art so the two types are easy to tell apart.
Artist page — group albums by release type
- Albums on the artist page can now be grouped into sections by their OpenSubsonic
releaseTypes(Album, EP, Single, Compilation, Live, Soundtrack, Remix). Section order is deterministic across languages, with unknown types appended at the end. - Falls back to the previous flat list when the server doesn't return
releaseTypesor all albums share the default Album type — no behaviour change for non-OpenSubsonic servers. - Section headers are localised in all 9 supported languages.
Library — Browse by Composer
By @Psychotoxical, suggested by mmourez (issue #465), PR #487
- New Composers library section listing every artist credited as composer on at least one track, with a detail page showing all works they hold in that role. Aimed at classical-music libraries where the recording artist is the orchestra and the composer tag carries Bach / Mozart / Chopin.
- Requires Navidrome 0.55+ (uses the native role-filter API — Subsonic
getArtistonly walks AlbumArtist relations and returns zero albums for composer-only credits). Older / pure-Subsonic servers see a one-line capability banner. Music-folder scope is honoured. - Composers are a first-class share entity —
psysonic2-share links and the right-click Share menu both copy acomposerlink. Sidebar entry is off by default (classical-music use case is a niche).
Song Info — absolute file path on Navidrome servers
By @Psychotoxical, suggested by volcs0, PR #504
- The Path row in the Song Info dialog now shows the absolute server-side filesystem path of a track on Navidrome servers — it was effectively empty before because Subsonic's
getSongnever returned a usable path. Non-Navidrome servers fall back to whatever the Subsonic response carried.
Lossless Albums — rail on Home + dedicated page + sidebar entry
By @Psychotoxical, PR #506
- New Lossless Albums browse mode: a rail under "Most Played" on Home and a dedicated infinite-scroll
/lossless-albumspage with full Albums-page header parity (selection mode + Enqueue / Add Offline / Download ZIPs). - Detection limits to containers that are always lossless (FLAC, WAV, AIFF, DSF/DFF, APE, WV, SHN, TTA) —
m4aandwmaare excluded because they can carry both lossless and lossy. Albums stream into the page progressively as they are found. - New sidebar entry Lossless (Gem icon), visible by default.
Settings — OpenDyslexic font option for dyslexic readers
By @Psychotoxical, PR #507
- New OpenDyslexic font option in the existing font picker — a dyslexia-friendly typeface with a heavier baseline and asymmetric
b/d,p/qglyphs that many dyslexic readers find easier to track than a typical sans. Continues the accessibility line started by the WCAG contrast audits and the colour-vision-deficiency themes. - Bundled locally (
@fontsource/opendyslexic, SIL OFL — no CDN dependency). Covers Latin, Latin-Extended and Cyrillic; Chinese falls back to the system font, called out via a new font-subtitle field on the picker.
Player Bar — album context menu on song title right-click
By @Psychotoxical, PR #512
- Right-clicking the track title in the player bar now opens the same album context menu that album cards expose — open, play next, enqueue, go to artist, favorite, rate, share, download, add to playlist.
- Mirrors the existing left-click on the title (which already navigates to the album) and is suppressed during radio playback and previews.
Orbit — in-app diagnostics popover with copyable event log
By @Psychotoxical, prompted by reports from nzxl + RavingGrob, PR #524
- New Activity-icon button in the Orbit session bar opens a diagnostics popover. Live mini-display (role, host vs. guest track, position, drift, state-age) plus a scrolling event log fed by a 200-entry ring buffer.
- Copy + Clear buttons. Copy drops formatted
[ISO] [scope] bodylines on the clipboard — paste straight into a bug report. Events are also bridged tofrontend_debug_logwhen Settings → Logging is on Debug. - Instrumentation covers every previously-silent decision point in the guest tick (
initial-sync,track-changefollowed / diverged,play-pause-flip) plus host state pushes, so the "stopped after the first song" symptom is now diagnosable from the buffer alone.
Queue Toolbar — customizable button order + per-button visibility
- Settings → Personalisation grows a new Queue Toolbar section. Drag-and-drop reorders the toolbar buttons; a per-button toggle hides individual entries; a Separator item can be placed anywhere to break the row into visual groups. A Reset button restores the default layout.
- Persistence via a new
queueToolbarStore(Zustand + localStorage), so the layout survives restarts. - Behaviour-preserving default:
[Shuffle] [Save] [Load] [Share] [Clear] | [Gapless] [Crossfade] [Infinite]— same buttons in the same order as before. - Auto-hides the toolbar when no real button is visible (a lone Separator no longer takes up space on its own).
- i18n coverage across all 9 locales.
Settings — Advanced Mode toggle + playlist page layout
By @kveld9 + @Psychotoxical, PR #556
- Advanced Mode. A new toggle in the Settings header reveals advanced sub-sections across all tabs — community-contributed options that don't necessarily reflect the design philosophy of the Psysonic maintainers, kept available but out of the way. Current advanced sub-sections all live under Personalisation: Artist page sections, Queue Toolbar, and the new Playlist page layout.
- Playlist page layout. New sub-section that hides individual elements on the playlist page: Add Songs, Import CSV, Download ZIP, Cache Offline, and the Suggestions rail at the bottom. All toggles on by default so existing playlists look unchanged.
- One-time migration: users who had previously customised any of the three sub-sections (or opened the per-tab Advanced group) get Advanced Mode auto-enabled on first launch — existing tweaks stay visible.
Romanian (ro) translation
By @MihaiCatalin120, PR #663
- Complete Romanian (
ro) locale for navigation, player, playlists, settings, help, and errors. - Psysonic now ships in nine UI languages: English, German, Spanish, French, Dutch, Norwegian Bokmål, Russian, Chinese (Simplified), and Romanian.
HTTP — gzip + brotli decompression for the Rust-side clients
By @Psychotoxical, PR #704
- Every HTTP client on the Rust side now advertises
Accept-Encodingand transparently decodes compressed responses. The JSON-heavy endpoints — Navidrome native/api, Bandsintown, Radio-Browser, Last.fm — were the gap; earlier curl measurements put the wire savings on those payloads at roughly 76–93 %. No behaviour change beyond smaller transfers.
Search — queue pasted share links from Live Search and mobile search
By @cucadmuh, inspired by @DanielWTE's PR #551, PR #716
- Pasting a
psysonic2-share link into Live Search or the mobile search overlay shows a dedicated row: track and queue links enqueue instead of replacing the queue like global paste; album, artist, and composer links preview on the share server without switching the active server until you confirm. - Queue shares offer Preview (scrollable track list) before Add to queue (search) or Play queue (global Ctrl+V). Shared content resolves against the matching saved server; bulk enqueue still respects Orbit guard rules.
Offline Library — show cached albums from all servers
- Offline Library lists cached albums from every saved server, not only the active one. Cover art loads from each album's home server; play and enqueue switch servers when needed.
- Sidebar, mobile More, disconnect navigation, and the offline banner treat any cached content as available offline. With multiple servers, cards show which server an album belongs to.
Settings → Personalisation — player bar layout
By @kveld9 + @Psychotoxical, based on PR #627, PR #721
- New sub-section that hides individual controls in the player bar: Star rating, Favorite (heart), Last.fm love, Equalizer, Mini player. Last.fm love still only renders when a Last.fm session exists; the overflow row collapses when both Equalizer and Mini player are hidden.
- Lives under the Advanced group in Personalisation (only visible when the global Advanced Mode toggle is on). All toggles on by default; persisted across restarts.
Queue panel — persist header duration mode
By @kveld9 + @Psychotoxical, based on PR #625, PR #724
- The queue header chip (total duration / remaining time / ETA finish clock) now persists across app restarts.
Tracklists — Plays / Last played / BPM columns + Song Info rows
By @Psychotoxical, suggested by jbigginswyl (#516), PR #730
- New opt-in columns Plays, Last played, and BPM on the Album / Playlist / Favorites tracklists, plus matching rows in the Song Info modal. Pulls Navidrome's existing
playCount/played/bpmfrom the Subsonic response — no extra API calls. The playlist tracklist also gets the Genre column for parity with Album + Favorites. - BPM cells render
—when Navidrome returns 0 (untagged file); Plays / Last played render—only when truly absent.
Mainstage hero — prev / next arrow buttons
By @Psychotoxical, thanks to zunoz for the report on the Psysonic Discord, PR #735
- The featured-album strip on Mainstage now has Previous / Next chevron buttons on each edge of the hero. The existing 8 px dot indicators were a small target, and a near-miss often opened the underlying album instead of switching slides; the new 44 px buttons give a comfortable hit area on both desktop and touch.
- The dot indicators are kept as decorative — no click handler, no hover — so a missed click no longer navigates to the album. The rest of the hero stays click-through.
Settings — Clock Format setting (Auto / 24h / 12h)
By @Psychotoxical, thanks to zunoz for the report on the Psysonic Discord, PR #742
- Settings → System → App Behavior now exposes a tri-state Clock Format select: Auto (default — keeps existing locale-driven behaviour, so first launch after the update is a no-op for everyone), 24h, and 12h. Affects the Queue side panel's ETA label and the sleep-timer preview, which previously followed the OS locale with no in-app override.
Album page — OpenSubsonic disc subtitles after the CD heading
By @Psychotoxical, thanks to zunoz for the report on the Psysonic Discord, PR #753
- Multi-disc albums in OpenSubsonic / Navidrome can carry a per-disc subtitle (e.g. "Sessions" on CD 3 of a deluxe edition). The album tracklist previously dropped it and only showed CD N, so adjacent discs of a reissue read the same in the header. The separator now renders CD N — Subtitle in both desktop and mobile lists.
Home — "Because you listened" recommendation rail
By @Psychotoxical, PRs #489, #493
- New Home rail that surfaces albums similar to one of your favourite artists — "Because you listened to …" recommendations.
- Anchor pool round-robin merges Most Played, Recently Played and Favorites (deduped per artist), so each visit lands on a different listening mode instead of walking only the top-played list. Per-server rotation; renders on fresh servers with starred or recently-played items but no frequent-play history. Zero extra API calls — all three seed lists are already in the Home initial fetch.
- Responsive layout: 3 cards in one row on 2K-class screens, 2 at 1080p, all 3 stacked vertically on narrow / mobile widths. Toggleable in the Home customizer; respects the existing perf flags.
Playlists — virtualized tracklist for large playlists
- Opening a very large playlist (10 000+ tracks) no longer mounts every row into the DOM. The playlist tracklist is windowed with
@tanstack/react-virtualon the shared app scroll viewport — the same convention as Artists, Composers, and the library card grids. - Row rendering moved into a memoized
PlaylistRowwith a stable callback bundle so virtualizer scroll updates do not re-render the full list. - Drag-and-drop reordering is preserved: drop-indicator overlay and edge auto-scroll during drags.
Changed
Build — lazy-loaded routes and Vite chunk warnings
- Heavier app routes are lazy-loaded so the initial JS bundle stays smaller.
- Production builds again warn on oversized Vite chunks (default chunk size limit restored).
Dependencies — npm / Cargo refresh and rodio 0.22
- Frontend and Tauri/Rust dependencies bumped across the workspace; playback stack migrated to rodio 0.22.
UI — cover cache, mainstage rails, and smoother virtual lists
- Cover art loads faster while scrolling: network fetches share a small pool, disk cache is not blocked by downloads, and storage eviction is debounced during rapid scrolling. Mainstage and home rails window artwork more generously and dedupe duplicate API ids so covers stay visible while scrubbing sideways.
- Albums, Artists (list mode), and Tracks virtual lists scale overscan to about one screen of extra rows instead of a tiny fixed cushion. Assorted scroll and layout polish on artist detail, playlists, most played, live search, and player surfaces.
Covers / image cache — parallel fetch + downscale, registry guard, search slot hints
- When a requested thumb size is missing on disk but another size of the same cover is cached, remote fetch and client downscale run in parallel and the first good result wins (the loser aborts).
- Artist thumbnails in search get higher fetch priority than album thumbs when the pool is busy; cover prefetch starts a bit earlier ahead of the scroll viewport.
Settings — adding a server no longer switches to it
By @Psychotoxical, PR #475
- Adding a new server from Settings → Servers no longer switches to it — the entry appears in the picker but the current active server stays active, so playback, queue and library view are no longer interrupted. The login screen at
/loginis unchanged: signing in there still selects the chosen server.
Most Played — quick actions, real context menu, prominent plays badge
By @Psychotoxical, suggested by nzxl, PR #482
- Always-visible Play and Enqueue quick-action buttons on each album row. Right-click on a row now opens the standard album context menu (Play / Add to queue / Play next / Add to playlist / Go to artist); right-click on a Top Artists card opens the artist context menu.
- The play count moved from a small right-aligned column to a localized pill right next to the album title (
11 playsin EN,11× gespieltin DE), since the play count is the central datum on this page.
Multi-select — Shift+Click range selection on grid pages
By @Psychotoxical, PR #484
- In multi-select mode on Albums, Random Albums, New Releases and Playlists, holding Shift while clicking a second card now selects every item between the anchor (last clicked) and the click target — the standard OS-level pattern. Range expansion follows the user-visible order, so filters and sort affect what gets included.
- Plain click still toggles a single item and moves the anchor to it; behaviour without Shift is unchanged.
Help — full rewrite with live search and 10 cleanly-themed sections
By @Psychotoxical, PR #485
- Help page rebuilt from scratch: 45 focused entries across 10 themed sections. Dropped entries the UI itself answers, consolidated natural groupings, and added entries for features that didn't exist when the original Q/A list was written (Orbit, Magic Strings, LUFS, Mini Player, Smart Playlists, Multi-select, etc.).
- New live in-page search: case-insensitive substring across every Q+A; sections without hits collapse out, matches auto-expand so the answer is visible without clicking.
Community themes — redesign pass
- Removed five themes that overlapped or felt strenuous on the eyes: Amber Night, Ice Blue, Monochrome, Phosphor Green, Rose Dark.
- Added eight new dark themes covering the colour families people most commonly ask for: Obsidian Black, Carbon Grey, Volcanic Dark, Forest Green, Violet Haze, Copper Oxide, Sakura Night, Obsidian Gold.
- Light polish on the existing AMOLED Black Pure surface variables so card surfaces no longer collapse onto a pure-black background that read as a single flat slab.
Settings — collapse-by-default cleanup, font picker without dropdown, OpenDyslexic at top
By @Psychotoxical, PR #508
- Every Settings sub-section now boots collapsed — each tab no longer feels like a wall of controls before you've looked for something specific. ThemePicker also no longer auto-expands the group containing the active theme (the blue dot in the group header already surfaces which group holds it).
- Font picker lost its inner dropdown button — opening the Font sub-section now reveals the full font list directly; one click sets the font. OpenDyslexic moves to the top so dyslexic readers don't scroll past 14 sans-serifs to find their option.
Settings — language picker uses endonyms
By @Psychotoxical, suggested by cucadmuh, PR #514
- The Settings language picker now shows each language written in itself —
English,Deutsch,Español,Français,Nederlands,Norsk,Русский,中文,Română— same nine labels in every locale instead of translating each name into the current UI language. A native speaker can recognise their own language regardless of which UI language is active; same convention used by most OS-level language pickers.
Backend — Cargo workspace with 5 domain crates (Rust refactor)
By @cucadmuh + @Psychotoxical, PR #532
- Rust backend split from one crate into a Cargo workspace of five domain crates — audio, analysis, sync/offline, integrations, and core; the top crate keeps only Tauri shell wiring. No user-visible behaviour change — command surface and smoke tests match the pre-refactor tree.
- Foundation for narrower diffs per domain (Orbit and waveform work in this release were the first consumers).
Covers — no flash of previous track artwork on skip
- Player bar, queue header, and Now Playing covers no longer flash the previous track's artwork for a frame when skipping (hook and image component now reset together on track change).
Library card grids — virtualization + configurable column cap
- Library card grids (albums, playlists, composers, genre and label pages, offline library, artists grid, and similar rails) share one virtualised layout with a user cap on columns (Settings → Appearance → Library card grids, 4–12, default 6) for smoother scrolling on large libraries.
Hot cache — promote completed ranged streams larger than 64 MiB
- Fully buffered HTTP streams larger than the in-RAM promote cap (long M4A / ALAC albums included) spill to disk first, then move into hot cache on promote instead of being skipped. Stale spill files are cleaned on startup.
Playback — stream buffering indicator on cover art
- While an HTTP stream is still opening, cover art in the player bar and queue is greyscaled with a clock overlay and the seekbar stays at 0 until playback actually starts.
Frontend — large modules split into focused files (React/TypeScript refactor)
By @cucadmuh + @Psychotoxical
- Frontend counterpart to the backend split: largest page components, stores, and stylesheets broken into focused files; duplicated helpers consolidated; i18n and CSS split per namespace. No user-visible behaviour change — moves verified by TypeScript, Vitest, and production builds, with characterization tests added along the way.
Removed
Settings — Animations 3-state setting under Seekbar Style
By @Psychotoxical, PR #495
- The Animations 3-state setting (Full / Reduced / Static) under Settings → Appearance → Seekbar Style is gone — the newer perf-flag system and per-feature performance work cover the expensive animation paths more directly (marquee toggle in the Sidebar, global animations via
data-perf-disable-animations, seekbar via per-feature toggles). - Anyone who had
'reduced'or'static'selected silently lands on the normal animation path on first launch — the persist layer strips the obsolete field, no user-facing prompt.
Fixed
Hot cache, HTTP streaming replay, and queue source indicator
- Fully buffered HTTP downloads are kept in memory or hot disk when the queue ends, so replaying the same track can skip a full re-download when hot cache is on.
- Replay and resume wait for hot-cache promotion before the next play when the engine has already ended, so playback can switch to the local URL.
- Format detection for ranged streams uses URL, response headers, and song metadata before probing; generic
video/mp4Content-Type is no longer mistaken for audio. - Queue panel source icons (stream / hot cache / offline) update on resume, undo, and gapless track switches — not only on explicit play. Analysis cache skips redundant waveform work when data already exists.
Sidebar — New Releases read state under storage cap
- When the persisted "seen" New Releases list hits its 500-id cap, newly read ids are merged at the front so unread badges stay accurate.
Windows — tray double-click
- Double-click the tray icon opens or focuses the main window without opening the context menu.
Playback stability — preview seekbar, sleep/wake recovery, and card-hover jitter
- Preview seekbar no longer creeps forward while preview playback is paused, and no longer jumps when preview ends.
- After sleep/wake, Windows and Linux reopen the audio output and recover playback; the watchdog only arms after a long poll gap so normal playback is not disturbed.
- Album/artist cards no longer lift on hover (removed pointer-edge jitter on some Linux setups); artwork zoom is unchanged.
Analysis queue control — prune stale backfill jobs and cap warmup window
- Stale loudness/waveform backfill jobs are dropped when tracks leave the queue; warmup is capped to the current track plus the next five so bulk queue updates do not schedule endless analysis work.
Sidebar — Playlists icon and hover hitbox in collapsed mode
By @Psychotoxical, PR #481
- The Playlists icon in the collapsed sidebar was off-centre and had a wider hover background than every other item. Collapsed mode now reuses the standard nav-link path — same hitbox, same alignment as Artists, Albums, Favorites, etc.
Tracklist — drop now-playing pulse + EQ-bar animations
By @Psychotoxical, PR #488
- The currently-playing track in any tracklist (AlbumDetail, ArtistDetail, PlaylistDetail, Favorites, RandomMix) ran an opacity pulse on the entire row plus three EQ-bar transforms — both compositor properties, but on WebKitGTK without compositing (Linux + NVIDIA proprietary) every animated row fell back to a full software repaint per frame. AlbumDetail held the WebProcess at ~80 % CPU for the duration of playback.
.track-row.activekeeps the accent-tinted background but no longer pulses. The "now playing" indicator is now a singleAudioLinesicon — one SVG per active row instead of three animated spans.
Tray — broken navigation after restoring via desktop / start-menu shortcut
By @Psychotoxical, reported by netherguy4, PR #501
- When the main window was closed to the tray and then re-opened via the desktop / start-menu shortcut (instead of the tray icon), the window came back but the next navigation rendered a blank page. Restoring via the tray icon worked correctly. Root cause: the tray-close path pauses CSS animations and only the tray-icon restore path resumed them — the single-instance plugin's restore path was missing the resume step, leaving fade-in route wrappers frozen at
opacity: 0. Both restore paths are now consistent.
Track preview — volume slider ignored during preview
By @Psychotoxical, reported by netherguy4, PR #502
- The Rust preview sink had its volume set once at preview start and was never updated afterwards —
audio_set_volumeonly ramped the main sink, so slider drags during preview had no audible effect on the preview level. The preview sink now stays in lock-step with the slider while a preview is in flight.
Radio — queue navigation, dedup, and similar-first variety
By @Psychotoxical, reported by netherguy4, PR #503
- Queue navigation through duplicates — reaching a track's second occurrence used to snap the highlight back to the earlier slot and the next auto-advance played the wrong follow-up;
next()/previous()/ repeat-one / queue-row click now pass an explicit target index instead of resolving by id. - Radio dedup across
enqueueRadio, thenext()top-up, and intra-batch overlap (top + similar) is now closed by a radio-session-scoped seen-set, reset on artist change andclearQueue. - Variety: starting Radio no longer queues five top tracks of the seed artist before any similar-artist material plays — the seed path and both top-up paths lead with similar songs and only fall back to top tracks when similar comes back empty.
Security — Tauri patch for IPC origin-confusion (GHSA-7gmj-67g7-phm9)
By @Psychotoxical, PR #509
- Bumped Tauri 2.11.0 → 2.11.1 to pick up the upstream patch for GHSA-7gmj-67g7-phm9 — an origin-confusion bug that could let a remote-origin page invoke local-only IPC commands (severity medium). Psysonic exposes file-system and credential-bearing IPC, so closing the gate is worth the bump.
Home — Because-you-listened rail compact in narrow layouts
By @Psychotoxical, PR #520
- When the rail container drops below the 2-card threshold (≈ 696 px — sidebar + queue both open, etc.), the home Because-you-listened section now switches to the standard
AlbumRowlayout instead of stretching the hero-style cards to full width. - Wide layouts (≥ 696 px) keep the existing 3-up hero cards with the "Similar to X" pill, album metadata, and release-type pills — full-screen view is unchanged.
Context menu — render above the floating player bar
By @Psychotoxical, reported by Prymz, PR #522
- Right-clicking a track near the bottom of the screen with the floating bar enabled used to cut off the menu (issue #521) — an inline
zIndex: 999on the menu wrapper overrode the stylesheet'sz-index: 10000and sat below the floating bar at1000. The override is removed so the stylesheet rule wins; submenus follow.
Orbit — guest playback fixes
By @Psychotoxical, reported by nzxl + RavingGrob, PR #525
- Local queue-extension paths are now suppressed for the entire Orbit session lifecycle (radio top-up, infinite-queue top-up, queue-exhaustion fallback, proactive "≤ 2 auto-tracks ahead" topper). Without the lockout, joining could pop a "Add 5 tracks to the Orbit queue?" prompt and the local queue silently drifted off the host's playlist.
- Natural track-end no longer reads as "guest manually paused" — the divergence check now distinguishes the two via
currentTime(resets to 0 onaudio:ended, mid-track on real pause), so the guest no longer sits silent on host-driven track changes that arrive in the 0–2.5 s gap after the guest's own track has ended. - Initial-sync and Catch Up wait for the audio engine to report playing before seeking (up to 5 s on initial-sync, 4 s on Catch Up). The previous fire-and-forget seek silently no-oped against a not-yet-ready engine — guest played from 0:00 while believing they were synced.
- Catch Up button no longer flickers and matches the 26 px height of its neighbours so the bar's vertical layout stays stable. Visibility uses two-stage hysteresis (show after drift > 3 s for 3 s, hide only after drift < 1 s for 1 s, PR #527).
- Double-clicking the inline play button on a track row now suggests/enqueues to the host's queue, matching the row's existing double-click behaviour.
- Track preview is hidden + blocked during an Orbit session — preview runs through the same Rust audio engine as shared playback, so starting one as a guest would clobber the host's track.
- Audio reliably starts on join even after a slow cold-start: the engine-state shortcut is gated on actually matching the host's expected state, and a recovery check resets the anchor whenever the engine is paused while the host is still playing (PR #526).
- Initial-sync seek visually sticks on join — the post-
playTrackpoll now waits forcurrentTime > 0.1before applying the seek, so the waveform no longer snaps back to 0:00 (PR #528). - Host single-track plays no longer wipe the Orbit queue — a
playTrack(track, [track])call (e.g. "Play this album" on a single-track album) slipped past the orbit bulk-guard. Now intercepted: appends + jumps instead of replacing (PR #529). - Host pause / resume reaches guests immediately — the host now also pushes state on every
isPlayingflip, in addition to the 2.5 s timer. Previously a pause could take up to ~5 s to land (PR #537, reported by xrexy on Discord). - Guest seekbar is read-only inside an Orbit session — drag / click / wheel / hover all disabled with a
not-allowedcursor. Previously a guest seek would jump the local player and either snap back or push the guest into a diverged state (PR #537, reported by xrexy on Discord).
Offline downloads — the cancel button works again + the sidebar toast keeps its size
By @Psychotoxical, PR #694
- The ✕ on the sidebar download toast now actually cancels the download. Previously it only dropped not-yet-started tracks between batches of 8, so for albums of ≤ 8 tracks the click did nothing and in-flight transfers ran to completion. Cancellation now reaches the Rust side and aborts in-progress transfers mid-file (their
.partfiles are cleaned up). Tracks that already finished before the cancel are kept. - The download progress toast no longer gets squished when the main window is small — the label ellipsis-truncates on a narrow sidebar instead of overflowing.
Player UI — broken album-art icon when switching tracks
- Fixes #606: the player bar cover (and other cached-image surfaces) no longer flashes the broken-image placeholder for a split second when skipping tracks.
Album & player — split OpenSubsonic album credits and performers
- Album pages show every album artist as separate links when the server sends OpenSubsonic credits (fixes #552).
- Player bar, mobile now playing, and mini player show per-performer artist links on multi-artist tracks, matching the album tracklist.
Search — hide duplicate artist hits with zero albums
By @cucadmuh, thanks to zunoz for the report on the Psysonic Discord, PR #697
- Live search, mobile search, advanced search, and similarity fallbacks now hide duplicate artist rows with zero albums (Subsonic indexing noise). Artists with no album-count field are unchanged for legacy servers.
Internet Radio — Add / Edit station modal no longer clipped on empty library
By @cucadmuh, thanks to voidboywannabe for the report on the Psysonic Discord, PR #699
- Add Station / Edit on Internet Radio mount their modal at document body level (same as Search Directory), so the dialog is no longer clipped when the station list is empty.
Settings — contributors list sorted chronologically
By @Psychotoxical, PR #700
- The Settings → System → Contributors list rendered in raw insertion order, so the original maintainer (since v1.0.0) showed up last and the hand-maintained ordering drifted as new entries were appended. It is now sorted on render — ascending by the app version a contributor first appeared in, tie-broken by their first-contribution PR number — so it stays correct no matter where new entries land in the source list.
Now Playing — stable list keys on dashboard cards
- Now Playing dashboard lists (similar artists, in-player album tracks, top songs) no longer reuse the same React key when the server sends duplicate ids — dev warnings gone; playback unchanged.
Playback — track no longer clipped at the end with gapless and crossfade off
By @Psychotoxical, PR #708
- With gapless and crossfade both disabled, the last up to ~1 second of every track was cut off — the progress task ended playback on the Subsonic duration hint (floored to whole seconds) while the decoded audio almost always runs slightly longer. It now ends on the sample-accurate source-exhaustion signal that gapless already relies on. No change to gapless or crossfade behaviour.
Artists — infinite scroll after first page
- Artists infinite scroll loaded only the first page: the bottom sentinel appeared after the first fetch, but the scroll observer never attached because its subscription missed that timing. Scrolling now loads further pages reliably.
Statistics / playlists — duration totals rounded to the nearest minute again
By @Psychotoxical, PR #710
- Aggregate duration labels (album and playlist totals, total playtime in Statistics) could read up to ~59 s short and round the wrong way at the hour boundary — a 59:30 total showed "59 m" instead of "1 h 0 m". A
formathelper consolidation had switched the shared formatter from rounding to truncating; the round-to-nearest-minute behaviour is restored.
Mixes — rating filter and Lucky Mix queue fill
- Settings → Ratings → filter by rating now applies consistently to Lucky Mix, Random Mix, Instant Mix, infinite-queue top-ups, and after you change a star (stale rating cache and misleading song-level refs fixed).
- Lucky Mix toast reports the real queue length and keeps filling until the target size (up to 50) is actually enqueued.
Multi-server — queue playback stays on the source server when browsing another library
- With a queue playing on server A, browsing server B no longer breaks streams, scrobble, cover art, or seek — playback APIs follow the queue server.
- Player and Now Playing covers and metadata load from the queue server when it differs from the browsed one; artist/album links and queue actions switch to that server before navigating.
- Opening Now Playing (sidebar, mobile route, or queue info panel) switches to the queue server before metadata loads. Scrobble, now-playing report, and saved queue state follow the queue server as well; enqueue and play-next from another browsed server show a toast instead of mixing libraries.
UI — selectstart blocker no longer throws on Text node targets
- Selecting copyable text on Now Playing no longer crashes when the selection starts on a bare text node (the global select-start handler now resolves text nodes to their parent element).
UI — consistent Orbit / Server / Live header dropdown styling
By @Psychotoxical + @cucadmuh, PR #725
- The three header dropdowns (Orbit launch, Server picker, Live listeners) each had their own container styling. Live in particular used a glass / backdrop-filter utility that read poorly on many themes. All three now share the
.nav-library-dropdown-panelcontainer — same background, border, shadow and radius via the existing semantic tokens. Item layouts per dropdown stay case-specific.
Queue — Lucky Mix coalesced into one Ctrl+Z / Cmd+Z undo step
- Lucky Mix is now one undo step — Ctrl+Z / Cmd+Z restores the full queue from before the mix instead of stepping through every enqueue.
Queue panel Info — artist image now follows the current track
By @Psychotoxical, PR #732
- The Info tab paired the previous track's artist image URL with the new track's cache key for one frame on each switch —
CachedImage's IndexedDB then persisted that mismatched blob, so every subsequent track stayed stuck on the previous artist's image. Source and cache key now always come from the same track.
Album header — Artist Bio button hidden on Various-Artists compilations
By @Psychotoxical, PR #733
- The Album header showed an Artist Bio button on every album, but when the album-artist label is "Various Artists", "Various", "VA" or a language equivalent there is no single artist to fetch a bio for and the button opened an empty modal. Both the mobile icon and the desktop button are now hidden when the album-artist label matches that compilation heuristic.
Album header — Artist Biography modal stays in viewport and scrolls internally
By @Psychotoxical, thanks to zunoz for the report on the Psysonic Discord, PR #734
- The Artist Biography modal lived under the album page tree, where an ancestor broke
position: fixedon the overlay — opening a long bio scrolled the whole page instead of staying pinned, and the modal itself stretched past the visible area. It now portals todocument.bodyand scrolls internally, with the title + close button pinned.
Playback — M4A / MP4 streaming (moov-at-end) and seekbar during buffer
- Server-streamed M4A/MP4 with moov at end of file (common iTunes/Navidrome exports) start audibly sooner: tail prefetch fetches metadata while the body still downloads.
- Symphonia ISO-BMFF demuxer patch scans the file tail for moov on large atoms instead of failing probe with "end of stream".
- Seekbar and elapsed time stay at 0 until audio actually starts (with cover buffering state — see Changed above).
Artist info — image-mismatch fix extended; square Queue Info hero; ArtistDetail glow removed
By @Psychotoxical, PR #739
- The cache-mismatch shape fixed in PR #732 for the Queue Info panel was latent in the About the Artist card on NowPlaying as well. Fixed at the source — every consumer of
useNowPlayingFetchers/useArtistDetailDatais now safe by construction. ArtistDetail's inline bio block is now the sharedArtistCardso there is a single rendering path. - The artist hero in Queue Info was 16:10 with
object-fit: cover, so portrait photos always lost top and bottom equally — perceived as cropped even on roughly square sources. Now 1:1, symmetric crop. - The ArtistDetail avatar no longer paints a 36 px accent-coloured
boxShadowring around the photo.
Share Top Albums — full-resolution preview, Square preview fits the modal
By @Psychotoxical, thanks to zunoz for the report on the Psysonic Discord, PR #740
- The Square preview was clipped at the bottom — the preview frame only capped height, so the 1:1 canvas overflowed and the last grid row was hidden. Both dimensions are now capped per format, so the preview always fits without clipping.
- The preview also looked blurry because the canvas was rendered at 540 px and cover thumbs at only 256 px. The preview now renders at the full export width (1080) and decodes covers at the export tile size (600), so text is crisp and album thumbnails downsample cleanly.
Home — Mainstage row title matches the sidebar and page label
By @Psychotoxical, thanks to zunoz for the report on the Psysonic Discord, PR #741
- The Mainstage row whose title chevron jumps to
/new-releaseswas labelled Recently Added while the sidebar entry and the page itself read New Releases — three different names for the same destination. All three now read New Releases.
UI — consistency fixes across badges, action buttons, hero and tracks header
By @Psychotoxical, thanks to zunoz for the report on the Psysonic Discord, PR #745
- Unified the corner radius on badges, pills and non-player buttons; Player Bar, Fullscreen and Mini Player keep their circular family identity. Secondary action rows on Artist, Album, Tracks, Favorites and Most Played all share the same
btn-surfacetreatment so the same affordance reads the same per page and per theme. - Hero pills stay visible against light-toned cover art (opaque fill), and the pagination dots are readable on every backdrop (brighter inactive dot with a dark outline, accent-coloured active dot).
- Composers grid no longer reserves ~200 px per virtual row for ~78 px text-only tiles. The Tracks "browse all" header now lives inside the scroll container so columns line up with the rows under wider fonts like OpenDyslexic, and the header stays pinned while scrolling.
Favorites — artist link no longer triggers playback, bulk selection no longer shifts the rows
By @Psychotoxical, thanks to zunoz for the report on the Psysonic Discord, PR #746
- Clicking the artist in the Favorites songs table opened the artist page and started the song — the cell was missing the click guard the album cell already had. Now matches every other tracklist in the app.
- Selecting a song no longer pushes the column header and every row down by one line. The "X selected / Add to playlist / Clear" cluster moved out of the full-width bar into the existing action-buttons row (right-aligned), matching the album toolbar, so the next item stays under the same cursor position.
Equalizer — frequency-response curve no longer disappears on re-expand
By @Psychotoxical, thanks to zunoz for the report on the Psysonic Discord, PR #747
- Collapsing and re-expanding Settings → Audio → Equalizer sometimes left the curve area blank —
ResizeObserverdoesn't reliably fire for thedisplay: none → blocktransition the surrounding<details>triggers. Atogglelistener now redraws explicitly on open.
Library — empty-state on Mainstage, Albums, New Releases and Random Albums
By @Psychotoxical, thanks to zunoz for the report on the Psysonic Discord, PR #750
- Selecting an empty library no longer leaves Mainstage, Albums, New Releases and Random Albums as a fully blank canvas — a shared "Your library is empty." message is shown in place of the empty rails / grids. Pages that already had a dedicated empty-state keep their per-page wording. On Albums and New Releases, an active filter still shows the regular filtered-results behaviour rather than the library-empty message.
Player — persisted queue capped to ±250-track window (QuotaExceededError fix)
- Playing or shuffling a large playlist (10 000+ tracks) serialised the entire queue to
localStorageon every persistedset, triggering aQuotaExceededErrorstorm that killed playback and stalled the main thread. Controlled test on a 10 509-track playlist: 9 quota errors before, 0 after. partializenow persists only a ±250-track window around the current position (≤ 501 tracks), remappingqueueIndexinto the slice. The authoritative full queue is recovered from the server viagetPlayQueueon startup — no queue data is lost.
M4A playback — probe failures and distorted audio on moov-at-end files
- M4A from hot cache or local replay no longer fails probe or plays distorted — the demuxer patch passed wrong byte lengths after seek.
- Moov-at-end streams wait for tail prefetch before probe so partial buffers do not fall back to a full re-download unnecessarily.
- Completed download buffers are validated before decode; sparse or incomplete files trigger an automatic refetch.
Audio — seamless playback resume on output device switch
- Switching output device (Bluetooth, USB, HDMI, AirPlay) no longer restarts the track from the beginning — playback resumes at the same position.
- Fully cached and local files replay on the new device inside Rust; streams and radio use the existing frontend restart path but seek back to the saved position.
Virtualization — Artists, Composers and Tracks lists no longer drop rows on scroll
By @Psychotoxical, PR #766
- Same scroll-margin bug as the one fixed by #764 for the Album Detail "More by …" rail, on four more virtual lists: Artists grid, Artists list, Composers list and the Tracks virtual song browser. The virtual wrapper sat below the sticky page header but TanStack measured row positions from the scroll-element top — rows still on screen could unmount, and at larger header offsets the list refused to render at all.
- The measurement is now a shared
useVirtualizerScrollMarginhook used by every virtual-list call-site (including the existingVirtualCardGridfix from #764).
Album cards — per-artist click on multi-artist albums
By @Psychotoxical, PR #767
- The artist subtitle under an album card rendered a multi-artist string as a single link to the album's primary
artistId. On an artist-detail page that id is the page's own artist, so the click resolved to the current URL and the router silently no-op'd — the cursor said clickable, nothing happened. - Album cards now use the same
OpenArtistRefInlinecomponent the album-detail header uses: each artist becomes its own ·-separated link. Behaviour on servers that don't expose the structured list is unchanged. - Root cause was a stale field name: psysonic's internal type called the OpenSubsonic album-artist array
albumArtists, but the spec (and Navidrome) returns it asartists, so the structured branch never fired and the song-level fallback was carrying the album-detail header on its own.
Multi-server — Lucky Mix and Now Playing no longer revert the browsed server
- Lucky Mix on a browsed server while another server still owned the queue used to abort and snap the UI back — opening Now Playing triggered a forced server switch. Lucky Mix now clears the old queue and pins the active server before building when browse and playback differ.
- Now Playing and the queue info panel keep your browsed server in the connection indicator; song metadata still loads from the playback server. Album and artist links switch to the queue server when you open the library.