perf(modals): gate 8 always-mounted modals with {#if}, drop blur shadow

Two perf agents independently identified the same dominant cost behind
the 2-3s modal-open latency on non-maximized windows: 8 modals declared
at the top of `+page.svelte` (PlaylistModal, PlaylistImportModal,
FolderEditModal, AboutModal, QualityFallbackModal, KeyboardShortcuts,
KeybindingsSettings, LinkResolverModal) were always mounted, with only
their inner templates gated by `{#if isOpen}`. Their `<script>` blocks
ran for the lifetime of the app and their reactive declarations
re-evaluated on every parent tick — and the parent ticks at ~4Hz during
playback via `playerStore.notifyListeners()`.

PlaylistModal alone is 1,420 lines / 18 reactive declarations. Sum
across 8 modals × ~4Hz × the +page.svelte reactive graph (188+ $state)
explains why mounting any *new* component (Discovery's customize modal,
the back-to-top button) raced with reactive churn and landed seconds
after the user clicked.

Fix: wrap each modal at the mount site in `{#if isOpen}`. The inner
component instance now only exists while the modal is open. Two-line
change per modal, zero API change. The `isOpen` prop on each modal is
left in place so the modal's internal close logic (focus mgmt, ARIA,
close animation) keeps working unchanged.

Also drops the 64px Gaussian box-shadow on `ModalLite.svelte` that I'd
added one commit prior. Under software compositing the blur radius
re-rasterizes whenever anything beneath repaints, defeating the whole
point of removing the overlay. Replaced with a flat composite shadow
(1px edge + 16px small-offset) — reads as "floating" without the
per-frame Gaussian cost.

Documents the pattern in `qbz-nix-docs/qbz-adr/ADR-003-conditional-
mount-pattern.md` so the same anti-pattern can be swept across the rest
of the codebase systematically. The ADR identifies search heuristic
(`<X isOpen={...} />` outside `{#if}`), the cost mechanism, and the
roadmap touchpoints (ModalRouter extraction from +page.svelte) where
the rule should be applied during decomposition.

cc
This commit is contained in:
vicrodh committed 2026-05-12 09:16:34 -06:00
1 parent e3cdc97a1d
commit e9504efa24
2 files changed
+161 -125

No files matched your search

