* perf(tracklist): replace animated EQ-bar + active-row pulse with static icon
The currently-playing track in any tracklist (AlbumDetail, ArtistDetail,
PlaylistDetail, Favorites, RandomMix) was rendered with two animations
on top of an already-busy DOM:
- `.track-row.active` ran `track-pulse` 3s opacity 1 → 0.6 → 1 infinite
on the entire row subtree (title button, several Lucide icons, star
rating SVGs, hover affordances). Opacity is a compositor property, but
on WebKitGTK without compositing — Linux + NVIDIA proprietary +
WEBKIT_DISABLE_COMPOSITING_MODE=1 — every animated row falls back to a
full software repaint of the subtree per frame.
- The "now playing" indicator in the track-number cell was three
`<span class="eq-bar">` siblings with `transform: scaleY()` keyframes
(`eq-bounce`), each on its own delay/duration. Same composite story:
three software-repainted layers per frame, every frame.
On AlbumDetail (long tracklist + cover-art header background + the
already-running WaveformSeek progress rAF in the player bar) the
combined cost held the WebProcess at ~80 % CPU with 1.2 GB RSS for the
entire duration of playback. CPU dropped immediately on pause/stop;
on Composers / Settings (no track rows) the symptom never appeared.
Profiler confirmed continuous Layout & Rendering work synchronised with
isPlaying, not the canvas itself.
Replace both animations with static visuals:
- `.track-row.active` keeps the `--accent-dim` background, drops the
pulse animation entirely.
- The "now playing" indicator becomes a single Lucide `AudioLines` icon
(four vertical bars of different heights — reads as an EQ icon, no
animation, one SVG per active row instead of three animated spans).
`.eq-bars` className now only sets the accent colour.
Cleanup: dead `@keyframes track-pulse`, `@keyframes eq-bounce`,
`.eq-bars.paused` rule, plus a duplicate `.eq-bar` block in theme.css
(with a `wave` keyframe that was being shadowed by components.css and
had no other consumers).
No behaviour change beyond removing the animation; the row is still
visibly the active one and the icon still marks the playing track.
* docs: CHANGELOG entry for tracklist animation perf fix (PR #488)
The squash-merge of PR #419 was performed against an outdated PR base
that predated several main-side refactors and features. The resulting
squash inadvertently re-introduced files that had already been removed
(`src-tauri/src/audio.rs` monolith, `app-icon.png`) and reverted main's
content for ~20 files (`src-tauri/src/lib.rs` decompose, `src/App.tsx`
animation-pause, `src/components/AlbumRow.tsx` headerExtra, etc).
This commit:
* Restores all collateral-damage files to their pre-#419 main state
(5662dafe), including the audio module split, lib decompose,
Cargo.toml `windows` dep, animation-pause-on-blur logic, and
`reducedAnimations` toggle plumbing in WaveformSeek/Settings.
* Keeps the genuine #419 work intact: tri-state duration toggle,
position counter, persistent Now Playing collapse, animated EQ
indicator (in QueuePanel.tsx, 8 locales, authStore, components.css).
* Merges authStore.ts so both `reducedAnimations` (from #426) and
`queueNowPlayingCollapsed` (from #419) coexist.
* Adds the `.eq-bars.paused` CSS rule manually since components.css
needed a 5662dafe base + the single #419 addition.
* Fixes a latent type error in OverlayScrollArea.tsx (`useRef<T>(null)`
is `RefObject<T>` / read-only under current `@types/react`; widened to
`useRef<T | null>(null)` so the existing `wrapRef.current = el`
assignment compiles).
Verified locally with `cargo check` and `npm run build` — both green.
* feat(queue): add ETA display, equalizer indicator and collapsible now playing
* deleted endsAt and showDuration strings, changed eta update to 30s
* feat(queue): ETA tooltip, persistent Now Playing collapse, EQ bar pause, remove redundant Play icon
* feat(queue): fold ETA into existing total/remaining toggle as third mode
The standalone ETA span next to the track counter is removed; instead the
clickable duration label in the queue header now rotates through three
modes per click: total → remaining → eta → total. Counter (N/M) stays
where it was.
ETA mode keeps the live-feel treatment from the original PR (accent
colour while playing, muted at 50% opacity when paused). The other two
modes use plain accent.
i18n: queue.etaTooltip removed (no longer a separate descriptive label),
queue.showEta added as the action tooltip ('Show estimated end time')
in all 8 locales — matches the showRemaining / showTotal pattern.
* docs(changelog): add #419 queue UX improvements entry
Adds the [1.45.0] / Added entry for this PR's queue panel refinements
(position counter, tri-state duration toggle including ETA, collapsible
Now Playing section, animated EQ indicator).
---------
Co-authored-by: Psychotoxical <171614930+Psychotoxical@users.noreply.github.com>
* fix(preview): sync audio start, ring animation, and download timeout
Three coupled fixes for the track-preview engine:
1. Audio sync. `Sink::try_seek` was running on a worker thread after
`sink.append(source)`, so the sink began playing position 0 while
the seek was still iterating to the mid-track target. With the
30 s `take_duration` cap counting wall-clock from append, audio
could only become audible ~25% into the preview window. The seek
now runs on the bare source before append, then `take_duration`
wraps it — playback starts at the seek position with the cap
measured from there.
2. Ring animation gating. The CSS progress-ring animation was
bound to `is-previewing` (set on click), so the ring sprinted
ahead of any download/decode/seek warmup and didn't reset
cleanly when switching from one preview to another. Added an
`audioStarted` flag in `previewStore` that flips on
`audio:preview-start` from the engine; CSS animation is now
gated on `audio-started` instead. `is-previewing` still drives
tooltip/icon for instant click feedback. Same SVG is reused for
a 25%-arc rotating loading spinner while waiting for audio,
with a 150 ms delay so cached/short previews don't flash.
3. Download timeout. The shared `audio_http_client` caps at 30 s,
which aborts mid-download on multi-hundred-MB uncompressed
files (e.g. 18-min Hi-Res WAV ~600 MB). The preview engine now
builds a dedicated client with a 5 min timeout for the bytes
fetch. Watchdog still bounds the playback window at 30 s once
the audio actually starts.
Touches `audio/preview.rs`, `previewStore.ts`, `components.css`
plus the eight tracklist/player-bar components that render the
preview button.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(changelog): add preview audio sync fix for PR #423
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(ui): unify queue toggle handle behavior
Show the queue toggle in the header when the queue is collapsed and use a seam-aligned drag handle when it is open. Hide the seam handle while the main content is actively scrolling to reduce accidental interactions.
* feat(ui): add adaptive header search collapse behavior
Collapse header search to a magnifier when top controls get crowded and expand it as an overlay only while active. Use measured header space with hysteresis to avoid flicker and keep neighboring controls stable.
* chore(ui): remove leftover search prototype artifacts
Drop an unused icon import from live search and remove an unused header container-type rule left from an earlier layout experiment.
* feat(ui): persist sidebar and queue visibility state
Save left sidebar collapse and right queue open/closed visibility in local storage helpers so both panel modes are restored after app restart.
* feat(ui): unify player overflow menu behavior
Use a single overflow menu for click and wheel interactions, with a volume-only mode that keeps the same layout and volume controls as the full menu.
* feat(ui): add wheel seek controls to waveform
Apply 10-second wheel seek steps with trailing 1-second debounce and keep the waveform preview stable so the playhead moves smoothly during rapid scroll input.
* fix(now-playing): stabilize narrow dashboard layout
Switch now-playing responsiveness to container-based breakpoints and prevent stacked widgets from overlapping when width is constrained.
* fix(search): reduce collapse jitter and avoid header overlap
Add a short collapse-state cooldown to prevent threshold flicker and hide conflicting header controls while collapsed search expands as an overlay.
* fix(i18n): localize player overflow controls across locales
Replace hardcoded player overflow labels with translation keys and add the missing keys for all shipped locale files.
* fix(search): keep advanced control clickable in collapsed mode
Prevent focus loss on the advanced search button in collapsed overlay mode so its click handler consistently runs.
* fix(i18n): restore queue translation in offline library
Use the existing queue.appendToQueue key for the offline enqueue button tooltip and label instead of a missing key and hardcoded English text.
* fix(ui): apply overlay scrollbar to right-panel text tabs
Switch now-playing content, lyrics, and info panes to OverlayScrollArea and harden tour-item layout so long concert metadata stays within panel bounds.
* fix(ui): add unread indicator for new releases and guard sidebar drag clicks
Track unread new-release IDs per server/library scope and clear the badge when opening the New Releases page. Also prevent click-through navigation after sidebar drag release and keep related i18n/responsive sidebar-adjacent refinements in this snapshot.
* fix(ui): stabilize live dropdown layering and unread reset flow
Render the topbar Live dropdown via a portal so it consistently overlays sidebar layers. Rework new-releases unread tracking to handle library scope baselines, ignore stale refresh races, and mark items as seen after a 5-second stay on the New Releases page.
* feat(ui): add localized New badges for recently added albums
Show a theme-consistent New badge on album cards and album detail for albums created within the last 48 hours. Localize the badge label across all supported locales and centralize recency logic in a shared utility to avoid duplication.
* fix(album): prevent tracklist jump when entering multiselect
Move bulk selection actions from the tracklist body into the album toolbar next to the track filter. Keep selection controls stable in the header area so enabling multiselect no longer shifts the tracklist content downward.
* fix(tray): add playback-state badge and finalize queue handle tooltip
Show play/pause/stop icons in the Linux tray now-playing entry and persist state safely in Tauri managed state.
Also switch the queue-resize handle tooltip to the dedicated localized key across all locales.
* fix(header): prioritize search collapse before Live/Orbit labels
Make topbar compression deterministic by collapsing search first and compacting Live/Orbit labels only in sustained low-space mode.
Add sticky hysteresis-based header compact state to prevent oscillation while resizing.
* fix(ui): stabilize header compaction and show tray state icons
Prevent topbar flicker in the narrow-width range by tightening compact-mode thresholds, gating on real overflow, and removing width transitions from live search.
Also include playback state icons in tray tooltip text across platforms while preserving tooltip length limits.
* fix(tray): keep tooltip iconization Windows-only
Revert Linux tray tooltip/title fallback attempts and keep state icons only in Windows tray tooltips, while Linux continues to show playback state in the now-playing menu entry.
* fix(ui): restore queue resize response after overlay scroll interactions
Hide the queue handle while scrolling on both the main route viewport and the now-playing viewport, and clear stale thumb-drag state before starting queue resize.
Also ignore inactive/faded overlay thumbs in resizer suppression so horizontal pointer transitions no longer leave the queue seam unresponsive.
* docs(changelog): summarize ui-refinements branch features
Document the branch-level feature additions in 1.45.0 as separate changelog sections and group remaining branch-local fixes under a single polish entry.
* docs(changelog): add PR #397 references for ui-refinements
Attach PR metadata to the new 1.45.0 ui-refinement sections and the polish entry so release notes map directly to the merged branch discussion.
* feat(player): preview-active state on play button (ring + stop icon)
Checkpoint: play button mirrors the inline preview button from tracklists
during preview playback — hollow circle, accent ring depleting over the
preview duration, Square (stop) icon. Click still resumes main playback,
which the Rust audio engine cancels the preview for.
i18n key player.previewActive in all 8 locales for tooltip + aria-label.
* feat(player): show preview track in player bar + smart stop semantics
The player-bar info cell (cover, title, artist) now mirrors the previewing
track during preview playback, with a small accent "Preview" pill above
the title and an accent top-border on the bar. Rating, fullscreen hint
and album/artist link clicks are suppressed while previewing — they
target the queued track, not the preview.
Stop semantics for the two transport buttons during preview:
- Big play button (Square+ring visual): stops preview, main auto-resumes
if it was playing before. Matches the tracklist preview-button behaviour.
- Small Stop button: new audio_preview_stop_silent Rust command — stops
preview AND leaves main paused, so "Stop = silence" actually goes silent.
previewStore now stores the full PreviewingTrack (id, title, artist,
coverArt) — the seven startPreview call sites pass it through.
i18n key player.previewLabel in all 8 locales.
* feat(audio): rust preview engine with secondary sink
Adds a parallel rodio Sink on the existing OutputStream for 30s
mid-track previews. Two new Tauri commands (audio_preview_play,
audio_preview_stop) plus three events (audio:preview-start /
-progress / -end). The main sink is paused with Sink::pause() and
auto-resumed on preview end iff it was playing beforehand.
* feat(playlists): migrate suggestion preview to rust audio engine
Replaces the HTML5 <audio> path with the new rust preview engine.
previewStore mirrors the engine's start/progress/end events so any
tracklist row can render preview UI from a single source of truth.
Spacebar redirects to stopPreview while a preview plays, hardware
mediakeys are silently dropped (Q5), and tray clicks cancel the
preview before forwarding the original action.
* feat(albums): inline play + preview buttons in tracklist rows
Track number stays static on hover instead of swapping to a play
icon — the dedicated Play and Preview buttons in the title cell
take over click-to-play and click-to-preview. Active+playing rows
keep the eq-bars (also on hover), active+paused rows fall back to
the static accent-coloured number.
Pilot for the wider rollout to other tracklists.
* feat(tracklists): roll out inline play + preview buttons
Mirrors the AlbumTrackList pilot across the remaining track-row
based lists: PlaylistDetail main tracks, Favorites, ArtistDetail
top tracks, RandomMix (both genre-mix and filtered-songs lists).
Track number stays static, the dedicated Play + Preview buttons
in the title cell take over click-to-play and click-to-preview.
* feat(settings): track preview toggle + configurable position and duration
Adds an opt-out switch and two sliders to Settings → Audio:
start position (0-90 % of track length, default 33 %) and preview
duration (5-60 s, default 30 s). The progress-ring animation
follows the duration via a CSS variable so the visual matches the
engine's auto-stop. Disabling the feature hides every inline
preview button via a single root-level data attribute, no per-row
conditional rendering required.
i18n keys added in all 8 locales.
* fix(audio): cancel preview when main playback (re)starts
audio_play, audio_play_radio, audio_resume and audio_stop did not
know about the parallel preview sink, so clicking Play on a track
that was currently being previewed left the preview running on top
of the freshly started main playback. New helper clears the resume
flag, bumps the preview generation, drops the sink and emits an
'interrupted' end event before any of those commands touches the
main sink.
* feat(settings): per-location track preview toggles
Splits the single trackPreviewsEnabled toggle into a master + 6 per-location
sub-toggles (suggestions, albums, playlists, favorites, artist, randomMix).
Master remains the kill switch; sub-toggles are only honoured when master
is on. Each tracklist container is marked with `data-preview-loc="<id>"` and
hidden via scoped CSS when the matching root attribute is "off".
startPreview now takes a location argument so the store can guard logic too.
i18n added in all 8 locales.
* fix(contextmenu): use ChevronsRight for Play Next to distinguish from preview
- Add "Reset to defaults" button to column picker in AlbumTrackList, PlaylistDetail, Favorites (all 8 locales)
- Fix device sync cross-OS status detection: store filenameTemplate in manifest, use it for path computation on import
- Redesign filename template UI: preset buttons (Standard/Multi-Disc/Alt. Folder), clickable token chips ({artist}, {album}, etc.), clear button
- Fix suggestions section in PlaylistDetail missing album column rendering
- Add PRIVACY.md documenting all third-party integrations (Last.fm, LRCLIB, Apple Music, Discord); link from README
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
.content-body has overflow-x:hidden — setting overflow-x:visible on
.tracklist would clip wide tables instead of scrolling them. Keep auto.
Also remove Spanish code comments (contributors are international).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces dedicated sort buttons with clickable column headers in album and playlist detail views. Three-click cycle: asc → desc → natural order. Sortable by title, artist, album, favorite, rating, duration. Active column shown bold with ▲/▼ indicator.
- Use OpenSubsonic `artists[]` array to render each artist separately
with · separator; artists with an ID are clickable, others plain text
- Fall back to single artist (artistId/artist) on non-OpenSubsonic servers
- Settings reset-to-defaults buttons changed from btn-ghost to btn-danger
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.
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.
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>
### 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.
- 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>