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>
Remove tauri-plugin-updater, relaunch_after_update command, generate-update-
manifest.js, the generate-manifest CI job, and all signing/upload steps for
.sig files and latest.json. The updater plugin, its pubkey and endpoint config
in tauri.conf.json, and the updater:default capability are all gone.
Replace AppUpdater.tsx with a lightweight component that:
- Fetches https://api.github.com/repos/Psychotoxical/psysonic/releases/latest
after a 4 s delay (no install, no download, no progress bar)
- Shows a dismissible toast with two buttons:
GitHub → releases/latest
Website → https://psysonic.psychotoxic.eu/#downloads
i18n: remove updaterInstall/Downloading/Installing/Download/ExperimentalHint
keys from all 7 locales; add updaterWebsite; update updaterVersion wording.
CI: remove generate-manifest job and all .sig signing/upload steps from
build-macos-windows. Also remove TAURI_SIGNING_PRIVATE_KEY env vars (no
longer needed). Release now just builds and uploads the installable binaries.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
TooltipPortal clears the overlay when mousedown hits any [data-tooltip] anchor so click-open controls (e.g. library scope) do not leave a stale hover tooltip. The library scope trigger omits data-tooltip while the dropdown is open.
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).
Swap DOM order and styles in the info cluster:
- fs-track-title: clamp(28px,4.5vw,68px) / weight 900 / var(--accent) — primary
- fs-artist-name: clamp(13px,1.5vw,22px) / weight 400 / 55% opacity — secondary
- fs-cluster gap: 12px → 8px (denser hierarchy)
- fs-meta: margin-top: 4px for breathing room after artist name
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Increase slot height from 3.6vh → 6vh (CSS + JS factor 0.036 → 0.06)
so that long lyric lines can wrap onto a second line without being clipped.
Fixed height on .fs-lyric-line is kept intentional — rail math requires
uniform slots. 2 wrapped lines at 2vh font / 1.4 line-height = 5.6vh,
comfortably inside the 6vh slot. font-size adjusted from 2.2vh → 2vh.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
CSS: remove `font-weight` from transition list on `.fs-lyric-line` — it
triggers layout reflow on every animation frame when the active line changes.
Replaced with `transform: scaleX(1.015)` on `.fsl-active` (compositor-only).
Added `contain: layout style` to `.fs-lyrics-overlay` to isolate reflows.
Cargo.lock: update for thread-priority crate (added in previous audio commit).
CI: replace delete-asset workaround with `updaterJsonKeepUniversal: false`
to prevent tauri-action from uploading a latest.json with wrong signature.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
The queue-current-track section already shows the active track above the
list. Scrolling to queueIndex+1 (next track) is the correct behavior —
it keeps the upcoming tracks in view, not the already-visible current one.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* stop queue panel from constant rerendering
* add queueHeader type
* fix(queue): move hook before early return, fix auto-scroll target
- usePlayerStore must be called before conditional return (Rules of Hooks)
- Auto-scroll to songs[queueIndex] not [queueIndex+1] — current track,
not the next one
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Psychotoxical <dev@psysonic.app>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add Russian UI strings with idiomatic phrasing and plural forms
- Split en/de/fr/nl/zh/nb strings into src/locales/*.ts for maintainability
- Register ru in i18n and Settings language picker
- Add languageRu to all locales; extend English help (supported languages)
Made-with: Cursor
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>
- fix: Radio from context menu passed artist name as ID to getSimilarSongs2 (closes#29)
- fix: CI Windows NSIS upload — tauri-action no longer searches missing bundle/msi/
- fix: Linux/AUR ring linker error — use cc instead of rust-lld via .cargo/config.toml
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Background of Hero carousel, Album Detail and Playlist Detail was
flickering for up to 20 seconds on first visit. useCachedUrl with
fallbackToFetch=true returned the raw server URL immediately, causing
a double render — once with the HTTP URL and again when the blob was
ready. Fixed by passing fallbackToFetch=false in all three locations.
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>
Merges songToTrack() helper and audio_update_replay_gain Tauri command
from @trbn1 without losing v1.22.0 DnD/queue-management work:
- All track construction sites use songToTrack() for correct replay gain
- psy-drag system (mouse-event DnD) preserved across all components
- enqueueAt() position-aware insertion preserved in QueuePanel
- updatePlaylist / smart-save / active-playlist tracking preserved
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replay gain was not working because track objects were created manually without
including replay gain metadata from the Subsonic API response.
Changes:
- Add songToTrack() helper function to properly map SubsonicSong to Track with
replayGainTrackDb, replayGainAlbumDb, and replayGainPeak fields
- Add audio_update_replay_gain Tauri command for dynamic volume recalculation
when replay gain settings change mid-playback
- Add updateReplayGainForCurrentTrack() to recalculate volume when toggling
replay gain setting
- Fetch fresh track data on cold resume (app relaunch) to ensure replay gain
values are current from server
- Update all files that create track objects to use songToTrack()
Fixes issue where toggling replay gain ON/OFF or changing between track/album
mode had no effect on currently playing or newly played tracks.
PR #7 blocked stale Rust progress ticks during the 100 ms debounce
window but lifted the guard exactly when invoke('audio_seek') went out.
The engine can still emit 1–2 ticks with the old position before the
seek takes effect. Add seekTarget: once the debounce fires, record the
requested time and keep ignoring progress until current_time is within
2 s of that target, then clear it.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add min-width to .player-time elements so they don't resize when the
displayed time string changes length (e.g. "9:59" → "10:00"). Not all
fonts support the tabular-nums font-variant, so without a min-width the
time elements can change size and cause the waveform seekbar to
continuously resize as playback progresses.
Also align the elapsed time to the right and duration to the left so
they sit flush against the waveform edges.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When clicking/dragging the waveform seekbar, the playback position would
briefly flash back to the previous position before snapping to the new
one.
The seek function optimistically updates the store with the target
position immediately but debounces the actual audio_seek IPC call by
100ms. During that window, audio:progress events from the Rust backend
continue to arrive carrying the old playback position, overwriting the
optimistic update and causing the visual flash.
The fix skips incoming audio:progress events while a seek debounce is
pending, since the store already holds the correct target position. Once
the debounce fires and audio_seek is sent to the backend, progress
events resume normally.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>