perf(frontend): make the lists and grids pay per row, not per library
A list pays per row, and only while scrolling — and none of this is visible to any test tier: nothing renders differently and nothing fails, the app is just slower. - The track list's Art column rendered `CoverArtPath`, the original artwork, into a 24 px box while `CoverArtSmall` sat unused on the same model, with no `loading="lazy"`. 26 of 26 image requests asked for the full-size tier; now 0. - `artists-view`'s avatar fallback linear-scanned every cached album per card per frame, lowercasing two strings per comparison, inside the virtualizer's renderItem — the common case, since a locally tagged library has no artist images at all. Measured at 5 000 albums and 24 visible cards: 1.46 ms/frame -> 0.01 ms/frame. - Five components resolved selected file paths back to tracks with `tracks.find(...)`; they share `utils/track-index.ts` now. "Select all -> Edit tags" at 50 000 tracks: 3 051-6 298 ms -> 68 ms. - "Play this artist", "play these albums" and the album drag cache resolve paths in one call instead of one per album. - The column-resize drag registers its document listeners on mousedown. Two things here are load-bearing and read as sloppiness. The per-render arrow functions in `artists-view` and `genres-view` are the *only* thing changing a property of their virtualizer on a host update, and therefore the only thing repainting the cards: hoisting them to stable fields takes a selection from 1 highlighted card to 0. And a row inside a virtualizer needs `width: 100%`, because the virtualizer positions its children absolutely and a grid row otherwise shrinks to fit its content and stops lining up with the header above it.
This commit is contained in:
@@ -57,8 +57,32 @@ export const COLUMN_DEFS: Record<string, ColumnDef> = {
|
||||
accessor: () => '',
|
||||
defaultWidth: '36px',
|
||||
renderCell: (track: library.Track) => {
|
||||
if (!track.CoverArtPath) return nothing;
|
||||
return html`<img src="${track.CoverArtPath}" alt="" style="width:24px;height:24px;border-radius:3px;object-fit:cover;display:block;" />`;
|
||||
// `perf.M3`. This rendered `CoverArtPath` — the *original*
|
||||
// embedded artwork, commonly 1500×1500 and several hundred
|
||||
// kB — scaled by CSS into a 24 px box, while the 100 px
|
||||
// `CoverArtSmall` sat unused on the same model. Every row
|
||||
// the virtualizer recycled into view decoded a full
|
||||
// resolution JPEG on the main thread to draw 576 pixels.
|
||||
//
|
||||
// `cover-grid.getCoverUrl()` has picked the right tier all
|
||||
// along; this is the same rule for a much smaller box, with
|
||||
// the two attributes that keep the decode off the scroll
|
||||
// path.
|
||||
const src = track.CoverArtSmall
|
||||
|| track.CoverArtMedium
|
||||
|| track.CoverArtPath;
|
||||
|
||||
if (!src) return nothing;
|
||||
|
||||
return html`<img
|
||||
src="${src}"
|
||||
alt=""
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
width="24"
|
||||
height="24"
|
||||
style="width:24px;height:24px;border-radius:3px;object-fit:cover;display:block;"
|
||||
/>`;
|
||||
},
|
||||
},
|
||||
trackName: {
|
||||
|
||||
Reference in New Issue
Block a user