Files
yellowjacket/.planning/milestones/v1.0-phases/07-backend-performance/07-02-PLAN.md
T
yonlu 6ce0661fca chore: complete v1.0 Consolidation milestone
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
2026-03-05 09:34:43 -05:00

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
frontend/src/store/library-store.ts
true
PERF-03
truths artifacts key_links
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)
path provides contains
frontend/src/store/library-store.ts Deferred eagerFetch — constructor omits data fetch, Wails DomReady event or document ready triggers it EventsOn
from to via pattern
frontend/src/store/library-store.ts (constructor) frontend/src/store/library-store.ts (eagerFetch) Wails EventsOnce for dom-ready event OR document.readyState listener 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.

<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.ts

From 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)
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:

    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

</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>