Commit Graph

75 Commits

Author SHA1 Message Date
Psychotoxical 2b97a7b01e exp(orbit): host merge, guest queue view, settings popover, i18n
Guest suggestions now auto-merge into the host's play queue at random
positions inside the upcoming range, so they actually surface alongside
host-picked tracks instead of piling up at the end. Per-item dedupe via
addedBy:addedAt:trackId keys survives reshuffles.

Guests get a read-only mirror of the session in place of their queue
panel — live-card for the host's current track, suggestion list with
submitter attribution, and a footer reminding them the host owns
playback. Track metadata is resolved lazily and cached locally.

New host-only settings popover (gear in the bar) with auto-approve and
auto-shuffle toggles plus a "Shuffle now" action. Settings live in the
OrbitState blob and push immediately to Navidrome; missing fields on
older blobs default to "both on".

15-min shuffle now touches the real playerStore queue via a new
shuffleUpcomingQueue action — previous behaviour only reshuffled the
guest-facing suggestion history. A manual shuffle is available from the
settings popover so the interval can be verified without waiting.

Start-modal reworked into a single step: share-link is visible and
copy-able from the start, auto-copied on Start if the host hasn't
already hit Copy. Link carries a live name-derived slug (parser strips
it, SID is authoritative). LAN/remote-server hint above the facts.
Random session-name suggester pulls from three pattern pools for ~10k
unique combinations.

