Added
Themes — Kanagawa, Atom One and 1984 Palettes
By @Psychotoxical, PR #390
Open Source Classics gains three new theme families: Kanagawa, Atom One and 1984, adding nine new themes in total.
- Kanagawa: Wave, Dragon and Lotus
- Atom One: Dark and Light
- 1984: Default, Cyberpunk, Light and Orwell
Each theme defines the full token set, including background, accent, text, Catppuccin compatibility, waveform, status and select-arrow tokens. This lets login, queue sidebar and subpages inherit the palette cleanly without component-specific overrides.
The theme picker now groups Open Source Classics by family with dedicated family headings. Theme scheduler dropdown labels are also family-prefixed, making it clearer which palette family a scheduled theme belongs to.
Audio Preview — Rust Preview Engine and Tracklist Rollout
By @Psychotoxical, PRs #392, #394
Psysonic now has a native Rust-powered preview engine for tracklist previews. Instead of using a separate HTML5 audio path, previews run through a parallel rodio sink on the existing output stream, with dedicated Tauri commands and engine events for preview start, progress and end.
When a preview starts, the main player pauses and only resumes automatically if it was playing beforehand. Starting normal playback, radio playback, resume or stop actions cancels any active preview first, so preview audio and main playback cannot overlap.
The new preview UI is rolled out across the main tracklist surfaces:
- Albums
- Playlist detail including suggestions
- Favorites
- Artist detail top tracks
- Random Mix genre and filtered-song lists
Track numbers now stay stable on hover, while dedicated inline Play and Preview buttons handle playback actions from the title cell. Active playing rows keep the equalizer bars, including on hover; active paused rows fall back to a static accent-colored track number. SongRow is intentionally left untouched in this pass.
Settings → Audio now includes a preview section with a master toggle, configurable start position, configurable duration and per-location toggles. Users can keep previews enabled on discovery-heavy surfaces while hiding them on owned-content views. The preview progress ring follows the configured duration automatically.
Preview state is mirrored through a new previewStore, giving the UI one reliable source of truth for preview progress, active state and the currently previewing track metadata.
While a preview is playing, the player bar mirrors the previewed track with cover, title, artist, a dedicated Preview label and an accent top border. Actions that would otherwise target the queued track, such as rating, fullscreen hint and album/artist links, are suppressed during preview playback.
The main play button also reflects preview state with a stop-style preview control and progress ring. Its behavior matches the inline preview buttons: stopping the preview resumes the main player only if it was already playing before. The smaller Stop button uses silent preview stop semantics, cancelling the preview and leaving the main player paused so Stop always means silence.
Spacebar stops an active preview, media keys are ignored during previews, and tray actions cancel the preview before continuing with the requested player action.
Play Next in the context menu now uses a double-chevron icon, making it visually distinct from the Preview button.
The feature includes updated i18n coverage across all supported locales, including the new player-bar preview labels.
Tray — Now Playing Tooltip and Localized Menu Labels
By @Psychotoxical, PR #395, closes #383
The system tray now reflects the current playback state more clearly. On Windows and macOS, the tray tooltip shows the currently playing track as Artist – Title on play, pause and track changes, falling back to Psysonic when nothing is playing.
On Linux, where AppIndicator does not expose a hover-tooltip API, the same now-playing text is shown as a disabled entry at the top of the tray menu instead.
Tray menu labels are now localized across all supported languages, including Play/Pause, Next/Previous, Show/Hide, Exit and the Linux-only empty-state label. The frontend updates the tray labels on startup and whenever the app language changes, without rebuilding the tray icon.
Sidebar Discovery Indicators
Sidebar navigation now includes a dedicated unread indicator for New Releases, with persistence per server/library scope and delayed mark-as-seen behavior after opening the New Releases page.
Albums added within the last 48 hours now receive a localized New badge in both album cards and album detail header.
Adaptive Header Controls
The top header behavior was reworked for narrow widths: search, Live and Orbit controls now compress in a deterministic order with improved stability in edge-width ranges.
Waveform Wheel Seeking
Waveform mouse-wheel seeking now uses fixed step-based jumps with debounce smoothing for more predictable navigation and less jitter during rapid scrolling.
Queue Panel — Position Counter, Tri-State Duration Toggle, Collapsible Now Playing, EQ Indicator
The queue panel got a sweep of UX refinements. The header now shows the current position as (N/M) next to the queue title for at-a-glance context.
The clickable duration label in the header rotates through three modes per click instead of two: total queue time, remaining time, and estimated end-of-queue clock time (e.g. · 02:10). ETA updates every 30 seconds, is formatted in the user's locale, and is visually highlighted with the accent colour while playing.
A new chevron next to the queue title collapses the Now Playing section and queue toolbar, and the collapsed state is persisted across restarts, so users who treat the queue as a pure list can keep it that way.
The currently playing row in the queue list is now indicated by animated equalizer bars to the left of the track title; the bars freeze in place when playback is paused. The previous small play icon next to the title is removed since the EQ bars carry the same signal more clearly.
Queue — Drag Outside to Remove
You can remove a track from the play queue by dragging its row outside the queue sidebar (main window) or outside the mini player’s queue list. Drop targets still support reordering when you release inside the queue area.
The drag ghost shows a trash affordance only while the cursor is outside the queue bounds; inside the queue it behaves as a normal reorder drag. The mouse-event psy-drop path now carries cursor coordinates so removal can be detected when the drop target is not the queue panel itself.
Statistics — Shareable Top-Albums Card
By @Psychotoxical, PR #425
Statistics page can now export your most-played albums as a shareable PNG, accessible via a share icon next to the Most Played Albums section header. Three aspect ratios for different platforms (Story 9:16, Square 1:1, Twitter Card 16:9), three grid sizes (3×3, 4×4, 5×5), with each cover carrying a thin info strip showing rank + play count.
The card pulls the wordmark and accent color directly from the active theme, so a Catppuccin export looks Catppuccin and a Nord export looks Nord. Cover art reuses the existing IndexedDB cache, so no extra Subsonic round-trips on repeat exports. The header label is hardcoded English ("Top Albums") so a shared image stays legible to followers regardless of their language.
Saving uses the native OS save dialog — no silent dump into Downloads, the user picks the path each time. Data source is local-only (Subsonic getAlbumList(frequent)); Last.fm is intentionally not used. There is no time-window selector because Navidrome's API exposes only cumulative play counts, not per-event play history.
Shortcuts — Action Registry, Dynamic CLI Help, New Input Targets
Shortcut, keyboard, global-hotkey, mini-window and CLI inputs are now all routed through one TypeScript action registry — a single source of truth for what an action does, how it's labelled, and which input transports can fire it. CLI --player help is generated dynamically from the registry, so command coverage stays in sync with the action set automatically.
Nine new input actions were added (requested by zunoz on Discord): start search, start advanced search, toggle sidebar, mute, open / toggle equalizer, toggle repeat, open Now Playing, show lyrics, favorite current track. Help is bound to F1 by default and hidden from the Settings input list; existing users get the F1 binding back-filled into their persisted keybindings on next launch.
Translations for the new action labels follow in a separate i18n nachhol-PR (de, fr, nl, zh, nb, ru, es).
Settings — 3-State Animation Mode (Full / Reduced / Static)
By @Psychotoxical, PR #441, suggested by harumscarum on Telegram
The boolean Reduce animations toggle in Settings → Appearance is now a three-state picker matching the ReplayGain Auto/Track/Album pattern:
- Full (default for new users): native frame rate, marquee scrolls normally.
- Reduced (default for users who had the legacy toggle on): 30 fps cap on the animated seekbar wave; the player title marquee runs at half speed.
- Static: the rAF loop driving the animated seekbar is disabled entirely — the seekbar repaints from the ~2 Hz
audio:progressheartbeat only. The player title/artist no longer scroll; long names are truncated with an ellipsis. Lowest GPU/CPU cost of the three.
Existing users with reducedAnimations: true are migrated 1:1 to Reduced on first launch; everyone else lands on Full. The picker is in the same place as before. A contextual hint below the picker explains what the selected mode does.
Tracks — Highly Rated Rail and Per-Card Star Display
By @Psychotoxical, PR #443, prompted by Foxhunter-de in discussion #442
The Tracks page gets a new Highly Rated rail above Random Pick, surfacing your top-rated tracks (sorted by rating, descending). The rail auto-hides on non-Navidrome servers and on libraries with no rated tracks yet. The standard reroll button forces a fresh fetch.
Every song card across the app whose rating is greater than zero now shows a small five-star row below the artist line, filled to the rating value. Read-only display — rating is still done via the row's context menu or the Now Playing star widget.
Backed by an opt-in 60 s in-memory cache for ndListSongs (used only by the new rail; paginated browsing is unaffected). The cache is cleared automatically when you rate a track, switch server, or click the rail's reroll button.
Random Mix — Playlist Size Selector and Filter Panel Cleanup
By @Psychotoxical, PR #445, prompted by Foxhunter-de in discussion #442
Random Mix can now build longer mixes. A new playlist-size picker (50 / 75 / 100 / 125 / 150) sits at the top of the filter panel. Clicking a size button immediately reloads the current mix at the new count — Genre Mix and All Songs both honored, no extra Remix click needed. The choice is persisted across restarts.
The filter panel layout was tightened up at the same time: two sub-headings (MIX SETTINGS and EXCLUSIONS) with a divider between them, and a slightly larger panel-level header so the visual hierarchy reads cleanly. A small italic note below MIX SETTINGS explains that large mix sizes may return fewer unique tracks if the server's random pool runs short.
Under the hood, fetchRandomMixSongsUntilFull now scales batch size, max-batch ceiling and dup-streak budget with the requested target — so a 150-track mix can finish in a single round-trip on most libraries instead of stalling out at ~120.
UI — Bulk Entity Ratings, Random Albums Multi-Select, Album New Badge
Multi-album and multi-artist context menus now include a shared star-rating row for the current selection (mixed ratings show empty until you set a value; keyboard navigation supported), with new aria-label strings across locales. Random Albums passes the active selection into each AlbumCard so the same bulk context menu works from the roll grid. The album New badge moves to the top-right of the cover and stacks with the offline badge so the two no longer overlap.
NixOS — Flake: X11-wrapped default vs session GDK
The flake exposes two Linux installables: psysonic / default pins GDK_BACKEND=x11 for a stable GTK/WebKit stack on mixed Wayland setups; psysonic-gdk-session drops that override so GDK follows the session (native Wayland where the stack supports it). nixos-install.md documents trade-offs and zsh-safe quoting for nix run 'github:…#…' URLs.
Linux / WebKit — performance probe, progress IPC, and UI isolation
- Performance Probe — Modal at Ctrl+Shift+D (sidebar logo is decorative only). Collapsible Phase 1/2 and an open-by-default Phase 3 for the toggles used most in profiling. Flags persist in
localStorage, map todata-perf-*on the document root, and can disable targeted subsystems (shell/network hooks, mainstage sections, PlayerBar waveform only, live progress UI, rail artwork, and similar) to isolate WebKit/WebProcess CPU on Linux. - Approximate live CPU (Linux) — Tauri command reading
/proc(including WebKit helper process names) for rough host CPU share while the probe is open. getPlaybackProgressSnapshot/subscribePlaybackProgress— Live time, seekbars, lyrics, and related UI subscribe without writing every progress tick into the persisted player store.- Perf telemetry gating — Hot-path counters (
audioProgressEvents,waveformDraws,homeCommits) increment in development builds always, and in production only while the Performance Probe is open, avoiding extra global writes during normal playback. audio:progress(Rust) — Throttled by minimum interval and position delta, with immediate emit on pause transitions, to reduce IPC during playback.- Persisted player store —
currentTime/progress/bufferedcommits are coarse-grained; live UI reads the snapshot channel instead. WaveformSeek— Same<canvas>2DfillRectbar renderer as before (not pre-rendered bitmap layers). Progress is fed from the snapshot channel; sparse backend ticks are bridged with prediction/smoothing and a capped repaint cadence; animated preview ticks no longer stop solely because the window lost focus while still visible; static styles stay aligned with external seeks while paused by syncing the visual progress ref to the snapshot when the interpolationrAFloop is not running.- MPRIS — Position updates while playing use the snapshot channel on a conservative cadence; play/pause transitions send snapshot time (not coarse store
currentTime). Removed the redundant store-driven position branch that could push stale values after timeline coarsening. - Home / card artwork — Album and song cards always use
CachedImagefor covers; removed unuseddirectImageSrcplumbing fromHome,AlbumRow,SongRail,AlbumCard, andSongCard. - Tracks — Highly Rated and Random Mix
SongRailrows enable the same horizontal artwork windowing defaults as Home. - Hero — Auto-advance respects visibility in the app scroll viewport, pauses when the window is blurred, and recovers after returning on-screen or on focus/visibility changes.
- Home
AlbumRow/SongRail— Artwork visibility budget uses real card geometry so covers fill the viewport without requiring an initial horizontal scroll nudge. - Server switch menu — Portaled with fixed coordinates so it stacks above the sidebar.
- Linux / Nix —
PSYSONIC_ALLOW_NATIVE_GDKskips the defaultGDK_BACKEND=x11pin when using thegdk-sessionwrapper;tauri:devno longer forcesGDK_BACKENDovernix developdefaults.
Fixed
-
Settings → Audio no longer blanks the app on macOS (Issue #382, PR #384, by @Psychotoxical): Fixed a macOS-only crash where opening Settings → Audio could turn the whole app into a blank window. The Equalizer canvas now waits until it has valid layout dimensions before drawing, and redraws automatically once the section is visible.
-
Polish (PR #397, by @cucadmuh): multiple branch-local interaction fixes around sidebar drag/drop behavior, Live dropdown layering, queue-resize handle behavior during scroll/overlay-scrollbar interaction, and now-playing narrow-layout stability.
-
Track preview audio in sync with progress ring; huge files no longer abort (Issue #421, PR #423, by @Psychotoxical): Previews used to start audio about 25 % into the preview window on mid-track starts because
Sink::try_seekran in parallel withsink.appendwhile the 30 stake_durationcap was already counting wall-clock from append. The seek now runs on the bare source before append, and the progress-ring animation only starts once the engine actually emitsaudio:preview-start— a small loading spinner is shown during the download/decode/seek warmup. The preview HTTP-client timeout was raised from 30 s to 5 min, so multi-hundred-megabyte Hi-Res files no longer abort the download mid-fetch. -
Windows playback stutter under GPU load (Issue #334, PR #426, by @Psychotoxical): Audio could stutter and crackle on Windows whenever another app put GPU/CPU pressure on the system (browser, 3D apps, games). The WASAPI render thread is now promoted to MMCSS "Pro Audio" via
AvSetMmThreadCharacteristicsW, so it survives priority contention from competing graphics work. Reproed and validated under a Half-Life parallel-load stresstest. Companion mitigations for high-GPU situations: cosmetic UI animations now pause when the window loses OS focus, and a new Reduce animations toggle in Settings → Appearance caps animated seekbar styles (pulsewave, particletrail, liquidfill, retrotape) to 30 fps for users on GPU-constrained machines (off by default). -
Linux dev — sidebar and main content invisible after HMR (PR #434, by @Psychotoxical): The
data-app-blurred="true"CSS rule introduced in #426 used a*selector to pause every animation while the window was unfocused. On WebKitGTK + no-compositing this triggered a stale rendering bug after Vite hot-reloads — the sidebar and main content stayed unpainted until any user interaction nudged a re-render. The rule now targets only the concrete heaviest infinite animations (eq bars, marquees, now-playing dot pulse, fullscreen mesh blob / portrait,.spin); release builds are unchanged in behaviour. -
Mono playback (right channel only) after natural track end with gapless OFF (PR #439, by @Psychotoxical): When gapless playback was disabled and a track ended naturally, the next track could play only on the right channel for the rest of its duration. The 500 ms track-separation silence prepended in this exact transition was built with
Zero + take_duration, whose integer-nanosecond math at 44.1 kHz / 2 ch leaks half a frame (44103 samples instead of 44100), shifting the next source's L/R parity in the device frame stream. Replaced with a frame-alignedSamplesBuffer. Manual skip and album-first-play were unaffected because they bypass the silence prepend. Independently identified by xrexy on Discord while the diagnosis was landing here.