Commit Graph
2040 Commits
Author SHA1 Message Date
vicrodh b3573af497 fix(ui): replace flickering Loader spinner with SVG progress ring
The Lucide Loader icon has multiple line segments that create a
stroboscopic flicker when rotated. Replace with a clean SVG circle
progress ring: shows actual download progress via stroke-dashoffset
with smooth 300ms transitions, and a simple indeterminate rotation
when queued. No more flickering.
2026-02-08 11:16:34 -06:00
vicrodh c5eb685f11 fix(home): wire download button action in Continue Listening tracks
The DownloadButton in TrackRow receives onDownload from direct props,
not from menuActions. Continue Listening was only passing onDownload
inside menuActions (for context menu) but not as a direct prop, so
clicking the download icon did nothing. Pass onDownload and
onRemoveDownload as direct TrackRow props.
2026-02-08 10:59:49 -06:00
vicrodh e29256170c fix(search): prevent UI freeze during search (#56)
Frontend: yield to UI loop with tick() after setting search results
before running background download status checks (fire-and-forget
instead of blocking await). Backend: drop client mutex lock
immediately after HTTP request completes so parsing and blacklist
filtering don't hold the lock and block other commands.
2026-02-08 10:41:41 -06:00
vicrodh d2531ea700 refactor(ui): move gapless toggle from player bar to settings
Remove gapless button from NowPlayingBar and place it in
SettingsView under Playback section. The toggle is now sensitive to
blocking settings: auto-disables when ALSA Direct backend or
streaming-only mode is activated. Gapless itself never blocks other
settings. Added inline experimental label and descriptive tooltips
with proper i18n for all 4 locales.
2026-02-08 10:18:52 -06:00
vicrodh 71bfb8dc17 feat(ui): add gapless playback toggle button to player bar
Add a toggleable gapless button next to the repeat button in the
NowPlayingBar. The setting persists in audio_settings.db and controls
whether the audio thread pre-queues the next track for gapless
transitions. Includes i18n keys for all 4 locales.
2026-02-08 09:45:13 -06:00
vicrodh a21f7c7a93 feat(audio): implement gapless playback for cached tracks
Backend: Add PlayNext command to audio thread that appends decoded
sources to the existing Rodio Sink for seamless transitions. The audio
thread detects when ~5s remain and signals gapless_ready via SharedState.
Position-based transition detection updates track metadata when the
queued source begins playing.

Frontend: playerStore detects gapless_ready flag, invokes
play_next_gapless (cache-only lookup), and handles transitions by
advancing the queue without stop/play cycle. playbackService gains
gaplessTransition option to skip stop_playback when backend already
transitioned.

Gapless is only eligible when: same sample rate/channels, cached data,
Rodio backend (not ALSA Direct), not streaming, not a user skip.
Falls back to normal stop-play flow when ineligible.
2026-02-08 09:05:39 -06:00
vicrodh eaacc9e3a7 feat(audio): add real-time EBU R128 volume normalization (Phase 2)
Replace static ReplayGain-only normalization with real-time loudness
analysis that works with any audio source including Qobuz streams.

New components:
- DynamicAmplify: Source wrapper that reads gain from shared atomic
  with 50ms linear ramp to prevent clicks on gain changes
- AnalyzerTap: Source wrapper that batches samples and sends them
  to the analyzer thread via bounded channel (never blocks audio)
- LoudnessAnalyzer: Background thread computing EBU R128 integrated
  LUFS, first measurement at ~10s, refinement every ~5s
- LoudnessCache: SQLite persistence for computed LUFS values so
  replayed tracks get instant gain application

Pipeline when normalization ON:
  Decode -> Diagnostic -> AnalyzerTap -> DynamicAmplify -> Visualizer

Pipeline when normalization OFF (bit-perfect, unchanged):
  Decode -> Diagnostic -> Visualizer

Re-enable normalization toggle in NowPlayingBar UI.
2026-02-08 08:24:51 -06:00
vicrodh ac4cd8f4eb chore(ui): hide normalization icon until Phase 2
ReplayGain-only normalization is not useful for Qobuz streaming
(streams lack RG tags). Icon will be re-enabled once EBU R128
real-time analysis is implemented.
2026-02-07 23:26:47 -06:00
vicrodh 00d0ce7835 feat(ui): add normalization toggle to player bar
Adds 3-state normalization icon between fullscreen and volume controls.
Uses mask-image SVGs for theme-compatible coloring. Reduces song card
min-width by 72px to accommodate the new control.
2026-02-07 22:55:18 -06:00
vicrodh 717eb6fed2 feat(audio): expose normalization gain in playback events
Add normalization_gain field to SharedState and PlaybackEvent so the
frontend can display whether normalization is actively applied to the
current track. The gain factor is stored as AtomicU32 (f32 bits) and
emitted with each playback:state event.

cc
2026-02-07 22:47:14 -06:00
vicrodh 6edd2a3cda feat(audio): add volume normalization (Phase 1 — ReplayGain metadata)
Add ReplayGain-based volume normalization to the audio pipeline.
When enabled, extracts ReplayGain track gain/peak tags from audio
file metadata (Vorbis comments, ID3v2 TXXX) and applies a gain
factor via rodio's Amplify wrapper.

Pipeline order: Diagnostic (raw) -> Amplify (normalization) -> Visualizer
When normalization is OFF (default), no Amplify wrapper is inserted
and the pipeline remains 100% bit-perfect.

- New module: audio/loudness.rs (extraction + gain calculation)
- New settings: normalization_enabled, normalization_target_lufs
- New Tauri commands: set_audio_normalization_enabled/target
- Supports -14 LUFS (default), -18 LUFS, -23 LUFS targets
- Clipping prevention using peak metadata
- Gain preserved across resume/seek operations

cc
2026-02-07 21:32:32 -06:00
vicrodh a237be3a83 Differentiate DAC Passthrough tooltip from ALSA Direct bit-perfect
DAC badge tooltip now says "Active" instead of "Bit-perfect" since
PipeWire passthrough is best-effort, not guaranteed bit-perfect like
ALSA Direct hw:X,Y. Added a brief note in Settings when DAC Passthrough
is enabled clarifying the distinction. Also i18n'd the hardcoded
dacPassthroughTooltipOverride string in SettingsView.
2026-02-07 20:37:31 -06:00
vicrodh be56ecff90 i18n: replace hardcoded strings in AudioOutputBadges tooltip
Moved all hardcoded English/Spanish tooltip text to i18n keys
under audioBadges section in all 4 locale files.
Removed dead dacTooltip derived property (unused in template).
2026-02-07 18:11:49 -06:00
vicrodh af624d4642 feat(audio): add internal bit-depth diagnostic capture
Lock-free OR-mask analysis of f32 samples converted to i32,
bypassing PipeWire processing entirely. DiagnosticSource wraps
the audio source at decode level for both rodio and ALSA Direct
paths. Tauri commands: start_bitdepth_capture / stop_bitdepth_capture.

cc
2026-02-07 15:58:26 -06:00
vicrodh 3253e05263 Merge branch 'fixes/1.1.10/debug' into pre-release 2026-02-07 13:17:43 -06:00
vicrodh fdc8a96a7e chore: remove DEBUG-43 diagnostic logging
cc
2026-02-07 13:09:46 -06:00
vicrodh bcbe31d1f4 chore(vendor): fix unused import and mut warnings in rodio
cc
2026-02-07 12:55:23 -06:00
vicrodh 3990d7bdcf fix(updates): gate launch modals on session readiness
decideLaunchModals() was racing activate_user_session() because
setLoggedIn() triggers the $effect before the backend store opens.
Preferences defaulted to showWhatsNewOnLaunch=true, ignoring the
user's saved setting.

Add sessionReady flag set after activate_user_session completes.
The $effect now waits for session + home view before running.
Also reset all update state on logout for clean re-login cycle.

cc
2026-02-07 12:48:34 -06:00
vicrodh 220ac36b33 fix(css): remove remaining store accesses from $derived() blocks
+layout.svelte: $isLoading inside $derived() -> isReady() function
HomeView.svelte: $t() inside $derived.by() -> getGreetingText() function

These caused vite-plugin-svelte CSS extraction to fail, cascading
PostCSS errors to unrelated components during HMR.
2026-02-07 12:41:26 -06:00
vicrodh 1d284ac82a fix(css): remove remaining store accesses from $derived() blocks
+layout.svelte: $isLoading inside $derived() -> isReady() function
HomeView.svelte: $t() inside $derived.by() -> getGreetingText() function

These caused vite-plugin-svelte CSS extraction to fail, cascading
PostCSS errors to unrelated components during HMR.
2026-02-07 12:23:01 -06:00
vicrodh ecd006d176 fix(css): remove $t() from $derived() to fix PostCSS extraction
ImmersiveHeader splitTabs used $t() inside $derived(), causing
vite-plugin-svelte CSS extraction to receive raw script content
instead of CSS — cascading PostCSS errors to unrelated components.

ShortcutInput had the same pattern for displayShortcut.

Both now use the labelKey/function pattern instead.
2026-02-07 12:13:24 -06:00
vicrodh 28742fa19e fix(updates): ensure preferences are loaded from backend after login
initUpdatesStore() set its initialized flag before the async preference
fetch completed. If get_update_preferences failed (backend store not
yet ready), preferences stayed at defaults (both true) and were never
re-fetched. This caused:
- "Show What's New" toggle appearing enabled despite being disabled
- "Check for updates on launch" toggle not persisting
- Changelog modal reappearing on every restart

Changes:
- Split initialized flag into versionLoaded/prefsLoaded so preference
  loading retries until the backend store is ready
- Call refreshUpdatePreferences() in decideLaunchModals() to always
  get fresh data before making modal decisions
- Call refreshUpdatePreferences() in handleLoginSuccess() after
  session activation so SettingsView reflects saved values

cc
2026-02-07 11:58:32 -06:00
vicrodh 865e25e9b1 fix(listenbrainz): restore token and enabled state during session init
init_at() loaded credentials from the user-scoped SQLite cache but
never called restore_token() on the in-memory client. The client
remained empty, so get_status() always returned disconnected after
app restart. Also restore the enabled flag from persisted settings.

Fixes #54

cc
2026-02-07 11:56:34 -06:00
vicrodh e48bbb99df feat: add pkgrel input to AUR workflow
On release events pkgrel defaults to 1. On manual dispatch it can be
set to any value (default 1), allowing same-version rebuilds like
1.1.9-2 without editing the workflow.
2026-02-07 03:34:29 -06:00
vicrodh 1666672f0c fix: version unavailable in changelog button
initUpdatesStore used Promise.all for get_current_version and
get_update_preferences. Since preferences need the SQLite store
(only available after login), the failure killed both promises,
leaving currentVersion as empty string.

Split into independent calls so the version (a compile-time constant
that never needs a session) is always available regardless of
preference store state.
2026-02-07 03:33:52 -06:00
vicrodh b3607d1154 update AUR PKGBUILD version to 1.1.9 2026-02-07 03:01:56 -06:00
vicrodh befef6b1e3 cleanup: fix compiler warnings in playback commands
Remove unused mut on preferred_quality (limit_quality_for_device is
disabled). Add #[allow(dead_code)] to the preserved function.
2026-02-07 03:01:52 -06:00
vicrodh bb496ffaac fix: use bundled SQLite for snap builds
Remove LIBSQLITE3_SYS_USE_PKG_CONFIG and related env vars that forced
linking against system SQLite 3.37.2 on core22. The rusqlite bundled
feature compiles SQLite 3.38+ from source, which provides the
sqlite3_error_offset symbol needed by rusqlite 0.31.
2026-02-07 03:01:47 -06:00
vicrodh 5d617365c2 fix: show changelog button provides loading state and error feedback
The button previously failed silently when version was unavailable or
the GitHub API request failed. Now shows loading state while fetching
and a toast notification on failure.
2026-02-07 02:53:43 -06:00
vicrodh 29eb8b0d96 cleanup: remove DEBUG-43 console logging
Remove all [DEBUG-43] console.warn/error entries added during blank
album investigation. Keep essential error logging without the prefix.
2026-02-07 02:53:38 -06:00
vicrodh 12faa5837c fix: rename t variable in $derived() blocks to prevent PostCSS errors
The Svelte 5 CSS extraction breaks when t is used as a callback
parameter inside $derived() or $derived.by() blocks. Renamed to
track, item, or lt in PlaylistDetailView, FavoritesView,
PlaylistSuggestions, and PlaylistTagFilter.
2026-02-07 02:53:33 -06:00
vicrodh 5e3e3c252a update snapcraft and flatpak metadata for 1.1.9 2026-02-07 01:58:03 -06:00
vicrodh 3f18378c13 bump version to 1.1.9 2026-02-07 01:52:38 -06:00
vicrodh 09774c151f add DE and FR translations for playlist import modal 2026-02-06 23:39:04 -06:00
vicrodh feb007945b playlist import: add name customization and folder selection
Split import into two-step flow: Fetch (preview) then Import.
After fetching, user can edit the playlist name and optionally
assign it to a folder before importing. Backend already supported
name_override — this just exposes it in the UI. Also i18n all
new strings and existing hardcoded modal text.
2026-02-06 23:38:08 -06:00
vicrodh 3776925a5f auto-resume playback after audio settings change
When the user changes any audio setting (output device, exclusive mode,
DAC passthrough, backend type, ALSA plugin), the stream is reinitialized.
Previously playback would die and the user had to manually skip
forward/backward to recover. Now playback automatically resumes at the
same position after reinit, since the backend already preserves position
and audio data across ReinitDevice.
2026-02-06 22:20:23 -06:00
vicrodh 1e642758ad integrate home data cache for instant back-navigation
On mount, check if a valid cache exists (< 5 min, same genre filter).
If valid, restore all section data instantly and scroll position,
skipping all API calls. Cache is saved automatically when all
sections finish loading. Invalidated on genre filter change or
home settings change.
2026-02-06 22:08:10 -06:00
vicrodh cba9b5a3fa remove full-page overlay, add progressive skeleton rendering
Replace the blocking overlay system with per-section skeleton
placeholders that appear immediately and swap to real content
as each section loads independently. Download status checks are
now fire-and-forget (no longer block section display). Remove
all DEBUG-43 console warnings. Remove unused sidebarExpanded prop.
2026-02-06 22:06:19 -06:00
vicrodh 08dc86b987 add homeDataCache store for instant Home navigation
Module-level in-memory cache that stores all 11 Home sections'
resolved data with TTL (5 min) and genre filter snapshot validation.
Follows the same pattern as searchState.ts.
2026-02-06 21:20:06 -06:00
vicrodh f2d8408268 fix: preserve playback state across audio settings changes
ReinitDevice was resetting position to 0 and clearing audio data,
making it impossible to resume after changing audio settings.

Now uses pause_playback_timer() to capture real-time position and
keeps audio data intact. The existing Resume handler already
recreates the engine, re-decodes, and seeks to the saved position.

Removes unnecessary stop_playback calls from frontend that were
destroying the preserved state after backend/device changes.
2026-02-06 16:57:22 -06:00
vicrodh aef4285fd9 fix: defer visualizer, remote control, and last.fm to post-login
Visualizer FFT thread, remote control API server, and Last.fm session
restore were all initializing before user session activation, causing
errors ("No active session") and wasting resources.

Move all three to run after activate_user_session:
- Visualizer: add started guard to prevent double-start
- Remote control: sync_server now has per-user settings available
- Last.fm: restoreLastfmSession moved from bootstrapApp to handleLoginSuccess
2026-02-06 09:27:09 -06:00
vicrodh ff8c69f573 fix: eliminate login screen flash on auto-login
Move setLoggedIn() before the await activate_user_session() in
handleLoginSuccess. LoginView already activates the session for
auto-login, so the second call is idempotent. By setting the auth
state immediately, the template switches from LoginView to the app
without the ~475ms flash of the login form during the await.
2026-02-06 08:50:50 -06:00
vicrodh 5cd8769f84 debug: promote command handler logging to info level (#43)
The frontend JS console is inaccessible when the UI blanks, so all
diagnostic logging must go through the Rust backend (terminal).

Promote get_featured_albums, get_discover_index, get_artist_basic to
log::info with [DEBUG-43] prefix. Add OK/FAILED result logging to
get_album, get_track, get_featured_albums, get_artist_basic, and
get_discover_index command handlers.

This ensures the terminal shows the full sequence:
  reco_get_home_ml → featured_albums(x5) → discover_index → tracks → albums → artists
2026-02-06 08:29:42 -06:00
vicrodh 916b4c145a debug: add aggressive logging to home loading flow (#43)
Log every decision point in loadHome():
- totalVisibleSections count
- mlPromise invoke/resolve/reject with seed counts
- fetchDiscoverData invoke/result with container keys
- checkAllSectionsReady transition with hasContent/error state
- catch block entry (which blocks all content rendering)

Backend: reco_get_home_ml logs call, success with counts, and
explicit error when RecoState.db is None.
2026-02-06 08:25:21 -06:00
vicrodh e14a364484 fix: add API response status logging and 404 handling to client
Add [API] log lines with HTTP status to all client methods for
debugging blank views (#43). Handle 404 explicitly in get_track
(returns TrackUnavailable) and get_album (returns ApiResponse)
instead of attempting JSON deserialization of error responses.
2026-02-06 08:17:44 -06:00
vicrodh 2bff1290a3 fix: add catch handlers to all home section promise chains (#43)
Missing .catch() on 9 .then() chains in HomeView meant any rejected
promise (e.g. 404 from a deleted track) would prevent markSectionFinished()
from being called, leaving the loading overlay permanently visible.

Each catch now logs the error, clears the loading flag, and calls
markSectionFinished() so the overlay always dismisses.
2026-02-06 08:17:38 -06:00
vicrodh 1fce799674 fix: use gold hi-res badge with drop-shadow for legibility
Switch from hi-res-gray.svg back to the gold hi-res.svg. Add a
sub-pixel drop-shadow filter to reinforce thin text strokes against
the gold gradient, fixing chromatic anti-aliasing blur at 24px.
2026-02-06 06:26:01 -06:00
vicrodh 828b31671c fix: invalidate API cache on favorite mutations
add_favorite and remove_favorite now invalidate the corresponding
album/track/artist entry in the API cache after a successful API call.
This prevents stale JSON (missing the updated favorite status) from
being served on the next detail fetch. Added targeted invalidate_album,
invalidate_track, and invalidate_artist methods to ApiCache.
2026-02-06 06:13:14 -06:00
vicrodh 5b8e9920eb feat: cache genres with 7-day TTL
Genre lists from Qobuz are near-static, so caching them avoids
unnecessary API calls. Added cached_genres table with GENRE_TTL_SECS
(7 days), get/set methods, and wired cache into get_genres() command.
Also updated cleanup_expired and clear_all to include the new table.
2026-02-06 06:11:54 -06:00
vicrodh a4eb35002c refactor: centralize API headers into api_headers/authenticated_headers methods
Replace 30+ manual .header("X-App-Id")/("X-User-Auth-Token") calls with
two helper methods:
- api_headers(): always sends X-App-Id, optionally X-User-Auth-Token
- authenticated_headers(): requires auth, fails if not logged in

This fixes the root cause of #43 (get_album/get_track missing auth token)
by making it impossible to forget headers. Also removes DEBUG-43 verbose
response body logging from client.rs since the root cause is resolved.
2026-02-06 05:41:41 -06:00