perf(library): prefetch inactive tabs so switching feels instant#61
perf(library): prefetch inactive tabs so switching feels instant#61InstaZDLL wants to merge 1 commit into
Conversation
Reported pain: clicking Artistes / Albums / Morceaux for the first time leaves the user staring at a blank tab for 1-2 s while the backend runs `list_artists` / `list_albums` / `list_tracks` and the result crosses Tauri's IPC bridge. Subsequent visits were already instant because each tab's state array persists across switches, so the only gap was the cold cache on first click. Reshape the loading logic: 1. A `tabCacheKeys` memo derives one stable key per tab from the inputs that legitimately invalidate its data (library set, sort, `filterNoCover`, cover-reload, edit signal). The same key drives foreground and background paths. 2. `loadedKeysRef` records which key currently lives in each tab's state array. The foreground loader now short-circuits when the active tab already matches its desired key (e.g. the user clicked back to a tab that just got prefetched). 3. A second effect waits 200 ms after the active tab settles, then serially fetches each non-active tab whose stored key is stale. Best-effort: a failure just defers the work to the foreground loader when the user clicks. Serial (not parallel) so a large `list_tracks` query doesn't fight the active tab for SQLite pool slots. `fetchTab` / `applyTab` helpers centralise the per-tab branch so the two paths can't drift apart on the next refactor. Net effect: the first foreground tab still loads as before; every subsequent tab change renders the cached data instantly while a silent refresh runs in the background only when the cache key has actually invalidated.
|
No actionable comments were generated in the recent review. π βΉοΈ Recent review infoβοΈ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: π Files selected for processing (1)
π WalkthroughWalkthrough
ChangesSystème de cache par onglet et préfetch background
Estimated Code Review Effortπ― 4 (Complex) | β±οΈ ~45 minutes Poem
π₯ Pre-merge checks | β 4 | β 1β Failed checks (1 warning)
β Passed checks (4 passed)
βοΈ Tip: You can configure your own custom pre-merge checks in the settings. β¨ Finishing Touchesπ Generate docstrings
π§ͺ 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 |
|
Reverting β I misread the original complaint. Daisy said the tab itself opened instantly and only the artwork flickered for 1-2 s. My prefetcher kept SQLite busy in the background, so when the user clicked a new tab the foreground fetch queued behind a slow list_tracks/etc. and the perceived lag got worse. Closing without merging and tackling the actual issue (artwork load smoothness) in a separate PR. |
β¦rey (#62) * fix(library): fade artwork over its gradient placeholder instead of grey 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. * perf(library): virtualize Albums and Artists grids 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. * fix(library): render artist avatars from the full-res source 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
User-reported pain: clicking Artistes / Albums / Morceaux for the first time leaves the user staring at a blank tab for 1β2 s while the backend runs the corresponding
list_*query and the JSON crosses Tauri's IPC bridge. Subsequent visits to the same tab were already instant β each tab's state array persists acrossactiveTabswitches β but the cold-cache first click was painful.Approach
Reshape the loading logic in LibraryView so the user only pays the fetch cost once per cache-invalidation, regardless of which tab they click first:
tabCacheKeysmemo β derives one stable key per tab from the inputs that legitimately invalidate its data (library set, sort,filterNoCover, cover-reload signal, edit signal).loadedKeysRefβ records which key currently lives in each tab's state array. The foreground loader short-circuits when the active tab already matches its desired key (e.g. the user clicked back to a tab that just got prefetched).list_tracksquery doesn't fight the active tab for SQLite pool slots.fetchTab/applyTabhelpers β centralise the per-tab switch so the two paths can't drift apart on the next refactor.Net effect
Test plan
bun run typecheckbun run linteditRefetch/sort changes)Summary by CodeRabbit