--- phase: 08-frontend-performance-ux plan: 01 type: execute wave: 1 depends_on: [] files_modified: - frontend/src/store/library-store.ts - frontend/src/components/search-bar/search-bar.ts - frontend/src/styles/tokens.css.ts autonomous: true requirements: - PERF-05 - UX-01 must_haves: truths: - "Library store notifications during rapid updates (scan, invalidation) are coalesced into a single subscriber notification per microtask tick" - "CSS custom properties for icon sizing (--yj-icon-sm, --yj-icon-md, --yj-icon-lg) and type scale (--yj-text-xs through --yj-text-lg) are defined and available to all components" - "Search input is debounced ~150ms before triggering filter/rank computation" artifacts: - path: "frontend/src/store/library-store.ts" provides: "queueMicrotask-based notification coalescing" contains: "queueMicrotask" - path: "frontend/src/styles/tokens.css.ts" provides: "Design token definitions for icon sizes and type scale" contains: "--yj-icon-sm" - path: "frontend/src/components/search-bar/search-bar.ts" provides: "Debounced search input" contains: "debounce" key_links: - from: "frontend/src/store/library-store.ts" to: "subscribers" via: "queueMicrotask coalescing in notify()" pattern: "queueMicrotask" - from: "frontend/src/styles/tokens.css.ts" to: "all components" via: "CSS custom property inheritance from :host or adopted stylesheets" pattern: "--yj-icon-sm|--yj-text-xs" --- Add performance plumbing (store debouncing, search debounce) and define the design token foundation (icon sizes, type scale) that all subsequent plans depend on. Purpose: Library store fires 8+ notifications during scan invalidation (4 parallel fetches × 2 notifications each). Coalescing via queueMicrotask prevents layout thrashing. Design tokens establish the visual vocabulary that Plan 04 will systematically apply. Output: Debounced store, debounced search, design token CSS file. @/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 @frontend/src/store/library-store.ts @frontend/src/components/search-bar/search-bar.ts From frontend/src/store/library-store.ts: ```typescript type Subscriber = () => void; class LibraryStore { private subscribers = new Set(); // Current notify — called ~12 times during invalidate→eagerFetch cycle: private notify(): void { this.subscribers.forEach((callback) => callback()); } // Called from: getTracks/getAlbums/getArtists/getGenres (loading start + end), // invalidate(), setCoverSize() subscribe(callback: Subscriber): () => void { this.subscribers.add(callback); return () => this.subscribers.delete(callback); } } export const libraryStore = new LibraryStore(); ``` From frontend/src/store/search-store.ts: ```typescript class SearchStore { private term = ''; setTerm(term: string): void { if (term === this.term) return; this.term = term; this.notify(); } } export const searchStore = new SearchStore(); ``` From frontend/src/components/search-bar/search-bar.ts: ```typescript // Current: directly sets search term on every input event // searchCtrl is a SearchController with a `term` setter this.searchCtrl.term = input.value; ``` Existing CSS custom properties (already defined, DO NOT redefine): - --yj-text-primary, --yj-text-secondary, --yj-text-tertiary - --yj-bg-surface, --yj-bg-elevated, --yj-bg-overlay, --yj-bg-base - --yj-border, --yj-border-subtle - --yj-accent, --yj-accent-bg - --yj-hover-overlay, --yj-selection-bg, --yj-error Task 1: Add queueMicrotask debouncing to library store and search input debounce frontend/src/store/library-store.ts, frontend/src/components/search-bar/search-bar.ts **Library store debouncing (library-store.ts):** Replace the current `notify()` method with a queueMicrotask-based coalescing pattern: 1. Add a private boolean field `private notifyScheduled = false;` 2. Replace `notify()` implementation: ```typescript private notify(): void { if (this.notifyScheduled) return; this.notifyScheduled = true; queueMicrotask(() => { this.notifyScheduled = false; this.subscribers.forEach((callback) => callback()); }); } ``` This coalesces ALL notify() calls within the same microtask tick into a single subscriber notification round. During invalidate() → eagerFetch() → 4 parallel fetches × 2 notifications each = 8+ calls → 1 actual notification. The subscribe() API is unchanged — this is transparent to subscribers. **Search input debounce (search-bar.ts):** Add a ~150ms debounce to the search input handler so that rapid typing doesn't trigger expensive filter/rank computation on every keystroke. 1. Add a private timer field: `private searchDebounceTimer: ReturnType | null = null;` 2. In the input handler, instead of immediately setting `this.searchCtrl.term = input.value`: - Clear any existing timer - If the input is empty, set term immediately (instant clear feedback) - Otherwise, set a 150ms timeout that sets `this.searchCtrl.term` Do NOT debounce the visual update of the input field itself — only debounce the propagation to the search store. The input should still show characters as typed. cd frontend && npx tsc --noEmit 2>&1 | head -30 Library store notify() uses queueMicrotask to coalesce multiple calls per tick. Search input debounces store propagation by 150ms while maintaining instant visual feedback on the input element. Task 2: Define design token CSS custom properties for icon sizes and type scale frontend/src/styles/tokens.css.ts Create a new file `frontend/src/styles/tokens.css.ts` that exports a Lit `css` tagged template with design token definitions. Use the same pattern as other style files in the project — export a `css` tagged template literal from `lit`. ```typescript import { css } from 'lit'; /** * Design tokens for consistent sizing across all components. * Import and include in a component's static styles array: * * import { designTokens } from '../../styles/tokens.css'; * static styles = [designTokens, css`...`]; */ export const designTokens = css` :host { /* ── Icon sizes ── */ --yj-icon-sm: 14px; --yj-icon-md: 18px; --yj-icon-lg: 24px; /* ── Type scale ── */ --yj-text-xs: 11px; --yj-text-sm: 12px; --yj-text-md: 13px; --yj-text-lg: 15px; --yj-text-xl: 18px; } `; ``` **Design rationale:** - Icon sizes: sm=14px covers small inline icons (favorites, sort indicators), md=18px covers standard toolbar/sidebar icons, lg=24px covers feature icons (now-playing placeholder, large action icons) - Type scale: xs=11px for smallest text (cover-grid small cards), sm=12px for secondary info and labels, md=13px for body text and inputs, lg=15px for headings and emphasis, xl=18px for large titles - These values are derived from the actual pixel values already scattered across the codebase — this consolidates them rather than inventing new sizes - :host scope means tokens are available within each component that imports the stylesheet Verify the file path exists: check for a `frontend/src/styles/` directory. If it doesn't exist, create it. cd frontend && npx tsc --noEmit 2>&1 | head -30 Design token file exists at frontend/src/styles/tokens.css.ts, exports `designTokens` css template with --yj-icon-sm/md/lg and --yj-text-xs/sm/md/lg/xl custom properties on :host. 1. `cd frontend && npx tsc --noEmit` compiles without errors 2. library-store.ts contains `queueMicrotask` in the notify method 3. search-bar.ts has debounce logic with ~150ms delay 4. frontend/src/styles/tokens.css.ts exists and exports designTokens 5. No behavioral regressions — subscribe() API is unchanged, search still works - Library store notify() coalesces multiple calls within a microtask tick into one notification round - Search input propagation to store is debounced by ~150ms (empty input clears immediately) - Design token file defines --yj-icon-sm/md/lg and --yj-text-xs/sm/md/lg/xl - TypeScript compiles without errors After completion, create `.planning/phases/08-frontend-performance-ux/08-01-SUMMARY.md`