Files
yellowjacket/.planning/phases/14-performance-optimization/14-01-PLAN.md
T

167 lines
9.0 KiB
Markdown

---
phase: 14-performance-optimization
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- frontend/src/components/cover-grid/cover-grid-styles.ts
- frontend/src/components/track-list/track-list.ts
- frontend/src/components/queue-panel/queue-panel.ts
- frontend/src/components/artists-view/artists-view.ts
- frontend/src/components/genres-view/genres-view.ts
- frontend/src/components/playlist-view/playlist-view.ts
- frontend/index.css
autonomous: true
requirements: [PERF-SCROLL-01, PERF-SCROLL-02]
must_haves:
truths:
- "All scroll containers use CSS contain to limit browser layout/paint scope"
- "Virtualizer scroll containers are GPU-promoted for composited scrolling"
- "The main content area uses CSS containment to isolate layout from sidebar/header/footer"
- "Album cards use content-visibility auto to skip rendering when off-screen"
artifacts:
- path: "frontend/src/components/cover-grid/cover-grid-styles.ts"
provides: "CSS contain and will-change on scroll containers, content-visibility on album cards"
contains: "contain:"
- path: "frontend/src/components/track-list/track-list.ts"
provides: "CSS contain and will-change on virtualizer host"
contains: "contain:"
- path: "frontend/src/components/queue-panel/queue-panel.ts"
provides: "CSS contain on queue panel scroll area"
contains: "contain:"
- path: "frontend/index.css"
provides: "CSS containment on .main-panel and .content-area"
contains: "contain:"
key_links:
- from: "frontend/index.css"
to: ".main-panel"
via: "CSS contain: strict on layout boundary"
pattern: "contain:\\s*(strict|layout)"
- from: "cover-grid-styles.ts"
to: ".grid-scroll-container"
via: "will-change: transform for GPU compositing"
pattern: "will-change"
---
<objective>
Add CSS containment, GPU layer promotion, and content-visibility to all scroll containers and layout boundaries for dramatically smoother scrolling performance.
Purpose: The browser currently cannot optimize layout/paint for any component — no `contain`, no `will-change`, no `content-visibility` anywhere. Adding these CSS properties allows the browser to skip layout recalculation for off-screen content and use GPU-composited scrolling for list containers.
Output: All scroll-heavy components have CSS containment; scrolling moves to the compositor thread where possible.
</objective>
<execution_context>
@/home/caleb/.config/opencode/get-shit-done/workflows/execute-plan.md
@/home/caleb/.config/opencode/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@frontend/index.css
@frontend/src/components/cover-grid/cover-grid-styles.ts
</context>
<tasks>
<task type="auto">
<name>Task 1: Add CSS containment to app shell layout boundaries</name>
<files>frontend/index.css</files>
<action>
Add CSS containment properties to the app shell layout to isolate layout recalculation boundaries:
1. On `.content-area`: Add `contain: layout style;` — isolates the main content + queue panel from affecting header/sidebar/footer layout. Do NOT use `contain: strict` here because strict includes size containment which would break the flex layout.
2. On `.main-panel`: Add `contain: strict;` — the main panel has explicit dimensions (flex: 1, overflow: hidden) so strict containment (layout + size + paint + style) is safe and maximally beneficial. This means any DOM changes inside the main panel cannot trigger layout recalculation outside it.
3. On `.main-panel > *`: Add `contain: layout style paint;` — each view component inside main-panel gets paint containment (creates new stacking context, isolates paint) plus layout containment. Do NOT add size containment since height: 100% needs to resolve from parent.
4. On `body div.sidebar`: Add `contain: layout style paint;` — sidebar is a fixed-width element that shouldn't affect main panel layout.
5. On `.bottom-bar`: Add `contain: layout style;` — footer has fixed height, isolate from content reflows.
Do NOT add `will-change` to the app shell elements — those are for scroll containers only (Task 2).
</action>
<verify>
The app builds successfully: `cd frontend && npx vite build --mode development 2>&1 | tail -5`
Visual check: all layout areas still render correctly (no collapsed panels, no overflow issues).
</verify>
<done>App shell layout boundaries have CSS containment isolating layout recalculation between header, sidebar, main panel, and footer.</done>
</task>
<task type="auto">
<name>Task 2: Add GPU promotion and containment to all scroll containers</name>
<files>
frontend/src/components/cover-grid/cover-grid-styles.ts
frontend/src/components/track-list/track-list.ts
frontend/src/components/queue-panel/queue-panel.ts
frontend/src/components/artists-view/artists-view.ts
frontend/src/components/genres-view/genres-view.ts
frontend/src/components/playlist-view/playlist-view.ts
</files>
<action>
Add CSS containment and GPU layer promotion to every scroll container and virtualized list component. The goal is to make scrolling happen on the GPU compositor thread rather than the main thread.
**cover-grid-styles.ts:**
- On `:host`: Add `contain: layout style;` (already has `overflow: hidden`)
- On `.grid-scroll-container`: Add `contain: paint;` and `will-change: transform;` — this is the actual scroll container for the album grid. `will-change: transform` promotes it to its own GPU layer so scrolling is composited. `contain: paint` creates a new stacking context.
- On `.album-card`: Add `content-visibility: auto;` with `contain-intrinsic-size: auto var(--card-width, 176px) auto calc(var(--card-width, 176px) + 40px);` — this tells the browser to skip rendering album cards that are not in the viewport. The intrinsic size hint prevents layout shift. Note: lit-virtualizer already handles virtualization, but content-visibility provides an additional browser-native layer for cards near the viewport edges that are rendered but not visible.
**track-list.ts (in static styles):**
- On `:host`: Add `contain: layout style;`
- On `lit-virtualizer`: Add `contain: paint;` and `will-change: transform;` — the virtualizer element is the scroller for the track list.
**queue-panel.ts (in static styles):**
- On `:host` or the scroll container: Add `contain: layout style paint;`
- On `lit-virtualizer`: Add `contain: paint;` and `will-change: transform;`
**artists-view.ts (in static styles):**
- On `:host`: Add `contain: layout style;`
- On the grid virtualizer parent scroll container: Add `contain: paint;` and `will-change: transform;`
**genres-view.ts (in static styles):**
- Same pattern as artists-view.
**playlist-view.ts (in static styles):**
- On `:host`: Add `contain: layout style;`
- On `.playlist-list` (the native scroll container): Add `contain: paint;` and `will-change: transform;` — even though this isn't virtualized, GPU compositing still helps scrolling.
**Important:** Do NOT add `will-change: transform` to `:host` elements — only to actual scroll containers. `will-change` on non-scrolling elements wastes GPU memory. Only apply it to elements with `overflow-y: auto/scroll`.
**Important:** Verify that `contain: paint` doesn't clip absolutely-positioned tooltips/popups that need to overflow. Context menus and popups use `wa-popup` which are appended to the shadow root, so they should still work. But verify this.
</action>
<verify>
`cd frontend && npx vite build --mode development 2>&1 | tail -5` completes without errors.
Run the app and test: (1) scroll the track list rapidly — should feel smoother, (2) scroll the album grid — should feel smoother, (3) right-click a track — context menu should still appear correctly and not be clipped, (4) open a cover grid album dropdown — should still work and not be clipped by contain: paint.
</verify>
<done>All 6 scroll-heavy components have CSS containment on hosts and will-change: transform on scroll containers for GPU-composited scrolling. Content-visibility on album cards skips rendering for off-viewport cards.</done>
</task>
</tasks>
<verification>
After both tasks:
1. `cd frontend && npx vite build --mode development` builds without errors
2. App starts and all views render correctly
3. Scrolling in track list, album grid, queue panel, artists, genres, playlists all work without visual artifacts
4. Context menus, popups, and tooltips are not clipped by paint containment
5. Album grid dropdown (expanded album) still renders correctly between grid splits
</verification>
<success_criteria>
- CSS `contain` property present on all 6 scroll component `:host` elements
- CSS `will-change: transform` present on all 6 scroll containers (not hosts)
- CSS `contain: strict` on `.main-panel` in index.css
- CSS `content-visibility: auto` on `.album-card` in cover-grid-styles
- No visual regressions (popups, context menus, dropdowns all work)
- Build succeeds
</success_criteria>
<output>
After completion, create `.planning/phases/14-performance-optimization/14-01-SUMMARY.md`
</output>