Commit Graph
212 Commits
Author SHA1 Message Date
vicrodh 17e2f711e3 refactor(ui): rename user-facing "Favorites" to "Library"
Aligns with Qobuz's own Library rename in their mobile and desktop
beta apps. Command names, store names, i18n keys, file names, and
internal route identifiers are all unchanged — this is strictly a
user-visible text + icon rewrite.

- Sidebar / TitleBar nav Favorites entry uses /static/music-library.svg
  instead of the lucide Heart
- Artist and label favorite buttons use /static/user-add.svg (albums
  and tracks keep the Heart affordance)
- 5 locale files: all values containing "Favorites" / "Favoritos" /
  "Favoriten" / "Favoris" / "Favoritos" rewritten to "Library" and
  locale-appropriate equivalents (Biblioteca / Bibliothek /
  Bibliothèque / biblioteca)
- "Add to Favorites" / "Remove from Favorites" buttons and toasts
  renamed to "Add to Library" / "Remove from Library"
- Hardcoded title= tooltip strings on track Heart buttons replaced
  with $t() calls

cc
2026-04-18 19:50:28 -06:00
vicrodh bcd07cf084 feat(i18n): Mixtapes and Collections keys across 5 locales
Adds mixtapes / collections / itemType / discographyBuilder /
collectionDetail namespaces, plus 6 common.* entries. Mexican
Spanish per project convention for es.json.

cc
2026-04-18 19:00:26 -06:00
vicrodh c2da9512ff refactor(ui): extract SourceBadge component from AlbumCard
Zero visual change. The Mixtape/Collection detail view will import the
same component. Adds qobuz_streaming as a new value alongside the four
existing LocalLibrary values; AlbumCard's own prop type is unchanged.

cc
2026-04-18 17:13:29 -06:00
vicrodh b62ffc730a refactor(home): rename yourMixes to qobuzMixes
Disambiguates Qobuz's auto-generated "Your Mixes" section from the
upcoming user-owned Mixtapes feature (spec
qbz-nix-docs/specs/2026-04-18-mixtapes-collections-design.md).

- Renamed HomeSectionId type union entry from 'yourMixes' to 'qobuzMixes'
- Updated DEFAULT_SECTIONS in homeSettingsStore.ts
- Renamed translation keys in all 5 locale files (en, es, de, fr, pt)
- Updated section labels to "Qobuz Mixes", "Mixes de Qobuz", etc.
- Updated all component references in HomeView, DynamicSuggestView, FavQView,
  ForYouTab, TopQView, and WeeklySuggestView

cc
2026-04-18 16:53:06 -06:00
vicrodh 6b91bd3872 feat(lyrics): active-line color picker, uppercase toggle, bigger L/XL
Add two persisted prefs to the lyrics sidebar display controls:

- activeColor: hex color override for the karaoke/active-line accent.
  Empty string means follow the theme's --accent-primary. Rendered via
  a new --lyrics-active-color CSS custom property set inline on the
  LyricsLines container; all four references to --accent-primary in
  the active-line gradient and glow shadows now fall back through it.
  Popover row shows a native color input plus a 'Theme' clear button
  that appears only when a custom color is set.

- uppercase: render all lines as uppercase in the sidebar. Applied via
  text-transform on .lyrics-lines.compact.uppercase. Switch toggle in
  the popover.

Bump L and XL sidebar sizes by the requested amount:
- L: 17/20 -> 18/21
- XL: 20/24 -> 22/26
(small and medium are unchanged.)

Both new prefs persist per-user in the same localStorage payload via
the existing sanitize/reload flow, and the hex validator rejects
malformed values on read.

cc
2026-04-18 12:41:34 -06:00
vicrodh 539bc72d85 feat(lyrics): add copy-to-clipboard button in sidebar header
A Copy icon button to the left of the Display settings trigger writes
the current track's lyrics (joined with newlines) to the clipboard via
the Tauri clipboard-manager plugin. The icon briefly swaps to a Check
for 1.5s on success, and a success/error toast is shown. The button is
disabled while lyrics are loading, when an error is displayed, or when
no lyrics are available.

