docs(14-performance-optimization): create phase plan with 4 plans in 2 waves
This commit is contained in:
@@ -0,0 +1,294 @@
|
||||
---
|
||||
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>
|
||||
Reference in New Issue
Block a user