feat: improve reading progress with validation and auto-mark

- Add autoMarkAsReadOnCompletion setting (opt-in, default: false)
- Implement auto-mark as read when reaching 95%+ completion
- Add validation for progress bounds (0-1) per NIP-39802 spec
- Align completion threshold to 95% to match filter behavior
- Skip invalid progress events with warning log

Improvements ensure consistency between completion detection and
filtering, while adding safety validation per the NIP spec.
This commit is contained in:
Gigi
2025-10-19 10:34:53 +02:00
parent 442c138d6a
commit 3b31eceeab
4 changed files with 14 additions and 5 deletions

View File

@@ -189,9 +189,10 @@ const ContentPanel: React.FC<ContentPanelProps> = ({
syncEnabled: settings?.syncReadingPosition, syncEnabled: settings?.syncReadingPosition,
onSave: handleSavePosition, onSave: handleSavePosition,
onReadingComplete: () => { onReadingComplete: () => {
// Optional: Auto-mark as read when reading is complete // Auto-mark as read when reading is complete (if enabled in settings)
if (activeAccount && !isMarkedAsRead) { if (activeAccount && !isMarkedAsRead && settings?.autoMarkAsReadOnCompletion) {
// Could trigger auto-mark as read here if desired console.log('📖 [ContentPanel] Auto-marking as read on completion')
handleMarkAsRead()
} }
} }
}) })

View File

@@ -4,7 +4,7 @@ interface UseReadingPositionOptions {
enabled?: boolean enabled?: boolean
onPositionChange?: (position: number) => void onPositionChange?: (position: number) => void
onReadingComplete?: () => void onReadingComplete?: () => void
readingCompleteThreshold?: number // Default 0.9 (90%) readingCompleteThreshold?: number // Default 0.95 (95%) - matches filter threshold
syncEnabled?: boolean // Whether to sync positions to Nostr syncEnabled?: boolean // Whether to sync positions to Nostr
onSave?: (position: number) => void // Callback for saving position onSave?: (position: number) => void // Callback for saving position
autoSaveInterval?: number // Auto-save interval in ms (default 5000) autoSaveInterval?: number // Auto-save interval in ms (default 5000)
@@ -14,7 +14,7 @@ export const useReadingPosition = ({
enabled = true, enabled = true,
onPositionChange, onPositionChange,
onReadingComplete, onReadingComplete,
readingCompleteThreshold = 0.9, readingCompleteThreshold = 0.95, // Match filter threshold for consistency
syncEnabled = false, syncEnabled = false,
onSave, onSave,
autoSaveInterval = 5000 autoSaveInterval = 5000

View File

@@ -36,6 +36,13 @@ export function processReadingProgress(
try { try {
const content = JSON.parse(event.content) const content = JSON.parse(event.content)
const position = content.progress || 0 const position = content.progress || 0
// Validate progress is between 0 and 1 (NIP-39802 requirement)
if (position < 0 || position > 1) {
console.warn('Invalid progress value (must be 0-1):', position, 'event:', event.id.slice(0, 8))
continue
}
// Use event.created_at as authoritative timestamp (NIP-39802 spec) // Use event.created_at as authoritative timestamp (NIP-39802 spec)
const timestamp = event.created_at const timestamp = event.created_at

View File

@@ -60,6 +60,7 @@ export interface UserSettings {
paragraphAlignment?: 'left' | 'justify' // default: justify paragraphAlignment?: 'left' | 'justify' // default: justify
// Reading position sync // Reading position sync
syncReadingPosition?: boolean // default: false (opt-in) syncReadingPosition?: boolean // default: false (opt-in)
autoMarkAsReadOnCompletion?: boolean // default: false (opt-in)
} }
export async function loadSettings( export async function loadSettings(