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

8.0 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
08-frontend-performance-ux 03 execute 2
08-01
08-02
frontend/src/components/track-list/track-list.ts
true
PERF-05
UX-02
truths artifacts key_links
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
path provides contains
frontend/src/components/track-list/track-list.ts Optimized renderTrackRow with cached class strings and pre-computed column values classMap|ifDefined|cached
from to via pattern
frontend/src/components/track-list/track-list.ts renderTrackRow repeat() directive Called per-item by repeat() — must be fast 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.

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

@.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):

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:
    const classes = ['base-class', condition ? 'class-a' : '', ...].filter(Boolean).join(' ');
    // Used as: class="${classes}"
    
  3. Replace with:
    // 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

<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>
After completion, create `.planning/phases/08-frontend-performance-ux/08-03-SUMMARY.md`