Purple & Gold palette (Deuteranopia / Protanopia / Tritanopia safe).
Vision Dark: near-black #0D0B12 + Gold #FFD700 (~14.7:1 WCAG AAA).
Vision Navy: deep navy #0A1628 + Gold #FFD700 (~14.5:1 WCAG AAA).
New 'Accessibility' group in ThemePicker.
Removed stale theme count from aboutFeatures across all locales.
This is an initial step toward colorblind accessibility — color variables
alone cannot cover all CVD use cases. Structural improvements (secondary
indicators, pattern/shape cues) are still needed in future iterations.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds full keyboard navigation (arrow keys, Enter, Ctrl+Enter for context menu) across Folder Browser columns. Context menus for all row types with keyboard-operable submenus and star-rating control via arrow keys + Enter. Now-playing path is visually emphasized and stays stable; rebuilds on hotkey re-invoke or active-path follow-along. Adaptive column layout for deep trees with right-side visibility priority. New configurable 'Open Folder Browser' keybinding. StarRating animation sync for keyboard-driven changes. All 7 locales updated.
Adds no_compositing_mode Tauri command; frontend adds html.no-compositing on Linux to replace GPU-only CSS effects (backdrop-filter, filter, mask-image) with software-friendly equivalents.
Settings → Appearance → Fullscreen Player: toggle for artist portrait visibility + 0–80% dimming slider.
Fix: long words in lyric lines now wrap correctly.
Replaces two sidebar entries (Random Mix, Random Albums) with a single 'Build a Mix' item (Wand2 icon) at /random. Landing page shows two cards — Mix by Tracks and Mix by Albums — with glow-on-hover. Old routes updated throughout. All 7 locales updated.
Small fix for hot playback cache: eviction keeps current and next only;
prefetch up to five tracks when under cap, always fetch the immediate next;
grace for the previous current until debounce; run evict immediately on
MB or folder changes; re-read cap after download; optional Track.size;
live disk usage on Audio settings.
Lyrics Sources:
- Replace lyricsServerFirst + enableNeteaselyrics toggles with a new
Settings section — three sources (Server, LRCLIB, Netease) each
individually toggled and drag-reorderable via psy-drop DnD
- useLyrics.ts iterates sources in user-defined order, skipping disabled
ones; embedded SYLT from local files still wins unconditionally
- onRehydrateStorage migration from legacy fields on first load
ReplayGain Pre-Gain:
- New authStore fields: replayGainPreGainDb (0…+6 dB) and
replayGainFallbackDb (-6…0 dB, untagged / radio)
- audio.rs compute_gain applies pre_gain_db and fallback_db
- Settings sliders under ReplayGain mode selector
- Radio HTML5 volume scaled by fallbackDb factor on playRadio
i18n(ru): incorporate PR #148 translation improvements (kilyabin)
All 7 locales updated for new keys.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Resolves PLS and M3U/M3U8 playlist URLs to their first direct stream URL
before playback and ICY metadata fetching. Stations configured with a
.pls or .m3u URL (e.g. SomaFM, schizoid.in) now play correctly and
report track metadata via ICY headers.
- Rust: parse_pls_stream_url / parse_m3u_stream_url helpers
- Rust: resolve_playlist_url (shared) + resolve_stream_url Tauri command
- fetch_icy_metadata: auto-resolves playlist URLs before connecting
- playerStore: playRadio() awaits resolve_stream_url before setting audio src
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Users who had both hotCacheEnabled and preloadMode !== 'off' before
mutual exclusion was enforced will have both reset to off on next start.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Netease is queried only when server and LRCLIB both return nothing,
preserving the existing lyrics chain completely. Off by default.
- Rust command `fetch_netease_lyrics` proxies Netease API (CORS bypass)
- `src/api/netease.ts` TypeScript wrapper via invoke
- `authStore.enableNeteaselyrics` toggle (default: false)
- `useLyrics`: Netease fires last in fallback chain when enabled
- Settings toggle in the Lyrics section
- `lyricsSourceNetease` label in LyricsPane
- i18n: all 7 languages (en, de, fr, nl, nb, ru, zh)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Resolves conflict in ContextMenu.tsx: keep useShallow import from main
alongside getSimilarSongs added by this PR.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Gapless now always chains at 30s regardless of preloadMode ('off' no
longer breaks gapless playback)
- Byte pre-download (audio_preload) skips when Hot Cache is active to
avoid duplicate downloads
- When both Gapless and Preload are active, bytes are pre-fetched early
and the chain reuses the cached data (separate bytePreloadingId guard)
- player-album-art-wrap gets background: var(--bg-card) so the brief
opacity-0 loading state shows a themed colour instead of black
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Throttle audio:progress from 100ms to 500ms; WaveformSeek and
FsSeekbar use imperative DOM updates instead of React re-renders
- Fix all usePlayerStore() calls without selectors across pages and
components (useShallow / individual selectors throughout)
- Remove filter:blur and transform:scale from Hero, AlbumDetail and
FullscreenPlayer backgrounds — eliminated expensive software
compositing layers on WebKitGTK
- Replace translate3d with 2D translate in FS mesh and portrait
animations; remove will-change:transform from mesh blobs
- Move Hot Cache section from Storage tab to Audio tab; group Preload
and Hot Cache under a shared 'Next Track Buffering' section with a
mutual-exclusivity note and auto-disable logic
- Add 'off' toggle to Preload (replaces Off button with toggle switch);
enabling either method now automatically disables the other
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Parse type and serverVersion from Subsonic ping; persist per-server identity and
run a background Instant Mix probe (random songs + getSimilarSongs) for
Navidrome 0.60+. Hide the AudioMuse server toggle when the probe returns no
similar tracks; clear prefs when the server is no longer eligible.
Artist pages fall back to Last.fm similar artists when AudioMuse is enabled but
the server returns none. Instant Mix failures set a per-server issue flag with a
settings warning and toast. Settings description links the official plugin repo
via i18n Trans.
Add per-server toggle for AudioMuse-style discovery: Instant Mix via
getSimilarSongs, server similar artists instead of Last.fm on artist pages,
and higher similar-artist count in Now Playing when enabled.
When browsing a single music folder, filter similar/top song results by
album ids from that scope (Navidrome does not apply musicFolderId to those
endpoints). Request larger similar batches under a narrow scope.
Keep radio-from-track as two blocks (shuffled top songs, then shuffled
similar-by-artist) so it differs from Instant Mix. Show Alpha badge beside
the setting. Add research/ to .gitignore.
In album gain mode, tracks without albumGain tags received no
ReplayGain adjustment at all. Now falls back to trackGain before
defaulting to 0 dB — fixes inconsistent loudness across albums
where only some tracks have albumGain metadata.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a background device-watcher that polls the OS default output device
every 3 s via CPAL. When the device changes (Bluetooth headphones connecting,
USB DAC plugging in, etc.) the stream is reopened on the new device, the old
Sink is dropped (it was bound to the now-closed OutputStream), and
audio:device-changed is emitted to the frontend.
Frontend handler (TauriEventBridge): if playing, restarts the current track
from the saved position; if paused, clears the warm-pause flag so the next
resume uses the cold path (audio_play + seek) which creates a new Sink on
the new device.
Fixes#143 (audio through speakers after connecting Bluetooth headphones).
Also covers the intermittently reported single-channel output after long idle,
which can be caused by the OS reconfiguring the audio device in the background.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements ITaskbarList3::ThumbBarAddButtons for Prev/Play-Pause/Next
buttons in the Windows taskbar thumbnail preview. Icons are drawn at
runtime via GDI (no binary assets). WndProc subclass intercepts
THBN_CLICKED and emits the same media:* events as souvlaki/tray.
update_taskbar_icon command swaps Play↔Pause icon on state change.
Frontend syncs via playerStore subscribe alongside mpris_set_playback.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove currentTime from Zustand persist partialize: it caused 100ms
localStorage writes that grew with the queue, crashing WKWebView SQLite
after ~10 min of Artist Radio on macOS
- Trim old played radio tracks from queue (keep last 5) to cap localStorage
payload size during proactive radio top-up in next()
- Add reconnect logic for internet radio stall events (max 5 retries)
- Shuffle initial Artist Radio queue via Fisher-Yates so positions 2+
are drawn from similar-artist tracks, not predictable top-5 tracks
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ZIP downloads (PlaylistDetail, Albums, NewReleases, RandomAlbums) now use
invoke('download_zip') via Rust streaming instead of fetch+blob+arrayBuffer,
eliminating JS heap saturation on large files. Progress shown in ZipDownloadOverlay.
Offline cache downloads (600+ songs) no longer freeze the UI: transient job
state moved to a separate non-persisted offlineJobStore, reducing localStorage
writes from ~1200 down to 2 for an entire download. Also adds playlist offline
toggle — clicking the cache button when already cached removes it from cache.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- NowPlaying polling: only active while dropdown is open + respects
Page Visibility API — was firing every 10s unconditionally (~8.6k req/day)
- Connection check interval: 30s → 120s (4× reduction)
- Queue sync debounce: 1.5s → 5s (prevents burst on rapid skip)
- Rating prefetch: add 7-minute in-memory TTL cache for artist/album
ratings — repeated random album loads no longer re-fetch known ratings
Fixes server log flooding reported by users behind reverse proxies (Traefik).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
New page /folders with macOS Finder-style column layout:
- getMusicDirectory / getMusicIndexes / getMusicFolders Subsonic API methods
- Root level uses getIndexes.view (music folder IDs are not getMusicDirectory IDs)
- Columns fill available width with flex: 1, min-width: 200px
- Auto-scroll to rightmost column on expand
- Cancelled-flag prevents stale state on fast navigation
- Clicking a track plays it in the context of the current column's tracks
- FolderOpen/Folder/Music icons, accent-colored selection, ellipsis truncation
- Hidden in sidebar by default (user can enable in Settings)
- i18n: EN DE FR NL NB ZH RU
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Time-based theme scheduler: auto-switches between day/night theme
based on configurable times; setInterval re-checks every minute
- UI scale slider (80–150%) via CSS zoom on document root; preset
buttons aligned to actual slider positions
- CustomSelect: scroll listener keeps dropdown anchored on scroll
- All features i18n in 7 languages
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Seekbar styles (5 new, by Psychotoxical):
- Neon Glow: transparent track, played section as multi-layer glowing neon tube
- Pulse Wave: flat line with animated gaussian sine pulse at playhead (rAF)
- Particle Trail: particles spawn at playhead and drift with glow (rAF)
- Liquid Fill: glass tube fills with liquid and animated wave surface (rAF)
- Retro Tape: two spinning reels connected by tape, shrink/grow with progress (rAF)
Ratings system (PR #130 by cucadmuh):
- Shared StarRating component with pulse/clear animations, disabled/locked states
- Entity ratings for albums and artists via OpenSubsonic probe (setEntityRatingSupport)
- Skip→1★: after N consecutive manual skips, set unrated track to 1★ (persisted per server)
- Mix rating filter: exclude low-rated songs/albums/artists from RandomMix, RandomAlbums, Hero, Home
- Batch refill in fetchRandomMixSongsUntilFull to fill list despite strict filters
- Prefetch artist/album ratings via getArtist/getAlbum for incomplete list payloads
- Settings: new Ratings section for skip threshold and per-axis mix filter stars
- i18n: all 7 languages (en, de, fr, nl, zh, nb, ru)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Apply the same per-axis star cutoff to random mix (multi-batch fetch until
full), random albums, Hero, and Home hero/discover rows. Prefetch artist and
album ratings via Subsonic when list payloads omit them. Exclude items when
0 < rating ≤ threshold; 0 or missing counts as unrated.
Settings: rating filter description interpolates sidebar menu labels; Russian
strings for custom title bar; short axis labels aligned across locales.
Add Ratings section under General: manual skip count before setting 1★,
and per-axis minimum stars for random mixes/albums (song, album, artist).
StarRating shows five stars with selection capped at three; shared max in authStore.
Queue filtering via passesMixMinRatings; OpenSubsonic-style entity rating fields on types.
Add five seekbar styles selectable in Settings → Appearance:
Waveform, Line & Dot, Bar, Thick Bar, and Segmented. Each option
shows an animated canvas preview using requestAnimationFrame.
Style is persisted in authStore (seekbarStyle, default: waveform).
Translations added for all 7 languages.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Probe OpenSubsonic after connect; persist album/artist ratings via setRating when supported. Add shared StarRating (repeat same star to clear, pulse and shrink animations, hover freeze after clear). Widen tracklist duration column and bump narrow saved widths in localStorage. Use StarRating in AlbumHeader, track lists, and playlist rows.
- New authStore field useCustomTitlebar (default: true)
- set_window_decorations Tauri command toggles native decorations at runtime
- Settings toggle visible only on Linux (no restart required)
- i18n keys added to EN + DE
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- iTunes Search API calls are now gated behind enableAppleMusicCoversDiscord
(default: false). No external requests to Apple are made unless the user
explicitly enables the new opt-in toggle in Settings.
- When the toggle is flipped, Discord presence is immediately re-sent for
the current track (coversSettingChanged bypasses the early-return guard).
- When paused, activity type switches to Playing (no auto-timer) and state
text shows "Paused" instead of the artist name.
- New authStore field + setter; new Settings toggle shown only when Discord
RPC is enabled; i18n keys added to all 7 languages.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
New sidebar item (TrendingUp icon, /most-played route) with:
- Top Artists section: derived by grouping frequent albums by artistId
and summing playCount — no extra API calls needed. Shows up to 10
artists as a responsive grid with avatar, rank and total play count.
- Top Albums section: paginated list using getAlbumList('frequent'),
50 albums per batch with a "Load more" button. Each row shows rank,
cover thumbnail (44px), album name, artist (navigates to artist
page), year and play count.
- Sort toggle (most plays first / fewest plays first).
Also adds playCount?: number to SubsonicAlbum — Navidrome returns this
field in getAlbumList2?type=frequent but it was missing from the type.
Closes#86
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds library scoping per server: users can select one Navidrome music
folder or 'All' from a new sidebar dropdown. Choice is persisted per
server ID in localStorage. A musicLibraryFilterVersion counter triggers
refetches across browsing pages when the scope changes.
- getMusicFolders() + libraryFilterParams() in subsonic.ts
- New fields in authStore: musicFolders, musicLibraryFilterByServer,
musicLibraryFilterVersion, setMusicFolders, setMusicLibraryFilter
- Sidebar dropdown via createPortal; collapses to icon in narrow mode
- API scoping applied to: getAlbumList2, getRandomSongs, getArtists,
getStarred2, search3
- Full i18n coverage (en, de, fr, nl, zh, nb, ru)
Review fixes applied (co-authored):
- Sidebar: title={} → data-tooltip + data-tooltip-pos='right'
- authStore: setMusicFolders merged into single set() call
- Locales: removed dead libraryScopeHint key from all 7 files
- Genres.tsx: removed musicLibraryFilterVersion dep (getGenres unscoped)
Co-authored-by: cucadmuh <cucadmuh@users.noreply.github.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Sidebar.tsx: title={} → data-tooltip + data-tooltip-pos="right" on
scope subtitle (CLAUDE.md rule: never use native title= for tooltips)
- authStore.ts: merge two set() calls in setMusicFolders into one atomic
update — avoids double render on folder list arrival
- locales (all 7): remove dead libraryScopeHint key (defined but never
rendered in any component)
- Genres.tsx: remove musicLibraryFilterVersion from effect deps —
getGenres is intentionally not folder-scoped, re-fetching on filter
change was a no-op waste
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Apply musicFolderId across Subsonic requests, bump a filter version so library views reload, and add a fixed-position sidebar dropdown (with capped height when there are many folders).
Fetches album artwork from the iTunes Search API and passes the URL
directly to Discord's large_image field. Subsonic cover URLs require
auth and can't be used by Discord directly.
- 3-strategy search: exact quoted → relaxed → track title fallback
- 1-hour in-memory cache per artist|album key
- iTunes search runs in tokio::task::spawn_blocking (reqwest::blocking
must not run on the Tokio async executor)
- artwork_cache wrapped in Arc<Mutex<...>> for cross-thread sharing
- Fallback to the pre-uploaded "psysonic" asset when no match found
- Cargo.toml: blocking feature alongside rustls-tls
Co-Authored-By: kilyabin <kilyabin@users.noreply.github.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Internet Radio full release (HTML5 engine, card UI, RadioDirectoryModal,
cover upload), Backup/Restore, Albums year filter, Statistics Library
Insights (playtime/genres/formats), Playlist cover upload, resizable
tracklist columns for Playlists & Favorites, crossfade fine control,
Settings Storage tab redesign, various fixes and UI polish.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Incorporates PRs #38 (nisarg-78), #42#43#44 (JulianNymark) with fixes:
Audio / Playback
- Artist Radio starts immediately from fast getTopSongs (local library);
getSimilarSongs2 (Last.fm) enriches queue in background — no more wait
- Fix seek audio glitch: EqualPowerFadeIn only resets to zero-gain on
seeks to track start (<100 ms); all other seeks resume at unity gain
- OGG/Vorbis container support via symphonia-format-ogg (PR #42)
- Human-readable audio error messages in SizedDecoder (PR #44)
- Audio playback errors shown as themed toast notifications (PR #43)
Queue / Radio
- Infinite Queue: proactive load of 5 tracks (was 25) when ≤2 remain
- Radio: proactive reload at ≤2 remaining tracks, independent of
Infinite Queue setting — radio no longer stops at last track
- Fix: clicking Start Radio multiple times no longer stacks duplicates
- Fix: Start Radio on artist keeps current song playing
- Manual tracks always appear before Radio, Radio before Auto-added
- Queue separators: "— Radio —" and "— Auto —" dividers
- Fix: radio proactive load now works even when songs lack artistId
(uses currentRadioArtistId module var as fallback)
UI / UX
- Click synced lyrics lines to seek (PR #38)
- Volume scroll wheel on volume slider (PR #38)
- Lyrics: active / completed / upcoming visual states (PR #38)
- Shared toast utility (src/utils/toast.ts) replaces inline toast fn
- Auto-updater: relaunch_after_update Rust command exits first to
release single-instance lock before spawning new process
About / Credits
- nisarg-78 and JulianNymark added to contributors (since v1.29.0)
- netherguy4 added as Special Thanks for feature ideas and feedback
- i18n: aboutSpecialThanksLabel in all 5 languages
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
### Added
- Favorite/Star button in player bar (requested by @halfkey)
- Bulk multi-select for album tracklist and playlist detail (add to playlist, remove from playlist)
- Song Info modal via right-click context menu (metadata: format, bitrate, sample rate, bit depth, channels, file size, path, replay gain)
- Recently Played section on Home page
- "Show activity in Now Playing" opt-in toggle in Settings → Behavior
### Fixed
- Queue cover art not updating on track change (useCachedUrl/CachedImage reset on cacheKey change)
- FullscreenPlayer background flickering (FsBg preloads image before layer transition)
- Playlist card delete confirmation visual (size expansion + pulse animation)
- Gruvbox Light Soft: back button and badge invisible against light background
### Changed
- buildStreamUrl: removed unused suffix parameter
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>