diff --git a/src/components/ContentPanel.tsx b/src/components/ContentPanel.tsx index 6ea193c0..15418a9f 100644 --- a/src/components/ContentPanel.tsx +++ b/src/components/ContentPanel.tsx @@ -162,20 +162,24 @@ const ContentPanel: React.FC = ({ // Callback to save reading position const handleSavePosition = useCallback(async (position: number) => { if (!activeAccount || !relayPool || !eventStore || !articleIdentifier) { + console.log('[reading-position] ❌ Cannot save: missing dependencies') return } if (!settings?.syncReadingPosition) { + console.log('[reading-position] ⚠️ Save skipped: sync disabled in settings') return } // Check if content is long enough to track reading progress if (!shouldTrackReadingProgress(html, markdown)) { + console.log('[reading-position] ⚠️ Save skipped: content too short') return } const scrollTop = window.pageYOffset || document.documentElement.scrollTop try { + console.log('[reading-position] 🚀 Publishing position', Math.round(position * 100) + '% to relays...') const factory = new EventFactory({ signer: activeAccount }) await saveReadingPosition( relayPool, @@ -188,8 +192,9 @@ const ContentPanel: React.FC = ({ scrollTop } ) + console.log('[reading-position] ✅ Position published successfully') } catch (error) { - console.error('[progress] ❌ ContentPanel: Failed to save reading position:', error) + console.error('[reading-position] ❌ Failed to save reading position:', error) } }, [activeAccount, relayPool, eventStore, articleIdentifier, settings?.syncReadingPosition, html, markdown]) @@ -217,12 +222,15 @@ const ContentPanel: React.FC = ({ // Load saved reading position when article loads (stabilized one-shot restore) useEffect(() => { if (!isTextContent || !activeAccount || !relayPool || !eventStore || !articleIdentifier) { + console.log('[reading-position] ⏭️ Restore skipped: missing dependencies or not text content') return } if (settings?.syncReadingPosition === false) { + console.log('[reading-position] ⏭️ Restore skipped: sync disabled in settings') return } + console.log('[reading-position] 🔄 Initiating restore for article:', articleIdentifier) const collector = collectReadingPositionsOnce({ relayPool, eventStore, @@ -232,7 +240,12 @@ const ContentPanel: React.FC = ({ }) collector.onStable((bestPosition) => { - if (!bestPosition) return + if (!bestPosition) { + console.log('[reading-position] ℹ️ No position to restore') + return + } + + console.log('[reading-position] 🎯 Stable position received:', Math.round(bestPosition.position * 100) + '%') // Wait for content to be fully rendered setTimeout(() => { @@ -242,13 +255,25 @@ const ContentPanel: React.FC = ({ const currentTop = window.pageYOffset || document.documentElement.scrollTop const targetTop = bestPosition.position * maxScroll + console.log('[reading-position] 📐 Restore calculation:', { + docHeight: docH, + winHeight: winH, + maxScroll, + currentTop, + targetTop, + targetPercent: Math.round(bestPosition.position * 100) + '%' + }) + // Skip if delta is too small (< 48px or < 5%) const deltaPx = Math.abs(targetTop - currentTop) const deltaPct = maxScroll > 0 ? Math.abs((targetTop - currentTop) / maxScroll) : 0 if (deltaPx < 48 || deltaPct < 0.05) { + console.log('[reading-position] ⏭️ Restore skipped: delta too small (', deltaPx, 'px,', Math.round(deltaPct * 100) + '%)') return } + console.log('[reading-position] 📜 Restoring scroll position (delta:', deltaPx, 'px,', Math.round(deltaPct * 100) + '%)') + // Suppress saves briefly to avoid feedback loop if (suppressSavesFor) { suppressSavesFor(1500) @@ -259,10 +284,14 @@ const ContentPanel: React.FC = ({ top: targetTop, behavior: 'auto' }) + console.log('[reading-position] ✅ Scroll restored to', Math.round(bestPosition.position * 100) + '%') }, 500) // Give content time to render }) - return () => collector.stop() + return () => { + console.log('[reading-position] 🛑 Stopping restore collector') + collector.stop() + } }, [isTextContent, activeAccount, relayPool, eventStore, articleIdentifier, settings?.syncReadingPosition, selectedUrl, suppressSavesFor]) // Save position before unmounting or changing article diff --git a/src/hooks/useReadingPosition.ts b/src/hooks/useReadingPosition.ts index 8c9b7cf6..4e632255 100644 --- a/src/hooks/useReadingPosition.ts +++ b/src/hooks/useReadingPosition.ts @@ -34,7 +34,9 @@ export const useReadingPosition = ({ // Suppress auto-saves for a given duration (used after programmatic restore) const suppressSavesFor = useCallback((ms: number) => { - suppressUntilRef.current = Date.now() + ms + const until = Date.now() + ms + suppressUntilRef.current = until + console.log('[reading-position] 🛡️ Suppressing saves for', ms, 'ms until', new Date(until).toISOString()) }, []) // Debounced save function @@ -49,6 +51,7 @@ export const useReadingPosition = ({ clearTimeout(saveTimerRef.current) saveTimerRef.current = null } + console.log('[reading-position] 💾 Instant save at 100% completion') lastSavedPosition.current = 1 hasSavedOnce.current = true lastSavedAtRef.current = Date.now() @@ -80,7 +83,9 @@ export const useReadingPosition = ({ } const remaining = Math.max(0, MIN_INTERVAL_MS - (nowMs - lastSavedAtRef.current)) const delay = Math.max(autoSaveInterval, remaining) + console.log('[reading-position] ⏱️ Rescheduling save in', delay, 'ms (pos:', Math.round(currentPosition * 100) + '%)') saveTimerRef.current = setTimeout(() => { + console.log('[reading-position] 💾 Auto-save (rescheduled) at', Math.round(currentPosition * 100) + '%') lastSavedPosition.current = currentPosition hasSavedOnce.current = true lastSavedAtRef.current = Date.now() @@ -96,7 +101,9 @@ export const useReadingPosition = ({ // Schedule new save using the larger of autoSaveInterval and MIN_INTERVAL_MS const delay = Math.max(autoSaveInterval, MIN_INTERVAL_MS) + console.log('[reading-position] ⏱️ Scheduling save in', delay, 'ms (pos:', Math.round(currentPosition * 100) + '%)') saveTimerRef.current = setTimeout(() => { + console.log('[reading-position] 💾 Auto-save at', Math.round(currentPosition * 100) + '%') lastSavedPosition.current = currentPosition hasSavedOnce.current = true lastSavedAtRef.current = Date.now() @@ -111,6 +118,7 @@ export const useReadingPosition = ({ clearTimeout(saveTimerRef.current) saveTimerRef.current = null } + console.log('[reading-position] 💾 saveNow() called at', Math.round(position * 100) + '%') lastSavedPosition.current = position hasSavedOnce.current = true lastSavedAtRef.current = Date.now() @@ -145,6 +153,9 @@ export const useReadingPosition = ({ // Schedule auto-save if sync is enabled (unless suppressed) if (Date.now() >= suppressUntilRef.current) { scheduleSave(clampedProgress) + } else { + const remainingMs = suppressUntilRef.current - Date.now() + console.log('[reading-position] 🛡️ Save suppressed (', remainingMs, 'ms remaining) at', Math.round(clampedProgress * 100) + '%') } // Completion detection with 2s hold at 100% diff --git a/src/services/readingPositionService.ts b/src/services/readingPositionService.ts index 223b8cd7..52cd7393 100644 --- a/src/services/readingPositionService.ts +++ b/src/services/readingPositionService.ts @@ -203,10 +203,14 @@ export function collectReadingPositionsOnce(params: { hasEmitted = true if (candidates.length === 0) { + console.log('[reading-position] 📊 No candidates collected during stabilization window') stableCallback(null) return } + console.log('[reading-position] 📊 Collected', candidates.length, 'position candidates:', + candidates.map(c => `${Math.round(c.position * 100)}% @${new Date(c.timestamp * 1000).toLocaleTimeString()}`).join(', ')) + // Sort: newest first, then highest progress candidates.sort((a, b) => { const timeDiff = b.timestamp - a.timestamp @@ -214,10 +218,13 @@ export function collectReadingPositionsOnce(params: { return b.position - a.position }) + console.log('[reading-position] ✅ Best position selected:', Math.round(candidates[0].position * 100) + '%', + 'from', new Date(candidates[0].timestamp * 1000).toLocaleTimeString()) stableCallback(candidates[0]) } // Start streaming and collecting + console.log('[reading-position] 🎯 Starting stabilized position collector (window:', windowMs, 'ms)') streamStop = startReadingPositionStream( relayPool, eventStore, @@ -225,13 +232,22 @@ export function collectReadingPositionsOnce(params: { articleIdentifier, (pos) => { if (hasEmitted) return - if (!pos) return - if (pos.position <= 0.05 || pos.position >= 1) return + if (!pos) { + console.log('[reading-position] 📥 Received null position') + return + } + if (pos.position <= 0.05 || pos.position >= 1) { + console.log('[reading-position] 🚫 Ignoring position', Math.round(pos.position * 100) + '% (outside 5%-100% range)') + return + } + console.log('[reading-position] 📥 Received position candidate:', Math.round(pos.position * 100) + '%', + 'from', new Date(pos.timestamp * 1000).toLocaleTimeString()) candidates.push(pos) // Schedule one-shot emission if not already scheduled if (!timer) { + console.log('[reading-position] ⏰ Starting', windowMs, 'ms stabilization timer') timer = setTimeout(() => { emitStable() if (streamStop) streamStop()