9.2 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 | |||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 14-performance-optimization | 02 | execute | 1 |
|
true |
|
|
Purpose: Currently, every navigation event destroys the active view via innerHTML = '' and creates a new component from scratch. This means virtualizers reinitialize, data refetches from cache, scroll positions must be restored, and cover art images reload. By caching views in the DOM and toggling display: none / display: block, navigation becomes instant.
Output: Navigation between primary views (tracks, albums, artists, genres, playlists, settings) is instant — no component destruction, no virtualizer reinit, no scroll position loss.
<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/index.ts @frontend/index.html @frontend/index.css From frontend/index.ts: - Navigation via CustomEvent('navigate', { detail: { view: '...' } }) - Primary views: 'albums', 'tracks', 'playlists', 'artists', 'genres', 'settings' - Detail views: 'artist-details', 'playlist-details', 'genre-details' (take extra attributes) - mainContent = document.getElementById('main-content') - Currently: mainContent.innerHTML = '' for each viewFrom frontend/index.html:
- Default view is track-list (rendered in HTML)
From frontend/index.css:
- .main-panel > * { height: 100%; }
Design:
- Maintain a
Map<string, HTMLElement>calledviewCachefor primary views (tracks, albums, artists, genres, playlists, settings). - Detail views (artist-details, playlist-details, genre-details) are NOT cached — they are ephemeral and created fresh each time (because they depend on specific IDs/names that change on each navigation). When navigating to a detail view, remove any existing detail view element and create a fresh one.
- On navigation, hide the current view (
display: none), then show the target view. If the target view isn't in the cache yet, create it and add to the cache.
Implementation in index.ts:
- Add a
viewCacheMap and acurrentViewstring variable at module scope:
const viewCache = new Map<string, HTMLElement>();
let currentViewEl: HTMLElement | null = null;
let currentDetailEl: HTMLElement | null = null;
- Define a
VIEW_TAGSmapping for cacheable primary views:
const VIEW_TAGS: Record<string, string> = {
tracks: 'track-list',
albums: 'cover-grid',
artists: 'artists-view',
genres: 'genres-view',
playlists: 'playlist-view',
settings: 'config-page',
};
-
Replace the entire
document.addEventListener('navigate', ...)handler with a new one that:a. For primary views (key exists in VIEW_TAGS):
- If a detail view element exists, remove it from DOM and set
currentDetailEl = null - If the view is already in
viewCache, retrieve it; otherwise create it viadocument.createElement(VIEW_TAGS[view])and add to bothviewCacheandmainContent - Hide
currentViewElby settingstyle.display = 'none' - Show the target element by setting
style.display = ''(empty string restores the default) - Update
currentViewElreference
b. For detail views (artist-details, playlist-details, genre-details):
- Hide
currentViewEl(set display: none) - If
currentDetailElexists, remove it from DOM - Create the detail element, set attributes (artistId, artistName, etc.), append to
mainContent - Set
currentDetailElto the new element
- If a detail view element exists, remove it from DOM and set
-
Initialize the default view (track-list) from the HTML:
const initialTrackList = mainContent.querySelector('track-list');
if (initialTrackList) {
viewCache.set('tracks', initialTrackList as HTMLElement);
currentViewEl = initialTrackList as HTMLElement;
}
In index.html: No changes needed — <track-list></track-list> remains the default.
In index.css: Add a rule for hidden views:
.main-panel > [data-view-hidden] {
display: none !important;
}
Actually, simpler to just use inline style.display since we control it in JS. No CSS changes needed for this. BUT we should add contain: layout style paint to hidden views to ensure they don't participate in layout even if display:none is somehow bypassed (belt and suspenders — Plan 14-01 already adds containment but this is the navigation-specific insurance).
Important considerations:
- The
searchStore.setCurrentView(view)call must remain at the top of the handler - View elements get
connectedCallbackcalled only once (on first creation), anddisconnectedCallbackis never called (they stay in DOM). This is fine — Lit components handle this correctly. Controllers subscribe inhostConnectedand unsubscribe inhostDisconnected, so subscriptions stay active. This is INTENDED for cached views. - Memory consideration: We're keeping at most 6 primary view components alive. Each is a single custom element with its own shadow DOM. The data they hold (tracks, albums, etc.) is already in the store cache regardless. The only extra memory is the DOM nodes for the virtualizer's rendered items (typically ~20-50 visible items per view). This is negligible.
- Scroll position: With view caching, scroll positions are naturally preserved because the DOM is never destroyed. This means the existing scroll save/restore logic in each component (
visibilityChangedhandlers,scrollToIndexcalls) can eventually be simplified, but DO NOT remove them in this plan — they still serve as fallback for data invalidation scenarios.cd frontend && npx vite build --mode development 2>&1 | tail -5builds without errors. Functional test: Start app → see tracks (default) → click Albums → albums appear → click Tracks → tracks reappear instantly WITH scroll position preserved → click Artists → artists appear → click back to Albums → albums still show previously loaded content → navigate to artist-details → back to Artists → artists view preserved. - Primary views (tracks, albums, artists, genres, playlists, settings) are created once and cached in DOM
- Navigation toggles visibility instead of destroying/recreating
- Detail views (artist-details, playlist-details, genre-details) are still created fresh (they're parameterized)
- Scroll positions naturally preserved by keeping DOM alive
- No component destruction means no virtualizer reinit, no data refetch, no image reload
<success_criteria>
- No
innerHTML = '<...>'patterns remain in index.ts for primary views viewCacheMap maintains at most 6 cached view elements- Navigation between cached views takes <16ms (one frame)
- All existing navigation paths still work (primary views, detail views, settings)
- No memory leaks (view count bounded, no listener accumulation) </success_criteria>