Files
yellowjacket/.planning/milestones/v1.0-phases/08-frontend-performance-ux/08-03-PLAN.md
T
yonlu 6ce0661fca chore: complete v1.0 Consolidation milestone
Archive milestone artifacts:
- milestones/v1.0-ROADMAP.md (full roadmap archive)
- milestones/v1.0-REQUIREMENTS.md (26/26 requirements complete)
- milestones/v1.0-phases/ (8 phase directories with plans, summaries, verifications)

Updated:
- PROJECT.md: full evolution review, all consolidation requirements validated
- ROADMAP.md: collapsed to milestone summary with archive link
- STATE.md: reset for next milestone
- MILESTONES.md: created with stats and accomplishments
- RETROSPECTIVE.md: created with lessons learned

Deleted:
- REQUIREMENTS.md (archived, fresh for next milestone)

8 phases, 17 plans, 34 tasks, 84 tests added, 6 days
2026-03-05 09:34:43 -05:00

169 lines
8.0 KiB
Markdown

---
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>