fix(library): fade artwork over its gradient placeholder instead of grey#62
Conversation
Reported pain: opening Artists or Albums showed each thumbnail flashing through a grey square before the bytes arrived, for 1-2 s across the whole grid. Tabs themselves opened instantly (state arrays persist between switches) β the perceived lag was entirely the artwork pop-in. Replace the `bg-zinc-100` placeholder behind each <img> with the same coloured gradient already used as the "no image" fallback (emerald for Artwork, violet for artist avatars). The <img> mounts with opacity-0 and fades to 100 on `onLoad`, so the placeholder gradient stays put underneath until the bytes are actually decoded. WebView-cached images that resolve synchronously skip onLoad entirely; the `ref` callback catches `complete && naturalWidth > 0` and pins the fade open immediately so we don't get stuck at zero. The shared logic lives in a new `<FadeInImage>` so both `Artwork` (album covers, track thumbnails) and the bespoke round artist avatars in LibraryView render through the same primitive. Falls back to React's documented "compare prev prop in render" reset pattern to satisfy react-hooks/set-state-in-effect. Closes the gap the reverted #61 perf branch tried β and missed β to address.
|
Warning Rate limit exceeded
To continue reviewing without waiting, purchase usage credits in the billing tab. β How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. π¦ How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. βΉοΈ Review infoβοΈ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: π Files selected for processing (3)
β¨ Finishing Touchesπ§ͺ Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Reported pain: switching to Albums or Artists with an 800-item
library mounted 800 React subtrees at once. Each tile carries an
<Artwork>/<FadeInImage> (state + useMemo) plus a popover-trigger
button, so the main thread stalled for ~1 s before the first paint
and then the IMG tags fanned out and filled in. The fade-in pass
from the previous PR couldn't disguise the layout cost.
Apply the TrackTable pattern to both grids: a row-level
`useVirtualizer`, with column count derived from the container's
width via ResizeObserver to match the original
`grid-cols-[repeat(auto-fill,minmax(180px,1fr))]` math. Each
virtual row renders `colCount` tiles; everything off-screen stays
out of the DOM.
Both grids consume `usePageScroll()` so the single page-driven
scrollbar is preserved (per the existing cross-cutting rule).
AlphabetIndex side-effect: `querySelector('[data-artist-index]') +
scrollIntoView` no longer works because the target artist may not
be mounted yet. ArtistList now exposes a `scrollToIndexRef`
callback the parent installs once on mount; the alphabet jump
delegates to `virtualizer.scrollToIndex(floor(idx / cols))`.
GenreList isn't touched β it tops out around 30-50 items in
practice and the fixed-height row card has none of the per-tile
state Artwork carries, so it'd be all cost and no benefit.
The Artistes grid was still asking `resolveArtwork` for the 2x (128 px) thumbnail variant β fine on a 64 px row icon, soft on the 180-220 px round tiles the virtualized grid actually paints on a 1080p / HiDPI screen. AlbumGrid had already switched to `size=full` for the same reason; just bring artists in line. Source artist images are the original Deezer 1000 px PNGs (or the user's sidecar JPEGs in the same range), so decoding the full-res copy at avatar size is cheap. No new fetch is introduced β the `full` slot was already populated by `list_artists` since #60.
Summary
Two-part fix for the Library tab flicker reported by Daisy:
Virtualize Albums and Artists grids β with an 800-item library, the previous flat-grid render mounted 800 React subtrees at once, each carrying an
<Artwork>/<FadeInImage>(state + useMemo) and a popover button. Main thread stalled ~1 s before the first paint, then the<img>tags fanned out and filled in. No amount of fade-in could disguise the layout cost.Fade artwork in over its gradient placeholder β once virtualization keeps the DOM size sane, individual tile pop-in is the only remaining glitch. Each
<img>now mounts atopacity-0over the same coloured gradient already used as the "no image" fallback, then fades to100ononLoad. WebView-cached images skip the fade entirely (handled in therefcallback whencomplete && naturalWidth > 0).What changed
src/components/views/LibraryView.tsxβAlbumGridandArtistList:useVirtualizerkeyed on row count (rows Γ cols)ResizeObserverrecomputescolCountfrom the container width to match the originalauto-fill,minmax(180px,1fr)Tailwind mathcolCounttiles via inline grid stylesusePageScroll()so the page-driven scrollbar stays (per the cross-cutting rule)AlphabetIndexswitches fromquerySelector + scrollIntoViewto ascrollToIndexRefcallback that drivesvirtualizer.scrollToIndex(floor(idx / cols))β off-screen artists aren't in the DOM anymoresrc/components/common/FadeInImage.tsx(new) β shared primitive:opacity-0, fades to100ononLoadrefcallback pins the fade open when the WebView serves bytes synchronouslysrcchanges via the documented "compare prev prop in render" pattern (satisfiesreact-hooks/set-state-in-effect)src/components/common/Artwork.tsxβ delegates the image rendering toFadeInImage, keeps the existing API.src/components/views/LibraryView.tsxβ artist tile insideArtistListusesFadeInImagedirectly with the violet gradient + first-letter asplaceholder.GenreListis intentionally untouched: ~30-50 items in practice, no per-tile state, virtualization would be all cost and no benefit.Net effect
rowsVisible Γ colstiles in the DOM at any timeTest plan
bun run typecheckbun run lint