Recovery banner replaced by a tri-state header badge left of the menu
button (Offline hard amber / Offline manual accent / Logged out gray,
hidden when online and authenticated) with a flyout carrying the full
status line, a Sign in button for the logged-out state (enabled unless
connectivity is CONFIRMED down - it was born dead behind the Unknown
verdict; action fires before the popup closes) and a quick Offline-mode
toggle for the manual state (Settings shortcut dropped per owner).
Discover/Library nav entries are hidden offline instead of disabled, on
all three nav surfaces.
Sidebar flyout incremental drift root-caused by a headless harness against
slint 1.16.1: an opacity binding on SidebarNavRow's ROOT (left over from
the disabled-gating approach) forces an inlined opacity wrapper that makes
absolute-position double-count self.y - one row-pitch of drift per row,
which the in-layout banner had masked as its own bug. Binding removed;
anchors measure exact in all states. Same-class latent hazard flagged in
NavTab/CompactNavBtn (cancels today, self.x=0).
Header MyQBZ dropdown rows carried no route (documented no-op): Collections
and Mixtapes now navigate from both the full tabs and the compact rail.
The offline-edge watcher also reacts to connectivity-only changes so the
network-folder browse set refreshes in place during a logged-out session.
cc
New local_playlists + local_playlist_tracks schema in the shared per-user
library.db (ids 'local:<uuid>', sources qobuz/local/plex, position-compacted,
8 repo tests in qbz-library). PlaylistRef routing at the navigation seam
makes local ids unrepresentable in Qobuz playlist calls by construction
(all take u64). Local playlists appear in sidebar, manager and picker
(picker hides the Qobuz set while offline); detail view reuses the existing
playlist row machinery and renders offline; creation modal gains the
offline-only toggle (forced on while offline - creating offline always
produces a local playlist, D8) and Edit can flip it later; explicit
Upload-to-Qobuz conversion for non-offline-only locals (confirm dialog,
local rows attach via the existing sidecar, entity kept on failure).
Never-to-Qobuz guard: queues built from an offline-only playlist carry a
core-level stamp cleared by every queue replacement; the QConnect sync
skips the cloud push and play-on-peer falls back to local playback while
the stamp is set.
Deferred (logged in the slice report): plex-row attach on upload, custom
reorder for local playlists, folder/favorite/hide on local items, manager
sort interleaving, offline cover thumbs for cached qobuz rows.
cc
New OfflinePlaceholder (mode-aware copy + Open Settings shortcut for induced
mode) mounted at the AppShell routing seam over the 12 Qobuz-only content
views; the matching nav rows (sidebar, header tabs, compact rail) render
disabled while offline instead of hiding (layout stability). Playback gains
the offline playable rule (local always, plex only under induced, qobuz only
when cached and within the subscription grace): explicit plays fast-fail
with an honest toast (distinct copy when the grace expired), manual and
auto advance skip unavailable tracks bounded by 5 consecutive (gapless
target structurally exempt), gapless prefetch skips unplayable successors,
and the network prefetch warmer pauses offline.
cc
- Detail row ⋯ menu (and the hero overflow + filter popups) were PopupWindows
with no explicit height, so they collapsed and the rows below painted over them.
Gave each an explicit height (+ close-on-click) like every other app menu, so
they paint above the rows.
- Row menu Remove now actually removes: it was a no-op ("remove" wasn't a RowMode)
-> wired to remove the item (repo::remove_item by position, reload). Add-to-queue
was also silently broken (emitted "play-later", unparsed) -> now "add-to-queue".
- Sidebar playlist right-click menu: added "Add to Mixtape/Collection" (opens the
picker with the playlist payload).
- Album -> Collection: an album can now be added to ANY collection kind incl.
artist_collection (removed the ArtistCollection exclusion in load_rows; Tauri
allows adding an album to an artist discography).
Offline in the row menu is net-new (Tauri's collection row menu has none; it would
be a batch resolve-then-cache) and is NOT added here. Backend crates untouched.
cargo check -p qbz-slint green.
cc
Phase-2 Slice 12a. Per the user's decision (DQ3), the "My QBZ" rename/change-icon
lives in Settings > Appearance, not a sidebar context menu:
- src/myqbz_prefs.rs: per-user {label, icon_path} persisted to
users/<id>/myqbz_branding.json (scoped like plex_settings/tray_settings);
init_for_user/seed/set_label/pick_icon(rfd)/reset_icon. Blank label coerces to
"My QBZ"; a missing/deleted custom icon falls back to the default my-qbz.svg
without crashing (path preserved). 4 unit tests.
- ui/settings/AppearanceSettings.slint: a "My QBZ" SettingRow (icon preview +
label LineEdit + Change icon... + Reset).
- ui/shell/Sidebar.slint: the My QBZ row binds label/icon to MyQbzBrandingState
(raw-icon flag renders a custom user image untinted; default glyph stays tinted).
- Seeded on enter_shell.
Backend crates untouched. cargo check -p qbz-slint green.
cc
Phase-2 Slice 2 of the MyQBZ port. Wires the mixtape DB + the first
visually-reachable surface in Slint (read-only):
- Migrations: run qbz_mixtape::schema::run_mixtape_migrations in enter_shell
(via library_db with_db -> with_connection), best-effort, on session open —
same per-user library.db the Tauri build migrates.
- Glue: new src/myqbz.rs mirrors playlist_manager.rs — list_collections via the
shared crate (items hydrated), model/sort/filter builders, mosaic artwork jobs,
navigate. NavEntry::Mixtapes/Collections + back/forward + scroll-restore scopes.
- UI: ui/myqbz/{MixtapesView,CollectionsView,CollectionMosaic,MyQbzShared}.slint,
1:1 with specs 10/11 (208px grid, mosaic custom->2x2/3x3->kind-glyph, toolbar,
both empty states, ICU plural). MixtapeCardItem + MyQbzState/MyQbzActions in
state.slint; mounted in AppShell. My QBZ sidebar entry routed to the two grids
(branded mask-tinted icons), matching the port's existing flyout-nav idiom.
Read-only boundary: create/open-detail are logging stubs; titlebar My QBZ
dropdown and the Settings>Appearance rename (DQ3) are deferred to later slices.
Backend crates untouched. cargo check -p qbz-slint green.
cc
Ephemeral folders: open / browse / play a folder without indexing it —
album-grouped pane, CUE seek fast-path (no full re-decode per virtual track),
folder-path persistence + rehydrate, "already playing" add-vs-replace choice
dialog (ephemeral-only), and now-playing / queue / menu gating so an ephemeral
track exposes no persistence actions.
Folders tree brought 1:1 with Tauri:
- Rail header: select-mode, collapse-all, open-ephemeral, tree search.
- Resizable rail (default ~20% narrower; drag handle, min 50% default /
max 50% of the content area).
- Subfolder cover grid + grid/list toggle, recursive track count, name search;
compact icon bulk bar (folder-check / play-next / queue / playlist / clear).
- Tree is the default mode; circular checkboxes homologated with TrackRow;
compact rows (smaller type + icon, no ambiguous trailing count).
- Flat/Tree icon toggle (disc-album / folder-tree), floated right.
Per-source TrackRow context menu (logic, not a blanket trim): Qobuz/offline =
full; local = play/next/queue + add-to-playlist; plex/ephemeral = play/next/
queue only.
App-wide standard: Math.round pixel-snap on icon centering across the UI to
kill sub-pixel blur (CircleAction, header back/forward/collapse, and the rest).
NowPlayingBar context icon -> turntable (frees disc-album for the Folders
toggle). New assets: folder-check, folder-tree, turntable.
cc
SidebarNavRow (mini sidebar) opened its section flyout with no header;
the closed-sidebar CompactNavBtn dropdowns show the section name + a
hairline. Set HeaderMenuState.title to the row label so the mini flyouts
match (same @tr strings).
cc
In the mini (icon-only) sidebar the rows show no name. Add a hover bubble
to the right of each folder/top-level-playlist row surfacing the name
(folders also append their playlist count), mirroring the native title
Tauri puts on its collapsed buttons. Rendered at the AppShell level
(SidebarTooltip + SidebarTooltipState) because the sidebar clips its
overflow; anchored with the same Flickable absolute-position correction as
the folder flyout. Closes on click so it does not linger behind the flyout.
cc
absolute-position of an element nested in a Flickable double-counts the
element's own layout offset (measured: abs.y = true_y + root.y), so the
collapsed-sidebar folder flyout drifted down by one row-pitch per row
(lower folders left the viewport) and the track drag ghost plus the
playlist drop-target hit box drifted the same way. Subtract root.{x,y}
to recover the true window position; scroll-safe because root.{x,y} is
the row's fixed in-layout offset, not the viewport scroll. Rows outside
a Flickable (the section nav rows) are unaffected and left as-is.
Sites: SidebarRow folder-flyout anchor + recompute-drop hit box;
TrackRow drag down/threshold/start/move coordinates.
cc
Shell & navigation:
- Three-state sidebar (open/mini/closed); collapse cycles via header button.
- Section nav as right-flyouts (sidebar) + compact header nav (closed) +
full header tabs (nav-in-sidebar off), via a shared HeaderMenuOverlay.
- Collapsed-folder right-flyout popup (max 4 + scrollbar), Rust-populated.
- Thin auto-hiding sidebar scrollbar pinned to the edge (Flickable, drag-safe).
- Slack-style bezel: rounded inset content panel over surface-card chrome.
- Header restructure: nav buttons, window-centered search, app menu.
Themes & components:
- Live theme switching: Dark / OLED Black / Tokyo Night / System (std-widgets).
- SegmentedTabBar (Discovery-style) with count badges for Library + Local Library.
- MP3 quality-badge tier (format-first classification) across badges.
- QbzSelect (Group/Sort) trimmed 20px.
Behavior fixes:
- Artist + Local Library Folder cover art now reach the rendered (visible/
grouped) models, not just the source set.
- Favorites Playlists/Labels random buttons.
- Playlist track click plays from the clicked track in visible order.
- Dropdown idle-close pauses while the cursor rests on the menu.
cc
Sidebar:
- Fix "Recursion detected" panic from focusing the playlist search input in init (defer via a single-shot Timer).
- Right-click context menus: playlist (Edit, Hide from sidebar, Move to folder with folder search + Move to root) and folder (Edit, Hide from sidebar), built on ContextMenu/ContextMenuItem; remove the unreachable hover "..." button.
- Filter hidden playlists/folders out of the list; add folders::set_folder_hidden.
- Folder list in the move-to-folder menu scrolls (cap 5 rows); close-on-click-outside so the folder-search input no longer dismisses the menu.
- Sort-menu Refresh now uses a dedicated refresh-cw icon.
Toast: in-app ToastKind/ToastState + Toast overlay mounted at the app root, crate::toast (show/hide, per-kind auto-hide via a reusable Timer, cross-thread *_weak helpers); surface play album/artist load failures.
SearchView: title inline with NavButtons + searchType filters floated right; tab strip on its own row; Most-popular hero for a track uses the new SearchTrackHero, sized to the artist card.
Playlist Manager: list view renders compact PmListRow rows instead of cards; grid/folder card fonts tightened to match Tauri; folder card centered with hover-reveal edit; search input narrowed to 70%.
cc
PLAYLISTS header: smaller title, recursive search (hides title), create
modal at 1:1 with Tauri (name/description/folder/public), sort menu, and a
collapse chevron. Folders: accent-tinted icon, open/close chevron as the
primary affordance (no more purposeless "..."), count shown on hover only.
Playlists render a 2x2 micro-collage of track covers (opt-out) instead of
the glyph, sourced from the existing playlist images (no extra fetch).
cc
Polish + consistency:
- Compact playlist header (cover 150px; nav->header spacing
homologated with AlbumView); description truncated to ~2 lines +
"Read more" modal; custom-artwork buttons only on cover hover.
- New Typography.section (18px) token for hero/section titles,
applied across Album / Artist / Mix / Playlist + Home carousels.
- Shorter track rows app-wide (50px row, 36px artwork, -1 fonts);
+3px column-header/divider gap; wider track-number column.
- Reusable ExpandableSearch (the JumpNavBar page-search pattern:
magnifier -> opens left -> X to close) in the playlist action row.
- Custom ListScrollbar (thicker, min thumb height, own gutter) since
Slint's ListView scrollbar can't be styled.
Sort:
- Default / Title / Artist / Album / Duration with asc/desc per
field, plus Custom (manual order persisted in library.db with
up/down reorder). Client-side search + sort combined.
WIP: track drag-and-drop onto sidebar playlists
- DragState/DragActions globals, drag source on the shared TrackRow,
follow-cursor DragGhost, sidebar playlist rows self-detect the drop
via absolute-position, multi-select group drag, add_tracks_to_playlist
on drop, Flickables freeze scroll during a drag.
- KNOWN ISSUE: the ghost is offset from the cursor (the row
absolute-position + pointer mapping is off, esp. inside Flickables).
Diagnostic logging is left in (TrackRow debug() + a start log) to
pin down the wrong term before the generic fix.
cc
Local, Tauri-shared playlist folders (library.db) now organize the
sidebar:
- New folders.rs controller (create / rename / delete / move via
qbz-library); sidebar.rs reworked to a flattened entries model
(folder headers + grouped playlists + root playlists) with a
session expand cache, rebuilt without a refetch on expand/move.
- Sidebar renders folders (expandable, with playlist counts) and
playlists (indented inside folders); a hover "..." opens a context
popup: Move to folder (root + each folder) for playlists, Delete
folder for folders.
- Header gains a New-folder button + a CreateFolderModal; folders
reload after create/delete; moves are optimistic + persisted.
Adds folder-open / folder-plus icons.
cc
- SidebarState/SidebarActions + crate::sidebar: the user's playlists
load into the left-nav list on shell entry; clicking a row opens
the playlist detail view, with the open playlist highlighted
(active-id). Empty state shown when there are none.
- The sidebar "+" opens a CreatePlaylistModal (name field + public
toggle); submit calls create_playlist, refreshes the sidebar, and
navigates to the new playlist.
cc
Shell chrome (steps 1-3):
- Left sidebar is collapsible — a toggle in the nav cluster (left of the
[<] [>] pair) and a draggable right edge that resizes 200-420px and
collapses below 160px. ShellState global drives it.
- Queue and Lyrics right sidebars: 320px panels rendered as layout
siblings, so opening one pushes the content area narrower rather than
overlaying it. The header captions/queue buttons toggle them
(mutually exclusive) and light up when active.
- The four nav tabs (Discover/Library/Local Library/My QBZ) open
dropdown menus listing their sub-sections; Discover -> Home is wired.
Detail views (steps 4-5):
- Track rows: the even-row tint is now clearly fainter than the hover
tint so the two states no longer look identical.
- The favorite and offline-cache glyphs in album track rows are real
buttons (hover state, emit media-action).
- Album header artist link uses the metadata text color, bold, instead
of the accent blue.
- Album description and artist biography span the full content width
below the header block.
cc
- Replace every Unicode glyph placeholder with Lucide icons via QbzIcon
(player transport, carousel nav, album-card overlay, album-page
actions, gear, search, checkbox, sidebar).
- Header: the search field is centered to the window width and gets a
search icon; the header and player bar now use the elevated surface,
so all chrome (header, sidebar, player bar) shares one color over the
darker content area.
- The sidebar user card moves to the header as a user element (name +
QBZ logo) that opens a dropdown — Settings, Keyboard Shortcuts,
Documentation, Report an Issue, About, plus Log Out and Close. Log Out
clears the session and returns to login; Close quits the app.
- Content ScrollViews disable the horizontal scrollbar — no view scrolls
horizontally, so the phantom reserved lane is gone.
cc
- The sidebar now uses the elevated surface (#1a1a1a) over the #0f0f0f
content area, matching the QBZ dark theme (--bg-secondary over
--bg-primary).
- Discover Home gains a proper 56px toolbar with a bottom border: the
tab pill on the left, a "Filter by genre" button and a settings gear
on the right. The toolbar is fixed; only the section carousels scroll
below it.
cc
Wires real authentication. "Sign in via Browser" now runs the actual
Qobuz OAuth flow with no Tauri and no WebView: opens the system
browser, captures the authorization code on a one-shot localhost
listener, exchanges it via QbzCore's Qobuz client, and activates a
real per-user session through AppRuntime.
- adapter.rs: SlintAdapter implements FrontendAdapter.
- auth.rs: system-browser OAuth, mirrors v2_start_system_browser_oauth
without Tauri. Includes a query-param parse test.
- main.rs: tokio runtime; sign-in / offline callbacks run real async
work and navigate to the shell on success.
- Sidebar: placeholder playlist fixtures removed.
qbz-slint now depends on qbz-app, qbz-core, and qbz-models.
cc
M1 progress. Adds the post-login shell: HeaderBar (nav tabs and
search), Sidebar (playlists list and user card), PlayerBar (transport,
now-playing, volume), and AppShell composing them with the content
region as a placeholder.
app.slint gains an AppScreen enum; clicking sign-in or start-offline
navigates login -> shell (no real auth yet — that is M2). New layout
foundation tokens (sidebar width, header/player-bar heights) plus
border and hover semantic colors.
cc