The atmosphere mount in ImmersiveView kept the default animated:true, so
the 4-layer warped background forced full-window redraws at display rate
even with playback paused. Gate it on NowPlayingState.playing (the other
three mount sites already pass animated:false).
cc
- DECODED_PIXEL_CACHE is now byte-budgeted (48 MB, 24 MB on <8 GB RAM
hosts) instead of a 256-entry cap that could silently hold ~370 MB of
1000px now-playing decodes.
- Route the full-resolution Image::load_from_path callsites (offline
manager/favorites, local library ephemeral albums, MyQBZ, playlist
covers) through the existing decode-to-display-size pipeline.
- Cap Home playlist cards at 40 (was 100).
cc
- The 450ms poll pushed ~10 NowPlayingState properties every tick even
with nothing playing; Slint properties have no equality guard, so this
forced a full-window repaint ~2.2x/s forever. Snapshot-compare the
pushed values and skip the event-loop hop when nothing changed (poll
cadence untouched - QConnect reporting and gapless detection depend
on it).
- Palette/atmosphere extraction ran on the UI thread with four full
pixel-buffer copies per track change; do one shared downscale off the
UI thread and only set the results in the event loop.
cc
- Gate the shader drain on the immersive view being OPEN, not just
shader-mode > 0: the three close handlers never reset shader-mode, so
a picked shader scene kept rendering a full-res pass at 30fps forever
after closing the immersive view.
- Start/stop the drain timer from set-enabled instead of running it 30x/s
for the app lifetime; park the FFT producer thread on a condvar while
the tap is disabled.
- Compute the 5 energy bands in one spectrum pass (was two identical
scans) and reuse the waveform buffer.
- Shader underlay: rotate a small pool of render targets instead of
creating a fresh 2560x1440 texture every frame (~440 MB/s alloc churn),
clamp the target to the window physical size, and build pipelines
lazily on first use instead of compiling all six WGSL shaders at
startup.
cc
glibc 2.43 re-versioned six libm float functions (sinhf, coshf, atan2f,
acosf, asinf, acoshf); a binary linked on such a host requires
GLIBC_2.43 and refuses to start on older distros. Wrap each symbol via
-Wl,--wrap to an f64-based shim in glibc_compat.rs so the binary only
needs the ancient symbol versions (runs on glibc >= 2.39).
cc
--memory-swap=26g OOM-killed the ~30-33 GB qbz_ui rustc. Bump to 24g RAM /
48g total — above the demand, still well under the host's 30G+46G so a
runaway is caught without freezing the box.
cc
qbz-ui's Slint build imports the lyrics fonts from the repo root
(../../../../static/fonts/*.ttf), which is outside the crates/ workspace
cross mounts — so the cross build couldn't find them. Mount static/ at its
host path via CROSS_CONTAINER_OPTS.
cc
cross 0.2.5's default aarch64 image is Ubuntu 16.04 (xenial): libfreetype-dev
doesn't exist there (it's libfreetype6-dev) and its OpenSSL 1.0.2 / archived
repos break modern deps. Pin the :main image (Ubuntu 20.04+) and use the
libfreetype6-dev name (valid on both).
cc
Native (on an ARM runner: apt deps + cargo build -p qbz) and cross (from
x86-64 via cross + Cross.toml arm64 sysroot) paths, auto-selected by host
arch. Documents the ~30 GB qbz_ui RAM wall (8 GB Macs / 4 GB Pis cannot build
natively). Cross path is best-effort/untested pending a first run.
cc
The thin Unicode arrows (left/right/up/down) were nearly headless at keycap
size even after the font bump, so the arrow-key shortcuts use solid triangle
glyphs now. The footer Reset All to Defaults icon rode high next to its label
(a bare fixed-size icon in the row) — wrapped in a centered column.
cc
The hamburger "Documentation" entry had no clicked handler, so it did
nothing. Point it at github.com/vicrodh/qbz/wiki via the generic
external-link handler.
cc
The editable Customize Shortcuts modal now lays its categories across three
columns (extracted KbEditGroup, reusing the cheatsheet's column models) so it
matches the cheatsheet and avoids a tall scroll; panel widened to fit. Keycap
text bumped to 15px in both modals so the arrow-key glyphs read clearly.
cc
- Acknowledgments now lead with Rust (the app's core, previously unmentioned)
and drop Kawarp, which is no longer used (es/de/fr/pt updated).
- Logo renders from a 128px source instead of the 512px original scaled to
56px, which aliased badly (femtovg has no mipmaps).
- Qobuz trademark notice dropped to 11px so it wraps to two lines, not three.
cc
The window-level hotkey handler only suppressed single-key shortcuts
(Space/q/?/1-5) when UiFocusState.text-input-focused was set. The ~24
std-widget LineEdit fields across 14 modals/settings never set it, so those
keys fired (and were swallowed) mid-typing. Each now mirrors its focus via
`changed has-focus`; import added where missing.
cc
The read-only shortcuts modal listed every category in one tall column that
scrolled. Split the groups round-robin into three column models (Rust) and
render them side by side; the panel widened to fit. The editable customize
editor still uses the full single-column list.
cc
"Where you left off" only restored five id-free top-level surfaces, so
browsing Discover or any album/artist always reopened on Home. Persist the
full nav destination (serde-encoded NavEntry in ui_prefs.last_nav) and
restore it via apply_entry — album/artist/playlist/mix/label/etc. re-fetched
by id. Online restores the exact view; offline keeps the local top-level
fallback; Search/Settings are not persisted; Home stays the safe root.
cc
The now-playing bar, MPRIS metadata, and the desktop notification showed
the clean album name; for a re-edition (e.g. Octavarium (2009 Remaster))
the variant was lost. Carry the album-level version on QueueTrack
(populated on the album-play path) and append it to the displayed album.
Last.fm scrobbling keeps the CLEAN album name so editions don't fragment
stats. New album_version field defaults to None on every other queue source.
cc
Albums with a distinct version (remaster, Hi-Res re-edition, deluxe, etc.)
rendered as identical duplicates because the version was dropped. The web
player appends it in parens; mirror that via format_album_title across the
discography (map_release), search (map_album), the album header (map_album),
and the shared card mapper (suggestions/label/favorites). Verified against
live API: some variants carry the suffix in title (version null), others in
the version field — appending only when present matches the web player with
no double suffix. Add the version field to the Album model.
cc
Second album-page row sourced from Last.fm: the album's primary artist ->
artist.getSimilar -> one top album per similar artist, resolved to Qobuz and
deduped against the Qobuz /album/suggest row (by artist|title and by id).
Gated on Last.fm being connected; resolved row cached per album for 30 days
(re-opens make zero API calls; only non-empty results cached so a transient
failure re-fetches). New build_similar_albums_seeded in qbz-external-reco.
reset_album now also clears this row so the previous album's recommendations
don't linger while the next loads.
cc
The album header (⋯) and cover-art PopupWindows used the default
close-on-click policy, which races the row's TouchArea — the popup can
dismiss on the press before the item's clicked fires, so some rows (e.g.
Share Qobuz link) needed a second open+click. Switch both to
close-on-click-outside and close explicitly per row, matching the existing
Sidebar/QueueSidebar fix.
cc
/album/suggest returns a page with only {limit, items} (no total or
offset). SearchResultsPage required them, so the whole AlbumSuggestResponse
failed to deserialize and the album Suggestions carousel silently never
showed. Default the pagination scalars so only items is required.
cc
The header credit line derived composers by scanning every track's
composer, which over-credited songwriters on non-classical albums
(e.g. Anthrax showed Spitz/Bello/Benante). The official web player
instead uses the single album-level composer field and suppresses the
Various Composers placeholder.
Mirror releaseArtistsMapper: append one album.composer, skipping it
when the name contains VARIOUS (case-insensitive) or the id is already
credited. Add the composer field to the Album model.
cc
The 11 Recommendations msgids (IntegrationsSettings RECOMMENDATIONS/toggle/
description, HomeView tab, ExternalRecoView row titles + empty state) had
zero catalog entries and fell back to English everywhere. Translate them in
es/de/fr/pt, plus the 2 new JACK-note strings. es is Mexican (register-
checked: no voseo/Castilian). ru/ja stay English-fallback pending a reviewed
pass.
cc
JACK routes through the JACK graph (resamples to the graph rate), so it
can't be bit-perfect — but nothing told the user. Add a `backend-is-jack`
flag (settings.rs computes it beside alsa/pipewire at both set-sites;
state.slint property) and a WarningBanner in AudioSettings, gated on it,
pointing at ALSA-direct / PipeWire-passthrough for bit-perfect. UI-only;
backend behavior unchanged.
Blind change (no build this session) — validates on the next qbz_ui build.
cc
- About: contributors 4->5 per row so the 10 handles fill the widened
840px modal in 2 even rows instead of 3 sparse ones; modal height
720->770px.
- Splash: move the legal disclaimer out of the card to the bottom of the
screen (the main container, matching Tauri) and shrink it to 10px.
cc
Blocked albums looked like they "stayed" and clicking a card opened the
adjacent album: the model dropped the blocked album but the artwork jobs
were built from the UNFILTERED data with pre-filter indices, so every
cover landed on the shifted card (and the card's data was the next album).
- artist: artwork_jobs now skips blocked releases and uses a post-filter
album_idx that matches apply_artist's filtered+sorted model; the
"Latest release" is dropped at the source so the section hides and queues
no stale cover.
- favorites albums + home Recently Played: filter at the data source so the
model AND the artwork jobs derive from the same filtered list.
- albumview E1: append composers from the tracklist to the credit line —
album.artists carries only performers, so the composer (e.g. Mozart) was
missing; now derived distinct from track.composer with a Composer role.
cc
Phase 2 (Slint UI + wiring) for album-level blocking, plus the album-view
enhancements that share these files.
Album blacklist UI:
- "Block this album" in all three album menus: the page-header menu as a
Block/Unblock toggle (gated to Qobuz via AlbumState), and the AlbumCard +
AlbumListRow grid menus (one-way, drop-on-reload; local/Plex excluded).
Menu heights bumped for the extra item.
- Blacklist Manager gains an Artists | Albums tab bar (segmented, ADR-008);
an AlbumRow with a cover thumbnail (blind-eye fallback) + title + artist +
"Added {date}"; tab-aware controls (shared enable toggle, per-tab search /
Clear-All / count) and confirm modal. Settings row retitled "Blacklist",
status shows both counts.
- state.slint: BlacklistedAlbumItem + album-items/album-count/active-tab on
BlacklistState; set-tab/album-select/remove-album/clear-all-albums/
block-album on BlacklistActions; AlbumState.is-album-blocked.
- main.rs: ("album","block"/"unblock") arm (header, resolves display fields
from AlbumState); 5 album BlacklistActions callbacks; seed_blacklist_status
pushes album_count.
- blacklist_manager.rs: album build/push + set_tab/block_album/remove_album/
clear_all_albums; covers resolve async via a new ArtworkTarget::BlacklistAlbum
through spawn_loads.
Album-view enhancements (same files):
- E1: full credited-artist header line with localized roles, each name a
clickable artist link (Album.artists drove this; was interpreter-only).
- E3: classical work-header composer name is now a clickable artist link
(work title + composer id/name split through TrackData/TrackItem).
i18n: es (Mexican) for all new strings; de/fr/pt/ru/ja fall back to English
pending a translator pass.
cargo check -p qbz green (Slint codegen + bin).
cc
Phase 1 (headless). Adds the album-id filter axis on top of the artist
blacklist, orthogonal to it (an album is hidden by its OWN id regardless
of artist — the fix for Qobuz same-name artist merges).
- qbz-core: AlbumBlacklistFilter (HashSet<String>); album_blacklisted /
track_blacklisted / discover_album_blacklisted / pick_most_popular /
parse_search_all / search_all gain an album-id arm; fail-open guards
widened so an album-only block is not skipped. Artist arms (artist
search retain, pick_most_popular artists) stay artist-only. +6 tests.
- bridge: is_album_blacklisted / album_ids_snapshot / card_blacklisted;
stamp_row + is_track_blacklisted take the row's album id (source!="qobuz"
guard preserved; render/queue stay in lockstep).
- enforce at every album-bearing consumer, threading the album snapshot:
search (page-1 + load-more), home, discover, for-you (+spotlight),
album rows + queue + carousels, playlist/mix/favorites rows.
- close pre-existing leaks (both axes at once, owner decision E): label
pages (were entirely unfiltered — the prime merged-artist surface),
search load-more/replace-category, favorites albums grid, artist
discography (own-id), myqbz collection albums.
- albumview: full readable release date in the header ("Feb 19, 2026")
via the localized %b formatter, replacing the year-only slice.
cargo test -p qbz-app (12) + qbz-core (39) green; cargo check -p qbz green.
cc
Parallel album-id axis alongside the u64 artist blacklist. New
album_blacklist TEXT-PK table in the same artist_blacklist.db (additive,
idempotent, no migration), an in-memory HashSet<String> mirror, and
is_album_blacklisted/add_album/remove_album/get_all_albums/album_count/
clear_all_albums. Shares the single enabled flag with the artist axis.
12 unit tests green (incl. shared-flag + axis-independence).
cc
- Album view: classical "work" section grouping (PR #536 port) — new
Track.work model field + run-length per-work headers in AlbumView.
- i18n: add Russian + Japanese locales (1365 msgids each); extend plural.rs
(ru 3-form one/few/many, ja single-form) + tests; fix the language-selector
reseed (main.rs set_languages + ui_prefs index maps). Seven languages total.
- About modal: native port of the Tauri About (version, links, build info,
one-paragraph acknowledgments, inline contributors with GitHub avatars,
signature). Contributors credit hoyon (PR #536) and mxnix (PR #517, ru).
- What's New modal: renders the GitHub release-tag description (markdown
sections + TOC); release fetch + build.rs emits build date/commit.
- Discover Recommendations: move Cache window + Refresh now into the Customize
modal, gated to the reco tab; hide the section-config UI there; add an
explainer paragraph. New strings translated to all six non-English locales.
- About modal width +120px; splash screen gains the legal disclaimer footer.
cc
The sleep-timer PopupWindow used the default close-policy (close-on-click),
so any click inside it — including on the preset picker — closed it before
the value could change. Set close-policy: close-on-click-outside. Also
replaced the QbzSelect (itself a PopupWindow, which Slint cannot open nested
inside another PopupWindow) with inline preset rows, so the presets are
actually pickable.
cc
In multi-select mode the entire TrackRow body is a selection target (Tauri
whole-row selectable), not just the 13px checkbox. The body click routes the
same media-action("track", id, "toggle-select") the checkbox does, so the
central toggle arm reads the modifier — Shift+Click range and Ctrl+Click
work on the body too. The "..." context-menu button + the checkbox keep
their own handlers (the menu stays reachable); the other in-row controls
(artist/album link, favorite heart, offline button) are disabled in select
mode so the click passes through to the row. Double-click no longer plays in
select mode. Inert (blacklisted) rows never select.
cc
Extend the keyboard multi-select feature to the surfaces that had no
working selection in the Slint port.
- Mix (DailyQ/WeeklyQ/FavQ/TopQ all render through one MixView): mode
toggle + MultiSelectBar, per-row checkbox via the central toggle arm + a
ContentView::Mix branch (model + recount), bulk play-next/queue/
add-to-playlist. Shift-range, Ctrl+A and Escape inherited automatically.
- Label Popular Tracks: was a dead stub (checkboxes with no bar/recount/
router arms). Converted the local mode flag to LabelState.multi-select,
added a real MultiSelectBar + label.rs recount/select_all/clear/selected,
and the missing select-toggle/bulk router arms.
- LocalLibrary Albums grid (a previously-missing surface): AlbumCardItem
gains a `selected` field; a `select-mode` prop is threaded through the
shared AlbumCard / AlbumListRow / AlbumGrid / AlbumCollectionView (default
false, so Discover/Label/Favorites are untouched). Albums toolbar toggle
+ MultiSelectBar; local_library.rs album select fns (flat + grouped,
id-resilient Shift-range over AlbumCardItem); album->tracks bulk resolve
via fetch_album_tracks_blocking on spawn_blocking for queue/play-next/
add-to-playlist/add-to-mixtape; Ctrl+A and Escape routed by active-tab.
cargo check -p qbz green (qbz_ui compiles clean).
cc
Bring back the Tauri Excel-style keyboard multi-select to the Slint port:
Shift+Click extends an additive range from the last-clicked anchor,
Ctrl/Cmd+A selects all (select-all-only, never toggles to clear), and
Escape leaves select mode (clear + mode off). Plain/Ctrl+Click stay a
single per-row toggle (1:1 Tauri, which reads only shiftKey at click).
- New selection.rs: per-surface anchor (thread-local, id-resilient across
model re-sort/filter) + generic apply_shift_range and select_all over
any VecModel via a setter closure.
- keybindings::mods() made pub; Escape branch in handle_escape.
- Central toggle-select arm (Album/Artist/Playlist/Favorites) + dedicated
paths for LocalLibrary Tracks and Offline Manager (OfflineRow, range
setter guarded to kind==track).
- Ctrl/Cmd+A routed by NavState.view to the active surface's select-all,
gated on its multi-select flag; behind the existing text-input guard.
cc
Sleep timer (queue footer): tokio monotonic deadline + 1Hz countdown that
fires core().pause() at expiry; presets + custom minutes via a popover.
Stop after this song: 4 QbzCore passthroughs + playback consume-before-advance
seam and the load-bearing gapless-prefetch gate, plus the queue marker/menu so
the Queue persists when playback halts.
qbz-log crate: TeeLogger wrapping env_logger (stderr + 5000-line ring +
~/.local/share/qbz/logs/qbz.log) with write-time secret redaction (regex +
live-secret registry) and a diagnostics bundle builder.
Dev panel logs: in-app viewer that shows content the moment it opens, advanced
controls behind a discreet [+], "Copy all" as the sole footer button, a copy
button on the upload URL, and a redacted public paste upload.
Diagnostics: qbz-app::diagnostics (extracted, src-tauri untouched) feeding a
7-section panel with the live active output device + sample rate/format (pactl)
and a complete redacted report for the upload.
Report an Issue: wires the header menu item to a 1:1 Tauri modal that opens the
GitHub bug-report template.
Appearance persistence: 6 toggles that were live-only now persist via ui_prefs
+ handler arm + startup seed (window-title-show, show-volume-steppers,
sidebar-playlist-collage, local-library-track-artwork, in-app-toasts,
theme-filter).
Recommendations cache (per-tab only): TTL select 24/36/48/72h + Refresh now in
ExternalRecoView; no change to the other Discover tabs.
Bump workspace version 0.1.0 -> 1.2.15 so app_version is correct in the
diagnostics report and the QConnect About string. i18n es/de/fr/pt for all new
strings.
cc
A tray double-click fired two activate calls within milliseconds. On Wayland
each toggle destroys and recreates the winit surface and the wgpu shader
underlay, so a render still in flight then use-after-frees a TextureView
(wgpu-core panic "TextureView ... is no longer alive"). Collapse a double-click
into a single toggle with a 400ms debounce; the first click always passes.
cc
Circular 'liquid' audio-spectrum shader scene (Motion-Array style): FFT bands wrapped around a ring, smoothed into an organic blob glowing in the album palette, with a rotating radial-rays backdrop and a beat-triggered burst.
Wired as shader-mode 6 (liquid_spectrum.wgsl + SHADER_SOURCES[4] + render_frame), but the first cut did not land visually, so the picker row is commented out and the g-cycle is left at 0..5 — the scene is unreachable but kept intact for a future pass.
cc
Wave Bed (focus mode 6): centered vertical-mirror waveform. Spectral Ribbon (shader-mode 4): paint-as-you-play GPU spectrogram with green axes + header overlay, bass-at-bottom orientation aligned to the axes, and a real-time ceiling line tracking the highest active frequency. Line Bed (shader-mode 5): wgpu line-strip waterfall with a leveled camera, B-spline-smoothed continuous curves, a primary->accent height gradient, a bottom cutoff above the player, 2x supersampling, and a sampled-texture heights feed (no vertex storage buffer, so it runs on Slint's downlevel wgpu device).
Also: Plasma reworked to be far more reactive (4 ink emitters, beat splats, faster turbulence, quicker decay); immersive song card no longer truncates the album/artist line; player heart reflects favorite state; player bar narrowed; sample-rate and bit-depth wired through for the spectral header.
cc
Expand the shader Uniforms to 144 bytes (phase/beat/level/level_smooth + 8 log
bands + 5 energy bands + the album-art palette triad), feed them per frame from
the 30 fps visualizer drain (new FrameAudio + a forward-motion phase accumulator)
and push the palette on track change. Move plasma and aurora onto the new
uniforms and add a bottom-right ImmersiveSongCard shown over any shader scene.
Pairs with the faster tunnel from the previous commit.
cc
Amplify the forward-flight clock (phase * 6, seam-safe at the 4096 wrap), add
radial speed-lines fanning out of the vanishing point (shimmer with treble and
the beat), and bend the corridor CENTERLINE as a function of depth so the path
winds while the rectangular cross-section stays undeformed. Stronger beat punch.
tunnel.wgsl only; plasma and aurora untouched.
cc
The detail toggle used sidebar::contains to decide "following", which is
true for owned playlists shown in the sidebar and false for followed ones
opened from Favorites > Followed (a followed playlist may live in a folder
or not be rendered in the sidebar at all). Being in the sidebar is a
consequence of following, not the determinant, so derive it authoritatively
from get_user_playlists (present but owner != me). Reflect the state in the
button: a check icon + "Unfollow on Qobuz" tooltip when following.
cc
When the sidebar is fully collapsed, the header playlists flyout now has
a search box (sharing the sidebar's Rust-side filter, cleared on every
close so it never leaks into the expanded list) and an Import entry
(offline-gated, mirroring the sidebar "..." menu).
cc
Persist the source playlist id when a Qobuz playlist is copied (new
copied_playlists table in library.db, mirroring Tauri's user-scoped
qbz_copied_playlists set) and seed is-copied on detail-view load, so the
Copy button stays hidden on later opens, surviving an app restart.
cc
Classic NPB's "+" fired a one-click add-to-playlist; now it opens the same
grouped add menu (library / playlist / queue / play-next / mixtape / collection)
as New and Large. Classic keeps its inline favorite toggle next to it so the
favorite state stays visible.
cc
The Large now-playing cover sat flush against the window bottom. Lift it 2px and
take those 2px from the cover<->spectrum gap (10 -> 8) so the dock height is
unchanged; the bottom pad applies only while the spectrum is shown so the
off-state height still matches large-dock-height.
cc
A followed/subscribed Qobuz playlist could not be removed: the port flagged it
owned (via sidebar membership, which includes followed playlists) and called
delete_playlist, which Qobuz accepts but no-ops on a playlist you don't own.
- Ownership now follows Tauri: owner.id == current user id. Owned => Delete;
not-owned => the Follow/Unfollow toggle (previously hidden because the
playlist looked owned). Carry owner_id on PlaylistData; reset is-owner /
is-following / is-copied per navigation so state can't leak.
- The delete handler re-derives ownership server-side (get_playlist) as a
backstop for the manager/sidebar entry points and routes owned => delete,
followed => unsubscribe. delete_playlist never runs on a non-owned playlist.
- Post-removal nav only steps back in history when viewing that playlist's
detail; otherwise it stays on the invoking surface (sidebar refreshed),
instead of always routing to Favorites > Playlists.
cc
- Appearance: drop the red 'unsupported' tint on Language; hide UI Scale and
the Title Bar / Window Controls / Immersive blocks (commented, not deleted).
- Window title: "Show track in window title" now works -- the OS window title
becomes "{track} - {artist} | qbz" via a reactive binding (editable template
row hidden; format is fixed).
- Plex and Scrobblers collapse carets enlarged (28/18) and moved to row level so
they are vertically centered with the master toggle.
- Hamburger: "Open Qobuz Link" -> "Open Music Link".
- Link modal: a long pasted URL no longer grows the input/modal (min-width 0 on
the clip container caps content-width propagation).
cc
The floating back-to-top button overlapped the last track rows / the docked
Suggested Songs. Reserve the FAB footprint as a bottom footer so content
scrolls clear of it. Constant (not scroll-gated) to avoid a layout binding
loop (footer height -> track-list height -> visible-height -> FAB condition).
cc
- Radio Stations seed only Qobuz-sourced recent albums; local/Plex albums
carry ids the Qobuz /radio/album endpoint can't resolve, so they no longer
generate radio tiles (they still appear in Recently Played Albums).
- Recently Played albums/tracks route artwork by scheme (spawn_search_loads):
http -> Qobuz CDN, /library/ or /photo/ -> Plex thumb, else local file. The
plain HTTP loader left Plex/local covers blank.
- Radio Stations carousel extracted to a RadioCarousel component with prev/next
nav buttons (drag preserved), mirroring SlimCarousel.
cc