All user-visible Orbit strings moved to a dedicated orbit i18n
namespace (en + de); other locales fall back to en via i18next.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 02:49:42 +02:00
Psychotoxical 9e1256e200 exp(orbit): mount hooks and session top strip
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 01:07:40 +02:00
cucadmuh 2d320d8681 fix(linux): stop Wayland GTK drag proxy and PsyDnD ghost (#268)
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.
2026-04-22 22:28:24 +02:00
Frank Stellmacher 7c9a300022 feat(share): library deep links (psysonic2) with paste + toolbar actions (#261)
* 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>
2026-04-22 11:21:00 +02:00
cucadmuh b61c168430 fix(ui): overlay scrollbars, resizer hit-test, and Linux mini wheel (#255)
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.
2026-04-21 22:44:28 +02:00
Frank Stellmacher aa0776e811 perf(bundle): lazy-load rarely-visited pages + split vendor chunks (#249)
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>
2026-04-21 21:38:07 +02:00
Frank Stellmacher 3b3833007b feat(logging): add runtime log levels and debug log export (#241)
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>
2026-04-21 12:12:54 +02:00
Psychotoxical 5a51c2c713 feat(floating-bar): liquid-glass look on macOS and Windows
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>
2026-04-19 20:00:10 +02:00
Psychotoxical fab1dbf863 perf(floating-bar): subscribe themeStore via selector
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>
2026-04-19 19:44:17 +02:00
kveld9 ffffe268ab introducing floating bar with toggle 2026-04-19 13:01:31 -03:00
Psychotoxical 693766134b feat(mini-player): optional preload toggle for Linux + macOS
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>
2026-04-19 15:52:03 +02:00
Psychotoxical 60da17f7cc feat(mini-player): user-bindable keyboard shortcut to toggle mini player
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>
2026-04-19 13:05:06 +02:00
Psychotoxical 2871db9a96 feat(mini-player): persistent geometry, queue DnD + context menu, overlay scrollbar, live theme sync
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>
2026-04-19 12:08:05 +02:00
Psychotoxical 6bd2bfc01c feat(changelog): replace auto-modal with sidebar banner + /whats-new page
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>
2026-04-19 02:28:45 +02:00
Psychotoxical cef2db92cb feat(mini-player): floating mini window — early alpha (#162)
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>
2026-04-18 23:32:20 +02:00
Psychotoxical 66c0ecbc1f fix(windows): tray double-click flicker + GPU use when minimized
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>
2026-04-18 20:20:24 +02:00
Maxim Isaev ba43ed867a feat(linux): add WebKitGTK smooth wheel scroll toggle in settings
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.
2026-04-18 05:09:33 +03:00
Psychotoxical 832bacb569 feat(ui-scale): re-enable scoped interface scaling
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>
2026-04-18 01:34:49 +02:00
Maxim Isaev 6533991e7d feat(cli): expand remote player, opaque play id, tray without libindicator
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.
2026-04-15 01:47:46 +03:00
Maxim Isaev c75297fcf6 feat(cli): completions, library/audio commands, server switcher
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.
2026-04-15 01:47:46 +03:00
Psychotoxical 17a5c92174 feat(device-sync): USB/SD card sync page (WIP)
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>
2026-04-14 00:02:13 +02:00
Psychotoxical ec021516c7 fix(audio): device-reset false positive + stderr noise + i18n tooltip
- 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>
2026-04-13 19:00:36 +02:00
Psychotoxical 16cb4f5a6f feat(audio): audio output device selection (closes #169)
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>
2026-04-13 18:20:23 +02:00
Psychotoxical dba89cc4e4 fix(offline): replace blocking overlay with slim banner; add server settings link (#170)
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>
2026-04-13 17:52:17 +02:00
Maxim Isaev 6439200e95 feat(keybindings): in-app modifier chords; fix seek shortcut units
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.
2026-04-13 01:01:50 +03:00
cucadmuh ae2e1bcb97 feat(folder-browser): keyboard nav, context menus, now-playing path UX
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.
2026-04-12 11:27:26 +02:00
kilyabin 3d07a877f2 feat(fullscreen): performance fixes + appearance settings
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.
2026-04-12 11:26:44 +02:00
kilyabin bf38a286cd feat(nav): merge Random Mix & Albums into Build a Mix hub
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.
2026-04-12 11:26:27 +02:00
Psychotoxical c9dfbcc19f chore(settings): reset uiScale to 1.0 on startup while scaling is disabled
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>
2026-04-10 11:17:43 +02:00
Psychotoxical 5f0fb5dcbd feat(audio): auto-switch to new audio output device at runtime
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>
2026-04-10 00:40:12 +02:00
Psychotoxical 5d067b1f8b fix(app): hide resize grips in native fullscreen on all platforms
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>
2026-04-09 13:23:48 +02:00
Psychotoxical ba670bd1e8 feat: fix UI freezes in ZIP and offline cache downloads
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>
2026-04-08 22:33:23 +02:00
kilyabin 37799fb861 feat: disable native decorations on Linux, hide custom TitleBar for tiling WMs
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
2026-04-08 19:36:39 +04:00
Psychotoxical 5848c621fd feat(folder-browser): Miller columns directory navigation
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>
2026-04-08 14:40:29 +02:00
Psychotoxical e64d151079 feat(settings): theme scheduler, UI scale, CustomSelect scroll fix
- 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>
2026-04-08 12:23:24 +02:00
Maxim Isaev 705c80ef07 feat(ratings): Subsonic entity ratings and StarRating UX
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.
2026-04-08 01:09:17 +03:00
Psychotoxical c1624342d3 fix: scroll content-body to top on route change
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 22:22:51 +02:00
Psychotoxical 47cea7e3d6 fix(titlebar): detect fullscreen via onResized+isFullscreen instead of manual state
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>
2026-04-07 22:07:52 +02:00
Psychotoxical 5ed1b58d67 feat(titlebar): hide custom title bar in native fullscreen (F11)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 22:05:31 +02:00
Psychotoxical 45fa606ae1 feat(titlebar): make custom title bar optional via Settings toggle
- 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>
2026-04-07 21:47:29 +02:00
Psychotoxical d49137475f feat(titlebar): custom Linux title bar with now-playing display
- 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>
2026-04-07 21:12:17 +02:00
Psychotoxical 4b8b6ae797 feat(most-played): add dedicated Most Played page (#86)
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>
2026-04-07 20:00:07 +02:00
Maxim Isaev f08619fb3d feat(subsonic): per-server music folder filter and sidebar picker
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).
2026-04-07 13:59:59 +03:00
cucadmuh 0f3033d84e feat: add hot playback cache (queue prefetch, alpha)
Ephemeral on-disk cache for upcoming queue tracks. Adds Rust commands (download/delete/purge), hotCacheStore with LRU eviction, serial prefetch worker, playback gate, and Settings UI. Disabled by default.

- fix: boundary check before file delete in delete_hot_cache_track
- fix: remove stale languageRu2 key from ru.ts
- fix: restore accidentally removed lyricsServerFirst/fsLyricsToggle/lyricsSource* strings in ru.ts
2026-04-07 10:52:26 +02:00
Psychotoxical 50ac1b8284 feat: v1.34.0 — Mobile UI Early Preview, Russian 2, macOS network fix
Co-authored-by: kilyabin <kilyabin@users.noreply.github.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 12:46:13 +02:00
Psychotoxical 8cd4cdcd64 feat: v1.33.0 — Fullscreen Player redesign, Norwegian, configurable preload
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 02:00:48 +02:00
Psychotoxical c873880a26 feat: v1.31.0 — AutoEQ, resizable tracklist columns, Discord Listening type, layout fixes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 02:04:14 +02:00
Psychotoxical 7263d93d42 feat: v1.29.0 — Radio fast-start, seek fix, OGG, error toasts, contributors
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>
2026-04-02 22:17:55 +02:00
Psychotoxical 55e7cb835b feat: v1.27.0 — In-App Auto-Update, Configurable Home, Icon Consistency
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 02:14:57 +02:00
Psychotoxical 434ee0ecf1 feat: v1.26.0 — Bulk Select, Song Info, Favorite Button, Recently Played
### 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>
2026-04-01 20:57:57 +02:00