From e01c8d33fc5273eec87dc409f924a3f23ab52bff Mon Sep 17 00:00:00 2001 From: Gigi Date: Thu, 23 Oct 2025 00:31:29 +0200 Subject: [PATCH] fix(reading-position): use throttle instead of debounce for saves Changed from debounce (which resets timer on every scroll) to throttle (which saves at regular 3s intervals). This ensures position is saved during continuous slow scrolling. Key changes: - Don't reset timer if one is already pending - Track latest position in pendingPositionRef - Save the latest position when timer fires, not the position from when scheduled This prevents the issue where slow continuous scrolling would never trigger a save because the debounce timer kept resetting. --- src/hooks/useReadingPosition.ts | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/hooks/useReadingPosition.ts b/src/hooks/useReadingPosition.ts index d97ce509..790ca466 100644 --- a/src/hooks/useReadingPosition.ts +++ b/src/hooks/useReadingPosition.ts @@ -29,6 +29,7 @@ export const useReadingPosition = ({ const completionTimerRef = useRef | null>(null) const lastSavedAtRef = useRef(0) const suppressUntilRef = useRef(0) + const pendingPositionRef = useRef(0) // Track latest position for throttled save // Suppress auto-saves for a given duration (used after programmatic restore) const suppressSavesFor = useCallback((ms: number) => { @@ -37,7 +38,7 @@ export const useReadingPosition = ({ console.log(`[reading-position] [${new Date().toISOString()}] 🛡️ Suppressing saves for ${ms}ms until ${new Date(until).toISOString()}`) }, []) - // Debounced save function - simple 2s debounce + // Throttled save function - saves at 3s intervals, not debounced const scheduleSave = useCallback((currentPosition: number) => { if (!syncEnabled || !onSave) { return @@ -65,20 +66,26 @@ export const useReadingPosition = ({ return } - // Clear any existing timer and schedule new save + // Update the pending position (latest position to save) + pendingPositionRef.current = currentPosition + + // Throttle: only schedule a save if one isn't already pending + // This ensures saves happen at regular 3s intervals during continuous scrolling if (saveTimerRef.current) { - clearTimeout(saveTimerRef.current) + return // Already have a save scheduled, don't reset the timer } - const DEBOUNCE_MS = 3000 // Save max every 3 seconds + const THROTTLE_MS = 3000 // Save every 3 seconds during scrolling saveTimerRef.current = setTimeout(() => { - console.log(`[reading-position] [${new Date().toISOString()}] 💾 Auto-save at ${Math.round(currentPosition * 100)}%`) - lastSavedPosition.current = currentPosition + // Save the latest position, not the one from when timer was scheduled + const positionToSave = pendingPositionRef.current + console.log(`[reading-position] [${new Date().toISOString()}] 💾 Auto-save at ${Math.round(positionToSave * 100)}%`) + lastSavedPosition.current = positionToSave hasSavedOnce.current = true lastSavedAtRef.current = Date.now() - onSave(currentPosition) + onSave(positionToSave) saveTimerRef.current = null - }, DEBOUNCE_MS) + }, THROTTLE_MS) }, [syncEnabled, onSave]) // Immediate save function