--- 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" --- 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. @/home/caleb/.config/opencode/get-shit-done/workflows/execute-plan.md @/home/caleb/.config/opencode/get-shit-done/templates/summary.md @.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 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 Task 1: Replace class string construction with classMap directive in renderTrackRow frontend/src/components/track-list/track-list.ts 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.) cd frontend && npx tsc --noEmit 2>&1 | head -30 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. Task 2: Optimize column value computation and apply classMap to queue-panel renderTrackItem frontend/src/components/track-list/track-list.ts, frontend/src/components/queue-panel/queue-panel.ts **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) cd frontend && npx tsc --noEmit 2>&1 | head -30 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. 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 - 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 After completion, create `.planning/phases/08-frontend-performance-ux/08-03-SUMMARY.md`