- Type `window.__psyHidden` once via global declaration; drop 6 `(window as any)` casts.
- `WindowVisibilityProvider` now ORs `window.__psyHidden` into the hidden state
(and into the initial state). On WebView2 `document.hidden` does not always
flip when `win.hide()` is called, so effects gated by `useWindowVisibility()`
can now actually skip creating intervals/rAF instead of relying solely on the
per-tick fallback check.
- Slow visible-poll from 200 ms to 500 ms (5 → 2 wakeups/s for visibility alone).
- Fix stray leading-space indent in `App.tsx` `window:close-requested` comment.
Co-authored-by: Psychotoxical <dev@psysonic.app>
* fix(windows): stop GPU rendering when windows are hidden
* perf(ui): tighten hidden-window rendering mitigation
Align implementation and documentation with what the injected pause/resume
scripts actually do, reduce main-thread and compositor wakeups while windows
are invisible, and make lifecycle cleanup reliable.
Rust (Tauri)
- Document PAUSE/RESUME as compositor-oriented hints: set __psyHidden, optional
--psy-anim-speed for CSS that opts in, and pause @keyframes via
animation-play-state only (not CSS transitions, not arbitrary timers or rAF).
- Walk descendants under #root instead of document-wide querySelectorAll('*')
to cut cost on large pages; skip the walk if #root is missing (flag still set).
- Call eval(PAUSE_RENDERING_JS) before hide() on all hide paths (tray menu,
tray icon toggle, mini open/close/show-main, native mini close) so the
webview sees __psyHidden while still fully schedulable.
- Shorten pause_rendering command rustdoc to match the script.
Frontend
- WindowVisibilityProvider: replace self-rescheduling useCallback with a single
effect that keeps one pending timeout id, uses a cancelled flag on unmount,
and syncs hiddenRef from document.hidden at mount (no orphaned timers after
tests/HMR).
- WaveformSeek (preview + animated seekbar): while hidden, poll with a 400 ms
timeout instead of requestAnimationFrame every frame; cancel both rAF and
timeout on teardown.
Why
- Accurate comments prevent future refactors from assuming “full JS idle”.
- Smaller DOM walks and fewer rAF wakeups reduce spikes when hiding the main
window or mini player on Windows WebView2 and elsewhere.
* perf(ui): pause global CSS + timers when Tauri hides the window
Builds on PR #273 (`7803d8e` + `0e07a73`). Adds a second HTML flag driven only by
Rust pause/resume inject so infinite animations still pause when WebView2 keeps
`document.hidden === false` after `win.hide()`, and stops a few periodic JS
timers while the window is invisible.
- `lib.rs`: set/remove `data-psy-native-hidden` on `<html>` in PAUSE/RESUME JS;
document why in rustdoc (rest of pause/resume unchanged from PR tip).
- `components.css`: same `animation-play-state: paused !important` rules as
`data-app-hidden` for `[data-psy-native-hidden="true"]`; refresh the comment.
- `App.tsx`: comment distinguishes tab visibility vs Tauri-native hide.
- `Hero.tsx`: do not run the 10 s carousel interval while hidden
(`useWindowVisibility` + `__psyHidden` in tick).
- `PlaybackScheduleBadge.tsx`, `playbackScheduleFormat.ts`: no 400/500 ms
intervals while hidden; skip ticks when `document.hidden` or `__psyHidden`.
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.
Block capture-phase dragstart in the webview so stray native HTML5 drags
(e.g. SVG reorder grips) do not create a stuck translucent GTK surface on
Wayland compositors.
Harden PsyDnD lifecycle when mouseup never reaches the webview: blur,
visibilitychange, pointerup/cancel in capture, and Escape cancel the
floating label without dispatching a drop.
Add -webkit-user-drag:none on sidebar customizer grips.
* 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.
Cold-start bundle was a single 1.88 MB / 577 KB-gzipped chunk that
parsed every page in the app before first paint, including pages a
typical session never opens.
Two-part fix:
1. Lazy-load 10 rarely-visited pages via React.lazy() — each becomes
its own chunk that only downloads when the route is hit:
Settings, Statistics, Help, WhatsNew, DeviceSync, OfflineLibrary,
LabelAlbums, AdvancedSearch, FolderBrowser, InternetRadio.
Whole <Routes> tree is wrapped in <Suspense fallback={null}> — no
visible loading state, the browser cache hits on subsequent visits.
2. Vite manualChunks splits dependencies that change rarely from app
code: react/react-dom/react-router, the @tauri-apps/* family, and
i18next. Tauri auto-updater pulls smaller deltas when only app
code changed (vendor chunks stay byte-identical and cached).
Build output:
Before: 1.88 MB / 577 KB gz monolithic
After cold start: ~450 KB gz (index 370 + react 54 + tauri 6 + i18n 20)
= 22% smaller initial download
Plus 11 lazy chunks. Notably WhatsNew's bundled CHANGELOG (215 KB /
76 KB gz) is now off the cold-start path entirely.
Co-authored-by: Psychotoxical <dev@psysonic.app>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add a System setting for Off/Normal/Debug logging, apply readable local timestamps to backend logs, and enable exporting buffered runtime logs to a file when debug mode is active.
Co-authored-by: Maxim Isaev <im@friclub.ru>
Inspired by kveld9's PR #211 proposal. Adapted to stay within WebView2
and WKWebView GPU budgets — 8px blur instead of 28, theme-token colours
via color-mix instead of hardcoded white, no saturate/brightness/
contrast stack, no transitions, no !important.
Linux keeps the solid floating bar from PR #216 on every compositing
mode. Platform is detected once on mount via a data-platform attribute
on <html>.
Co-Authored-By: kveld9 <179108235+kveld9@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Both AppShell and PlayerBar read floatingPlayerBar via destructured
useThemeStore() without a selector, so every unrelated theme change
(accent, font, scheduler state, …) would force a re-render. Switch to
s => s.floatingPlayerBar so these components only re-render when the
toggle actually flips.
Follow-up to PR #216.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a "Preload mini player" toggle in Settings → General → App behaviour
(off by default). When enabled, the mini webview is built hidden at app
start so the first open is instant, matching the Windows experience.
Costs one extra WebKit process running in the background (~50–100 MB).
Windows already pre-creates the mini unconditionally as a hang workaround
(commit 71fbc717); the toggle is hidden there and the invoke is skipped,
so that platform is untouched.
- authStore: new `preloadMiniPlayer: boolean` (default false) + setter.
- lib.rs: new `preload_mini_player` command; idempotent, no-op when the
window already exists. Registered in the invoke handler.
- App.tsx: main window invokes `preload_mini_player` when the toggle is
on and the platform is not Windows.
- Settings.tsx: toggle row under "Minimize to Tray", gated with
`!IS_WINDOWS` so Windows users don't see it.
- i18n: `preloadMiniPlayer` + `preloadMiniPlayerDesc` added to all 8
locales (en, de, es, fr, nb, nl, ru, zh).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds an 'open-mini-player' entry to the in-app keybindings (Settings →
Shortcuts), default unbound. Pressing the chord from the main window
opens the mini and minimises main; pressing it from the mini hides the
mini and restores main — both paths invoke open_mini_player which
already handles the toggle.
The mini window has its own keydown listener (same pattern as Space /
arrows for play/skip) that reads the binding from useKeybindingsStore.
Binding changes in main propagate to an open mini through the existing
storage-event sync (now also covers psysonic_keybindings) so the user
doesn't need to restart the mini after rebinding.
Localized in all 8 supported locales.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This iteration fills in the pieces that make the mini player usable as a
daily standalone window:
- Window position persists to <app_config_dir>/mini_player_pos.json on
every WindowEvent::Moved (throttled 250 ms); first launch lands in the
bottom-right of the main window's monitor.
- Queue keeps a 260 px floor when expanded (~2 visible rows). The
user-resized expanded height is restored on next toggle via
localStorage so reopening doesn't snap back to the default 440 px.
- set_mini_player_always_on_top(true) now forces a false-then-true cycle
so the WM re-evaluates the layer after a hide/show; the frontend also
re-asserts the pin state on mount and on window focus, so the user no
longer has to click the pin button twice for it to stick.
- Native scrollbar in the queue is hidden in favour of a JS-driven
overlay thumb so items use the full width of the queue area.
- PsyDnD reorder works inside the mini queue: drag emits 'mini:reorder',
the bridge calls reorderQueue on the source-of-truth store in main.
- Localized queue-item context menu (MiniContextMenu) with Play now /
Remove from queue / Open album / Go to artist / Favorite / Song info.
Cross-window actions forward to main via new bridge events
(mini:reorder, mini:remove, mini:navigate, mini:song-info); a
psy:navigate CustomEvent picked up by AppShell handles routing.
- Theme, font and language changes in main propagate live to the mini
via a 'storage' event listener that re-hydrates the persisted Zustand
stores and calls i18n.changeLanguage.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Removes the giant startup changelog modal. After an update, a compact
neutral-palette pill now sits above Now Playing in the sidebar saying
"Changelog — vX.Y.Z". Clicking opens a proper /whats-new page in the
main content area; X dismisses. Page renders the current version's
CHANGELOG entry with inline Markdown (headings, lists, blockquotes,
hr, bold/italic/code, and real [label](url) links that open via the
Tauri shell plugin).
Visual tokens are hardcoded slate/gray/blue so the banner and page look
identical across every theme (dark, light, skeuomorphic, whatever) —
requested for consistent contrast. Auto-mark-as-seen removed from the
page; the banner only goes away on explicit dismiss, so users can
re-read as often as they want.
Settings → About gains a "Release notes" row with a link that resets
the seen-version and opens the page, so the banner can be retriggered
manually (also helpful for dev builds ahead of the current tag).
The existing "Show changelog on update" toggle now gates the banner
instead of the modal; description strings updated in all 8 locales.
fix(themes): WCAG contrast audit — mocha + winmedplayer + wista
Mocha (Catppuccin dark)
.track-size used --ctp-overlay0 directly (3.36:1 on bg-app, 2.57
on bg-card). Swapped to --text-muted → 7.37 / 5.65. Fix also lifts
macchiato/frappe/latte which shared the failure.
WinMedPlayer (Luna)
--text-muted #b8d0f8 (3.87:1 on bg-app) → #e8f0ff (5.28)
--border #2a5090 (1.31 on bg-app) → #071027 (3.12, meets 3:1 UI)
Wista (Vista Aero)
Lyrics pane sits on bg-sidebar #0e1e3e but used --text-primary
(#0d1d3c) for active lines — 1.01:1, literally invisible. Added
component overrides: .lyrics-line / .lyrics-status / word-synced
variants → #aac8f0 (9.60), .lyrics-line.active → #ffffff (16.48).
Palette: --warning #c8980c → #735a00 (2.37 → 5.91 on bg-app),
--text-muted #4870a8 → #3f6aa0 (4.23 → 4.65 on bg-card).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A second webview window (label "mini") with a compact player: album art,
title, artist, prev/play/next, progress bar, pin-on-top toggle, expand
back to main, close. Main minimizes on open and restores when the mini
is hidden or closed. Spacebar toggles, arrow keys skip tracks.
Cross-window sync: main window subscribes to playerStore and pushes
`mini:sync` via emitTo on track / play-state change. Audio progress
already broadcasts to all windows via `audio:progress`. Control actions
(prev/next/toggle) come back via `mini:control`; main-window restore
goes through a direct Rust command because WebKitGTK pauses JS in a
minimized webview.
Tiling-WM detection reused from existing `is_tiling_wm()` — always-on-top
is skipped on Hyprland/Sway/i3 since it's ignored there anyway.
Early alpha: no queue expand, no lyrics, no EQ, no drag-snap. Scope kept
deliberately tight for v1; follow-ups planned.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1. Tray double-click (reported by @cucadmuh's brother-in-law):
Windows fires a Click event on *both* halves of a double-click, so
our left-click handler toggled visibility twice — the window popped
up and immediately vanished. Switch the Windows branch to the
`TrayIconEvent::DoubleClick` variant (Windows-only in tray-icon);
other platforms keep the Click-on-Up behaviour. Matches the standard
Windows tray convention (Discord, Telegram, etc).
2. GPU use while minimized:
WebView2 on Windows keeps compositing infinite CSS animations
(mesh-aura-a/b, portrait-drift, eq-bounce, track-pulse, led-pulse …)
even when the window is minimized — steady visible GPU load on
systems that should be idle. App.tsx now toggles a
`data-app-hidden="true"` attribute on <html> on `visibilitychange`,
and a single CSS rule pauses every `animation` at once via
`animation-play-state: paused !important`. Zero cost when visible,
catches all current and future infinite animations without
per-element listeners.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Persist preference in auth store, sync from App on Linux, and expose a
Tauri command using webkit2gtk to toggle enable-smooth-scrolling at
runtime. Default on to match upstream; users may disable for discrete
GTK-style line steps.
Apply a one-time rehydrate migration so smooth scrolling stays on after
updates even if an older build persisted the wrong default.
Document-level zoom broke portal positioning and Tauri window measurement,
so the slider had been forced to 100 %. Scope the zoom to a wrapper inside
.main-content instead — sidebar, queue, player bar and the Linux custom
title bar live in sibling grid cells of .app-shell and stay 1:1.
- App.tsx: drop the document-level zoom + auto-reset; wrap main-content
children in <div class="main-content-zoom" style={zoom: uiScale}>.
- layout.css: add .main-content-zoom (flex column, fills parent).
- Settings.tsx: re-enable the slider and snap it to the 6 preset stops
(80/90/100/110/125/150) so the thumb lands directly under each preset
button. Legacy off-preset values snap to the nearest stop on load.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add CLI commands for stop, shuffle, repeat, mute, star, rating, reload,
server list/switch, and Subsonic search; publish extra snapshot fields and
server/search JSON for tooling. Single `play <id>` resolves track, album,
or artist; artists play a shuffled full discography.
Guard Linux tray creation with catch_unwind when Ayatana/AppIndicator .so
is missing. Harden bash completion against zsh sourcing (compopt). Narrow
AudioEngine field visibility to silence private_interfaces warnings.
Embed bash and zsh completion scripts and add a `completions` subcommand.
Extend the player CLI (library, audio device, instant mix) and surface
`music_library` in snapshots. Add a shared active-server switcher in the
header and locales; instant mix can reseed the queue from CLI and UI.
Adds a new Device Sync page for transferring music from Navidrome to
USB drives or SD cards. Sidebar entry is hidden by default.
- Four new Tauri commands: sync_track_to_device, compute_sync_paths,
list_device_dir_files, delete_device_file
- Filename template engine with cross-platform path sanitization
- 4-concurrent-worker download, live progress via device:sync:progress event
- Persistent source list (albums/playlists/artists) with checkbox deletion
- Expandable artist tree in browser panel (per-album selection)
- i18n: DE + EN complete; other locales have stub keys
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Device watcher requires 3 consecutive misses (~9 s) before triggering
audio:device-reset, preventing false positives when ALSA temporarily
hides a busy device from output_devices() enumeration
- Add stderr suppression to open_stream_for_device_and_rate (last source
of ALSA terminal noise on Linux)
- Fix tooltip showing raw key 'common.refresh' — replaced with
settings.audioOutputDeviceRefresh (added to all 8 locales)
- Device switch restarts track from beginning (no seek-back)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds the ability to choose which audio output device Psysonic plays
through — useful for USB DACs, dedicated soundcards, and multi-device setups.
Rust (audio.rs):
- open_stream_for_device_and_rate(device_name, rate): opens a named
device by cpal name, falls back to system default if not found
- AudioEngine.selected_device: persists the chosen device across
stream reopens so hi-res rate switches stay on the right device
- audio_list_devices: Tauri command — returns all output device names
- audio_set_device: Tauri command — switches device immediately,
drops old sinks, emits audio:device-changed
- start_device_watcher: now handles two cases:
1. No pinned device + system default changed → reopen on new default
2. Pinned device disappeared (DAC unplugged) → clear selected_device,
fall back to system default, emit audio:device-reset
Frontend:
- authStore: audioOutputDevice (string | null), persisted
- playerStore: applies stored device on cold start
- App.tsx: listens to audio:device-reset, clears authStore device
and restarts playback on the fallback device
- Settings → Audio tab: device dropdown at top (above Hi-Res and EQ),
uses CustomSelect (portal-based, styled), all 8 locales
- CustomSelect: added disabled prop
Note: exclusive mode (WASAPI exclusive, CoreAudio exclusive) is out
of scope for now.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When offline with no cached content, the full-screen OfflineOverlay blocked
all navigation including Settings, making it impossible to fix a broken
server config. Replace it with the same slim banner used in offline-cache mode.
- OfflineBanner now handles both cases: cache (existing) and no-cache (new)
- No-cache banner shows server name and a direct link to Settings → Server tab
- OfflineOverlay component is no longer used (import removed from App.tsx)
- All 8 locales: added offlineNoCacheBanner and serverSettings keys
Fixes#170
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Persist chords as ctrl/alt/shift/super plus key code; legacy single-key
bindings still match without modifiers. Settings capture uses buildInAppBinding;
App uses matchInAppBinding and skips chords also registered as global shortcuts.
Share MODIFIER_KEY_CODES and formatBinding with global shortcut formatting.
Fix seek-forward/backward hotkeys: seek() expects 0-1 progress, not seconds.
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.
Users who had previously set a custom scale are silently reset to 100%
on next launch so they are not stuck at a broken zoom level. The stored
value is cleared in fontStore so the slider starts at 100% once the
feature is re-enabled.
Also credits nisrael for the ICY / AzuraCast PR #146.
TODO: remove the reset effect and re-enable the slider in Settings.tsx
when UI scaling is properly reworked.
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>
Previously onResized only tracked fullscreen on Linux. Now all platforms
set isWindowFullscreen, and an initial check on mount catches windows
that start maximized/fullscreened. CSS: .app-shell[data-fullscreen] .resizer
{ display: none } added in layout.css (shipped with updater commit).
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>
On tiling window managers (Hyprland, Sway, i3, bspwm, AwesomeWM, Openbox, etc.)
the window has no title bar at all — neither native nor custom. On stacking DEs
(GNOME, KDE, XFCE) the custom TitleBar can be toggled in settings as before.
Native decorations are disabled on all Linux at startup. The frontend checks
is_tiling_wm() to decide whether to render the custom TitleBar:
- DE (GNOME/KDE): custom TitleBar shown (user-toggleable in settings)
- Tiling WM: no TitleBar at all, setting hidden from Settings page
Detection is based on environment variables:
- HYPRLAND_INSTANCE_SIGNATURE, SWAYSOCK, I3SOCK (direct compositor signatures)
- XDG_CURRENT_DESKTOP check for known tiling WMs
Changes:
- Added is_tiling_wm_cmd() Tauri command for frontend detection
- Native decorations disabled on all Linux at startup
- TitleBar not rendered on tiling WMs
- Custom TitleBar setting hidden in Settings for tiling WMs
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>
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.
Manually tracking setIsWindowFullscreen was unreliable (e.g. WM-triggered
fullscreen bypasses the keybinding handler). Now listens to window resize
events and queries actual fullscreen state after each resize.
Tested on CachyOS + KDE Plasma and Fedora + GNOME.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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>
- set_decorations(false) on Linux at startup via #[cfg(target_os = "linux")]
- New TitleBar component: drag region + minimize/maximize/close buttons
- Currently playing track (▶/⏸ + artist – title) shown in the center
- app-shell grid gains a 32px titlebar row when data-titlebar is set
- IS_LINUX utility constant via navigator.platform
- macOS and Windows are completely unaffected
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>
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).
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>