+76 -62
View File
@@ -6898,75 +6898,89 @@
/>
{/if}
<!-- Playlist Modal -->
<PlaylistModal
isOpen={isPlaylistModalOpen}
mode={playlistModalMode}
playlist={playlistModalMode === 'edit' ? playlistModalEditPlaylist : undefined}
trackIds={playlistModalTrackIds}
isLocalTracks={playlistModalTracksAreLocal}
plexRatingKeys={playlistModalPlexRatingKeys}
isHidden={playlistModalMode === 'edit' ? playlistModalEditIsHidden : false}
currentFolderId={playlistModalMode === 'edit' ? playlistModalEditCurrentFolderId : null}
{userPlaylists}
onClose={handlePlaylistModalClose}
onSuccess={handlePlaylistCreated}
/>
<!-- Modals are gated by `{#if}` at the mount site so their <script>
blocks (and the reactive declarations within) don't execute when
the modal is closed. Previously each modal was always mounted with
only its template gated internally, which kept its full reactive
graph alive and re-evaluating on every parent tick — the dominant
cost of opening any new modal at non-maximized window sizes. -->
{#if isPlaylistModalOpen}
<PlaylistModal
isOpen={isPlaylistModalOpen}
mode={playlistModalMode}
playlist={playlistModalMode === 'edit' ? playlistModalEditPlaylist : undefined}
trackIds={playlistModalTrackIds}
isLocalTracks={playlistModalTracksAreLocal}
plexRatingKeys={playlistModalPlexRatingKeys}
isHidden={playlistModalMode === 'edit' ? playlistModalEditIsHidden : false}
currentFolderId={playlistModalMode === 'edit' ? playlistModalEditCurrentFolderId : null}
{userPlaylists}
onClose={handlePlaylistModalClose}
onSuccess={handlePlaylistCreated}
/>
{/if}
<!-- Playlist Import Modal -->
<PlaylistImportModal
isOpen={isPlaylistImportOpen}
onClose={closePlaylistImport}
onSuccess={handlePlaylistImported}
/>
{#if isPlaylistImportOpen}
<PlaylistImportModal
isOpen={isPlaylistImportOpen}
onClose={closePlaylistImport}
onSuccess={handlePlaylistImported}
/>
{/if}
<!-- Folder Edit Modal (sidebar entry-point — issue #364) -->
<FolderEditModal
isOpen={isSidebarFolderEditOpen}
folder={editingSidebarFolder}
onClose={closeSidebarFolderEdit}
onSave={handleSidebarFolderSave}
onDelete={handleSidebarFolderDelete}
/>
{#if isSidebarFolderEditOpen}
<FolderEditModal
isOpen={isSidebarFolderEditOpen}
folder={editingSidebarFolder}
onClose={closeSidebarFolderEdit}
onSave={handleSidebarFolderSave}
onDelete={handleSidebarFolderDelete}
/>
{/if}
<!-- About Modal -->
<AboutModal
isOpen={isAboutModalOpen}
onClose={() => isAboutModalOpen = false}
/>
{#if isAboutModalOpen}
<AboutModal
isOpen={isAboutModalOpen}
onClose={() => isAboutModalOpen = false}
/>
{/if}
<!-- Quality Fallback Modal -->
<QualityFallbackModal
isOpen={isQualityFallbackOpen}
trackTitle={qualityFallbackTrackTitle}
onTryLower={handleQualityFallbackTryLower}
onSkip={handleQualityFallbackSkip}
onClose={() => isQualityFallbackOpen = false}
/>
{#if isQualityFallbackOpen}
<QualityFallbackModal
isOpen={isQualityFallbackOpen}
trackTitle={qualityFallbackTrackTitle}
onTryLower={handleQualityFallbackTryLower}
onSkip={handleQualityFallbackSkip}
onClose={() => isQualityFallbackOpen = false}
/>
{/if}
<!-- Keyboard Shortcuts Modal -->
<KeyboardShortcutsModal
isOpen={isShortcutsModalOpen}
onClose={() => isShortcutsModalOpen = false}
onOpenSettings={() => {
isShortcutsModalOpen = false;
isKeybindingsSettingsOpen = true;
}}
/>
{#if isShortcutsModalOpen}
<KeyboardShortcutsModal
isOpen={isShortcutsModalOpen}
onClose={() => isShortcutsModalOpen = false}
onOpenSettings={() => {
isShortcutsModalOpen = false;
isKeybindingsSettingsOpen = true;
}}
/>
{/if}
<!-- Keybindings Settings Modal -->
<KeybindingsSettings
isOpen={isKeybindingsSettingsOpen}
onClose={() => isKeybindingsSettingsOpen = false}
/>
{#if isKeybindingsSettingsOpen}
<KeybindingsSettings
isOpen={isKeybindingsSettingsOpen}
onClose={() => isKeybindingsSettingsOpen = false}
/>
{/if}
<!-- Link Resolver Modal -->
<LinkResolverModal
isOpen={isLinkResolverOpen}
onClose={() => isLinkResolverOpen = false}
onResolve={handleResolvedLink}
onOpenImporter={() => { isLinkResolverOpen = false; openPlaylistImport(); }}
/>
{#if isLinkResolverOpen}
<LinkResolverModal
isOpen={isLinkResolverOpen}
onClose={() => isLinkResolverOpen = false}
onResolve={handleResolvedLink}
onOpenImporter={() => { isLinkResolverOpen = false; openPlaylistImport(); }}
/>
{/if}
{#if updateRelease}
<UpdateAvailableModal