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
7.3 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 | |||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 07-backend-performance | 02 | execute | 1 |
|
true |
|
|
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.
<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 @frontend/src/store/library-store.ts @frontend/index.tsFrom frontend/src/store/library-store.ts:
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:
// 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)
-
Remove the
this.eagerFetch()line from the constructor. The constructor should only do:- Register the
LibraryScanCompleteevent listener - Call
this.loadCoverSize()
- Register the
-
Add a deferred fetch trigger. The best mechanism for this Wails app is to check
document.readyStateand 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: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
loadevent and notDOMContentLoaded: TheDOMContentLoadedevent fires when the HTML is parsed but before stylesheets, images, and subframes finish loading. Theloadevent fires after everything is ready. Usingloadensures 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
loadcauses a noticeable delay in data availability (because it waits for ALL resources),DOMContentLoadedis acceptable — it fires earlier and still defers past the initial module evaluation. Use judgment based on what feels right, but do NOT userequestAnimationFrameorsetTimeouthacks. -
Keep
eagerFetch()call ininvalidate()unchanged — post-scan invalidation should still eagerly re-fetch everything immediately (the app is already running and rendered at that point). -
Keep
eagerFetch()method itself unchanged — it should still call all 4 getters (getTracks,getAlbums,getArtists,getGenres). -
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
eagerFetchmethod — 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
Frontend builds
cd frontend && npx vite build
</verification>
<success_criteria>
- 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
</success_criteria>
<output>
After completion, create `.planning/phases/07-backend-performance/07-02-SUMMARY.md`
</output>