docs(08-frontend-performance-ux): create phase plan
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
---
|
||||
phase: 08-frontend-performance-ux
|
||||
plan: 03
|
||||
type: execute
|
||||
wave: 2
|
||||
depends_on:
|
||||
- "08-01"
|
||||
- "08-02"
|
||||
files_modified:
|
||||
- frontend/src/components/track-list/track-list.ts
|
||||
autonomous: true
|
||||
requirements:
|
||||
- PERF-05
|
||||
- UX-02
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "renderTrackRow does not allocate arrays or join strings for CSS classes on every render call"
|
||||
- "Column values used in rendering are pre-computed or cached, not recomputed per-cell on every render"
|
||||
- "Scrolling through a 10k+ track list is smooth with no visible jank"
|
||||
artifacts:
|
||||
- path: "frontend/src/components/track-list/track-list.ts"
|
||||
provides: "Optimized renderTrackRow with cached class strings and pre-computed column values"
|
||||
contains: "classMap\\|ifDefined\\|cached"
|
||||
key_links:
|
||||
- from: "frontend/src/components/track-list/track-list.ts renderTrackRow"
|
||||
to: "repeat() directive"
|
||||
via: "Called per-item by repeat() — must be fast"
|
||||
pattern: "renderTrackRow"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Optimize the track-list renderTrackRow method to minimize per-row allocations and template computation during scrolling and filtering.
|
||||
|
||||
Purpose: renderTrackRow is the hot path for the largest list component. It's called for every visible row on every scroll event. Current implementation builds CSS class strings via array filter/join and computes column values per-cell on every call. With 10k+ tracks, reducing per-row work directly impacts scroll smoothness.
|
||||
Output: Optimized renderTrackRow with cached class strings and efficient column rendering.
|
||||
</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
|
||||
@.planning/phases/08-frontend-performance-ux/08-CONTEXT.md
|
||||
@.planning/phases/08-frontend-performance-ux/08-01-SUMMARY.md
|
||||
@.planning/phases/08-frontend-performance-ux/08-02-SUMMARY.md
|
||||
|
||||
@frontend/src/components/track-list/track-list.ts
|
||||
|
||||
<interfaces>
|
||||
<!-- The executor must read track-list.ts to understand the full renderTrackRow method.
|
||||
Key patterns to optimize: -->
|
||||
|
||||
Current renderTrackRow pattern (approximate):
|
||||
```typescript
|
||||
private renderTrackRow = (track: library.Track, index: number) => {
|
||||
// 1. Class string built via array filter/join on EVERY render:
|
||||
const classes = [
|
||||
'track-row',
|
||||
this.isSelected(track) ? 'selected' : '',
|
||||
this.isCurrentTrack(track) ? 'playing' : '',
|
||||
// ... more conditions
|
||||
].filter(Boolean).join(' ');
|
||||
|
||||
// 2. Column values computed per-cell via accessor:
|
||||
// col.accessor(track) called for each column on each row
|
||||
|
||||
// 3. Search highlighting applied per-cell
|
||||
};
|
||||
```
|
||||
|
||||
Optimization targets:
|
||||
1. Replace array filter/join class construction with Lit's classMap directive
|
||||
2. Pre-compute or cache column accessor results where possible
|
||||
3. Avoid object/array allocations in the render hot path
|
||||
</interfaces>
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Replace class string construction with classMap directive in renderTrackRow</name>
|
||||
<files>frontend/src/components/track-list/track-list.ts</files>
|
||||
<action>
|
||||
The current renderTrackRow builds CSS class strings by creating an array of conditional class names, filtering out falsy values, and joining with spaces — this allocates a new array and string on every render call for every visible row.
|
||||
|
||||
Replace with Lit's `classMap` directive which is purpose-built for conditional classes and avoids these allocations:
|
||||
|
||||
1. Add import: `import { classMap } from 'lit/directives/class-map.js';` (if not already imported)
|
||||
2. In renderTrackRow, find every pattern like:
|
||||
```typescript
|
||||
const classes = ['base-class', condition ? 'class-a' : '', ...].filter(Boolean).join(' ');
|
||||
// Used as: class="${classes}"
|
||||
```
|
||||
3. Replace with:
|
||||
```typescript
|
||||
// Used as: class=${classMap({ 'base-class': true, 'class-a': condition, ... })}
|
||||
```
|
||||
|
||||
Read the full renderTrackRow method carefully — there may be multiple class string constructions (row-level and cell-level). Convert ALL of them.
|
||||
|
||||
The classMap object literal is still allocated per-call, but classMap internally compares with previous values and only updates changed classes — it's significantly faster than string concatenation for Lit's update cycle.
|
||||
|
||||
Also check `renderTrackItem` in queue-panel.ts for the same pattern — if it uses array filter/join for classes, apply the same classMap conversion there too. (Queue panel was listed in CONTEXT.md as having this pattern.)
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd frontend && npx tsc --noEmit 2>&1 | head -30</automated>
|
||||
</verify>
|
||||
<done>All class string construction in renderTrackRow uses classMap directive instead of array filter/join. No .filter(Boolean).join(' ') patterns remain in track-list render methods. TypeScript compiles.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Optimize column value computation and apply classMap to queue-panel renderTrackItem</name>
|
||||
<files>frontend/src/components/track-list/track-list.ts, frontend/src/components/queue-panel/queue-panel.ts</files>
|
||||
<action>
|
||||
**Track-list column optimization (track-list.ts):**
|
||||
|
||||
Read the full renderTrackRow method to understand how column values are computed. The current pattern calls `col.accessor(track)` for each visible column on each row during render.
|
||||
|
||||
Optimization approach — evaluate what's actually expensive:
|
||||
1. If `col.accessor` is a simple property lookup (e.g., `track.Title`, `track.Artist`), it's already fast — no caching needed
|
||||
2. If any accessor does computation (string formatting, duration conversion, etc.), consider whether it can be memoized or moved outside the per-cell loop
|
||||
3. If search highlighting is applied per-cell, check if the highlight computation can be short-circuited when there's no active search term (skip the regex/string manipulation entirely when term is empty)
|
||||
|
||||
Focus on the highest-impact optimizations:
|
||||
- **Search highlight short-circuit**: When searchTerm is empty, skip all highlight logic entirely — just render the raw column value. This eliminates regex creation and string splitting for every cell in the common case.
|
||||
- **Duration formatting**: If a time/duration column reformats on every render, cache the formatted string on the track object or in a WeakMap.
|
||||
|
||||
Do NOT over-optimize — if accessor is just `track.Title`, a cache would be slower than the direct access. Only optimize where measurement or code inspection shows actual waste.
|
||||
|
||||
**Queue-panel classMap (queue-panel.ts):**
|
||||
|
||||
Apply the same classMap directive conversion to renderTrackItem in queue-panel.ts:
|
||||
1. Add import: `import { classMap } from 'lit/directives/class-map.js';`
|
||||
2. Find the class string construction pattern (array filter/join) in renderTrackItem
|
||||
3. Convert to classMap directive (same pattern as Task 1)
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd frontend && npx tsc --noEmit 2>&1 | head -30</automated>
|
||||
</verify>
|
||||
<done>Track-list search highlighting is short-circuited when search term is empty. Queue-panel renderTrackItem uses classMap. No unnecessary per-row allocations in render hot paths. TypeScript compiles.</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
1. `cd frontend && npx tsc --noEmit` compiles without errors
|
||||
2. No `.filter(Boolean).join(' ')` patterns in track-list.ts or queue-panel.ts render methods
|
||||
3. classMap directive is used for all conditional CSS classes in render hot paths
|
||||
4. Search highlighting short-circuits when search term is empty
|
||||
5. No regressions — row selection, playing indicator, and search highlighting still work
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- renderTrackRow uses classMap for all conditional CSS classes
|
||||
- renderTrackItem (queue) uses classMap for all conditional CSS classes
|
||||
- Search highlighting skips computation when search term is empty
|
||||
- No array allocations (filter/join) in render hot paths
|
||||
- TypeScript compiles without errors
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/08-frontend-performance-ux/08-03-SUMMARY.md`
|
||||
</output>
|
||||
Reference in New Issue
Block a user