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:
2 files changed
+161
-125
No files matched your search
+76
-62
@@ -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
|
||||
|
||||
Reference in new issue
Block a user