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
182 lines
7.3 KiB
Markdown
182 lines
7.3 KiB
Markdown
---
|
|
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"
|
|
---
|
|
|
|
<objective>
|
|
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.
|
|
</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
|
|
@frontend/src/store/library-store.ts
|
|
@frontend/index.ts
|
|
</context>
|
|
|
|
<interfaces>
|
|
<!-- Key types and contracts the executor needs. Extracted from codebase. -->
|
|
|
|
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)
|
|
```
|
|
</interfaces>
|
|
|
|
<tasks>
|
|
|
|
<task type="auto">
|
|
<name>Task 1: Defer eagerFetch from constructor to post-DOM-ready</name>
|
|
<files>frontend/src/store/library-store.ts</files>
|
|
<action>
|
|
**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
|
|
</action>
|
|
<verify>
|
|
cd frontend && npx tsc --noEmit
|
|
</verify>
|
|
<done>
|
|
- 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
|
|
</done>
|
|
</task>
|
|
|
|
</tasks>
|
|
|
|
<verification>
|
|
```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>
|