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
9.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 | 01 | execute | 1 |
|
true |
|
|
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.
<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@frontend/src/store/library-store.ts @frontend/src/components/search-bar/search-bar.ts
From frontend/src/store/library-store.ts:
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:
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:
// 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
Replace the current notify() method with a queueMicrotask-based coalescing pattern:
- Add a private boolean field
private notifyScheduled = false; - Replace
notify()implementation: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.
- Add a private timer field:
private searchDebounceTimer: ReturnType<typeof setTimeout> | null = null; - 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.
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.
<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>