Add log_sanitize::redact_url, which preserves a URL's scheme, host, path
and query-parameter keys while masking every value and any fragment.
Apply it to the OAuth WebView navigation log lines (main on_navigation,
popup-window request, and popup on_navigation) so navigation traces stay
useful for debugging the flow without carrying parameter values into log
files or user-uploaded reports.
Covered by unit tests for code/token params, base64-padded values,
fragments, and query-less URLs.
cc
cpal 0.17.3 requires alsa 0.11, which in turn pulls alsa-sys 0.4. The
`links = "alsa"` constraint permits only one alsa-sys in the dependency
graph, so qbz-audio's direct alsa stack is raised in lockstep:
alsa 0.10 -> 0.11 and alsa-sys 0.3 -> 0.4. Both workspace lockfiles
(crates/ and src-tauri/) are updated to cpal 0.17.3 / alsa 0.11.0 /
alsa-sys 0.4.0.
No source changes required: alsa_direct.rs uses only the stable
high-level alsa API (pcm, HwParams, Format, Direction, ValueOr, mixer),
and the snd_lib_error_set_handler FFI in alsa_error_handler.rs is
unchanged in alsa-sys 0.4. Both workspaces pass cargo check.
cc
The offline-skip recursion walked the entire remaining queue synchronously,
desyncing the logical queue from the still-playing engine. Fixes:
- The gapless-transition target is the track the engine is already playing;
never run the availability-skip branch for it.
- isTrackAvailable now counts the in-memory/disk cache (new v2_is_track_cached
command), not just downloaded copies.
- Cap consecutive offline skips and play-failure auto-skips at 5 in both
+page.svelte and playbackService.ts.
cc
Long sessions died when a single transient blip flipped the app to offline and
the queue then skipped every cached-but-not-downloaded track. Harden the
detector:
- Consecutive-failure hysteresis: declare offline only after 2 failed polls,
not a single blip.
- Replace the HEAD https://1.1.1.1 probe with a concurrent race of cheap
generate_204 endpoints, so a saturated link (long Hi-Res tracks) no longer
times the probe out.
- Positive liveness override: if a segment downloaded within the last 45s we
are online by definition, regardless of the probe.
cc
Recovered from 63ecc324 (authored on the native-port branch, never merged to
pre-release). Backend-only, no Slint. Scales the prefetch cap down on slow
links and during audio underruns, and exposes the per-segment download signal
the offline detector reuses as a positive liveness override (#467).
cc
Adds qbz-integrations as a dependency of qbz-core and embeds a
MusicBrainzClient on the QbzCore struct (initialized in new()). The
client's own enabled flag governs whether requests fire; calls return
the integration-layer ServiceUnavailable error when disabled.
New methods on QbzCore<A>:
musicbrainz_is_enabled()
musicbrainz_set_enabled(enabled)
musicbrainz_resolve_artist(name) -> Option<ResolvedArtist>
musicbrainz_get_artist_metadata(mbid) -> ArtistMetadata
musicbrainz_get_artist_relationships(mbid) -> ArtistRelationships
The metadata method calls location::extract_metadata on the full artist
response and then resolves the real country from the begin_area
hierarchy when a city-level location was found (MB's "country" field is
where the artist is currently active, not where they were born/formed).
The relationships method splits relations into members / past_members /
groups / collaborators by relation_type and direction, mirroring
v2_musicbrainz_get_artist_relationships exactly.
No cache wiring yet — every call hits the network. A cache wrapper can
be plugged in later via a setter without changing the method
signatures.
cc
Pure-logic move of two helper modules out of src-tauri/src/musicbrainz/
into the qbz-integrations crate, where the MusicBrainzClient and DTOs
already live:
src-tauri/src/musicbrainz/location_discovery.rs
-> crates/qbz-integrations/src/musicbrainz/location.rs
src-tauri/src/musicbrainz/genre_normalization.rs
-> crates/qbz-integrations/src/musicbrainz/genre.rs
The functions exposed are unchanged: extract_metadata,
compute_affinity_score, build_scene_cache_key, format_life_span_date
(from location); normalize_genre, is_broad_genre, extract_affinity_seeds,
genre_summary (from genre). Imports in src-tauri/src/commands_v2/
integrations.rs are repointed to the new home; the legacy file
declarations are removed from src-tauri/src/musicbrainz/mod.rs. No
behavior change.
Unblocks the Slint side from reaching the same helpers via qbz-core
without depending on src-tauri.
cc
The qbz-cache and qbz-cmaf entries on qbz-player landed with the CMAF
playback work yesterday but the lockfile sync was missed. No code
change, lockfile only.
cc
SearchAllResults and MostPopularItem now live in qbz-models; the Tauri
helpers re-export them under the legacy V2 aliases so command code and
its response serialization are unchanged.
cc
Extracts two self-contained, framework-agnostic modules out of
src-tauri so they can be reused by other clients without duplicating
logic:
- ImageCacheService / ImageCacheStats move into the qbz-cache crate
(LRU disk cache, MD5-keyed, SQLite index).
- The credential store (keyring + AES-256-GCM encrypted file for Qobuz
credentials and the OAuth token) moves into a new qbz-credentials
crate.
src-tauri/image_cache.rs and src-tauri/credentials/mod.rs become thin
re-exports, keeping their Tauri managed-state wrappers. The Tauri app
is behavior-identical; both modules were already self-contained (no
Tauri and no crate:: references).
cc
Moves the credentials module (keyring + AES-256-GCM encrypted file
store for Qobuz credentials and the OAuth token) out of src-tauri into
a new qbz-credentials crate. src-tauri/credentials re-exports it, so the
Tauri app keeps identical behavior while the Slint shell can share the
exact same implementation, encrypted file, and keyring entries.
The module was already fully self-contained (no Tauri, no crate::
references); only the dependency declarations move with it.
cc
Cover art now goes through the QBZ image cache instead of being
downloaded directly every time, matching the Tauri behavior.
- Moves ImageCacheService / ImageCacheStats into the qbz-cache crate
(LRU disk cache, MD5-keyed, SQLite index). src-tauri/image_cache.rs
re-exports it and keeps the Tauri managed-state wrapper, so the Tauri
app stays behavior-identical and both shells share one implementation
and one on-disk cache (~/.cache/qbz/images).
- qbz-slint artwork.rs checks the cache first, downloads and stores on
a miss, and trims the cache to 200MB on startup.
cc
Adds a framework-agnostic composition root for non-Tauri UI shells
(Slint, TUI, headless) as Task 1 of the Slint POC readiness audit
(session-20-slint-poc-readiness-audit).
AppRuntime<A> owns an Arc<QbzCore<A>> plus the runtime state machine,
built with the same recipe as the Tauri CoreBridge but with no Tauri
dependency. Scope is composition and accessors only; per-user store
ownership and session activation are deferred to Task 2.
qbz-app now depends on qbz-core, qbz-player, qbz-audio, and qbz-models.
The Tauri app does not consume this module; it is purely additive.
cc
Switch the session store SQLite connection from synchronous=NORMAL to
synchronous=FULL. With WAL + NORMAL the last writes can sit unsynced in
the WAL and be lost on a hard reboot / power loss, dropping the restored
session.
cc
Version bumped across Cargo.toml, tauri.conf.json, package.json, snapcraft.yaml (version + source-tag), flake.nix, and the lockfiles. metainfo.xml gains a 1.2.13 release entry, the About modal codename becomes "400+ stars", and the README contributors list adds @Vudgekek and @DoubleGate.
cc
The graphics recommendation banner no longer writes settings: the Apply button and the v2_apply_graphics_recommendation command are removed, so the banner only detects and explains. The user applies the change with the real toggles, matching the HiFi Wizard's advisory model. write_recommendation stays for the qbz --autoconfig-graphics CLI.
The Force DMA-BUF help text is now GPU-aware: the instability warning only shows on NVIDIA-only systems; non-NVIDIA and hybrid hosts are told it is safe to enable. On hybrid hosts the rendering GPU dropdown marks the integrated GPU as recommended.
cc
`/favorite/getNewReleases` includes albums whose stream availability is
still in the future, so they appear in the Release Watch grid even
though `get_album` 404s as soon as the user clicks them. Same shape for
regional restrictions and label takedowns surfaced via `streamable=false`.
Add `release_date_stream` and `streamable` to the Album struct (both
optional with serde defaults so existing call sites stay happy) and
filter the response inside `v2_get_release_watch` after the artist
blacklist pass: skip any album marked `streamable=false` or whose
`release_date_stream` is strictly greater than today (UTC). The total
count gets adjusted alongside so pagination math stays consistent.
cc
Two-pronged improvement to the graphics safety net for users hitting
the NVIDIA + Wayland + WebKit GBM issue:
1) New opt-in toggle 'NVIDIA Wayland compatibility (experimental)' in
Settings > Graphics. When ON:
- Sets __NV_DISABLE_EXPLICIT_SYNC=1 to bypass libnvidia-egl-gbm's
buggy implicit-sync paths that produce "Failed to create GBM
buffer of size NxN: Invalid argument" on driver 550+.
- Lifts the defensive compositing-mode shutdown that QBZ applies
to NVIDIA-only Wayland sessions — the user is signaling the real
fix is now in place, so the safety net is no longer needed.
- Persisted in graphics_settings.db column nvidia_compat_mode (new,
default off). Migrated via ALTER TABLE so existing installs don't
pick up a behaviour change.
2) Reset-graphics CLI affordance, surfaced only when the toggle is on:
inline copyable input + Copy button rendering `qbz --reset-graphics`
(or `flatpak run com.blitzfc.qbz --reset-graphics` based on detected
install method). Works around the global select-disable elsewhere
in SettingsView via user-select: all + onclick={e.select()} + the
explicit button. The boot watchdog still auto-reverts on crash, but
this is the manual escape hatch when the user wants to recover
without waiting for the watchdog to fire.
3) Watchdog recovery now lands in full CPU mode after ANY revert.
The single-knob revert (hw_accel / dmabuf / preferred_gpu) still
narrows the cause for the UI banner, but the safest post-crash
state is full CPU mode (HW accel off + DMA-BUF off + preferred_gpu
reset to auto). A partial revert can still hit the next crash if
the failure was stack-wide (driver bug, missing extension); going
all-the-way-CPU guarantees the user always reaches a paintable
window after a recovery — they can selectively re-enable from the
Settings banner.
4) preferred_gpu='software' now implies hardware_acceleration=off at
env-var application time. Forcing Mesa llvmpipe while keeping
WebKit GPU compositing on would just round-trip software pixels
through a "GPU" path that has no GPU — nonsensical configuration
that the watchdog had to clean up after. Now it's prevented at
source.
i18n keys (en/es/de/fr/pt): settings.graphics.nvidiaCompatMode,
nvidiaCompatModeDesc, resetCmdTitle, resetCmdDesc;
actions.copiedToClipboard.
cc
DiscordIpcClient::new became infallible in 1.0 — it now returns Self
directly instead of Result<Self>. Failures surface at connect() time
where the catch already lives. Removed the now-spurious `if let Ok()`
wrap around the constructor.
Closes#428 (applied manually because the bump needed a code change
that Dependabot can't author).
cc
Three bugs were stacking up to make session resume drop the saved offset
for streamed Qobuz tracks:
1. Position save was debounced, not throttled. Each playback tick reset
the 5s timer, so the save never fired while a track played
continuously — the persisted position froze at whatever value got
written during the first quiet window after track start (typically
~2s). Switch to throttle: fire the first call immediately, then once
per 5s window, with a tail call to flush the latest value when the
window ends.
2. togglePlay tried to apply the restore offset via a separate cmdSeek
right after cmdPlayTrack. For Qobuz streaming the audio thread's
Seek handler bails when current_audio_data is unset — streaming
uses current_streaming_source instead — so the seek was silently
dropped and the track started at 0:00. Pass start_position_secs
directly to v2_play_track. The streaming backend (legacy + V2
player) waits for enough buffer to cover the offset plus 8s
headroom, then eagerly decode-and-discards decoder output up to
the offset before engaging the audio engine — no race, no
first-pull underrun. Plex and local tracks keep the old play+seek
path; they load full audio_data so Seek works as-is.
3. The seekbar showed 0:00 from app launch until the user hit play,
even when a saved position was queued. setPendingSessionRestore
now pre-positions currentTime when positionSecs > 0 so the UI
reflects "paused at X" the moment the app opens.
Other plumbing:
- AudioCommand::PlayStreaming carries start_position_secs and
content_length (used to estimate bytes-per-second for the resume
buffer target).
- Player::play_streaming{,_dynamic} grow a start_position_secs
parameter; QConnect and api_server callers pass 0 (remote-driven
playback always starts fresh).
- cmdPlayTrack forwards startPositionSecs as a 5th optional arg.
cc
Solves the long-running performance complaints (especially Optimus
laptops where the dGPU silently hijacks WebKit). The end-to-end shape:
- graphics_detection enumerates physical GPUs from /sys/class/drm and
cross-references EGL vendor JSONs under /usr/share/glvnd/egl_vendor.d
to expose only stacks the running process can actually use. Works
inside Flatpak/Snap because /sys is still mounted read-only and the
sandbox-visible EGL vendor JSONs are the right thing to read.
- preferred_gpu column in graphics_settings.db (string: auto, integrated,
discrete, software, or a specific PCI slot id). Migrated with
ALTER TABLE ... DEFAULT 'auto'.
- env_vars_for_preferred_gpu returns Set/Unset actions applied at startup
before WebKit init. Integrated/AMD path explicitly unsets
__NV_PRIME_RENDER_OFFLOAD so KDE/Optimus sessions that pre-set it to 1
no longer override the EGL pin and yank rendering back onto NVIDIA.
- Auto resolver: on hybrid systems (iGPU + dGPU both usable), Auto pins
to the iGPU. Single-GPU and no-GPU systems remain no-op. The dropdown
surfaces what Auto resolved to ("Auto (Intel Graphics)") so the user
sees the decision without picking explicitly.
- boot_watchdog writes a pending.json before WebKit init listing the
risky knobs in effect. Frontend calls v2_mark_boot_succeeded after
first paint to clear it and promote to last-good.json. If the next
launch sees pending without a success ack, it blames matching fields,
reverts them in-DB, and increments per-knob failure counters; two
consecutive failures lock the knob until the user clears it.
- Settings > Graphics gets a Rendering GPU dropdown (between HW accel
and DMA-BUF) and a crash-recovery banner that lets the user re-enable
any auto-disabled knob. Restart-required affordance shared with the
other risky toggles.
- i18n keys for the dropdown, banner copy, and autoWithTarget label
across all five locales.
cc
autoconfig_graphics gains a hybrid Intel/AMD + NVIDIA branch that keeps
GSK renderer on Auto and DMA-BUF off so the iGPU handles compositing
without the dGPU getting dragged in. detect_gpu_name is exposed and
labels hybrid setups explicitly (e.g. "NVIDIA (driver 580) + Intel
(i915)"). RuntimeDiagnostics gets runtime_gpu_name and
runtime_desktop_environment so the Diagnostics panel can show what the
app sees at runtime, which is the first thing useful to triage
performance reports from log uploads.
cc