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
233 lines
9.0 KiB
Markdown
233 lines
9.0 KiB
Markdown
---
|
||
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>
|