diff --git a/src/renderer/src/components/UpdateCard.tsx b/src/renderer/src/components/UpdateCard.tsx index ce49ca1a994..639575a8e31 100644 --- a/src/renderer/src/components/UpdateCard.tsx +++ b/src/renderer/src/components/UpdateCard.tsx @@ -94,6 +94,8 @@ export function UpdateCard() { const reassuranceSeen = useAppStore((s) => s.updateReassuranceSeen) const markReassuranceSeen = useAppStore((s) => s.markUpdateReassuranceSeen) const hasStartedDownload = useRef(false) + const dismissAnimationTimerRef = useRef(null) + const collapseAnimationTimerRef = useRef(null) const [mediaFailed, setMediaFailed] = useState(false) const [mediaLoaded, setMediaLoaded] = useState(false) const [installError, setInstallError] = useState(null) @@ -204,6 +206,17 @@ export function UpdateCard() { return () => mq.removeEventListener('change', handler) }, []) + useEffect(() => { + return () => { + if (dismissAnimationTimerRef.current !== null) { + window.clearTimeout(dismissAnimationTimerRef.current) + } + if (collapseAnimationTimerRef.current !== null) { + window.clearTimeout(collapseAnimationTimerRef.current) + } + } + }, []) + // ── Visibility gates ────────────────────────────────────────────── const isUserInitiated = 'userInitiated' in status && status.userInitiated @@ -358,7 +371,13 @@ export function UpdateCard() { return } setExiting(true) - setTimeout(handleClose, 150) + if (dismissAnimationTimerRef.current !== null) { + window.clearTimeout(dismissAnimationTimerRef.current) + } + dismissAnimationTimerRef.current = window.setTimeout(() => { + dismissAnimationTimerRef.current = null + handleClose() + }, 150) } // Why: long-running phases (downloading, downloaded, error) minimize to the @@ -370,7 +389,11 @@ export function UpdateCard() { return } setExiting(true) - setTimeout(() => { + if (collapseAnimationTimerRef.current !== null) { + window.clearTimeout(collapseAnimationTimerRef.current) + } + collapseAnimationTimerRef.current = window.setTimeout(() => { + collapseAnimationTimerRef.current = null setCollapsed(true) setExiting(false) }, 150) diff --git a/src/renderer/src/components/browser-pane/BrowserAddressBar.tsx b/src/renderer/src/components/browser-pane/BrowserAddressBar.tsx index 4feb018560c..8e1e1654b76 100644 --- a/src/renderer/src/components/browser-pane/BrowserAddressBar.tsx +++ b/src/renderer/src/components/browser-pane/BrowserAddressBar.tsx @@ -68,6 +68,19 @@ export default function BrowserAddressBar({ const browserKagiSessionLink = useAppStore((s) => s.browserKagiSessionLink) const closingRef = useRef(false) const openedAtRef = useRef(0) + const blurCloseTimerRef = useRef(null) + const closingResetTimerRef = useRef(null) + + useEffect(() => { + return () => { + if (blurCloseTimerRef.current !== null) { + window.clearTimeout(blurCloseTimerRef.current) + } + if (closingResetTimerRef.current !== null) { + window.clearTimeout(closingResetTimerRef.current) + } + } + }, []) const searchEngine: SearchEngine = (browserDefaultSearchEngine as SearchEngine | null) ?? DEFAULT_SEARCH_ENGINE @@ -147,6 +160,10 @@ export default function BrowserAddressBar({ if (closingRef.current) { return } + if (blurCloseTimerRef.current !== null) { + window.clearTimeout(blurCloseTimerRef.current) + blurCloseTimerRef.current = null + } inputRef.current?.select() openedAtRef.current = Date.now() setOpen(true) @@ -164,7 +181,11 @@ export default function BrowserAddressBar({ // — producing the "flash then disappear" on first click. const elapsed = Date.now() - openedAtRef.current const grace = elapsed < 400 - setTimeout(() => { + if (blurCloseTimerRef.current !== null) { + window.clearTimeout(blurCloseTimerRef.current) + } + blurCloseTimerRef.current = window.setTimeout(() => { + blurCloseTimerRef.current = null if (grace && inputRef.current && document.activeElement === inputRef.current) { return } @@ -178,7 +199,11 @@ export default function BrowserAddressBar({ setOpen(false) setSelectedValueOverride(null) onNavigate(url) - setTimeout(() => { + if (closingResetTimerRef.current !== null) { + window.clearTimeout(closingResetTimerRef.current) + } + closingResetTimerRef.current = window.setTimeout(() => { + closingResetTimerRef.current = null closingRef.current = false }, 100) }, diff --git a/src/renderer/src/components/settings/RuntimePairingUrlGenerator.tsx b/src/renderer/src/components/settings/RuntimePairingUrlGenerator.tsx index dbf79339e6e..1948a102e31 100644 --- a/src/renderer/src/components/settings/RuntimePairingUrlGenerator.tsx +++ b/src/renderer/src/components/settings/RuntimePairingUrlGenerator.tsx @@ -113,6 +113,17 @@ export function RuntimePairingUrlGenerator({ const networkInterfaceLoadIdRef = useRef(0) const accessGrantLoadIdRef = useRef(0) + useEffect(() => { + if (copiedTarget === null) { + return + } + const target = copiedTarget + const timeout = window.setTimeout(() => { + setCopiedTarget((current) => (current === target ? null : current)) + }, 1400) + return () => window.clearTimeout(timeout) + }, [copiedTarget]) + const loadRuntimeAccessGrants = useCallback( async (options: { showToastOnError?: boolean } = {}): Promise => { const loadId = accessGrantLoadIdRef.current + 1 @@ -240,9 +251,6 @@ export function RuntimePairingUrlGenerator({ await window.api.ui.writeClipboardText(value) setCopiedTarget(target) toast.success(target === 'web' ? 'Copied web client URL.' : 'Copied pairing URL.') - window.setTimeout(() => { - setCopiedTarget((current) => (current === target ? null : current)) - }, 1400) } catch (error) { toast.error(error instanceof Error ? error.message : 'Failed to copy URL.') }