Files
yellowjacket/.planning/phases/08-frontend-performance-ux/08-01-PLAN.md
T

233 lines
9.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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"
---
<objective>
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.
</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
@frontend/src/store/library-store.ts
@frontend/src/components/search-bar/search-bar.ts
<interfaces>
<!-- Key types and contracts the executor needs. -->
From frontend/src/store/library-store.ts:
```typescript
type Subscriber = () => void;
class LibraryStore {
private subscribers = new Set<Subscriber>();
// 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
</interfaces>
</context>
<tasks>
<task type="auto">
<name>Task 1: Add queueMicrotask debouncing to library store and search input debounce</name>
<files>frontend/src/store/library-store.ts, frontend/src/components/search-bar/search-bar.ts</files>
<action>
**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<typeof setTimeout> | 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.
</action>
<verify>
<automated>cd frontend && npx tsc --noEmit 2>&1 | head -30</automated>
</verify>
<done>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.</done>
</task>
<task type="auto">
<name>Task 2: Define design token CSS custom properties for icon sizes and type scale</name>
<files>frontend/src/styles/tokens.css.ts</files>
<action>
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.
</action>
<verify>
<automated>cd frontend && npx tsc --noEmit 2>&1 | head -30</automated>
</verify>
<done>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.</done>
</task>
</tasks>
<verification>
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
</verification>
<success_criteria>
- 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
</success_criteria>
<output>
After completion, create `.planning/phases/08-frontend-performance-ux/08-01-SUMMARY.md`
</output>