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);