14 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | must_haves | ||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 14-performance-optimization | 04 | execute | 2 |
|
|
false |
|
|
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.
<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>
@.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 Task 1: Optimize scroll event handling and clean up queue panel scroll hack frontend/src/components/cover-grid/scroll-manager.ts frontend/src/components/queue-panel/queue-panel.ts **scroll-manager.ts — RAF-throttled scroll position saving:**The current scroll position save uses a 100ms debounce timer. This is suboptimal because:
- During continuous scrolling, position is never saved (debounce resets on each scroll event)
- When scrolling stops, there's a 100ms delay before the position is recorded
- 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:
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:
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:
- Native scrollbar drag works smoothly (no jumping/fighting)
- Keyboard navigation (arrow keys) doesn't cause scroll jumps
- Auto-scroll to current track works
- 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.
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.
Cover grid scroll position saves are RAF-throttled (once per frame). Queue panel scroll handling is cleaned up (overflow-anchor or documented monkey-patch).
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 timelibrary.extractMetadata— scan performance, check per-format timing in scan metricsqueue.SetQueue— Phase 1/2 dedup, check with large queuescoverart.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):
- Open Performance panel
- Click Record
- Perform the action (scroll, navigate, etc.)
- Stop recording
- Look at the Main thread timeline:
- Long yellow bars = JavaScript execution (too long = jank)
- Purple bars = rendering/layout
- Green bars = painting
- Grey bars = idle
- 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,
containCSS 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:
- Take heap snapshot before/after an action
- Compare snapshots to find leaks
- 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":
- Open DevTools Performance panel
- Record while scrolling the problematic view
- Look at frame times — are any >16ms?
- If JS is the bottleneck: check renderItem callback time
- If Layout is the bottleneck: check if
containCSS is present - If Paint is the bottleneck: check if
will-change: transformis on the scroll container
"Navigation is slow":
- Open DevTools Performance panel
- Record while navigating between views
- Look for long JS tasks between navigate event and first paint
- Check if the view is being destroyed/recreated (look for constructor calls)
- After Phase 14 view caching: navigation between cached views should show almost no activity
"Library operations feel slow":
./scripts/profile.sh cpu— capture during the operation- Check the flame graph for the specific Go function
- For database operations: check if SQL queries are optimal
- For scan operations: check scan metrics (they're already logged)
./scripts/profile.sh trace— for detailed timing of goroutine schedulingtest -f docs/PROFILING.md && echo "File exists"outputs "File exists". The file contains sections on Backend Profiling, Frontend Profiling, and Profiling Workflow. 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.
Verification steps:
- Run
make devto start the app - 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
- 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
- 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
- Read profiling guide:
- Open
docs/PROFILING.md - Does it make sense? Any confusing parts?
- Try
./scripts/profile.sh health— does it connect? User approves scrolling smoothness and navigation speed User has verified that scrolling is smooth, navigation is instant, and no interactions are broken. Type "approved" or describe issues found.
- Open
<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>