diff --git a/src/components/HighlightItem.tsx b/src/components/HighlightItem.tsx index 733d270d..d6617698 100644 --- a/src/components/HighlightItem.tsx +++ b/src/components/HighlightItem.tsx @@ -212,12 +212,23 @@ export const HighlightItem: React.FC = ({ pubkey, identifier }) - navigate(`/a/${naddr}`) + // Pass highlight ID in navigation state to trigger scroll + navigate(`/a/${naddr}`, { + state: { + highlightId: highlight.id, + openHighlights: true + } + }) } } } else if (highlight.urlReference) { - // Navigate to external URL - navigate(`/r/${encodeURIComponent(highlight.urlReference)}`) + // Navigate to external URL with highlight ID to trigger scroll + navigate(`/r/${encodeURIComponent(highlight.urlReference)}`, { + state: { + highlightId: highlight.id, + openHighlights: true + } + }) } } diff --git a/src/hooks/useReadingPosition.ts b/src/hooks/useReadingPosition.ts index 7a9aca92..d97ce509 100644 --- a/src/hooks/useReadingPosition.ts +++ b/src/hooks/useReadingPosition.ts @@ -29,15 +29,6 @@ export const useReadingPosition = ({ const completionTimerRef = useRef | null>(null) const lastSavedAtRef = useRef(0) const suppressUntilRef = useRef(0) - const syncEnabledRef = useRef(syncEnabled) - const onSaveRef = useRef(onSave) - const scheduleSaveRef = useRef<((pos: number) => void) | null>(null) - - // Keep refs in sync with props - useEffect(() => { - syncEnabledRef.current = syncEnabled - onSaveRef.current = onSave - }, [syncEnabled, onSave]) // Suppress auto-saves for a given duration (used after programmatic restore) const suppressSavesFor = useCallback((ms: number) => { @@ -48,7 +39,7 @@ export const useReadingPosition = ({ // Debounced save function - simple 2s debounce const scheduleSave = useCallback((currentPosition: number) => { - if (!syncEnabledRef.current || !onSaveRef.current) { + if (!syncEnabled || !onSave) { return } @@ -62,7 +53,7 @@ export const useReadingPosition = ({ lastSavedPosition.current = 1 hasSavedOnce.current = true lastSavedAtRef.current = Date.now() - onSaveRef.current(1) + onSave(1) return } @@ -85,26 +76,19 @@ export const useReadingPosition = ({ lastSavedPosition.current = currentPosition hasSavedOnce.current = true lastSavedAtRef.current = Date.now() - if (onSaveRef.current) { - onSaveRef.current(currentPosition) - } + onSave(currentPosition) saveTimerRef.current = null }, DEBOUNCE_MS) - }, []) - - // Store scheduleSave in ref for use in scroll handler - useEffect(() => { - scheduleSaveRef.current = scheduleSave - }, [scheduleSave]) + }, [syncEnabled, onSave]) // Immediate save function const saveNow = useCallback(() => { - if (!syncEnabledRef.current || !onSaveRef.current) return + if (!syncEnabled || !onSave) return // Check suppression even for saveNow (e.g., during restore) if (Date.now() < suppressUntilRef.current) { const remainingMs = suppressUntilRef.current - Date.now() - console.log(`[reading-position] [${new Date().toISOString()}] ⏭️ saveNow() suppressed (${remainingMs}ms remaining) at ${Math.round(positionRef.current * 100)}%`) + console.log(`[reading-position] [${new Date().toISOString()}] ⏭️ saveNow() suppressed (${remainingMs}ms remaining) at ${Math.round(position * 100)}%`) return } @@ -112,12 +96,12 @@ export const useReadingPosition = ({ clearTimeout(saveTimerRef.current) saveTimerRef.current = null } - console.log(`[reading-position] [${new Date().toISOString()}] 💾 saveNow() called at ${Math.round(positionRef.current * 100)}%`) - lastSavedPosition.current = positionRef.current + console.log(`[reading-position] [${new Date().toISOString()}] 💾 saveNow() called at ${Math.round(position * 100)}%`) + lastSavedPosition.current = position hasSavedOnce.current = true lastSavedAtRef.current = Date.now() - onSaveRef.current(positionRef.current) - }, []) + onSave(position) + }, [syncEnabled, onSave, position]) useEffect(() => { if (!enabled) return @@ -149,7 +133,7 @@ export const useReadingPosition = ({ // Schedule auto-save if sync is enabled (unless suppressed) if (Date.now() >= suppressUntilRef.current) { - scheduleSaveRef.current?.(clampedProgress) + scheduleSave(clampedProgress) } else { const remainingMs = suppressUntilRef.current - Date.now() console.log(`[reading-position] [${new Date().toISOString()}] 🛡️ Save suppressed (${remainingMs}ms remaining) at ${Math.round(clampedProgress * 100)}%`) @@ -196,24 +180,15 @@ export const useReadingPosition = ({ window.removeEventListener('scroll', handleScroll) window.removeEventListener('resize', handleScroll) - // Flush pending save before unmount (don't lose progress if navigating away during debounce window) - if (saveTimerRef.current && syncEnabledRef.current && onSaveRef.current) { + // Clear save timer on unmount + if (saveTimerRef.current) { clearTimeout(saveTimerRef.current) - saveTimerRef.current = null - - // Only flush if we have unsaved progress (position differs from last saved) - const hasUnsavedProgress = Math.abs(positionRef.current - lastSavedPosition.current) >= 0.05 - if (hasUnsavedProgress && Date.now() >= suppressUntilRef.current) { - console.log(`[reading-position] [${new Date().toISOString()}] 💾 Flushing pending save on unmount at ${Math.round(positionRef.current * 100)}%`) - onSaveRef.current(positionRef.current) - } } - if (completionTimerRef.current) { clearTimeout(completionTimerRef.current) } } - }, [enabled, onPositionChange, onReadingComplete, readingCompleteThreshold, completionHoldMs]) + }, [enabled, onPositionChange, onReadingComplete, readingCompleteThreshold, scheduleSave, completionHoldMs]) // Reset reading complete state when enabled changes useEffect(() => {