From fc20835dde17af7fa50534dc1b2fe6779ce36160 Mon Sep 17 00:00:00 2001 From: Youwes09 Date: Tue, 9 Jun 2026 14:54:12 -0500 Subject: [PATCH] Fix: SplashScreen MemoryLeak + WebUI Bypass --- src/lib/components/chrome/SplashScreen.svelte | 146 ++++++++++++++++-- .../settings/sections/DevToolsSettings.svelte | 1 + src/routes/+layout.svelte | 95 ++++-------- 3 files changed, 160 insertions(+), 82 deletions(-) diff --git a/src/lib/components/chrome/SplashScreen.svelte b/src/lib/components/chrome/SplashScreen.svelte index 31bd9be..fad48ec 100644 --- a/src/lib/components/chrome/SplashScreen.svelte +++ b/src/lib/components/chrome/SplashScreen.svelte @@ -1,9 +1,46 @@ + + @@ -373,6 +464,20 @@ {/if} {/if} + {#if isDev && mode === 'idle' && devMetrics} +
+ canvas · idle splash +
+ live 1}>{devLiveCount} + total mounts {devMetrics.totalMounts} + stamps {devMetrics.stampCount} + resizes {devMetrics.resizeCount} + uptime {fmtUptime(uptimeSecs)} + last resize {fmtAgo(devMetrics.lastResizeAt)} +
+
+ {/if} + {#if mode === 'idle'}
@@ -465,4 +570,11 @@ .err-btn:hover { border-color:var(--border-strong); color:var(--text-secondary); } .err-btn--primary { border-color:var(--accent-dim); color:var(--accent-fg); background:var(--accent-muted); } .err-btn--primary:hover { border-color:var(--accent); color:var(--accent-bright); } + + .dev-overlay { position:absolute; top:12px; left:12px; z-index:10; background:rgba(0,0,0,0.72); border:1px solid rgba(255,255,255,0.10); border-radius:6px; padding:8px 10px; pointer-events:none; backdrop-filter:blur(6px); } + .dev-title { display:block; font-family:var(--font-ui); font-size:9px; letter-spacing:0.14em; text-transform:uppercase; color:var(--accent); margin-bottom:6px; } + .dev-grid { display:grid; grid-template-columns:auto auto; column-gap:12px; row-gap:2px; } + .dev-k { font-family:var(--font-ui); font-size:10px; color:var(--text-faint); white-space:nowrap; } + .dev-v { font-family:var(--font-ui); font-size:10px; color:var(--text-secondary); text-align:right; white-space:nowrap; } + .dev-warn { color:#f87171; } \ No newline at end of file diff --git a/src/lib/components/settings/sections/DevToolsSettings.svelte b/src/lib/components/settings/sections/DevToolsSettings.svelte index 260b50a..9733bbe 100644 --- a/src/lib/components/settings/sections/DevToolsSettings.svelte +++ b/src/lib/components/settings/sections/DevToolsSettings.svelte @@ -102,6 +102,7 @@ } function triggerSplash() { + if (appState.idleSplash) return splashTriggered = true setTimeout(() => splashTriggered = false, 200) appState.idleSplash = true diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 6163767..3db14b5 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -7,7 +7,7 @@ import { settingsState, loadSettingsIntoState, updateSettings } from '$lib/state/settings.svelte' import { applyTheme, mountSystemThemeSync } from '$lib/core/theme' import { platformService } from '$lib/platform-service' - import { initRpc, setIdle, destroyRpc } from '$lib/core/discord' + import * as discord from '$lib/core/discord' import SplashScreen from '$lib/components/chrome/SplashScreen.svelte' import AuthGate from '$lib/components/chrome/AuthGate.svelte' import Sidebar from '$lib/components/chrome/Sidebar.svelte' @@ -24,7 +24,6 @@ const POLL_MS = 1500 let pollTimer: ReturnType | null = null - let idleTimer: ReturnType | null = null let polling = false async function pollLoop() { @@ -35,13 +34,12 @@ const isTauri = typeof window !== 'undefined' && '__TAURI_INTERNALS__' in window - let _splashDismissed = $state(false) - let bypassed = $state(false) - let themeEditorOpen = $state(false) - let themeEditorId = $state(null) + let splashDismissed = $state(false) + let themeEditorOpen = $state(false) + let themeEditorId = $state(null) const splashVisible = $derived( - !_splashDismissed || + !splashDismissed || appState.status === 'booting' || appState.status === 'locked' || appState.status === 'error' || @@ -49,17 +47,16 @@ ) const ringFull = $derived(appState.status === 'ready') + const showApp = $derived(!splashVisible) - const showApp = $derived( - !splashVisible && ( - appState.status === 'ready' || - bypassed - ) - ) - - function onSplashReady() { _splashDismissed = true } - function onSplashUnlock() { appState.status = 'ready'; _splashDismissed = true } - function onSplashBypass() { bypassed = true; _splashDismissed = true } + function onSplashReady() { splashDismissed = true } + function onSplashUnlock() { appState.status = 'ready'; splashDismissed = true } + function onSplashBypass() { + import('$lib/state/boot.svelte').then(({ bypassBoot }) => { + bypassBoot(appState.authMode ?? 'NONE', appState.authUser ?? '', appState.authPass ?? '') + }) + splashDismissed = true + } const isReaderRoute = $derived($page.url.pathname.startsWith('/reader')) const readerContainerized = $derived(settingsState.settings.readerContainerized ?? false) @@ -108,24 +105,19 @@ isTauri && settingsState.settings.autoStartServer ? 2000 : 100, ) - let discordInitialized = false if (settingsState.settings.discordRpc) { - await initRpc() - await setIdle() - discordInitialized = true + await discord.initRpc() + await discord.setIdle() } polling = true pollLoop() - resetIdleTimer() - return () => { polling = false if (pollTimer !== null) { clearTimeout(pollTimer); pollTimer = null } - if (idleTimer !== null) { clearTimeout(idleTimer); idleTimer = null } - if (discordInitialized) destroyRpc().catch(() => {}) - platformService.destroy().catch(() => {}) + discord.destroyRpc() + platformService.destroy() } }) @@ -147,49 +139,22 @@ }) $effect(() => { - if (appState.status === 'booting') _splashDismissed = false + if (appState.status === 'booting') splashDismissed = false }) - $effect(() => { - if (appState.status === 'ready') resetIdleTimer() - }) + let idleSplashLocked = false - $effect(() => { - if (appState.idleSplash && settingsState.settings.discordRpc) setIdle().catch(() => {}) - }) - - $effect(() => { - if (appState.status !== 'ready') return - // capture phase so events from any component — including modals — reset the timer - const onActivity = () => resetIdleTimer() - document.addEventListener('mousemove', onActivity, true) - document.addEventListener('keydown', onActivity, true) - document.addEventListener('touchstart', onActivity, true) - // passive:true tells the browser it can render scroll frames without waiting for the handler - document.addEventListener('touchmove', onActivity, { capture: true, passive: true }) - document.addEventListener('wheel', onActivity, { capture: true, passive: true }) - document.addEventListener('click', onActivity, true) - return () => { - document.removeEventListener('mousemove', onActivity, true) - document.removeEventListener('keydown', onActivity, true) - document.removeEventListener('touchstart', onActivity, true) - document.removeEventListener('touchmove', onActivity, { capture: true }) - document.removeEventListener('wheel', onActivity, { capture: true }) - document.removeEventListener('click', onActivity, true) - } - }) - - function resetIdleTimer() { - if (idleTimer) { clearTimeout(idleTimer); idleTimer = null } - // 0 means "Never" — skip the timer entirely - const mins = settingsState.settings.idleTimeoutMin ?? 5 - if (mins === 0) return - idleTimer = setTimeout(() => { - if (appState.status === 'ready') appState.idleSplash = true - }, mins * 60_000) + function showIdleSplash() { + if (idleSplashLocked || appState.idleSplash) return + appState.idleSplash = true } - function onIdleDismiss() { appState.idleSplash = false; resetIdleTimer() } + function onIdleDismiss() { + if (idleSplashLocked) return + idleSplashLocked = true + appState.idleSplash = false + setTimeout(() => { idleSplashLocked = false }, 400) + } function onSplashRetry() { import('$lib/state/boot.svelte').then(({ retryBoot }) => { @@ -219,7 +184,7 @@ {/if} {#if appState.idleSplash} - + {/if} {#if showApp}