Views with contain:layout in shadow DOM need explicit height:100%
on :host so their internal flex layout fills the parent's flex
space. Without it, the component doesn't know its height and the
internal content doesn't stretch to fill the panel.
Added height:100% to: cover-grid, artists-view, genres-view,
playlist-view, track-list, config-page (also box-sizing).
explore-view and detail views already had it.
Targeted optimizations for the DMABuf-disabled rendering path where
every frame is software-composited:
- Replace infinite CSS scroll-text animation with transition-based
cycle that only repaints during active scroll, not during pauses
- Remove CSS mask-image on scrolling text (mask + animation was the
single most expensive continuous repaint)
- Replace wa-icon in track rows with inline SVG — eliminates 30-50
shadow DOM trees (each with SVG fetch/parse) during scroll
- Remove hover transitions on album cards, artist cards, genre cards,
fav icons, queue remove buttons — each transition was causing
per-frame software repaints
- Use visibility:hidden instead of opacity:0 for queue remove button
(binary switch vs per-frame alpha blend)
- Add decoding=async to now-playing cover art images (prevents
main-thread blocking during image decode on track change)
- Add contain:strict to fixed-height track rows (33px) and queue
items (49px) — browser skips size contribution calculations
Root causes addressed:
- track-list had no _itemSize hint for flow layout — virtualizer
defaulted to 100px, measured actual ~33px rows, then called
_correctScrollError/scrollTo on every scroll causing visible jumps
- will-change:transform on virtualizer elements caused nested GPU
layers (virtualizer positions children with transforms internally)
adding compositor overhead instead of helping
- content-visibility:auto on album cards conflicted with virtualizer's
own DOM recycling, causing redundant layout recalculation
- track-list visibilityChanged handler wrote to store synchronously
on every event (per-item during scroll) without any throttling
- IIFE closure in renderTrackRow created a new function per row per render
Fixes applied:
- Add _itemSize:{height:33} + fixed height:33px on .track-row (matches
queue-panel pattern that already worked smoothly)
- Add overflow-anchor:none on track-list virtualizer
- Remove will-change:transform from all 6 scroll containers
- Remove content-visibility:auto from album cards
- RAF-throttle visibilityChanged scroll position saves
- Replace IIFE with direct cols.map() in template
- contain: layout style on :host of all 6 scroll-heavy components
- contain: paint + will-change: transform on all scroll containers for GPU compositing
- content-visibility: auto + contain-intrinsic-size on .album-card for off-screen skip
- cover-grid, track-list, queue-panel, artists-view, genres-view, playlist-view
Move search indicator from absolute-positioned overlay into sort toolbar
(or dedicated search-bar-row for artists/genres views). Shows indicator
on empty-state screens. Fix cover art not displaying for expanded album
tracks by checking expandedAlbumId before the albumName guard. Add
ScanWarning model bindings.
repeat() as child content of lit-virtualizer bypasses virtualization,
rendering ALL items instead of only visible ones. This caused 2+ minute
loading times and UI freezing with large libraries.
The correct approach: use lit-virtualizer's native .renderItem and
.keyFunction properties which integrate with its scroll-based viewport
management.
- cover-grid: all 3 virtualizers (single, before-split, after-split) use repeat() with album.ID key
- artists-view uses repeat() with artist.ID key
- genres-view uses repeat() with genre.name key
- Removed gridKeyFunction (now inlined in repeat calls)
- Removed .renderItem and .keyFunction properties from all virtualizers