--- phase: 07-backend-performance plan: 02 type: execute wave: 1 depends_on: [] files_modified: - frontend/src/store/library-store.ts autonomous: true requirements: - PERF-03 must_haves: truths: - "LibraryStore constructor does NOT call eagerFetch() — app shell renders instantly" - "After DOM is ready, eagerFetch() is called — all 4 data types (tracks, albums, artists, genres) are still loaded eagerly" - "Views display loading state while data arrives (existing isTracksLoading/isAlbumsLoading/etc. flags)" - "Post-scan invalidation still calls eagerFetch() to re-fetch everything" - "First view switch after startup has data available (no empty views)" artifacts: - path: "frontend/src/store/library-store.ts" provides: "Deferred eagerFetch — constructor omits data fetch, Wails DomReady event or document ready triggers it" contains: "EventsOn" key_links: - from: "frontend/src/store/library-store.ts (constructor)" to: "frontend/src/store/library-store.ts (eagerFetch)" via: "Wails EventsOnce for dom-ready event OR document.readyState listener" pattern: "eagerFetch" --- Defer library data loading from constructor time to after DOM is ready, so the app shell renders instantly without blocking on backend data fetches. Purpose: Currently, `LibraryStore`'s constructor calls `eagerFetch()` which immediately fires 4 async Wails binding calls (`GetAllTracks`, `GetAllAlbums`, `GetAllArtists`, `GetAllGenresWithCounts`). Since the store singleton is instantiated during ES module evaluation (at import time), these 4 backend roundtrips begin before the DOM has even finished rendering, competing with the app shell paint. Moving `eagerFetch()` to after DOM ready means the app shell renders first, then data loads begin. The user still gets all 4 data types eagerly loaded — the change is WHEN, not WHETHER. Output: Modified library-store.ts with deferred eagerFetch trigger. @/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 @frontend/src/store/library-store.ts @frontend/index.ts From frontend/src/store/library-store.ts: ```typescript class LibraryStore { constructor() { EventsOn(Events.LibraryScanComplete, () => { this.invalidate(); }); this.loadCoverSize(); this.eagerFetch(); // <-- THIS LINE MUST BE REMOVED FROM CONSTRUCTOR } private eagerFetch(): void { void this.getTracks(); void this.getAlbums(); void this.getArtists(); void this.getGenres(); } private invalidate(): void { this.tracks = null; this.albums = null; this.artists = null; this.genres = null; this.scrollPositions = { tracks: 0, albums: 0, artists: 0, genres: 0 }; this.notify(); this.eagerFetch(); // <-- THIS CALL IN invalidate() MUST REMAIN } } ``` From frontend/index.ts: ```typescript // At the bottom of index.ts, after all imports and setup: void Player.EmitCurrentState(); void Queue.EmitCurrentState(); // Library data fetching should happen around this point (after DOM is ready) ``` Task 1: Defer eagerFetch from constructor to post-DOM-ready frontend/src/store/library-store.ts **Modify the `LibraryStore` constructor to NOT call `eagerFetch()`:** 1. Remove the `this.eagerFetch()` line from the constructor. The constructor should only do: - Register the `LibraryScanComplete` event listener - Call `this.loadCoverSize()` 2. **Add a deferred fetch trigger.** The best mechanism for this Wails app is to check `document.readyState` and either call immediately or listen for the load event. Since the LibraryStore singleton is instantiated during module evaluation (import time), the DOM may or may not be ready: ```typescript constructor() { EventsOn(Events.LibraryScanComplete, () => { this.invalidate(); }); this.loadCoverSize(); this.deferEagerFetch(); } private deferEagerFetch(): void { if (document.readyState === 'complete') { // DOM already ready (shouldn't happen during module eval, but safe) this.eagerFetch(); } else { // Wait for DOM to be ready, then fetch window.addEventListener('load', () => { this.eagerFetch(); }, { once: true }); } } ``` **Why `load` event and not `DOMContentLoaded`:** The `DOMContentLoaded` event fires when the HTML is parsed but before stylesheets, images, and subframes finish loading. The `load` event fires after everything is ready. Using `load` ensures the app shell has fully rendered (CSS applied, layout complete) before data fetches compete for resources. This is the mechanism that ensures the fastest visual shell render. **Alternative (Claude's discretion):** If `load` causes a noticeable delay in data availability (because it waits for ALL resources), `DOMContentLoaded` is acceptable — it fires earlier and still defers past the initial module evaluation. Use judgment based on what feels right, but do NOT use `requestAnimationFrame` or `setTimeout` hacks. 3. **Keep `eagerFetch()` call in `invalidate()` unchanged** — post-scan invalidation should still eagerly re-fetch everything immediately (the app is already running and rendered at that point). 4. **Keep `eagerFetch()` method itself unchanged** — it should still call all 4 getters (`getTracks`, `getAlbums`, `getArtists`, `getGenres`). 5. **Keep all `isTracksLoading()` / `isAlbumsLoading()` / etc. accessors unchanged** — views already use these for loading states. When the deferred fetch runs, these flags will be set to true and views will show loading state naturally. **What NOT to change:** - Do NOT make loading per-view or lazy-per-access — user explicitly wants ALL views pre-loaded - Do NOT change `invalidate()` behavior - Do NOT change the data access methods (`getTracks`, `getAlbums`, etc.) - Do NOT remove `eagerFetch` method — just defer WHEN it's first called cd frontend && npx tsc --noEmit - LibraryStore constructor no longer calls eagerFetch() directly - eagerFetch() is deferred to after DOM ready (via load or DOMContentLoaded event) - invalidate() still calls eagerFetch() immediately (for post-scan refresh) - All 4 data types still loaded eagerly once triggered - TypeScript compiles without errors ```bash # TypeScript compiles cd frontend && npx tsc --noEmit # Frontend builds cd frontend && npx vite build ``` - LibraryStore constructor does NOT call eagerFetch() - eagerFetch() is triggered after DOM is ready - All 4 data types (tracks, albums, artists, genres) are still eagerly loaded once DOM is ready - Post-scan invalidation behavior is unchanged - TypeScript compiles and frontend builds After completion, create `.planning/phases/07-backend-performance/07-02-SUMMARY.md`