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

295 lines
14 KiB
Markdown

---
phase: 14-performance-optimization
plan: 04
type: execute
wave: 2
depends_on: ["14-01", "14-02"]
files_modified:
- frontend/src/components/cover-grid/scroll-manager.ts
- frontend/src/components/queue-panel/queue-panel.ts
- docs/PROFILING.md
autonomous: false
requirements: [PERF-SCROLL-03, PERF-DIAG-01]
must_haves:
truths:
- "Cover grid scroll manager uses RAF-throttled scroll events instead of debounced saves"
- "Queue panel scroll correction monkey-patch is replaced with a cleaner CSS/layout solution"
- "A profiling guide documents how to diagnose frontend and backend performance issues"
- "User verifies scrolling smoothness across all views"
artifacts:
- path: "frontend/src/components/cover-grid/scroll-manager.ts"
provides: "RAF-throttled scroll position saves"
contains: "requestAnimationFrame"
- path: "docs/PROFILING.md"
provides: "Performance diagnosis guide"
contains: "pprof"
key_links:
- from: "docs/PROFILING.md"
to: "scripts/profile.sh"
via: "References profiling script usage"
pattern: "profile.sh"
---
<objective>
Polish scroll performance with targeted fixes to the cover grid scroll manager and queue panel, then create a performance profiling guide and verify all optimizations with user.
Purpose: The cover grid scroll manager uses a 100ms debounced scroll save (fires after scrolling stops, not ideal for position tracking during rapid scrolling). The queue panel has a monkey-patched `_correctScrollError` which is a band-aid for lit-virtualizer's scroll correction fighting the native scrollbar. Both need cleaner solutions. Additionally, the user wants guidance on diagnosing performance issues using the existing pprof infrastructure.
Output: Cleaner scroll handling, profiling documentation, and user-verified scroll smoothness.
</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/src/components/cover-grid/scroll-manager.ts
@frontend/src/components/queue-panel/queue-panel.ts
@scripts/profile.sh
@backend/profiling/profiling.go
</context>
<tasks>
<task type="auto">
<name>Task 1: Optimize scroll event handling and clean up queue panel scroll hack</name>
<files>
frontend/src/components/cover-grid/scroll-manager.ts
frontend/src/components/queue-panel/queue-panel.ts
</files>
<action>
**scroll-manager.ts — RAF-throttled scroll position saving:**
The current scroll position save uses a 100ms debounce timer. This is suboptimal because:
1. During continuous scrolling, position is never saved (debounce resets on each scroll event)
2. When scrolling stops, there's a 100ms delay before the position is recorded
3. If the user navigates away during scrolling (before debounce fires), position is lost
Replace with **requestAnimationFrame throttling**: save the position once per animation frame. This fires at most once per ~16ms (60fps), captures position during scrolling (not just after), and naturally aligns with the browser's paint cycle.
Pattern:
```typescript
private scrollRAFId: number | null = null;
private onScroll = () => {
if (this.scrollRAFId !== null) return;
this.scrollRAFId = requestAnimationFrame(() => {
this.scrollRAFId = null;
// save current scroll position
this.saveScrollPosition();
});
};
```
Find the existing debounced scroll handler in scroll-manager.ts and replace it with this RAF-throttled version. Make sure to:
- Cancel any pending RAF in `destroy()` or cleanup method
- Keep the same `saveScrollPosition()` logic (storing to library store)
**queue-panel.ts — Replace _correctScrollError monkey-patch:**
The queue panel currently monkey-patches lit-virtualizer's internal `_correctScrollError` method to prevent it from fighting the native scrollbar during drag scrolling. This was documented as a fix for the "scroll bar not following" issue.
Instead of monkey-patching an internal API (which could break on lit-virtualizer updates), use CSS `overflow-anchor: none` on the virtualizer's scroll container. This CSS property tells the browser NOT to automatically adjust scroll position when content changes above the viewport — which is the same thing `_correctScrollError` does but from the browser side.
Add to the queue panel's lit-virtualizer CSS:
```css
lit-virtualizer {
overflow-anchor: none;
}
```
Then check if the monkey-patch can be removed. If `overflow-anchor: none` alone resolves the scrollbar desync, remove the monkey-patch code entirely. If the monkey-patch is still needed for a specific scenario (like the gutter click detection for native scrollbar drag), keep only the gutter detection part and remove the scroll error correction override.
**Important:** Test the queue panel with a large queue (5000+ tracks) and verify:
1. Native scrollbar drag works smoothly (no jumping/fighting)
2. Keyboard navigation (arrow keys) doesn't cause scroll jumps
3. Auto-scroll to current track works
4. The queue panel scrolls smoothly when dragging tracks to reorder
If `overflow-anchor: none` doesn't fully replace the monkey-patch, keep the monkey-patch but add a comment explaining WHY it's needed and what the CSS alone doesn't handle.
</action>
<verify>
`cd frontend && npx vite build --mode development 2>&1 | tail -5` builds without errors.
Cover grid: scroll position saves continuously during scrolling (not just after stopping).
Queue panel: scrollbar drag on 5000+ item queue works without scroll fighting.
</verify>
<done>Cover grid scroll position saves are RAF-throttled (once per frame). Queue panel scroll handling is cleaned up (overflow-anchor or documented monkey-patch).</done>
</task>
<task type="auto">
<name>Task 2: Create performance profiling guide</name>
<files>docs/PROFILING.md</files>
<action>
Create a practical profiling guide at `docs/PROFILING.md` that documents how to diagnose performance issues in YellowJacket. This should be a concise, actionable reference — not a textbook.
Structure:
## 1. Backend Profiling (Go / pprof)
**Setup:** `make dev` starts the app with pprof server on `:6060`.
**Quick Start:**
- `./scripts/profile.sh` — interactive menu
- `./scripts/profile.sh cpu` — 30s CPU profile (flame graph in browser)
- `./scripts/profile.sh heap` — current memory usage
- `./scripts/profile.sh health` — goroutine count, heap, GC stats
**When to use each profile type:**
| Profile | Use When | What It Shows |
|---------|----------|---------------|
| CPU | Something is slow | Time spent in each function (flame graph) |
| Heap | Memory growing | Current allocations by location |
| Allocs | GC pressure | Where allocations happen (even freed) |
| Goroutine | Hangs/deadlocks | All goroutines and their stack traces |
| Block | Lock contention | Where goroutines block on mutexes/channels |
| Mutex | Mutex bottleneck | Mutex contention hotspots |
| Trace | Scheduling issues | Timeline of goroutine scheduling, GC pauses, syscalls |
**Reading flame graphs:**
- Wide bars = more time spent
- Look for unexpected width (functions taking more time than expected)
- Bottom of stack = entry points, top = leaf functions where time is actually spent
- Use the search box to find specific packages (e.g., "library", "queue", "database")
**Common YellowJacket hotspots:**
- `database.GetAllTracks` — large library, check SQL query time
- `library.extractMetadata` — scan performance, check per-format timing in scan metrics
- `queue.SetQueue` — Phase 1/2 dedup, check with large queues
- `coverart.Generate*` — thumbnail generation, check per-tier timing
## 2. Frontend Profiling (Chrome DevTools)
Since YellowJacket uses Wails (WebView2/WebKit), you can use Chrome DevTools for frontend profiling.
**Opening DevTools:**
- On Wails dev builds, press `Ctrl+Shift+I` (or right-click → Inspect)
**Performance Panel (scrolling/rendering):**
1. Open Performance panel
2. Click Record
3. Perform the action (scroll, navigate, etc.)
4. Stop recording
5. Look at the Main thread timeline:
- Long yellow bars = JavaScript execution (too long = jank)
- Purple bars = rendering/layout
- Green bars = painting
- Grey bars = idle
6. Target: each frame should be <16ms for 60fps scrolling
**Key metrics for scroll smoothness:**
- Frame time: Should be consistently <16ms
- Layout recalculation: Should not happen during scrolling (if it does, `contain` CSS isn't working)
- Paint: Should be minimal and composited (green bars should be thin)
- JS execution during scroll: Should be minimal — lit-virtualizer does most work, but renderItem callbacks add up
**Memory Panel:**
1. Take heap snapshot before/after an action
2. Compare snapshots to find leaks
3. Look for growing arrays of detached DOM nodes (sign of view not cleaning up)
**What to look for in YellowJacket:**
| Symptom | Likely Cause | Check |
|---------|-------------|-------|
| Scroll jank | Layout thrashing | Performance panel → check for "Layout" bars during scroll |
| Slow navigation | View recreation | Performance panel → look for long constructors after navigate |
| Memory growth | Listener leaks | Memory panel → compare snapshots, filter "Detached" |
| Slow initial load | Blocking JS | Performance panel → check DOMContentLoaded to first paint |
## 3. Profiling Workflow for Specific Issues
**"Scrolling feels janky":**
1. Open DevTools Performance panel
2. Record while scrolling the problematic view
3. Look at frame times — are any >16ms?
4. If JS is the bottleneck: check renderItem callback time
5. If Layout is the bottleneck: check if `contain` CSS is present
6. If Paint is the bottleneck: check if `will-change: transform` is on the scroll container
**"Navigation is slow":**
1. Open DevTools Performance panel
2. Record while navigating between views
3. Look for long JS tasks between navigate event and first paint
4. Check if the view is being destroyed/recreated (look for constructor calls)
5. After Phase 14 view caching: navigation between cached views should show almost no activity
**"Library operations feel slow":**
1. `./scripts/profile.sh cpu` — capture during the operation
2. Check the flame graph for the specific Go function
3. For database operations: check if SQL queries are optimal
4. For scan operations: check scan metrics (they're already logged)
5. `./scripts/profile.sh trace` — for detailed timing of goroutine scheduling
</action>
<verify>
`test -f docs/PROFILING.md && echo "File exists"` outputs "File exists".
The file contains sections on Backend Profiling, Frontend Profiling, and Profiling Workflow.
</verify>
<done>docs/PROFILING.md exists with practical guidance on using pprof, Chrome DevTools Performance panel, and specific diagnostic workflows for scrolling, navigation, and library operation performance issues.</done>
</task>
<task type="checkpoint:human-verify" gate="blocking">
<name>Task 3: Verify performance improvements</name>
<files>none</files>
<action>
Phase 14 performance optimizations across 4 plans:
- CSS containment + GPU layer promotion on all scroll containers (Plan 01)
- View caching navigation (no more innerHTML destruction) (Plan 02)
- Render closure elimination + store notification optimization (Plan 03)
- Scroll event handling cleanup + profiling guide (Plan 04)
Verification steps:
1. Run `make dev` to start the app
2. **Test scrolling smoothness:**
- Open the track list view → scroll rapidly up and down → should feel smooth, no jank or stuttering
- Open the album grid → scroll rapidly → should be smooth, no blank areas appearing
- Open the queue panel → add 1000+ tracks → scroll rapidly → should be smooth
- Open artists view → scroll → smooth
- Open genres view → scroll → smooth
3. **Test navigation speed:**
- Click Tracks → Albums → Tracks rapidly → should feel instant (no flash of loading)
- Click Albums → Artists → Genres → Playlists → Settings → Tracks → each transition should be near-instant
- Navigate to an artist detail → back to artists → artists view should still have its scroll position
4. **Test that nothing broke:**
- Play a track from track list (double-click)
- Right-click → context menu works
- Drag tracks to queue
- Multi-select with Shift/Ctrl+click
- Search filters correctly
- Album dropdown (click album in grid → tracks show)
- Queue reorder via drag
5. **Read profiling guide:**
- Open `docs/PROFILING.md`
- Does it make sense? Any confusing parts?
- Try `./scripts/profile.sh health` — does it connect?
</action>
<verify>User approves scrolling smoothness and navigation speed</verify>
<done>User has verified that scrolling is smooth, navigation is instant, and no interactions are broken. Type "approved" or describe issues found.</done>
</task>
</tasks>
<verification>
Full Phase 14 verification:
1. Scrolling is measurably smoother across all views
2. Navigation between views is near-instant (cached views)
3. No visual regressions (context menus, popups, dropdowns, drag-drop)
4. Build succeeds: `cd frontend && npx vite build --mode development`
5. Go backend builds: `go build -tags webkit2_41 ./...`
6. PROFILING.md provides actionable guidance
</verification>
<success_criteria>
- Cover grid scroll position saves use RAF throttling
- Queue panel scroll handling is cleaner (overflow-anchor or documented hack)
- docs/PROFILING.md exists with Backend, Frontend, and Workflow sections
- User approves scrolling smoothness in the human-verify checkpoint
</success_criteria>
<output>
After completion, create `.planning/phases/14-performance-optimization/14-04-SUMMARY.md`
</output>