diff --git a/ui/src/layout/useScanElapsedTime.jsx b/ui/src/layout/useScanElapsedTime.jsx index 1e04f02b..c501ba50 100644 --- a/ui/src/layout/useScanElapsedTime.jsx +++ b/ui/src/layout/useScanElapsedTime.jsx @@ -1,12 +1,23 @@ -import { useEffect, useState } from 'react' +import { useEffect, useState, useRef } from 'react' import { useInterval } from '../common' export const useScanElapsedTime = (scanning, elapsedTime) => { const [elapsed, setElapsed] = useState(Number(elapsedTime) || 0) + const prevScanningRef = useRef(scanning) useEffect(() => { - setElapsed(Number(elapsedTime) || 0) - }, [elapsedTime]) + // Only update from server when scan starts or stops + const prevScanning = prevScanningRef.current + if (!prevScanning && scanning) { + // Scan just started - initialize with server value + setElapsed(Number(elapsedTime) || 0) + } else if (prevScanning && !scanning) { + // Scan just finished - use final server value + setElapsed(Number(elapsedTime) || 0) + } + // Update ref for next comparison + prevScanningRef.current = scanning + }, [scanning, elapsedTime]) useInterval(() => setElapsed((prev) => prev + 1e9), scanning ? 1000 : null) diff --git a/ui/src/layout/useScanElapsedTime.test.jsx b/ui/src/layout/useScanElapsedTime.test.jsx index efe958f9..96f0eec2 100644 --- a/ui/src/layout/useScanElapsedTime.test.jsx +++ b/ui/src/layout/useScanElapsedTime.test.jsx @@ -47,4 +47,75 @@ describe('useScanElapsedTime', () => { expect(result.current).toBe(3e9) }) + + it('initializes with server value when scan starts', () => { + const { result, rerender } = renderHook( + ({ scanning, elapsed }) => useScanElapsedTime(scanning, elapsed), + { + initialProps: { scanning: false, elapsed: 5e9 }, + }, + ) + + // Start scanning with a new elapsed time from server + rerender({ scanning: true, elapsed: 10e9 }) + + // Should use the server value when starting + expect(result.current).toBe(10e9) + + act(() => { + vi.advanceTimersByTime(2000) + }) + + // Should continue from server value + expect(result.current).toBe(12e9) + }) + + it('ignores server updates during scanning', () => { + const { result, rerender } = renderHook( + ({ scanning, elapsed }) => useScanElapsedTime(scanning, elapsed), + { + initialProps: { scanning: true, elapsed: 0 }, + }, + ) + + act(() => { + vi.advanceTimersByTime(3000) + }) + + expect(result.current).toBe(3e9) + + // Server sends updated elapsed time during scan + rerender({ scanning: true, elapsed: 10e9 }) + + // Should ignore server update while scanning + expect(result.current).toBe(3e9) + + act(() => { + vi.advanceTimersByTime(1000) + }) + + // Should continue from local timer + expect(result.current).toBe(4e9) + }) + + it('uses final server value when scan ends', () => { + const { result, rerender } = renderHook( + ({ scanning, elapsed }) => useScanElapsedTime(scanning, elapsed), + { + initialProps: { scanning: true, elapsed: 0 }, + }, + ) + + act(() => { + vi.advanceTimersByTime(3000) + }) + + expect(result.current).toBe(3e9) + + // Scan ends with final server value + rerender({ scanning: false, elapsed: 5e9 }) + + // Should use the final server value + expect(result.current).toBe(5e9) + }) })