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>
- layout: overflow:hidden on .app-shell + min-height:0 on sidebar/main/queue
prevents player bar from being pushed off-screen when window is resized
below OS minHeight constraint (ignored by some Linux WMs)
- lib.rs: pass main window HWND to souvlaki PlatformConfig on Windows so
SMTC hooks into the existing Win32 message loop instead of creating its
own — fixes media keys on Windows without crashing
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
New themes: Aqua Quartz (Mac OS X Aqua, skeuomorphic), Spider-Tech,
T-800, B-Runner, Hill Valley 85, TetraStack, Turtle Power, Insta,
ReadIt, The Book (new Social Media group), W3.1, Jayfin (Jellyfin).
W98 rebuilt from scratch: authentic #d4d0c8 warm-gray, full 4-layer
3D bevel on all panels/buttons (raised/sunken on press), title bar
gradient on song name, navy progress fill, 16px styled scrollbar.
Aqua Quartz: all button variants now jelly-styled, blue Source List
sidebar with white pill nav, aluminium pinstripe background.
Theme Picker: groups and themes sorted alphabetically, Mediaplayer
group renamed, Pandora/Order of the Phoenix/Imperial Sith removed.
Fix: AlbumDetail genre propagation, W98 accordion active state,
Aqua Quartz sidebar labels, W98 connection indicator contrast.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Play All, Enqueue All, and single-song play in AlbumDetail were
building Track objects without genre/starred fields.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
### Added
- SVG logo with theme-adaptive gradient in sidebar (full wordmark + P-icon for collapsed state)
- Player bar: song title/artist marquee scroll on overflow
- Player bar: live volume percentage tooltip on slider hover
### Changed
- Sidebar collapse button moved to right-edge hover tab
- Player bar: fixed 320px track info width, increased waveform margins
- Settings: Server tab opens by default
- Crossfade: experimental badge removed (stable)
- Help page: Lyrics/Keybindings/Font entries added, theme count corrected
### Fixed
- Global shortcuts double-fire (Rust-side ShortcutMap idempotency fix)
- W98 theme: comprehensive contrast fixes for navy hover backgrounds
- Help page: removed orphaned translation key under Playback
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
### Audio
- Replay Gain support (track + album mode, configurable pre-gain, hard limiter)
- Crossfade between tracks (configurable duration 1–10 s)
- Gapless preloading ⚠️ experimental/alpha — enable in Settings → Playback
- Atomic sink swap: old track plays until new one is decoded, then stops cleanly
### UI / UX
- Settings redesigned with tab navigation (General, Playback, About)
- Changelog viewer in Settings → About with collapsible version entries
- Download Folder Modal: choose folder + "remember" checkbox per-download
- EQ popup now accessible directly from the Player Bar
- "Also Featured On" section on Artist pages for non-album appearances
### Fixes
- Bundle identifier changed from dev.psysonic.app → dev.psysonic.player (fixes macOS warning)
- Version sync: all four version files (package.json, Cargo.toml, tauri.conf.json, PKGBUILD) now at 1.6.0
### Known Issues
- FLAC seeking via waveform seekbar does not work (MP3/OGG unaffected) — under investigation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
### 10-Band Graphic Equalizer
- Full EQ in Rust audio engine via EqSource<S> biquad peak filters
- 10 built-in presets + custom preset save/delete
- EqSource::try_seek() implemented — also fixes waveform seek which broke
silently when EQ was introduced (rodio returned SeekError::NotSupported)
- EQ state persisted in localStorage, synced to Rust on startup
### Connection Indicator
- LED in header (green/red/pulsing) with server name and LAN/WAN label
- Offline overlay with retry button when server is unreachable
- useConnectionStatus hook
### New App Icon
- New logo (logo-psysonic.png) applied across Login, Sidebar, Settings,
README and all Tauri platform icons (Windows, macOS, Linux, Android, iOS)
### Now Playing Page
- New /now-playing route added
### Fixes
- WaveformSeek: mousemove/mouseup on window to fix drag outside canvas
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>
- Remove auto-refresh timer from Random Albums (caused 100ms progress
interval + 30 concurrent fetches every 30s, eventually freezing the app)
- Limit concurrent image fetches to 5 (was unbounded)
- Cap in-memory object URL cache at 150 entries with revokeObjectURL eviction
- Add cancellation flag to useCachedUrl to prevent setState on unmounted components
- Fix hardcoded "Neueste" page title in New Releases (now uses i18n)
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>
- Add full search results page (/search?q=) with artist, album, song sections
- Fix search results column alignment (fixed-width Format column eliminates per-grid auto-sizing variance)
- Remove experimental gapless playback (caused song skipping and beginning cutoffs)
- Add known limitations to README and CHANGELOG (seeking, DnD reliability)
- Update GitHub Actions to Node.js 24 (checkout@v5, setup-node@v5)
- Add align-items: center to tracklist-header CSS
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>