Added
Settings — OpenDyslexic font option for dyslexic readers
By @Psychotoxical, PR #507
- Next step on the accessibility track after the colour-side work (WCAG contrast audits across every theme, dedicated colour-vision-deficiency theme variants for protanopia / deuteranopia / tritanopia): a dyslexia-friendly OpenDyslexic font option in the existing font picker.
- OpenDyslexic uses a heavier weighted baseline and asymmetric glyph shapes —
b/d,p/qnever mirror, italics are differentiated forms rather than slanted regulars — which many dyslexic readers find easier to track than a typical sans. - Bundled locally via
@fontsource/opendyslexic(SIL OFL, freely redistributable). No CDN dependency. The Settings picker grew an optional subtitle field on font entries so the OpenDyslexic row carries a "dyslexia-friendly · no RU/ZH support" hint without cluttering the other 14 fonts. - Latin + Latin-extended only. Cyrillic and CJK locales (RU, ZH) fall back to the system font when OpenDyslexic is selected; the subtitle calls that out upfront. Subtitle text is translated in all 8 locales.
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 walks Navidrome's native
/api/song?_sort=bit_depth&_order=DESCand dedupes to album ids along the way, stopping when the cursor crosses into lossy or runs out. Restricted to containers that are always lossless (flac,wav,aiff/aif,dsf/dff,ape,wv,shn,tta) —m4aandwmaare intentionally excluded because they can carry both lossless and lossy codecs and Navidrome'scodecfield isn't reliable enough to disambiguate. - Streaming load: albums stream into the page progressively as each internal fetch completes (
onProgresscallback) instead of blocking on the fullloadMore. - New sidebar entry Lossless (Gem icon), visible by default.
- i18n coverage across all 8 locales for the new strings (sidebar / home / page subtitle / empty / unsupported). RU and ZH are machine-translation quality, flagged for a polish pass.
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. Subsonic's
getSongonly ever returned a relative path (or nothing at all on Navidrome), which is why the row was effectively empty before. A new native-API call (/api/song/{id}) runs in parallel withgetSong; on non-Navidrome servers the dialog falls back to whatever the Subsonic response carried.
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 load artist images for rows in the Artists section via the same
getCoverArt/ image-cache path as album art (coverArtwhen present, otherwise the artist id where the server supports it), with a fallback icon when art is missing or fails. - Mobile artist hits use a round thumbnail next to square album art so the two result types read clearly at a glance.
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 8 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.
- Uses Navidrome's native API (
/api/artist?_filters={"role":"composer"}for the listing,/api/album?_filters={"role_composer_id":"…"}for the works) — SubsonicgetArtistonly walks AlbumArtist relations and returns zero albums for composer-only credits, so the native path is the only one that works. Requires Navidrome 0.55+; older / pure-Subsonic servers see a one-line capability banner. - Music-folder scope is honoured: role queries pass Navidrome's
library_idfilter so per-folder browsing matches the Albums / Artists pages. Bio + Last.fm portrait stay stable across scope changes. - Composers are a first-class share entity.
psysonic2-links withk=composerpaste to/composer/:id; the Share button on the detail page and the right-click menu both copy acomposerlink. - Sidebar entry is off by default (classical-music use case is a niche) — toggle in Settings → Sidebar.
Home — "Because you listened" recommendation rail
By @Psychotoxical, PRs #489, #493
- New Home rail that surfaces albums similar to one of your favourite artists — Spotify-style "Because you listened to …" recommendations.
- Anchor pool round-robin merges Most Played, Recently Played and Favorites (deduped per artist), so the per-mount rotation lands on a different listening mode each visit instead of walking only the top-played list. Pool size 12 lets the cursor visit all three sources before wrapping. Within each anchor,
getArtistInforeturns up to 12 similar artists; the rail randomly samples 6 of them and surfaces 3 albums (one random per matching artist) that exist on your server. - Anchor rotation is per-server: switching servers keeps independent rotation state instead of aliasing one server's anchor id onto the next server's pool. The rail also renders on fresh servers that have no frequent-play history yet, as long as they have starred or recently played items. 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 cards in one row at 1080p (the orphan third card on a second row is hidden via container query), and all 3 stacked vertically on truly narrow / mobile widths.
- Toggleable in the Home customizer like every other rail; respects the existing performance flags ("Disable rail artwork", "Disable Home album rows").
Changed
Settings — collapse-by-default cleanup, font picker without dropdown, OpenDyslexic at top
By @Psychotoxical, PR #508
- Every Settings sub-section now boots collapsed. Audio Device, Lyrics Sources, Last.fm, Sidebar, Random Mix, Offline Dir, Theme, Keybindings and Language used to auto-expand on first render — each tab felt like a wall of controls before the user had even looked for something specific. Click the section header to open what you actually need.
- ThemePicker 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 of the font list so dyslexic readers don't scroll past 14 sans-serifs to find their option.
Dependencies — npm / Cargo refresh and rodio 0.22
- Bumped frontend and Tauri / Rust dependencies across the workspace (
package.json,package-lock.json,Cargo.toml,Cargo.lock). - Playback stack migrated to rodio 0.22 with corresponding updates in decode, sources, engine, device I/O and related modules.
Build — lazy-loaded routes and Vite chunk warnings
- Heavier app routes are loaded lazily so the initial JS bundle stays smaller (
App.tsxand related entry wiring). - Restored default Vite
chunkSizeWarningLimitbehaviour so oversized chunks are reported again during production builds (vite.config.ts).
UI — cover cache, mainstage rails, and smoother virtual lists
- Image cache: Network fetches share a small concurrency pool; IndexedDB hits are no longer queued behind remote downloads. Debounced disk eviction avoids hammering storage during fast cover scrolling. Related shared blob URL / hot-path fixes for thumbnails.
- Mainstage & rails: Horizontal rows use reworked artwork windowing (higher initial budgets, extra slack ahead of the viewport, no budget reset when “load more” extends the list). Duplicate album/song IDs from the API are deduped for stable React reconciliation. CachedImage handles already-decoded / cache-hit images cleanly; rail Album / song cards load covers eagerly to reduce blank art while scrubbing sideways.
- Virtualised lists: Albums, Artists (list mode), and the Tracks virtual song browser derive TanStack Virtual
overscanfrom the measured scroll viewport (~ one screen of extra rows above and below) instead of a tiny fixed cushion. - Library & chrome: assorted scroll / layout improvements on Artist detail, Playlists, Most played; smaller touch-ups to Mini player, Live search, Album header, and dynamic colour extraction used by player / album surfaces.
Covers / image cache — parallel fetch + downscale, registry guard, search slot hints
- When a pixel size misses disk but another size of the same
cover:idis already cached, remotegetCoverArtand client JPEG downscale run in parallel; the first good blob wins and the other side aborts (network + downscale signals). - Sibling key reads use one IndexedDB readonly transaction instead of many separate transactions.
COVER_ART_REGISTERED_SIZEScentralises knowngetCoverArtwidths for invalidation and sibling lookup;coverArtRegisteredSizes.test.ts(Vitest) keeps literalcoverArtCacheKey(_, n)call sites insrcin sync with that list.downscaleCoverBlobrespects AbortSignal throughcanvas.toBlob.CachedImage:fetchQueueBiasgives artist search thumbs higher network-slot priority than album thumbs when the pool is saturated;observeRootMargindefaults wider so priority updates earlier ahead of the scroll viewport.
Settings — adding a server no longer switches to it
By @Psychotoxical, PR #475
- When you add a new server from Settings → Servers, the new entry now appears in the server picker but your current active server stays active — playback, queue and library view are no longer interrupted.
- The login screen on
/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
- Each album row now shows always-visible Play and Enqueue quick-action buttons, reusing the same flows as
AlbumCard(Play kicks the fade-out replace-and-play, Enqueue appends the album's songs to the queue end). - Right-click on an album row now opens the standard album context menu (Play / Add to queue / Play next / Add to playlist / Go to artist) instead of firing a hidden direct-play action; 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 plays(en),11× gespielt(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: dropped entries the UI itself answers, consolidated natural groupings, and added entries for features that didn't exist yet when the original Q/A list was written (Orbit, Magic Strings, LUFS, Mini Player, Smart Playlists, Track Preview, Search, Statistics, Tracks hub, Genre Browser, Discord, Bandsintown, Multi-select, Sidebar/Home/Artist customization, Sleep Timer, Open Source Licenses). 45 focused entries across 10 themed sections.
- 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. × button clears the query.
- Translated to all 8 supported locales (en, de, fr, nl, zh, nb, ru, es). Russian and Chinese are at machine-translation quality and would benefit from a polish pass by the original locale maintainers.
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.
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 scrolling can be disabled via
perfFlags.disableMarqueeScroll(Sidebar toggle), global CSS animations via the html-leveldata-perf-disable-animationsswitch, and seekbar performance is handled by the newer per-feature toggles. - Anyone who had
'reduced'or'static'selected silently lands on the normal animation path on first launch after upgrade — the persist layer strips the obsolete field, no user-facing prompt.
Fixed
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 — older Tauri versions had an origin-confusion bug that could let a remote-origin page loaded inside the webview invoke local-only IPC commands. Severity medium. Psysonic exposes a number of file-system and credential-bearing IPC commands (downloads, Navidrome native API, audio engine), so closing the gate is worth the bump.
- Lockfile-only refresh;
Cargo.tomlwas already unlocked attauri = "2". Full Tauri family (build / codegen / macros / runtime / runtime-wry / utils) bumped together at matching patch level.
Hot cache, HTTP streaming replay, and queue source indicator
stream_completed_cacheis no longer cleared onaudio_stop, so a fully buffered ranged download is not thrown away when the queue ends; starting the same track again can reuse RAM or hot-disk data instead of running a full HTTP ranged fetch from scratch (when hot cache is enabled and promotion succeeds).- Same-track
playTrackand coldresumeafteraudio:ended(engine not in paused-loaded state) await hot-cache promote soresolvePlaybackUrlcan switch topsysonic-local://before the nextaudio_play. - Ranged HTTP sources merge format hints from the URL tail,
Content-Type,Content-Dispositionfilename, and Subsonicsong.suffix(IPCstreamFormatSuffix). A bounded Range probe runs only when hints are still missing. Genericvideo/mp4Content-Type is not treated as an audio-container hint. - SQLite analysis: skip redundant CPU seeds when waveform and loudness rows already exist; emit
analysis:waveform-updatedonly after a real DB upsert, not on cache-hit no-ops. Ranged / legacy download tasks re-check playback generation after awaits before writing the completed-stream slot. - Queue panel source icon (stream / hot cache / offline) now updates on resume, queue-undo restore, and gapless
audio:track_switched, not only whenplayTrackruns. - TypeScript / robustness: non-null
activeServerIdbindings for promote IPC;.catchon the same-track promote → play promise chain with generation guard and prefetch-reset on failure.
Sidebar — New Releases read state under storage cap
- When persisted “seen” release IDs hit the 500-id cap, fresh reads are merged at the front of the capped set so unread badges do not come back incorrectly (
mergeSeenNewReleaseIdsCap).
Windows — tray double-click
- Double-click on the tray icon opens (or focuses) the main window without a spurious context-menu interaction; tray module import cleanup.
Playback stability — preview seekbar, sleep/wake recovery, and card-hover jitter
- Preview seekbar: while Rust preview playback pauses the main sink, the main seekbar no longer extrapolates forward, and preview end no longer causes a brief forward jump before snapping back.
- Sleep/wake audio recovery: added post-sleep output reopen hooks for Windows (power notifications) and Linux (logind
PrepareForSleepwake signal), plus a guarded fallback watchdog path and richer runtime diagnostics. - False-positive mitigation: the watchdog now arms only after a long poll gap (sleep/resume-like condition) and logs arm/clear/trigger decisions, reducing unexpected stream reopens during normal playback.
- Card hover stability: removed vertical lift on album/artist/base cards to avoid pointer-edge pulsation, kept artwork zoom smooth, and dropped per-card GPU layer hints that could regress software-composited Linux paths.
Analysis queue control — prune stale backfill jobs and cap warmup window
- Added a queue-prune path for pending analysis work so stale
http_backfill/cpu_seedjobs are dropped when tracks leave the active playback queue. - Limited loudness backfill warmup to the current track plus the next 5 tracks, reducing runaway analysis scheduling from large bulk queue updates.
- Added debug counters for prune results to make queue-pressure behavior visible during diagnostics.
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, because it still rendered through the expanded-mode wrapper (with
padding-rightand aflex: 1main link to fit the expand-toggle). 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
opacitypulse on the entire row plus threetransformkeyframe EQ-bar siblings — both compositor properties, but on WebKitGTK without compositing (Linux + NVIDIA proprietary +WEBKIT_DISABLE_COMPOSITING_MODE=1) every animated row falls back to a full software repaint of the subtree per frame. On AlbumDetail the combined cost held the WebProcess at ~80 % CPU for the duration of playback; CPU dropped immediately on pause/stop. .track-row.activekeeps the accent-tinted background but no longer pulses. The "now playing" indicator becomes a single LucideAudioLinesicon (one SVG per active row instead of three animated spans). Cleanup: deadtrack-pulse+eq-bouncekeyframes and a duplicate, shadowed.eq-barblock intheme.css.
Radio — queue navigation, dedup, and similar-first variety
By @Psychotoxical, reported by netherguy4, PR #503
- Queue navigation through duplicates:
playTrackre-resolved the active slot viafindIndex(... .id === ...), which returns the first matching id. Reaching a track's second occurrence snappedqueueIndexback to the earlier slot — highlight visibly jumped and the next auto-advance played the wrong follow-up.next(),previous(), theaudio:endedrepeat-one path, queue-row click and the queue-item Play Now now pass an explicit target index throughplayTrack. - Queue duplicates:
enqueueRadiodidn't dedupe incoming tracks; thenext()top-up deduped against the live queue but trimmed the played tail down to 5 history entries, so songs heard a few advances ago could be re-added by a later Last.fm / topSongs response; and the.filter(...)pass admitted intra-batch repeats (top + similar overlap is common) because it read the dedup set before mutating it. A radio-session-scoped seen-set, reset on artist change andclearQueue, closes all three paths. - Variety: Starting Radio on a track 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 (no Last.fm / small library).
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 ramps the main sink. Slider drags during preview therefore had no audible effect on the preview level. - With loudness normalization on (default
-4.5 dBpre-analysis attenuation), even a 100 % slider produced1.0 × 0.596 × 0.891 ≈ 53 %at the speaker, matching the reporter's "fixed at around 50 %" observation. - New
audio_preview_set_volumecommand and aplayerStoresubscription in the frontend keep the preview sink in lock-step with the slider while a preview is in flight (settings tweaks during preview are intentionally not synced — preview windows are short).
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: closing to the tray injects a "pause rendering" snippet that sets
data-psy-native-hidden="true"on<html>and pauses every CSS animation. The tray-icon restore path injects the matching "resume rendering" snippet before showing the window — the second-launch restore path (handled by the single-instance plugin) was missing that step, so route wrappers using.animate-fade-in(animation: fadeIn … both, starts atopacity: 0) stayed frozen invisible. - Fix: mirror the tray-icon restore path and resume rendering before
show()in the single-instance callback. Both restore paths are now consistent.