cc
2026-04-18 12:18:13 -06:00
vicrodh 6ee3b5c8cd feat(lyrics): use app Dropdown for font select and add XL size option
Swap the native <select> in LyricsControlsPopover for the shared Dropdown
component so the font picker matches the app's settings style. Widen the
popover to fit the Dropdown trigger and shrink its chrome to keep the
overall scale compact.

Add an XL font size (20/24 px) alongside S/M/L, extending the
LyricsFontSize type, the sanitize validator, the size class in
LyricsLines, and the i18n entry across all five locales.

cc
2026-04-18 08:54:22 -06:00
vicrodh d84d9a9836 feat(lyrics): add i18n keys for sidebar display controls
cc
2026-04-18 08:00:43 -06:00
vicrodh 260e5c499c feat(login): show MIT-license-aware trademark disclaimer on loading screen
Pin the API-usage + trademark disclaimer to the bottom of the initializing
state of LoginView so the legal notice is visible during startup, not only
on the login screen proper. Update legal.trademarkNotice in all five locales
to mention the MIT license explicitly.

cc
2026-04-18 00:15:17 -06:00
vicrodh b3ad548a9c feat(subscription-gate): extend offline grace window 3 → 30 days
Matches Qobuz's own mobile app posture. The previous 3-day window was
both user-hostile (punishes anyone on a flaky network) and a compliance
red flag (shorter windows read as 'we're hiding something', longer
windows read as 'we're lenient for a reason'). 30 days is the exact
parity answer.

The useful protection against bulk-download-and-ghost attacks comes
from the CMAF-at-rest offline cache (v2 format), not from aggressive
revalidation. This grace window is a belt over the suspenders.

Changes:
- GRACE_PERIOD_SECS: 3 → 30 days in config/subscription_state.rs, with
  a comment explaining the reasoning for future readers.
- Log message in session_lifecycle no longer says "3 days" (it quoted
  a constant, now says "grace window").
- Disclaimer string in all 5 locales (en/es/de/fr/pt) updated from
  "3 days" to "30 days" so the Offline Playback settings screen
  reflects the actual behavior.

No behavior changes for users with an active subscription. Users who
were within the old 3-day window don't lose cache — the window
extended, not contracted.

cc
2026-04-17 08:28:00 -06:00
vicrodh 16af395356 feat(report-issue): confirmation modal with logs redirect + opt-out
Settings menu > Report Issue now opens a modal instead of jumping
straight to the GitHub new-issue page. The modal explains that
sharing the (manually masked) logs speeds up triage and offers two
actions:

- Go to logs → navigates to Settings > Developer Mode and
  auto-opens the Logs modal so the user can review and upload.
- Create issue report → opens the GitHub issues page.

A persistent "Do not show this again" checkbox stores the preference
in localStorage (qbz-hide-report-issue-modal). When the flag is set,
the menu item skips the modal and goes straight to GitHub, matching
the previous behaviour.

Wiring: new settingsIntentStore lets UserCard cue SettingsView to
open at the `developer` section with showLogsModal=true, consumed and
cleared on mount so a plain Settings navigation afterwards uses the
normal default section.

i18n: new reportIssue block added to all 5 locales (en/es/de/fr/pt).

cc
2026-04-16 11:47:49 -06:00
vicrodh 321c87f353 feat(logs): opt-out toggle to bundle diagnostics into terminal paste
Settings > Developer Mode > View Logs now has a toggle (default ON,
persisted to localStorage as qbz-paste-bundle-diagnostics) that
prepends the full System Diagnostics snapshot plus a 10-second cast
scan to the terminal paste upload, so bug reporters share one link
instead of two. Console-tab uploads are unaffected.

- New service module diagnosticsSnapshot.ts centralises the snapshot
  payload (runtime + system + playback + qconnect + optional cast
  scan) so the panel and the paste flow don't drift apart.
- LogsModal shows "Collecting diagnostics..." while the scan is
  running, then "Uploading..." once it hands off to the paste server.

Also revert the auto-run cast scan on the diagnostics panel: bundling
into the paste covers the "one-shot bug report" use case, and the
panel opens instantly again.

cc
2026-04-16 11:20:50 -06:00
vicrodh d5e3fff45c feat(diagnostics): add OS / distro / install method / deps section
New v2_get_system_info command and matching UI section in the
System Diagnostics panel. What it reports:

- os (linux/macos/windows)
- arch (x86_64, aarch64)
- kernel_version from /proc/sys/kernel/osrelease
- Linux distro: id, version_id, pretty_name from /etc/os-release
  (falls back to /run/host/os-release so it works inside Flatpak)
- install_method: flatpak / snap / appimage / dev / native, detected
  via FLATPAK_ID + /.flatpak-info, SNAP, APPIMAGE env vars, and
  debug_assertions as the last fallback
- flatpak_runtime + version: parsed from /.flatpak-info
- loaded library versions (from /proc/self/maps, symlink-resolved):
  webkit2gtk-4.1, gtk-3 (or 4), glibc, alsa, pipewire, pulseaudio

All info is derived from local files/env; no sensitive data, no IDs,
no hashes. Included in the clipboard export.

cc
2026-04-16 11:14:26 -06:00
vicrodh d9b1efc091 feat(diagnostics): add Playback, Qobuz Connect, and Cast Discovery
Extend the System Diagnostics panel with three new sections aimed at
what debug tickets usually ask for, and include them in the clipboard
export:

- Playback: is-playing, volume, position/duration, current track
  (title, artist, album, source, local flag, quality/format/bit-depth/
  sample-rate). No track IDs or UUIDs — defensive regex redacts any
  hex/UUID-shaped strings before export so paste sites and CodeQL
  don't flag them as secrets.

- Qobuz Connect: running/transport_connected, role (controller /
  local-renderer / observer / none), active renderer friendly
  name/brand/model (no UUIDs or IPs), visible renderer count, last
  error (id-redacted).

- Cast Discovery: on-demand 10-second multicast scan for Chromecast
  and DLNA; reports per-protocol count and the friendly names that
  were discovered. No hosts, no ports, no UUIDs.

Playback rows stay live via a player-store subscription while the
panel is open.

cc
2026-04-16 11:05:07 -06:00
vicrodh ace9e93b84 feat(window): optional track metadata in window title (#324)
Add an opt-in setting under Appearance that lets the OS window title
reflect the currently playing track. Template supports {artist},
{track}, {album} placeholders; defaults to "{artist} — {track}".
Falls back to "QBZ" when disabled or with no track.

Persisted to localStorage (pure UX preference — no backend state).

cc
2026-04-16 08:58:03 -06:00
vicrodh 33d65681c4 fix(queue): restore infinite play (V2 reimplementation)
Issue #319: Infinite Play toggle had no backend wiring after the V2
migration — toggle only wrote to localStorage and the queue-end handler
ignored it, so playback stopped at the last track.

Reimplemented in V2 (no legacy wrappers):

- New `v2_create_infinite_radio` command that takes the last few played
  track IDs, uses the most recent track's artist as the radio seed via
  `RadioPoolBuilder::create_artist_radio` (Qobuz `/radio/artist`
  endpoint), and returns 50 V2QueueTrack ready to append. Does NOT
  mutate the queue or playback context — caller decides how to use them.

- `playbackPreferencesStore`: expose `'infinite'` in `AutoplayMode`
  type and add `isInfinitePlayEnabled()` helper.

- `+page.svelte`:
  - `handleToggleInfinitePlay` now syncs to backend via
    `setAutoplayMode('infinite' | 'continue')` so the choice persists
    in `playback_preferences.db` across restarts (was localStorage only).
  - On startup load infinite play from cached preferences (was
    localStorage).
  - When `nextTrack()` returns null AND infinite play is on: collect
    the current track + last 5 history tracks, call
    `v2_create_infinite_radio`, append the result via
    `v2_bulk_add_to_queue`, then advance — playback continues without
    interruption.

i18n: add `toast.failedToSavePreference` to all 5 locales.

cc
2026-04-15 21:20:04 -06:00
vicrodh 3d0df4afba feat(a11y): add WCAG Light, WCAG Dark, High Contrast, and Colorblind themes
Four accessibility-focused themes with WCAG AAA contrast ratios:
- WCAG Light/Dark: 7:1+ contrast for all text levels
- High Contrast: pure white on pure black, yellow accent (21:1)
- Colorblind: deuteranopia-safe palette (Wong colors), no red/green

Addresses discussion #313 (user with severe vision impairment).

cc
2026-04-15 20:30:26 -06:00
vicrodh b62a4b01f5 feat(audio): notify user when selected output device goes missing (issue #307)
Level A of issue #307. When the configured output device has
disappeared from the host device list at the time of play/resume
(KVM switched away, USB sink unplugged, PipeWire sink removed…)
the backend emits an 'audio:device-missing' event and the frontend
shows a warning toast:

  'Output device "XYZ" is no longer available. Using system default.'

The existing init_device fallback already routes audio to the system
default, so this is purely informational — the user now understands
why audio came out of a different sink instead of staring at a silent
progress bar.

Does NOT touch the sacred audio init path (pipewire_backend.rs,
init_device, audio_settings.rs). New module audio_device_watch.rs
adds a pure presence check, a new-state throttle so we don't emit on
every resume once the user's been notified, and a v2 command
v2_check_audio_device_presence so the frontend can poll on demand.

Ready for Level B (background watchdog) — the same helpers plug into
a timer without further refactor; Level B only needs to add a tokio
task that calls emit_missing_if_needed() every N seconds while
playback is active.

cc
2026-04-14 17:58:59 -06:00
vicrodh 7c2f2941fd feat(award): gold press ribbon + Other awards carousel on AwardView
1. Press ribbon restyled to a solid gold gradient (#f5c042 → #d49511)
   with dark (#1f1407) uppercase text and a subtle white text-shadow.
   Drops the red border-left. Qobuzissime / Album of the Week keep
   their dark-background treatment with coloured left accent.

2. AwardView now loads /award/explore on mount (first 30 entries,
   current award filtered out) and renders them as a horizontal
   'Other awards' carousel below the album grid — same layout as
   LabelView's 'More labels'. Each card shows the award laurel
   (or magazine cover when present) + name + magazine name; clicking
   routes into that award's AwardView.

i18n key award.otherAwards added for all five locales.

cc
2026-04-14 16:07:22 -06:00
vicrodh 9460838223 fix(album-sidebar): keep award card always clickable; toast if id missing
Previously disabled={!onAwardClick || !award.id} grayed out the award
card whenever /album/get returned an award entry without an id. User
reported the award was 'no longer clickable' on albums opened from the
artist page. Now the button always accepts clicks and the handler
shows a toast when no id is available instead of silently doing
nothing — keeps the affordance honest without sending 'null' to
/award/page.

cc
2026-04-14 14:51:54 -06:00
vicrodh a273174716 feat(award): use /award/getAlbums for the grid + follow award as entity
Two behavior fixes for AwardView based on user feedback.

1. Grid content. The previous implementation rendered the releases
   array from /award/page, which is user-scoped (only surfaces awards
   on albums the user already favorited), so the grid came up empty
   in almost every case. Switch to /award/getAlbums which is the
   public catalog listing with pagination. /award/page is still
   called, but only to hydrate the hero (name, image, magazine).

2. Follow-the-award. Awards themselves are followable on Qobuz mobile
   (it's what feeds the 'Awards' tab of Release Watch). Add:
   - favorite_awards table + store methods in favorites_cache.rs
   - 4 V2 commands (get/sync/cache/uncache) mirroring the label ones
   - awardFavoritesStore.ts — clone of labelFavoritesStore with string
     ids (Qobuz mobile API passes award_ids as strings)
   - Follow/Unfollow heart button in AwardView hero, matching the
     ArtistDetailView / LabelView visual
   - loadAwardFavorites() wired into the login bootstrap alongside
     loadArtistFavorites / loadLabelFavorites
   - i18n keys (award.follow / award.unfollow)

Also adds a Ver más button for paginated loading of the album grid.

cc
2026-04-14 13:02:37 -06:00
vicrodh 8730c891cc feat(award): dedicated AwardView with header matching Label/Album conventions
Adds the frontend for award detail pages. The hero header mirrors
LabelView's proportions 1:1 — 180px circular avatar left, inline
subtitle/name/magazine stack right, same 32px heading, same 12px
uppercase kicker, same 24px gap, same 40px bottom margin, same back
button treatment. Outer container uses the same paddings and scroll
behaviour as LabelView/AlbumDetailView.

Body pulls /award/page and builds one HorizontalScrollRow per
container (releases, new-releases, classics…). Container titles
localize via award.section.<id> with fallback to 'Acclaimed Releases'.

Wiring:
- 'award' added to ViewType and session restore map
- handleAwardClick in +page.svelte navigates to the view
- AlbumDetailView sidebar now passes onAwardClick so Phase 2 award
  cards become interactive
- Frontend AlbumAward.id is optional to match tolerant backend struct;
  sidebar disables the award button when id is absent
- i18n keys added in all five locales

cc
2026-04-14 12:37:41 -06:00
vicrodh 7bf9972846 feat(album-view): right sidebar with label and full awards stack
Ports the Qobuz web 'label + awards' sidebar pattern to AlbumView.
Track list and sidebar sit side-by-side above 1100px and collapse
vertically below it. Label card navigates to the existing LabelView.
Award cards call onAwardClick when provided (disabled until Phase 3
lands AwardView).

Wiring:
- Adds awards (Vec<PageArtistAward>) to both qbz-models Album and
  src-tauri api::models Album so /album/get payloads hydrate the
  field instead of silently dropping it.
- Adds awards to QobuzAlbum and AlbumDetail frontend types plus the
  convertQobuzAlbum adapter.
- New album.sidebar i18n keys in all five locales.

Card ribbon still shows only the LAST award (Phase 1 behavior);
sidebar renders every award as a tap target — mobile-style.

cc
2026-04-14 11:11:24 -06:00
vicrodh ebeb86d03d feat(release-watch): dedicated tabbed view mirroring Qobuz mobile
Ports the Qobuz mobile 'Radar de Novedades' / Release Watch screen 1:1
(see rr0/i.java): a standalone view with three lazy-loaded tabs —
Artists, Labels, Awards — each hitting /favorite/getNewReleases with
the matching type parameter (limit 150 per tab, matching the mobile
cap).

Home's Release Watch rail (which still shows the Artists feed by
default, matching the mobile's default tab) now has a 'See all' link
that routes to the new view.

Adds view 'discover-release-watch' to the navigation type, wires the
route in +page.svelte, and adds tab/empty-state i18n keys in all five
locales.

cc
2026-04-14 10:52:20 -06:00
vicrodh 89c9ee1028 feat(home): add editorial ribbons for Qobuzissime and Album of the Week
Adds a small top-left ribbon on AlbumCard that surfaces Qobuz's two
locale-stable editorial awards: 'Qobuzissime' (award id 88, purple) and
'Álbum de la semana Qobuz' (award id 151, yellow). The ribbon is
z-index 4, above the hover action-overlay (z-index 2), so it stays
visible when the user hovers the card.

HomeView maps every /discover/index rail via discoverAlbumToCardData
through a new pickAlbumRibbon(awards) helper that matches by stable
award id rather than translated name. Essential Discography and
Forgotten Favorites (which render DiscoverAlbum raw) call
pickAlbumRibbon inline.

i18n keys added to all five locales under 'ribbon.*'.

cc
2026-04-14 10:22:14 -06:00
vicrodh 8b0bd820d9 feat(labels): replace heart overlay with pill Follow button below card
Swaps the top-right heart-in-circle on 'More labels' cards for a
pill-shaped Follow/Following button underneath the label name, matching
the Qobuz mobile app's affordance. Uses UserPlus icon + short label
('Sigue' / 'Follow' / etc.) when not followed, Check icon + 'Siguiendo'
when followed. Adds 'label.followShort' and 'label.followingShort'
i18n keys for all five locales.

cc
2026-04-14 09:42:02 -06:00
vicrodh ebe999c43d feat(favorites): add Labels tab to Favorites view
Introduces 'labels' as a first-class favorites tab alongside
tracks/albums/artists/playlists. Adds the navigation view
'favorites-labels', plumbs it through sidebar, titlebar dropdown,
favorites preferences modal and navigation store, and renders a
circular grid of followed labels with search and empty states.
Clicking a card navigates to the existing LabelView detail page.

Spanish copy uses 'Sellos' and Portuguese 'Selos' to match
Qobuz mobile terminology.

cc
2026-04-14 09:28:34 -06:00
vicrodh a9a9ba5050 feat(labels): follow / unfollow labels (mirrors Follow Artist)
Adds the same follow flow we already have for artists, applied to
labels. Confirmed against the inferred OpenAPI spec
(qbz-nix-docs/qobuz-api-inferred-openapi-v9.7.0.3.yaml) — the
existing /favorite/create, /favorite/delete and
/favorite/getUserFavorites endpoints already accept fav_type='label'
and label_ids, so no new Qobuz endpoints were needed.

Backend
- favorites_cache: new favorite_labels SQLite table + add / remove /
  sync / get methods alongside the artist counterparts. clear_all
  drops the new table on logout.
- commands_v2::favorites: 4 new commands —
  v2_get_cached_favorite_labels, v2_sync_cached_favorite_labels,
  v2_cache_favorite_label, v2_uncache_favorite_label. Same wiring as
  the artist commands.
- lib.rs invoke_handler registers the four new commands.

Frontend
- src/lib/stores/labelFavoritesStore.ts: clone of
  artistFavoritesStore — same in-memory Set, same isLoaded flag,
  toggle calls /favorite/create with fav_type=label, sync paginates
  /favorite/getUserFavorites?type=labels.
- routes/+page.svelte: loadLabelFavorites() called alongside the
  other three after login is confirmed.
- LabelView.svelte: heart-shape Follow button in the label header
  info column, mirroring ArtistDetailView's .artist-actions
  treatment. Reactive to the store via labelFavoritesVersion bump
  so any future Favorites view that toggles a label will keep this
  view in sync.
- i18n: label.follow / label.unfollow in all five locales.

cc
2026-04-14 09:00:11 -06:00
vicrodh 40ba414486 feat(discover): move Release Watch to below Your Mixes + add home.releaseWatch i18n key
HomeSettingsModal renders the section label with $t(`home.${section.id}`),
so the home.releaseWatch key was missing and the settings row showed
the raw key. Added it to all 5 locales (Spanish 'Radar de Novedades'
per the iOS screenshot; other languages match Qobuz's own strings
from the Android resources).

In ForYouTab, the section is now placed immediately after Your Mixes
instead of before Essentials — personal-feed sections (DailyQ / WeeklyQ
/ FavQ / TopQ + followed-artist releases) now sit together at the top.

HomeView section title switched to $t('home.releaseWatch') to match
the naming convention other sections already use.

cc
2026-04-14 08:22:23 -06:00
vicrodh ea216adf0f feat(discover): add Release Watch section to Home and For You
The Qobuz mobile client (v9.7) surfaces a 'Release Watch' /
'Radar de Novedades' feed in its Discover screens — new releases
from the artists, labels and award series the user follows. We
didn't have parity for it. This adds it as a module in both
Discover tabs.

Backend:
- qbz-qobuz: new ALBUMS_RELEASE_WATCH endpoint constant
  (/albums/releaseWatch, plural as in mobile) and signed_get_auth
  client method returning a SearchResultsPage<Album>. Signing
  method is 'albumsreleaseWatch' per the Qobuz secret protocol.
- qbz-core: passthrough get_release_watch on QbzCore.
- src-tauri: core_bridge wrapper, new v2_get_release_watch Tauri
  command registered in lib.rs. Filters through the artist
  blacklist like other album endpoints.

Frontend:
- HomeView: new renderable section 'releaseWatch' in the home tab
  (default visible, placed right after New Releases). Parallel
  fetch alongside the existing discover_index call; mapped via
  the existing toAlbumCard helper.
- ForYouTab: section appears ahead of 'Essentials' since followed
  artists/labels are typically more personal.
- homeSettingsStore: new HomeSectionId 'releaseWatch' in the type
  union and DEFAULT_SECTIONS so the user can toggle/reorder it
  from home customization.
- i18n: discover.releaseWatch.title + subtitle added to all five
  locales, using Qobuz's own translations where they exist
  ('Radar de Novedades' for Spanish, 'Release-Radar' for German)
  with the 'followed artists/labels/awards' subtitle copy adapted
  from the mobile app's strings.xml resources.

cc
2026-04-13 22:33:59 -06:00
vicrodh 808b9db0db fix(offline): real offline mode — snapshot streaming, block network, diag logs (#279)
Addresses the reported behavior where tracks marked available offline
still buffered from Qobuz after the v1.2.2 upgrade. Three layers:

1. **Snapshot / restore stream-first-track.** offline_settings gains a
   new nullable column pre_offline_stream_first_track (SQLite ALTER
   migration). v2_set_manual_offline now coordinates with
   AudioSettingsState: entering offline mode stashes the user's current
   stream_first_track preference and forces it to false; exiting
   restores the snapshot. User's preference survives the round-trip
   even across app restarts.

2. **Explicit offline refusal.** v2_play_track now takes
   OfflineState, reads manual_offline_mode up front, and — when every
   local cache miss has been exhausted — returns a new
   RuntimeError::TrackNotAvailableOffline instead of silently reaching
   for the network. Low-quality offline hits still play (better than
   nothing). The playback path also masks stream_first_enabled while
   offline as a belt-and-suspenders defense.

3. **Cache-miss visibility.** Every call site that consults the
   offline cache DB now logs [V2/CACHE STALE] at warn level when the
   DB says a track is cached but the filesystem path is missing. This
   made the issue #279 behavior invisible; the orphan entries from
   per-user isolation migration (aab3e6764) are now diagnosable from
   logs.

Frontend:
- playbackService surfaces TrackNotAvailableOffline as a toast ('…
  isn't saved for offline playback') instead of the generic error
- SettingsView listens for a new audio-settings-changed event so the
  stream-first-track toggle in the UI flips when the offline toggle
  mutates it
- i18n key offline.trackNotAvailableOffline added to all 5 locales

cc
2026-04-13 22:13:16 -06:00
vicrodh 568dc31df9 fix(ui): collapse right-section to hamburger at narrow widths (#303)
When the now-playing bar is narrower than 1100px (reported overlap at
half a 1866px display), the right-section now collapses into two
elements:

- An overflow button (…) that opens a popup menu containing Cast,
  Qobuz Connect, Lyrics, Mini-player, Full-screen, and Normalization.
- A mute/volume button that opens a vertical XP-style slider popup
  upward; the horizontal inline slider is only rendered at wide
  widths.

Detection uses a ResizeObserver on the bar element so the breakpoint
reacts to the actual space the bar receives (sidebar state, window
chrome, etc.) rather than a viewport media query. In the same narrow
mode QualityBadge and QconnectBadge switch to their compact variants
so the badges remain visible without the controls overlapping.

Queue button and offline/degraded indicators stay visible in both
layouts. Volume lock (ALSA hw:) keeps the existing inline disabled
variant so the 100% indicator is always visible.

i18n key player.moreControls added for all 5 locales.

cc
2026-04-13 20:14:16 -06:00
vicrodh 33761c6462 feat(ui): surface BitPerfectMode in QualityBadge (#288)
QualityBadge now has three new visual states wired off the
bit_perfect_mode field the backend emits in PlaybackEvent:

- DirectHardware → subtle green check next to the tier label,
  border tinted green. Reassurance that audiophile listeners are
  getting the stream untouched.
- PluginFallback → purple down-arrow + new tooltip explaining the
  DAC doesn't support the source rate so ALSA (plughw) is doing
  software resampling. The tooltip's suggested fix is explicit
  about the mitigation (pick lower-rate source or a broader DAC)
  since this is a hardware-capability constraint, not a user
  setting issue.
- Disabled / null → no change.

Plumbed through the existing playerStore subscription pattern so
the indicator reflects the stream the moment it opens. i18n keys
added for all 5 locales (en/es/de/fr/pt).

cc
2026-04-13 19:38:06 -06:00
vicrodh f664ce327d i18n(updater): add autoUpdate keys and download actions
Adds updates.autoUpdate.* keys (title, checking, downloading,
downloadingProgress, installing, restarting, failed) and
actions.downloadAndInstall / actions.downloadManually to all 5
locales (en, es, de, fr, pt).

cc
2026-04-13 14:56:39 -06:00
vicrodh a9282e2091 feat(qbzd): manual daemon connect + persistence + headless login
CastPicker: manual IP:port input for connecting to daemons when
mDNS/avahi is unavailable. Saves recent connections to localStorage
(max 5). One-click reconnect from saved list. Remove button on hover.

Layout: manual input field at top, saved daemons as 'Recent',
mDNS discovered daemons below with scanning indicator.

Login: binds to 0.0.0.0 with auto-detected LAN IP so the OAuth
callback URL works from any browser on the network. 5 min timeout
for remote copy-paste workflow.

i18n: recentDaemons, discoveredDaemons in all 5 locales.

cc
2026-04-09 09:28:01 -06:00
vicrodh 586d27bf43 feat(qbzd): integrate daemon discovery into CastPicker
QBZ Daemon tab appears first in the device picker alongside
Chromecast and DLNA. Discovered daemons show name + host:port.

Clicking a daemon:
1. Pings to verify it's reachable
2. Connects via playbackTargetStore
3. Starts SSE event subscription
4. Closes picker

Connected daemon shows in the connected banner with disconnect.
Disconnecting returns to local playback mode.

i18n: noQbzdFound + noQbzdFoundHint in all 5 locales.

cc
2026-04-09 06:08:08 -06:00
vicrodh 8dac185261 feat(qbzd): remote mode indicator in NowPlayingBar
Accent-colored banner shows 'Controlling: {device_name}' with a
disconnect button when the app is targeting a remote qbzd daemon.
Hidden in local mode.

i18n: player.controllingRemote in all 5 locales.

cc
2026-04-09 04:22:00 -06:00
vicrodh e62b160d5d feat(login): add cancel buttons and captcha hint to OAuth flows
WebView OAuth: cancel button appears during loading, captcha hint
shows after 25s or when user closes WebView. Hint suggests trying
system browser instead with one-click action.

System browser OAuth: cancel link appears next to loading text,
immediately aborts the wait.

i18n: new keys captchaHint and trySystemBrowser in all 5 locales.

cc
2026-04-08 08:55:02 -06:00
Gwendal Beaumont d709d75f5f Merge branch 'pre-release' into refactor/external/i18n-components 2026-04-05 20:58:51 +02:00
vicrodh ba6998778a feat(ui): add Allow Quality Fallback toggle in Audio settings
Off by default. Toggle in Audio settings controls whether QBZ
silently downgrades quality when the requested tier is unavailable.
i18n keys added to all 5 locales.

cc
2026-04-05 10:46:08 -06:00
vicrodh 7588a4804a feat(ui): add system browser OAuth link in login view
Secondary link-style option below the main OAuth button. Opens the
user's default browser (Firefox, Chrome, etc.) instead of the Tauri
WebView. Useful for password managers and saved sessions.

i18n keys added to all 5 locales.

cc
2026-04-04 21:05:36 -06:00
Gwendal Beaumont 80a57723f7 fix(i18n): critical issues 2026-04-04 21:52:09 +02:00
vicrodh d79eb8e1a8 feat(ui): add Lock Output Device toggle for JACK/DAW routing (#263)
PipeWire-only setting, disabled when DAC Passthrough is active.
i18n keys added to all 5 locales.

cc
2026-04-04 12:12:08 -06:00
Gwendal Beaumont 8817c08914 feat(i18n): add new roles 2026-04-02 18:53:13 +02:00
Gwendal Beaumont 4dce62a78a feat(i18n): add translations to roles 2026-04-02 14:51:52 +02:00
Gwendal Beaumont 2ad5f0e271 feat(i18n): strings for HomeSettingsModal 2026-04-02 12:37:42 +02:00
Gwendal Beaumont 2e38db2235 feat(i18n): german translation strings 2026-04-02 12:02:48 +02:00
Gwendal Beaumont 023b424a66 feat(i18n): add de translations 2026-04-02 09:09:10 +02:00
Afonso Jorge Ramos 7edffcf537 feat(i18n): add macOS-specific menu bar tray keys for all locales 2026-04-01 15:44:02 +02:00
vicrodh d19c142d4f fix: switch log upload to paste.rs (dpaste.org returns 403/405)
paste.rs accepts raw POST body and returns URL directly.
Simpler API, no expiration config needed.

cc
2026-03-31 16:45:07 -06:00