Replace the mixed ReplayGain/Loudness controls with a single exclusive mode selector, hide RG-specific controls when LUFS is active, and align the queue tech badge behavior between RG and LUFS. Also update LUFS targets to -10..-16 ordering and default loudness target to -12.
Improve normalization consistency by separating cache refresh for non-playing tracks, hardening track-id cache lookups, and keeping UI gain state aligned with actual playback. Also reduce startup stalls by preferring non-seekable streaming when format hints are missing and by tuning ALSA/analysis paths to avoid underrun-prone churn.
Reported in #274 — the icons for the Gapless and Infinite Queue toggles
were the wrong way round. The infinity symbol was on Gapless, but reads
much more naturally on Infinite Queue (a queue that never ends), and the
arrow-up-to-line on Infinite Queue had no obvious mapping at all.
Move infinity to Infinite Queue. Use MoveRight (right-pointing arrow) for
Gapless — visualises 'flows straight through to the next track without
stopping' and stays distinct from the chain/link family at 13 px in both
the QueuePanel and Mini Player toolbars.
Closes#274
- Drop the ~110 lines of dead full-screen overlay CSS that was never
referenced in JSX (.lucky-mix-overlay*, .lucky-mix-cube*, full-screen
@keyframes). Keep the .lucky-mix-pip* rules since the inline queue
variant re-uses them.
- Extract availability gate into hooks/useLuckyMixAvailable.ts (pure
predicate + hook). Replaces five duplicated inline checks in Sidebar,
MobileMoreOverlay, RandomLanding, Settings/SidebarCustomizer, and the
internal re-check in buildAndPlayLuckyMix.
- Add a cancel mechanic:
* luckyMixStore gets a cancelRequested flag + cancel() method
* LuckyMixCancelled sentinel is thrown from a bailIfCancelled() helper
sprinkled between await boundaries in the build loop
* catch-block swallows the sentinel silently (no toast, no error state)
* auto-cancel subscription on playerStore detects manual user track
changes (anything not in queuedIds) and flips cancelRequested so the
finished mix does not later overwrite the user's choice
* inline .queue-lucky-loading dice indicator is now a button — click
triggers explicit cancel, hover fades the dice to signal the action
- Restore the queue snapshot when the build fails before ever starting
playback, so the user does not land in an empty player after an error.
If playTrack already ran, leave current state alone (their track plus
whatever was enqueued is more useful than a stale pre-click queue).
- Rework locale labels to consistently carry the "Mix" suffix so the
entry reads well next to "Random Mix" / "Random Albums" in the
sidebar: DE Glücks-Mix, ES Mezcla Suerte, FR Mix Chance, NB Lykkemiks,
NL Geluksmix, ZH 好运混音. EN stays "Lucky Mix", RU stays
«Мне повезёт». Toast/settings/randomNavSplit copy aligned to the new
names. New luckyMix.cancelTooltip key added in all 8 locales.
AudioMuse gating stays — as Cuca confirmed, the feature's quality relies
on a correct similar-track selection, so the requirement is intentional.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lucky Mix targets ~50 tracks in one run: pick seeds from your most-played artists/albums and 4+ rated songs, add similar tracks, then fill the mix with random library picks, skipping anything you rated 1–2.
On start it trims the old “upcoming” tail so the queue does not show stale next tracks, starts playback on the first viable seed, routes to Now Playing, and can emit structured steps to the backend debug log.
* feat(share): library deep links (psysonic2) with paste and toolbar actions
Add base64url-encoded payloads for track, album, artist, and ordered queue.
Handle paste outside inputs to switch server, validate entities, navigate or
play. Reuse clipboard helper for context menu, queue panel, album and artist
headers. Tests distinguish server invite strings from library shares.
* fix(share): play partial queue when some shared tracks are missing
Skip unavailable song IDs when pasting a queue share while preserving order.
Toast when some tracks are missing; error only if none resolve. Add
openedQueuePartial and queueAllUnavailable i18n; remove queueTracksMissing.
* feat(settings): paste server invites globally and scroll to add form
Handle psysonic1- magic strings outside inputs: navigate to settings or login
with prefilled add-server fields. Decode invites embedded in surrounding text.
Scroll the add-server block into view when opening from a paste so long server
lists stay oriented.
---------
Co-authored-by: Maxim Isaev <im@friclub.ru>
Add OverlayScrollArea with shared thumb metrics and drag handling; size the
thumb against the rail track height so panel insets cannot push it past the
visible rail. Route scroll uses a stable viewport id; Genres restores scroll
and infinite-scroll observation against that viewport (merged with upstream
virtualized genres list and lazy routes behind Suspense).
Suppress queue resizer activation when the pointer targets the main-route
overlay scrollbar; disable the resizer while dragging the thumb and use a
grabbing cursor on the body.
Apply WebKitGTK smooth wheel to the mini webview as well as main; the mini
window reapplies the persisted setting after auth store hydration.
* feat(queue): add Now-Playing Info tab with artist bio, song credits and Bandsintown tour dates
A third tab in the right-side queue panel surfaces context for the
currently playing track:
- Artist card: image + biography from Subsonic getArtistInfo (Last.fm
"Read more on Last.fm" anchor stripped), with read-more toggle that
only appears when the bio actually overflows the 4-line clamp.
- Song info: contributor credits from OpenSubsonic contributors[]
rendered stacked (name prominent, role muted). Section is hidden
entirely on servers without contributor support, and rows that just
re-state the main artist under role "artist" are filtered out.
- On tour: optional Bandsintown integration (opt-in, off by default).
HTTP fetch + JSON parsing happen entirely on the Rust side; the
frontend wrapper deduplicates concurrent calls and caches results in
RAM for the session. Limited to 5 events with a "Show N more" toggle.
When the toggle is off, the section becomes an in-place opt-in card
with a privacy info-tooltip explaining what data is sent — same
tooltip is also exposed on the matching toggle in Settings.
Caching: artist info and song detail are memoised by stable IDs across
component remounts, so jumping between tracks of the same album/artist
does not refire the network calls.
Implementation notes:
- Bandsintown app_id "js_app_id" — arbitrary strings (e.g. "psysonic")
now return HTTP 403 from rest.bandsintown.com; js_app_id is the ID
Bandsintown's own embeddable widget uses and is broadly accepted.
- Tour items use negative left/right margins so the date badge stays
visually aligned with the section title while the hover background
extends slightly past the section edges.
- New i18n namespace nowPlayingInfo across all 8 locales (en, de, fr,
nl, zh, nb, ru, es) including pluralised "Show N more" forms.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(now-playing-info): switch nested flex-columns to block layout to stop content-dependent drift
Repeated reports of the Tour and Song-info sections rendering at
inconsistent x-positions (sometimes left of the section title,
sometimes centred, sometimes correctly aligned) — varying by artist,
sidebar width, and even whether "Show more" was clicked.
Root cause: nested flex-direction: column containers
(.np-info → .np-info-section → .np-info-tour / .np-info-credits) where
the cross-axis (horizontal) sizing on WebKitGTK occasionally inherits
the intrinsic min-content of the longest child. With one
"Naherholungsgebiet/Freizeitgelände Lago Alfredo"-style venue name
that overflows the sidebar, the parent ul gets pushed past 100% width
and the entire layout drifts. min-width: 0 on every level didn't help
because cross-axis stretch in flex-column ignores it for content-driven
overflow.
Fix: collapse all the section/list containers to display: block. Block
layout is content-agnostic — children always render at 100% parent
width, left-aligned, deterministically. Spacing between siblings now
uses the lobotomy selector (`> * + * { margin-top }`).
Only the tour item itself stays flex (badge ↔ meta horizontal layout),
and that one still has overflow: hidden + flex: 1 1 0 + min-width: 0
on the meta column to truncate venue/place text with ellipsis.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Psychotoxical <dev@psysonic.app>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per @cucadmuh's feedback: the presence of ReplayGain matters more than
the actual numbers. The tech bar now shows a small 'RG' pill on line 1
whenever the track has any RG metadata; hovering reveals the values via
tooltip, clicking persistently expands the second line.
- Source icon stays inline before the format string (was getting
separated from FLAC by the centred stack layout)
- Pill uses --accent-tinted color-mix so it adapts to every theme
- ChevronDown rotates on expand for a clear affordance
- New persisted state: themeStore.expandReplayGain (default collapsed)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The tech info strip in the queue header was a single ellipsised line, so
ReplayGain values (Track / Album / Peak) got cut off on tracks with a
full codec + bitrate + samplerate prefix. Now the strip wraps into two
lines whenever RG values exist:
- Line 1: codec · bitrate · bit depth/sample rate (unchanged)
- Line 2: 'ReplayGain · T … dB · A … dB · Peak …' — slightly smaller
and dimmed for hierarchy, ellipsises independently if it still
overflows.
Tracks without RG metadata stay one line as before. New i18n key
`queue.replayGain` ('ReplayGain') added to all 8 locales.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add source badges for offline, cache, and stream playback in the queue tech line, including localized tooltips across shipped locales.
Track Rust preload-ready events by stream track id and latch the selected source per track, with dev logs to debug preload/source mismatches during next-track handoff.
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.
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>
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>
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.
Themes:
- Add Poison (phosphor green on charcoal), Nucleo (warm brass light), Classic Winamp (LCD glow, Winamp yellow/orange) themes
- Overhaul Psychowave — refined deep violet, no longer WIP
- Reorganise ThemePicker into groups: Catppuccin, Nord, Retro, Tokyo Night, Psysonic Themes
- Add --volume-accent CSS var for per-theme volume slider colour override
Queue:
- Full toolbar redesign with centred round buttons (Shuffle/Save/Load/Clear/Gapless/Crossfade)
- Crossfade popover with 1–10 s range slider, right-aligned to avoid viewport overflow
- Queue header: title + count + duration inline in accent colour, close button removed
- Tech info (codec/bitrate) as frosted-glass overlay badge on cover art
UI:
- CoverLightbox shared component for album cover (AlbumHeader) and artist avatar (ArtistDetail)
- NowPlayingDropdown: separate spinning/loading state — button always clickable, icon spins 600 ms min
- Settings: "Experimental" badge on Crossfade and Gapless toggles
- Help page: 2-column grid layout, new Crossfade & Gapless Q&A entry, updated theme/language entries
i18n:
- Full French (fr) and Dutch (nl) translations across all namespaces
- Language selector sorted alphabetically (nl, en, fr, de)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Queue: year added to now-playing meta, cover enlarged to 90px + top-aligned, default width 340px
- Artist pages: Last.fm/Wikipedia open in system browser with button label feedback
- README: AUR badge + install section, Nord theme mention, in-app browser bullet removed
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Random Mix: remove redundant Play All button in genre mix section
- Random Mix: Play All disabled with live counter during genre mix loading
- Random Mix: cap genre fetch at 50 genres to prevent over-fetching
- Random Mix: cache-bust getRandomSongs to always get fresh results
- Random Mix: clear songs on remix to fix display/play mismatch
- Queue: show song count and total duration in header
- Queue: context-active class keeps hover highlight on right-click
- Context Menu: add Favorite option for queue-item type
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Statistics page: library stat cards, recently played, most played, highest rated, genre chart
- Playlists page: list layout with sort (Name/Tracks/Duration) and filter input
- Favorites songs: full tracklist layout with artist column, context menu, enqueue-all button
- AlbumDetail: extracted AlbumHeader and AlbumTrackList components
- Artist cards: square cover, same sizing as album cards (clamp 140-180px)
- Random Albums: removed renderKey remount, added loadingRef guard, fixed manual refresh race
- Context menu: "Go to Album" option for song and queue-item types
- Queue panel meta box: artist → artist page, album → album page, removed year
- Random Mix: hover persistence via .context-active class while context menu is open
- i18n: "Warteschlange" consistently used for queue in German
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- PlayerBar redesigned: canvas waveform seekbar (500 bars, blue→mauve gradient + glow) replaces thin slider; new flex layout; queue toggle moved to content header (consistent with sidebar pattern)
- MilkDrop visualizer in Ambient Stage via Butterchurn; hidden audio analysis, preset shuffle, smooth blend transitions
- Tracklist: animated EQ bars for playing track, play icon when paused, align-items: center fix
- Artist pages: Last.fm + Wikipedia open in native Tauri WebviewWindow (in-app browser)
- Hero/Discover deduplication: single fetch of 20 random albums split between sections
- Update checker: runs every 10 minutes during runtime; version shown without v-prefix
- Settings version: now read from package.json instead of hardcoded
- Help page: new Random Mix section, updated Playback + Library sections, improved accordion styling
- Bump version to 1.3.0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
### Audio Engine
- Replace Howler.js with native Rust/rodio backend (src-tauri/src/audio.rs)
- audio_play/pause/resume/stop/seek/set_volume Tauri commands
- Generation counter cancels stale in-flight downloads on skip
- Wall-clock position tracking; audio:ended after 2 ticks near end
### Playback Persistence
- Persist currentTrack, queue, queueIndex, currentTime to localStorage
- Cold-start resume: play + seek to saved position on app restart
- Position priority: server queue > localStorage
### Random Mix
- Genre blacklist: excludeAudiobooks toggle + custom chip-based blacklist
- Clickable genre chips in tracklist to blacklist on the fly
- Super Genre Mix: 9 genres, progressive rendering, Load 10 more
- Promise.allSettled + 45s timeout for large Metal/Rock libraries
- Two-column filter panel at top of page
### Queue & UI
- Shuffle button in queue header (Fisher-Yates, keeps current track at 0)
- LiveSearch arrow key / Enter / Escape keyboard navigation
- Multi-line tooltip support (data-tooltip-wrap attribute)
- Update link uses Tauri shell open() to launch system browser
- Sidebar min-width 180px → 200px (fixes Psysonic title clipping)
- Tooltip z-index fix: main-content z-index:1 over queue panel
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- playerStore: Guard onend against spurious 'ended' events fired by
WebKit/GStreamer immediately after a direct audioNode.currentTime seek.
Root cause of the deterministic "second click on progress bar stops song"
bug: a lastSeekAt timestamp + position check now silently drops any
'ended' event that fires within 1 s of a seek if the playhead isn't
actually near the track end.
- playerStore: Debounce togglePlay with a 300 ms lock to prevent rapid
double-clicks from issuing pause→play before GStreamer has finished its
state transition, which caused a multi-second pipeline hang.
- NowPlayingDropdown: Clicking a Live entry now navigates to the album page.
- QueuePanel: DnD drop target index now calculated from clientY position
at drop time instead of refs, eliminating the dragend-before-drop race
on macOS WKWebView and Windows WebView2.
- styles: Increased Hero background blur for a more immersive look.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix queue DnD on macOS/Windows: delay ref cleanup in onDragEnd so onDropQueue
reads correct source/destination before dragend clears them
- Active track highlighting in album tracklist (pulsing accent bg + play icon)
- Marquee scrolling for long titles in Fullscreen Player
- Clickable artist/album in Player Bar and Queue now-playing strip
- Tracklist: format column moved after duration, auto width, kHz/filesize removed
- Settings dropdowns: visible border (base bg + overlay0 border)
- Sidebar: fixed responsive width via clamp(), removed drag-to-resize
- Favorites icon: HandMetal → Star in Random Mix page
- Linux app menu category set to Multimedia (AudioVideo)
- Windows MSI: stable upgradeCode for in-place upgrades
- Bundle: category/description fields at correct config level
- About: Claude Code credit, fixed German MIT licence wording
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>