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