From f3e1a761aafdb0c8995ec60ad2b60f7b33909628 Mon Sep 17 00:00:00 2001 From: fred Date: Thu, 27 Aug 2026 10:06:54 -0500 Subject: [PATCH] fix(web): clear the saved-badge reset timer on unmount CI 2891 failed on an unhandled ReferenceError: the 2s setTimeout(() => setSaveState('idle')) in the settings tabs fired after jsdom teardown. Replace the three uncleaned timers with one useSavedBadgeReset effect hook that clears on unmount. --- apps/web/src/spa/pages/settings.tsx | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/apps/web/src/spa/pages/settings.tsx b/apps/web/src/spa/pages/settings.tsx index 2e8ee779..098a0a48 100644 --- a/apps/web/src/spa/pages/settings.tsx +++ b/apps/web/src/spa/pages/settings.tsx @@ -57,6 +57,16 @@ function prefValue(prefs: Preference[], key: string, fallback: T): T { return p.value as T; } +// The reset must not outlive the tab: an uncleared setTimeout fires into a +// torn-down environment (unmount, or jsdom teardown under vitest). +function useSavedBadgeReset(saveState: SaveState, setSaveState: (s: SaveState) => void): void { + useEffect(() => { + if (saveState !== 'saved') return undefined; + const timer = setTimeout(() => setSaveState('idle'), 2000); + return () => clearTimeout(timer); + }, [saveState, setSaveState]); +} + // ─── Main Page ──────────────────────────────────────────────────────────────── export function SettingsPage(): React.ReactElement { @@ -111,6 +121,7 @@ function ProfileTab({ const [image, setImage] = useState(session?.user.image ?? ''); const [saveState, setSaveState] = useState('idle'); const [errorMsg, setErrorMsg] = useState(''); + useSavedBadgeReset(saveState, setSaveState); // Sync from session when it loads useEffect(() => { @@ -131,7 +142,6 @@ function ProfileTab({ return; } setSaveState('saved'); - setTimeout(() => setSaveState('idle'), 2000); } catch (err: unknown) { const message = err instanceof Error ? err.message : 'Failed to update profile'; setErrorMsg(message); @@ -194,6 +204,7 @@ function AppearanceTab(): React.ReactElement { const [defaultModel, setDefaultModel] = useState(''); const [saveState, setSaveState] = useState('idle'); const [errorMsg, setErrorMsg] = useState(''); + useSavedBadgeReset(saveState, setSaveState); useEffect(() => { api('/api/memory/preferences?category=appearance') @@ -239,7 +250,6 @@ function AppearanceTab(): React.ReactElement { : []), ]); setSaveState('saved'); - setTimeout(() => setSaveState('idle'), 2000); } catch (err: unknown) { const message = err instanceof Error ? err.message : 'Failed to save preferences'; setErrorMsg(message); @@ -323,6 +333,7 @@ function NotificationsTab(): React.ReactElement { const [emailDigest, setEmailDigest] = useState(false); const [saveState, setSaveState] = useState('idle'); const [errorMsg, setErrorMsg] = useState(''); + useSavedBadgeReset(saveState, setSaveState); useEffect(() => { api('/api/memory/preferences?category=communication') @@ -369,7 +380,6 @@ function NotificationsTab(): React.ReactElement { }), ]); setSaveState('saved'); - setTimeout(() => setSaveState('idle'), 2000); } catch (err: unknown) { const message = err instanceof Error ? err.message : 'Failed to save preferences'; setErrorMsg(message);