Files
qbz/crates/qbz-ui/ui/discover/HomeSkeleton.slint
T
vicrodh c3b921efff fix(reco): Weekly rows resolve fast, cache per-week, show per-row skeletons
Discover > Recommendations Weekly Exploration/Jams kept vanishing and loaded
slowly. Three root causes:

- Weekly track resolution ran every track through MusicBrainz's serial
  1.1s/req rate limiter (~110s for 100 tracks), so the rows never painted in
  practice (Fresh Releases, album-based with no MusicBrainz, returned fine).
  Resolve weekly tracks via fuzzy Qobuz search instead (skip_mb) -- fast and
  reliable for these mainstream playlists.
- The external-reco cache was only initialized on the offline entry path;
  the two online login paths (auth.rs) never bound it, so nothing cached
  online (0-byte db). Initialize it there too.
- Weeklies were bundled in the shared 48h results blob keyed only on
  auth-presence. Give them their own per-week cache (keyed by the
  ListenBrainz playlist mbid, 9d TTL, written only when non-empty, with a
  stale-fallback to the last good week) and rebuild them independently of the
  blob, so a transient empty build cannot hide them. Skip the 7d negative
  cache for weekly tracks so a throttle does not lock a row empty.

Per-row skeletons: the progressive paint now marks each expected row pending
at spawn and clears it as its builder resolves, so a slow row shows its own
skeleton instead of silently missing. Adds [reco] diagnostics.

cc
2026-06-27 21:05:57 -06:00

164 lines
4.5 KiB
Plaintext

// Loading skeleton for the Discover home — grey placeholder blocks
// shaped like the real carousels, gently pulsing (fade in/out) while
// the first fetch is in flight. Replaces the spinner + "Loading…"
// text so the first paint reads as the page filling in rather than a
// generic loader.
import { Theme } from "../foundation/semantic-colors.slint";
import { Radius } from "../foundation/radius.slint";
// A single shimmering block. Opacity breathes between two values on a
// shared phase so the whole skeleton pulses in unison.
component Shimmer inherits Rectangle {
in property <bool> phase;
background: Theme.surface-elevated;
border-radius: Radius.sm;
opacity: root.phase ? 0.85 : 0.4;
animate opacity { duration: 900ms; easing: ease-in-out; }
}
// One placeholder carousel: a short title bar over a row of card
// blocks (square art + two text lines).
component SkeletonRow inherits VerticalLayout {
in property <bool> phase;
in property <length> card-size: 200px;
spacing: 12px;
Shimmer {
phase: root.phase;
width: 180px;
height: 22px;
}
HorizontalLayout {
spacing: 32px;
for _ in [0, 1, 2, 3, 4]: VerticalLayout {
spacing: 8px;
Shimmer {
phase: root.phase;
width: root.card-size;
height: root.card-size;
}
Shimmer {
phase: root.phase;
width: root.card-size * 0.7;
height: 14px;
}
Shimmer {
phase: root.phase;
width: root.card-size * 0.45;
height: 12px;
}
}
}
}
export component HomeSkeleton inherits VerticalLayout {
property <bool> phase: false;
spacing: 40px;
// Drives the shared breathing phase.
Timer {
interval: 900ms;
running: true;
triggered => {
root.phase = !root.phase;
}
}
SkeletonRow { phase: root.phase; }
SkeletonRow { phase: root.phase; }
}
// ── Standalone per-row skeletons (Discover > Recommendations) ───────────────
//
// Each owns its breathing Timer so it can drop into a layout in place of a row
// whose builder has not resolved yet. Shapes mirror the real rows: a card
// carousel (artists/albums) and a slim 4-wide track grid (Weekly playlists).
// Card-carousel placeholder: title bar + a row of card blocks (art + 2 lines).
export component SkeletonCarouselRow inherits VerticalLayout {
in property <length> card-size: 180px;
property <bool> phase: false;
spacing: 12px;
Timer {
interval: 900ms;
running: true;
triggered => {
root.phase = !root.phase;
}
}
Shimmer {
phase: root.phase;
width: 180px;
height: 22px;
}
HorizontalLayout {
spacing: 24px;
for _ in [0, 1, 2, 3, 4, 5]: VerticalLayout {
spacing: 8px;
Shimmer {
phase: root.phase;
width: root.card-size;
height: root.card-size;
}
Shimmer {
phase: root.phase;
width: root.card-size * 0.7;
height: 14px;
}
Shimmer {
phase: root.phase;
width: root.card-size * 0.45;
height: 12px;
}
}
}
}
// Slim-grid placeholder: title bar + a 4-column x 3-row grid of slim row
// blocks, matching SlimCarousel's 4x3 layout (the Weekly Exploration/Jams rows).
export component SkeletonSlimRow inherits VerticalLayout {
property <bool> phase: false;
spacing: 12px;
Timer {
interval: 900ms;
running: true;
triggered => {
root.phase = !root.phase;
}
}
Shimmer {
phase: root.phase;
width: 180px;
height: 22px;
}
HorizontalLayout {
spacing: 8px;
for _ in [0, 1, 2, 3]: Shimmer {
phase: root.phase;
height: 60px;
horizontal-stretch: 1;
}
}
HorizontalLayout {
spacing: 8px;
for _ in [0, 1, 2, 3]: Shimmer {
phase: root.phase;
height: 60px;
horizontal-stretch: 1;
}
}
HorizontalLayout {
spacing: 8px;
for _ in [0, 1, 2, 3]: Shimmer {
phase: root.phase;
height: 60px;
horizontal-stretch: 1;
}
}
}