MB artists have beginarea as specific city (London, Birmingham) and
area as country (United Kingdom), never the subdivision (England).
Now the Lucene query includes the country: tag:"heavy metal" AND
(beginarea:"England" OR area:"England" OR area:"United Kingdom").
This finds all UK heavy metal artists instead of just the 9 that
literally have "England" as their area.
Resolve city to top-level subdivision at metadata time so the artist
sidebar shows "United Kingdom, England" instead of "United Kingdom,
Brixton". Label changed from "Born in"/"Founded in" to "From" to
match the broader regional context of scene discovery.
Rewrite resolve_parent_subdivision to iteratively walk the hierarchy
until finding the subdivision whose parent is the country.
Brixton -> Lambeth -> Greater London -> England (under UK).
Los Angeles -> California (under US).
Max 5 hops to prevent infinite loops.
Also: bump per_genre_limit to 200, display limit to 50,
fix misleading log (showed original city instead of resolved name).
Resolve cities UP to subdivision level via MB area relations API:
- Leyton -> England, Los Angeles -> California, Brixton -> England
- Uses direction:"forward" to walk "part of" chain upward
- Two-level inline lookup for districts/boroughs (avoids recursive async)
- Bump per_genre_limit to 100 for broader coverage
Was searching beginarea:"United States, Los Angeles" which returns 0
results. Now passes just "Los Angeles" for the search query and builds
"United States, Los Angeles" display separately for the UI header.
The browse API returns ALL artists from an area alphabetically with no
genre filtering. For LA that's 15K+ random artists. Now we search
tag:"thrash metal" AND beginarea:"Los Angeles" for each genre, giving
genre-relevant results directly from MusicBrainz.
Also adds dynamic loading messages that cycle through discovery steps
and temporarily disables all scene/validation caching for tuning.
Full scene discovery pipeline:
1. Resolve area MBID (if not provided) via MB area search
2. Check 30-day scene cache
3. Browse MB artists by area with tags
4. Score candidates with genre affinity algorithm
5. Validate top results against Qobuz catalog
6. Cache validation results individually (30-day TTL)
7. Cache full scene response for offset=0
Exposes artist location, life span, and affinity seeds to the
frontend. Caches results for 30 days. Reuses the existing
get_artist_with_relations API call, extracting area fields
that were previously ignored.
- models.rs: Area, ArtistMetadata, ArtistLocation, LocationPrecision,
AffinitySeeds, LocationCandidate, LocationDiscoveryResponse,
ArtistBrowseResponse, AreaSearchResponse structs. Added area and
begin_area fields to ArtistFullResponse and ArtistResult.
- client.rs: browse_artists_by_area (MB browse API by area MBID),
search_area (resolve area name to MBID).
- cache.rs: mb_artist_metadata (30d), mb_scene_cache (30d),
mb_qobuz_validation (30d) tables with get/set methods.
- genre_normalization.rs: normalize_genre, noisy tag filtering,
extract_affinity_seeds, genre_summary. Covers rock, metal,
electronic, hip-hop, jazz, and other genre families.
- location_discovery.rs: extract_metadata from ArtistFullResponse,
resolve_location with city/state/country precision, country code
to name mapping, affinity scoring, scene cache key builder,
MB date formatting.
Add dismissal system for "You May Also Like" discovery artists. Dismissals
are scoped to the genre tag used for the search, so dismissing AC/DC from
electronic recommendations won't affect metal recommendations.
- New discovery_dismissals table in reco DB (tag + normalized artist name)
- v2_dismiss_discovery_artist command persists dismissal per tag
- Discovery pipeline filters dismissed artists for both primary and secondary tags
- Backend returns 8 results (frontend shows 6, keeps 2 reserves)
- Thumbs-down button appears on hover, instantly replaces with reserve
- Returns primaryTag in DiscoveryResponse for frontend dismiss context
ListenBrainz similar-artists returned mainstream crossover artists (SOAD,
Led Zeppelin for Metallica) instead of genre-accurate results. Now uses
MusicBrainz tag search to find artists sharing the seed's primary genre tag
(e.g. "thrash metal" returns Megadeth, Slayer, Kreator, etc).
- Add search_artists_by_tag to MusicBrainz client
- Deterministic shuffle using seed MBID hash for variety across artists
- Secondary tag fallback when primary yields fewer than 5 results
- Rename label to "You May Also Like", limit results to 6
- Add Last.fm get_similar_artists method (unused, for future use)
- Add tags field to ArtistFullResponse model
- Tighten local history exclusion to play_count > 2
Replace lb-radio endpoint (popularity-based) with Labs similar-artists
POST endpoint that returns actual similarity scores. Add 85% threshold
filtering and expose similarityPercent field in DiscoveryArtist model.
Add v2_get_discovery_artists command that uses ListenBrainz lb-radio API
to find novel artist recommendations. Pipeline: fetch candidates from
ListenBrainz, filter out seed/similar/local-history artists, resolve on
Qobuz, filter blacklisted, return top 8 by affinity score.
When the user clears the image cache from settings, also clear
reco_album_meta, reco_track_meta, and reco_artist_meta tables.
This forces re-resolution from Tier 2/3 with the correct (small)
image URLs instead of keeping stale large (600px) URLs in Tier 1.
- Add composite indexes on reco_events for GROUP BY + ORDER BY queries
(event_type + album_id/track_id/artist_id + created_at DESC)
- Add composite index on reco_scores for scored lookups
(score_type + item_type + score DESC)
- Eliminate duplicate fallback queries in get_home_seeds_internal
by inlining the empty check into the scored branch
- Parallelize resolve_favorites with recent albums, tracks, and
artists (4-way tokio::try_join instead of 3 + sequential)
- Batch cache writes: collect all Tier 3 API results first, then
write to API cache and reco meta with single lock acquisitions
instead of locking per-item inside spawned tasks
Resolve albums, tracks, and artists concurrently with tokio::try_join
instead of sequentially. Artists were waiting behind album/track
resolution unnecessarily. Also change get_image() to prefer small
(230px) over large (600px) for card contexts, reducing download size.
When user enables Force DMA-BUF in Developer Settings, skip the
NVIDIA+Wayland compositing disable. This setting signals "I want
full GPU acceleration" and should grant both DMA-BUF and compositing.
- Detect Flatpak sandbox at startup and log it
- In Flatpak on Wayland, unset DISPLAY to prevent WebKitGTK from
falling back to XWayland internally (causes CPU-GPU texture
round-trips that degrade CSS blur performance)
- Per-GPU Wayland paths: only disable compositing and DMA-BUF for
NVIDIA on Wayland. AMD/Intel get full GPU acceleration on native
Wayland (compositing enabled, DMA-BUF enabled)
- Change GitHub Releases Flatpak manifest from --socket=x11 to
--socket=fallback-x11 to match Flathub and prevent XWayland
on Wayland sessions
Remove Spotify API credential setup from README since playlist import
now uses embed scraping. Update resolve_via_odesli_and_search doc
comment to reflect the new Spotify data path.
Spotify client_credentials API access ends 2026-03-06. Remove all
API token/proxy code and the paginated fetch_playlist_with_token
function. Playlist imports now go straight to embed page scraping
(limited to ~50 tracks, no ISRC/album data).
Link resolver for Spotify tracks/albums also switched from API to
embed page scraping via new fetch_embed_metadata function.
Remove Spotify credentials from .env.example since they are no
longer needed.
insert_qobuz_cached_track_direct was hardcoding track_number to 0,
causing all imported tracks to show wrong numbers. Now accepts
track_number and disc_number params, and both callers (backfill
downloads and offline cache sync) extract metadata from the audio
file via MetadataExtractor before inserting.
Also fix LocalLibraryView to use || instead of ?? for track_number
fallback, so 0 correctly falls back to index-based numbering.
Closes#147
Display parental_warning badge (E icon) next to track titles in all
views: TrackRow, QueuePanel, NowPlayingBar, ImmersivePlayer with all
13 visualizer panels, MiniPlayer surfaces, and AlbumDetailView.
Propagate parental_warning field through the full data chain from
Rust backend (qbz-models, queue, commands_v2, session_store, API
server) to frontend stores, adapters, and UI components. Badge uses
CSS mask technique with explicit.svg for theme compatibility.
ALSA Direct playback thread now supports gapless via mpsc channel:
when current source ends, it checks for a queued next source and
transitions seamlessly without draining the PCM buffer.
Format mismatch still blocks gapless (bit-perfect has priority).
Gapless now defaults to enabled for all backends.
v2_set_audio_gapless_enabled, v2_set_audio_normalization_enabled, and
v2_set_audio_normalization_target now call player.reload_settings()
after writing to SQLite, so changes take effect immediately without
requiring an app restart.
Embeds quality in album folder name instead of subfolder, so each
quality variant appears as a separate album in the local library.
Adds download error toast and fixes format labels to bracket notation.
Closes#116
Normalize notification artwork to square PNG (max 512x512, <=4 MiB) before sending Icon::Bytes via ashpd portal, so album art renders reliably without legacy fallback.\n\ncx
The XDG portal notification spec's icon field doesn't support inline
album artwork the way freedesktop Notifications does with image-path.
Dual-path approach:
- Native (non-Flatpak): use org.freedesktop.Notifications D-Bus
interface directly with image-path hint for album artwork
- Flatpak (sandboxed): use ashpd portal (Flathub requirement)
Uses ashpd::is_sandboxed() for detection and ashpd's re-exported
zbus for the direct D-Bus call, avoiding extra dependencies.
The XDG portal was not displaying album artwork when passed as a
file:// URI (showed KDE default icon instead). Send artwork as
Icon::Bytes so the portal receives the image data directly.
Replace direct D-Bus notifications (notify-rust) with XDG Desktop
Portal notifications (ashpd) for Flathub compliance. Portal
notifications use the app's .desktop file identity and don't require
--talk-name=org.freedesktop.Notifications permission.
- Replace notify-rust dep with ashpd (linux-only, tokio+notification)
- Use NotificationProxy for portal-based notifications
- Use stable ID "track-now-playing" to replace instead of stacking
- Convert artwork path to file:// URI via ashpd::Uri
- Graceful fallback when portal is unavailable
Detect when QBZ runs inside a Snap sandbox (via SNAP env var) and show
a one-time welcome modal with required plug connection commands. Adds a
Snap Sandbox section in Settings with copyable commands, mirroring the
existing Flatpak pattern.
Backend: snap.rs module, DB table snap_welcome_shown, V2 commands.
Frontend: updatesService, +page.svelte modal queue, SettingsView section.
v2_purchases_get_all and v2_purchases_get_by_type only set the
downloaded boolean but never populated downloaded_format_ids, so
check marks disappeared after app restart. Now both use
get_downloaded_purchase_formats() and build the format map, matching
the behavior already present in v2_purchases_get_album.
Fixes#119
Detects GPU (NVIDIA/AMD/Intel), display server (Wayland/X11), desktop
environment, and VM status. Computes optimal configuration based on a
decision matrix and writes it to the graphics settings DB.
Usage: qbz --autoconfig-graphics
Part of #127.
Add gsk_renderer field to graphics settings DB with UI dropdown
(Auto/GL/NGL/Vulkan/Cairo). Value is read at startup and set as
GSK_RENDERER env var. Env var override takes precedence. Also
included in --reset-graphics and reset now restores hw_accel to true.
Part of #127.
Add is_amd_gpu() and is_intel_gpu() detection functions alongside
existing is_nvidia_gpu(). Update GraphicsStartupStatus to expose
has_amd and has_intel to frontend. Log detected GPUs at startup.
Part of #127.
The hardware_acceleration toggle in settings was dead code — stored in
DB but ignored at startup (main.rs only checked QBZ_HARDWARE_ACCEL env
var). Now the DB value is read as the default, with env var override.
Fixes part of #127.