docs(07): create phase plan
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
---
|
||||
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>
|
||||
Reference in New Issue
Block a user