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
169 lines
8.0 KiB
Markdown
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>
|