* fix(player): freeze main seekbar during track preview
Preview pauses the main sink in Rust while isPlaying stays true in the
store, so WaveformSeek's interpolation rAF must not advance progress.
* fix(audio): recover output after sleep and stalled streams
Add platform-specific post-sleep recovery hooks for Windows and Linux, and add a watchdog that reopens the output stream when playback is active but sample progress stalls, so audio can recover without restarting the app.
* fix(ui): remove card hover lift and smooth artwork zoom
Remove vertical hover translation from album and artist cards, and move image fade transition out of inline styles so cover zoom uses CSS timing consistently.
* fix(player): prevent seekbar jump after preview ends
Reset interpolation anchor timing when preview freeze state changes so the main seekbar does not momentarily jump forward before resyncing.
* fix(audio): reduce false watchdog recoveries and add diagnostics
Arm stalled-output recovery only after long poll gaps that suggest sleep/resume, and add detailed watcher logs for arm/clear/trigger paths to diagnose unintended stream reopens.
* chore(ui): drop card GPU hints and clarify macOS sleep scope
Remove translateZ and will-change hints from album and artist cover images to avoid per-card compositing overhead on software-composited Linux paths, and document why post-sleep recovery hooks currently target only Windows and Linux.
* docs(audio): document intentional Win32 callback pointer lifetime
Add inline rationale for the two Box::into_raw pointers in Windows suspend/resume registration so future maintenance does not treat the process-lifetime pointers as accidental leaks.
* docs(changelog): summarize playback stability updates for PR #476
Add a high-level changelog entry for preview seekbar fixes, sleep/wake audio recovery hooks and watchdog diagnostics, and card-hover stability adjustments from PR #476.
* docs(contributors): add cucadmuh entry for PR #476
Logs the post-sleep audio recovery, preview-seekbar fixes and card
hover stability work in the Settings → System → Contributors list.
* chore: upgrade dependencies and migrate playback to rodio 0.22
Bump npm and Rust crates; adapt symphonia decoding, ringbuf 0.5, lofty tags,
and discord-rich-presence usage. Use native rodio Player/MixerDeviceSink and
cpal device descriptions; drop the unused cpal patch. Align Vite 8 build
targets and chunking; remove redundant dynamic imports and fix hot-cache debug
logging imports.
* perf(build): lazy-load routes and restore default chunk warnings
Lazy-load all routed pages with React.lazy to shrink the main bundle; wrap root
Routes in Suspense for lazy Login. Drop chunkSizeWarningLimit override so Vite
uses the default 500 kB threshold.
* fix(windows): tray double-click without spurious menu; clean unused import
Disable tray menu on left mouse-up on Windows so a double-click to hide the
main window does not immediately reopen the context menu (tray-icon default
menu_on_left_click). Gate std::fs in app_api/core behind cfg(linux) for
/proc-only code so Windows builds stay warning-free.
* fix(sidebar): preserve new-releases read state under storage cap
When merging seen album ids, keep the current newest sample first so the
500-id localStorage limit does not truncate freshly marked reads and bring
back the unread badge.
* fix(audio): hot-cache replay, analysis no-op skips, playback source UI
Retain stream_completed_cache across audio_stop so end-of-queue replay can
use RAM promote or disk hot file instead of re-ranging HTTP.
Add cpu_seed_redundant_for_track gate before file/bytes seeds and local-file
spawn; emit analysis:waveform-updated only on Upserted. Ranged/legacy promote
checks generation after await before filling the slot.
Frontend: promote on same-track and cold resume; set currentPlaybackSource on
resume, queue undo restore, and gapless track switch so cache/stream icons stay
accurate. Import tauri::Manager for try_state in audio_play.
* fix(ts): narrow activeServerId for hot-cache promote calls
promoteCompletedStreamToHotCache expects a string; bind non-null server ids
in repeat-one, playTrack prev/same-track, and cold resume paths so tauri
production build (tsc) succeeds.
* fix(player): handle same-track hot-cache promote promise chain
Add .catch for promoteCompletedStreamToHotCache → runPlayTrackBody so sync
throws and unexpected rejections do not surface as unhandled in DevTools;
reset defer-hot-cache prefetch and isPlaying on failure.
* chore(nix): sync npmDepsHash with package-lock.json
* chore(release): finalize 1.46.0 CHANGELOG with PR #463 links
Document the release with full GitHub PR #463 on every subsection so
entries stay attributable if sections are reordered. Fix ContextMenu
lines where dynamic imports were accidentally merged onto one line.
* docs(contributors): credit cucadmuh for #463
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(audio): promote WASAPI render thread to MMCSS Pro Audio on Windows
Wraps the outermost audio source in a `PriorityBoostSource` that calls
`AvSetMmThreadCharacteristicsW("Pro Audio")` on its first sample. The
cpal output-stream callback runs `Source::next` on the WASAPI render
thread, which is otherwise normal-priority and gets preempted under
WebView2 / DWM / GPU pressure — producing the audible click/stutter
reported in issue #334. No-op on Linux/macOS (PipeWire/rtkit and
CoreAudio promote their audio threads externally).
* fix(build): repair Windows compile after audio split + lib decompose
Two pre-existing build breakers on Windows that surfaced after the
`use super::*;` cleanup (9cc74a7) and the lib-decompose refactor
(cfeec22):
- `audio/device_watcher.rs` lost the `output_enumeration_includes_pinned`
import. Add it cfg-gated to `not(target_os = "linux")` since it's only
called inside the non-Linux pinned-device fallback path.
- `lib_commands/sync/tray.rs::is_tiling_wm()` is `#[cfg(target_os = "linux")]`,
but its Tauri command wrapper `is_tiling_wm_cmd()` is unconditional.
Add a `#[cfg(not(target_os = "linux"))]` stub returning `false`.
No behavior change on Linux. Restores `cargo build` on Windows + macOS.
* perf(ui): pause cosmetic animations when window loses OS focus
Companion to the WASAPI MMCSS fix in this branch — issue #334
reported audible audio stutter on low-end laptops, partly traced
to WebView2 still compositing CSS animations + waveform canvas at
full rate while the user has alt-tabbed into another app.
Existing `data-app-hidden` / `data-psy-native-hidden` flags pause
animations only when the window is fully hidden (minimized or
behind a tray). When the window is visible-but-unfocused — the
common "music in the background while I work" case — every infinite
keyframe + 60fps rAF loop keeps running.
Add a parallel `data-app-blurred` flag driven by `window.focus`/
`window.blur`, plus matching `__psyBlurred` on the global window
object for imperative checks. The CSS rule that pauses every
`animation-play-state` on hidden gets a third selector for blurred.
WaveformSeek's two 60fps rAF loops (animated styles + settings demo)
extend their existing `document.hidden || __psyHidden` short-circuit
to also include the new blurred flag, falling back to the same
400ms polling path that already exists for hidden.
Verified visually on the dashboard: alt-tab → `<html
data-app-blurred="true">`, mesh blobs in the fullscreen player +
marquee + waveform 60fps loop all pause. Click back into the
window → blob/marquee/waveform resume immediately.
Cross-platform: focus/blur events fire reliably on all targets, so
the optimization applies to Windows, Linux (incl. WebKitGTK with
software compositing where the savings are largest), and macOS.
* chore: remove stale app-icon.png from repo root
* perf(ui): add 'Reduce animations' toggle that caps animated seekbar styles to 30 fps
Off by default. When on, animated seekbar styles (pulsewave, particletrail,
liquidfill, retrotape) skip every other rAF and advance the animation timer by
a doubled delta, so wave speed is unchanged while draws are halved. Aimed at
low-end Windows GPUs where the 60 fps shadow-blur loop drives ~40 % WebView2
GPU usage even when focused.
Toggle lives directly under the seekbar style picker in Settings → Appearance.
i18n in all 8 supported languages.
Improve normalization consistency by separating cache refresh for non-playing tracks, hardening track-id cache lookups, and keeping UI gain state aligned with actual playback. Also reduce startup stalls by preferring non-seekable streaming when format hints are missing and by tuning ALSA/analysis paths to avoid underrun-prone churn.
Reverts #271, #292, #293, #294, #295, #296.
The flatpak CI pipeline itself works end-to-end, but the installed bundle
surfaced three separate manifest issues during smoke-test on Wayland+NVIDIA:
1. GDK_BACKEND is not set, so without an X11 display in the sandbox GTK
aborts with "Failed to initialize GTK".
2. libayatana-appindicator3 is not bundled in the GNOME 47 runtime, so
libappindicator-sys panics the main thread.
3. The release binary is compiled via \`cargo build --release\` rather than
\`cargo tauri build\`, so the \`custom-protocol\` feature is off and
Tauri falls back to devUrl — the window opens but shows
"Could not connect to localhost".
Rolling back so main stays on 1.43.0. A follow-up on the original PR
tracks the fixes needed before re-attempting.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Critical Windows bug-fix release.
Fixed
- Mini player no longer hangs the app on Windows (see 71fbc717): the
second WebView2 is now pre-built in .setup() so the first open is a
pure show/hide instead of a creation + minimize race. Windows is
back on native window decorations for the mini and skips the main-
window minimize/unminimize dance around show/hide.
- Mini player re-emits mini:ready on window focus so the snapshot from
main arrives reliably on first open even when pre-create finished
before main's bridge attached its listener.
Added
- "Preload mini player" toggle in Settings → General for Linux + macOS
so those platforms can opt into the instant-open behaviour that
Windows already has as a hang workaround.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Consolidates everything since v1.40.0 (public) into one coherent
release. v1.41.0 remains as an internal Draft on GitHub that was used
to verify the Cachix substituter pipeline and never went public — this
release folds its intended contents in and adds the mini-player feature
work, the player-bar time toggle (kveld9), the ReplayGain expand badge
and related UX polish on top.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Major release consolidating several months of work into a single coherent
version. The 1.34.x patch series accumulated many small features; 1.40.0
packages them up and signals the infrastructure milestone (macOS signing
+ notarization + auto-updater) clearly.
Highlights (see CHANGELOG for the full rundown):
- macOS builds are signed with a Developer ID certificate and notarized
by Apple — no more Gatekeeper "unidentified developer" dialog
- In-app auto-update on macOS via the Tauri Updater plugin; polished
modal with trust badges, restart countdown, and state-dependent buttons
- Linux WebKitGTK wheel scroll toggle (PR #207 by cucadmuh)
- Device Sync: user-configurable filename template replaced with a
fixed cross-OS scheme; playlists now live in their own self-contained
folders with sibling-referencing .m3u8; one-shot migration tool for
existing sticks
- WCAG contrast audits for middle-earth and nucleo themes
- Contributors list in Settings → About updated (PRs #205, #206, #207)
Windows signing + Windows auto-updater are the remaining gap; 2.0.0 is
planned once both are active.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
No functional changes. Throwaway build to serve as the "latest" release
that v1.34.22 will find, download, verify, and install via the Tauri
Updater. Will be deleted once the updater round-trip is confirmed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
A character was lost when the base64-encoded minisign public key was
transcribed into tauri.conf.json, producing a 41-byte decoded key
instead of the required 42 bytes. Every release built against that key
(v1.34.15 through v1.34.21) rejects any update manifest signature with
"Invalid encoding in minisign data". Replaced with the correct pubkey
read directly from ~/.tauri/psysonic-updater.key.pub.
v1.34.19 installed manually for testing cannot receive auto-updates
because the broken pubkey is baked into its binary; a fresh v1.34.22
install is required as the base for the updater test.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- AppUpdater.tsx: macOS now has a dedicated branch in the update modal —
no architecture-specific DMG asset shown (the Tauri Updater picks the
right platform from latest.json), clearer wording ("Downloads, verifies
and installs automatically"), and the primary button reads "Install
now" instead of "Download". Strings use defaultValue so locales without
the key fall back to English until translations catch up.
- Test release for the updater pipeline; CHANGELOG entry asks users to
ignore it. Will be deleted after the test.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Throwaway release to validate the macOS Tauri Updater end-to-end:
v1.34.19 (installed manually) → check() → latest.json → download +
install → relaunch. Contains no actual changes. CHANGELOG entry asks
users to ignore it; will be deleted after the test.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
tauri-action always re-packs Psysonic.app into .app.tar.gz after tauri
CLI finishes (regardless of includeUpdaterJson), so the .sig that tauri
CLI produces never matches the final tarball — and the sig tauri-action
was supposed to produce silently goes missing ("Signature not found for
the updater JSON. Skipping upload...").
Rather than fight the repack, sign the repacked tarball ourselves in a
follow-up step using `tauri signer sign` against the same private key
(from TAURI_SIGNING_PRIVATE_KEY), then upload the fresh .sig. tauri-
action still handles the DMG + .app.tar.gz upload; we only add the .sig.
Also removes an unused `std::rc::Rc` import in the vendored cpal patch.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Root cause: tauri-action runs a second "Packaging" pass after tauri CLI
finishes, which rewrites src-tauri/target/<target>/release/bundle/macos/
Psysonic.app.tar.gz with different bytes. The .sig produced by tauri CLI
no longer matches the new hash, so tauri-action silently deletes it —
leaving the directory with only .app and .app.tar.gz (no .sig for our
manifest generator to consume).
Fix: pass `includeUpdaterJson: false` to tauri-action so it skips the
repack + updater JSON upload entirely, then copy both the .app.tar.gz
and .app.tar.gz.sig produced by tauri CLI into the workspace root with
the expected asset names and `gh release upload` them.
Also disables build-linux and the Windows matrix entry during testing
to cut iteration time roughly in half.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace the glob find for the .sig file with an explicit target-based
path ($matrix → src-tauri/target/<target>-apple-darwin/release/bundle/
macos/Psysonic.app.tar.gz.sig) and dump directory listings so we can
see what tauri-action actually leaves behind if the path is wrong
- Skip verify-nix while we iterate fast on signing + updater (its auto-
commits of flake.lock / npmDepsHash cause rebase friction on every
release)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
tauri-action on macOS cross-target builds produces the .app.tar.gz.sig
locally but silently skips its upload ("Signature not found for the
updater JSON. Skipping upload..."), which caused generate-manifest to
abort on v1.34.15 because the .sig asset was absent.
New step after tauri-action locates the .sig under src-tauri/target/*/
release/bundle/macos/ and uploads it as Psysonic_aarch64.app.tar.gz.sig
or Psysonic_x64.app.tar.gz.sig — the exact filenames generate-update-
manifest.js expects.
Also bumps version to 1.34.16.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- tauri-plugin-updater wired into lib.rs with pubkey + GitHub Releases
endpoint in tauri.conf.json; updater:default capability granted
- AppUpdater.tsx: on macOS, the download button now invokes the updater
plugin (check + downloadAndInstall) which downloads the signed
.app.tar.gz, verifies the minisign signature against the bundled
pubkey, replaces /Applications/Psysonic.app, and relaunches. Windows
and Linux keep the existing "download DMG/EXE/AppImage via reqwest
then point to the folder" flow
- CI: pass TAURI_SIGNING_PRIVATE_KEY + _PASSWORD to tauri-action so the
.sig files are produced alongside the update bundles
- New generate-manifest job (after build-macos-windows) runs
scripts/generate-update-manifest.js which downloads the .sig files
from the release, assembles latest.json for darwin-aarch64 and
darwin-x86_64, and uploads it back as a release asset
Windows will be added to latest.json once the Certum cert is active.
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.
Adds YouLyPlus karaoke lyrics + static-only lyrics toggle, collapses
the advanced Discord options under one header, and ships the real
macOS microphone-prompt fix (cpal patch forcing DefaultOutput on all
output streams).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two independent user-facing fixes rolled together.
── Lyrics: YouLyPlus provider (issue #172) ──
Adds word-by-word synced (karaoke) lyrics as an alternative lyrics mode.
Backed by the public lyricsplus aggregator (Apple Music / Spotify /
Musixmatch / QQ Music) — no API keys on our side, subscription costs are
borne by the backend operator. Five mirrors are tried on network failure.
Settings → Lyrics now exposes a mode radio (Standard vs YouLyPlus) plus
a static-only toggle. YouLyPlus misses silently fall back to the existing
server + LRCLIB + Netease pipeline so obscure tracks still resolve. The
drag-to-reorder source list is hidden in YouLyPlus mode since the
external aggregator manages its own source priority.
Rendering: per-word spans on each line, active word highlighted with
accent-tinted glow. Subscribed imperatively via usePlayerStore.subscribe
so 500 ms progress ticks do not re-render the whole lyrics block. The
Fullscreen Player reuses the same pattern; the 5-line rail layout is
unchanged. white-space: pre on .fs-lyric-word preserves the trailing
spaces the API embeds in each syllabus token (flex context would
otherwise collapse them).
i18n: new keys in all 8 locales (de, en, fr, nl, nb, ru, es, zh).
── macOS mic-prompt suppression ──
Ships a vendored cpal 0.15.3 at patches/cpal-0.15.3/ wired via
[patch.crates-io]. The only change is in src/host/coreaudio/macos/mod.rs:
audio_unit_from_device now unconditionally uses IOType::DefaultOutput for
output streams instead of conditionally choosing HalOutput.
AUHAL (HalOutput) is classified as input-capable by macOS TCC and
triggers the microphone-permission dialog at first launch / after each
update even for playback-only apps. Previous attempts (removing
NSMicrophoneUsageDescription, setting com.apple.security.device.audio-input
false in Entitlements.plist) did not suppress the prompt because TCC
fires at AudioUnit instantiation, not at plist level. The upstream fix
in cpal PR #1070 / cpal 0.17 only helps when the pinned device equals
the system default; always-DefaultOutput covers all cases.
Tradeoff: per-device output selection is a no-op on macOS — the stream
always follows the system default. Settings.tsx surfaces this via
audioOutputDeviceMacNotice (hides the CustomSelect on macOS) and skips
audio_list_devices + device-watcher effects there. Matches the behaviour
of Apple Music / Spotify on macOS.
Also removes the now-obsolete com.apple.security.device.audio-input
entitlement (sandbox is disabled anyway, so it was ignored).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.
- Changelog for v1.34.11 (Opus, Device Sync, community themes, SSL fix)
- Contributors updated in Settings About (PR #181, #182, #183)
- Version bumped to 1.34.11 in package.json, tauri.conf.json, Cargo.toml
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- New two-panel layout with live album search (search3, 300ms debounce)
and 10 random albums when search is empty — replaces full paginated load
- Pre-sync summary modal: shows files to add/delete, net change, available
space; Proceed button disabled when space is insufficient
- calculate_sync_payload now filters already-synced tracks (path exists on
device) so add_bytes/add_count reflect the true delta
- Space check accounts for pending deletions: addBytes > availableBytes + delBytes
- Separate deviceSyncJobStore for ephemeral job progress state
- Removable drive detection + auto-poll every 5 s via sysinfo
- Desired-state diff: de-selecting a synced source stages it for deletion
instead of silently removing it
- Status badges (Synced / Pending / Deletion) with matching row highlights
- Live-Search badge (⚡) and Zufallsalben section label (⇌) in album browser
- Status summary row height aligned with search field (52 px)
- i18n: all new keys added to all 8 locales (en/de/fr/nl/nb/zh/ru/es)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wire a lazy CodecRegistry built with symphonia::default::register_enabled_codecs
plus symphonia_adapter_libopus::OpusDecoder for SizedDecoder and radio decoders.
Raise package rust-version to 1.89 to match the MSRV required by
symphonia-adapter-libopus.
Fixes two bugs reported in #178:
- getBaseUrl() now strips trailing slashes from server URLs, consistent
with restBaseFromUrl(). A trailing slash caused double-slash stream URLs
(//rest/stream.view) which Caddy rejects. Trailing slash also caused
browsing to return 0 results. Fix manually ported from PR #179 by kveld9.
- reqwest now loads the OS native certificate store in addition to
Mozilla's webpki roots (rustls-tls-native-roots feature). Fixes HTTPS
streaming failures where the server certificate is signed by a local CA
(e.g. Caddy internal CA) that is trusted in the system keychain but not
in Mozilla's root store.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Device watcher (3 s loop) and audio_list_devices both called
cpal output_devices(), triggering ALSA to probe unavailable backends
(JACK, OSS, dmix) and spam stderr with error messages.
Fix: redirect fd 2 to /dev/null for the duration of each enumeration
on Unix via libc dup/dup2 + RAII guard. Also add a module-level
cache in Settings.tsx so audio_list_devices is only invoked once
per app session instead of on every Audio tab activation.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements ITaskbarList3::ThumbBarAddButtons for Prev/Play-Pause/Next
buttons in the Windows taskbar thumbnail preview. Icons are drawn at
runtime via GDI (no binary assets). WndProc subclass intercepts
THBN_CLICKED and emits the same media:* events as souvlaki/tray.
update_taskbar_icon command swaps Play↔Pause icon on state change.
Frontend syncs via playerStore subscribe alongside mpris_set_playback.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- fix(fonts): update CSS var declarations to @fontsource-variable naming
('Inter Variable', 'Outfit Variable', etc.) so dynamic font switching works
- fix(layout): 100vh → 100% on .app-shell to fix Windows WebView2 playerbar drift;
1fr → minmax(0,1fr) in all grid-template-rows + remove min-height: 720px to fix
Linux playerbar disappearing at high zoom or small window sizes
- fix(updater): replace shell open() with Rust open_folder command to bypass
shell:allow-open capability scope blocking local paths on Windows
- fix(lyrics): add get_embedded_lyrics Tauri command (id3 crate for MP3 SYLT/USLT,
lofty for FLAC SYNCEDLYRICS/LYRICS); fix parseLrc regex for LRC without fractional
seconds; fix SubsonicStructuredLyrics to accept both synced and issynced fields
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>