primal-web-app icon indicating copy to clipboard operation
primal-web-app copied to clipboard

feat: seen notes filters

Open cloudsupper opened this issue 5 months ago • 0 comments

PR Description: Seen Notes Filter - Automatic Feed Filtering

Overview

This PR implements a production-ready seen notes filter that automatically hides previously viewed notes in the home feed, providing users with a "fresh content only" experience. The system uses fast bloom filters with localStorage persistence to track which notes users have already seen.

Key Features

Automatic Note Filtering

  • Smart feed filtering - Only unseen notes appear in the home feed
  • "All caught up" message when all notes have been viewed
  • Graceful fallbacks - Works normally if disabled or user not logged in
  • Zero configuration - Automatically enabled for logged-in users

Intelligent Note Tracking

  • Intersection Observer - Tracks notes when 50% visible for 5 seconds
  • Mouse hover detection - Quick 1-second timeout for interactive viewing
  • Dynamic DOM detection - Automatically finds new notes as they load
  • Sidebar exclusions - Trending sidebar notes are not tracked

Performance & Storage

  • localStorage-only - No network dependencies, works offline
  • Fast bloom filters - FNV-1a hashing for sub-millisecond operations
  • Automatic rotation - Filters rotate every 7 days or when 99% full
  • User-specific storage - Separate filters per user account

Implementation

Core Integration (Home.tsx)

// Minimal 4-line integration
const {
  filteredNotes,
  setupNoteTracking,
  isEnabled,
  hiddenNotesCount
} = useSeenNotesIntegration(() => context?.notes || []);

// Replace original notes rendering
<For each={filteredNotes()}>
  {note => (
    <div class="animated" {...setupNoteTracking(note)}>
      <Note note={note} shorten={true} />
    </div>
  )}
</For>

Technical Architecture

// localStorage keys per user
const STORAGE_KEYS = {
  OLD_FILTER: `seen_notes_old_filter_${pubkey}`,
  NEW_FILTER: `seen_notes_new_filter_${pubkey}`,
  METADATA: `seen_notes_metadata_${pubkey}`
};

// Bloom filter configuration
const BLOOM_PARAMS = {
  maxItems: 1000,     // Items before rotation
  bitArray: 33548,    // ~4KB storage per filter
  hashFunctions: 23   // Optimal for <1% false positive rate
};

Files Added/Modified

Core Implementation

  • src/lib/seenNotesFilter.ts - Bloom filter and storage manager
  • src/lib/seenNotesIntegration.tsx - Main integration hook
  • src/lib/feedIntegration.ts - Visibility tracking utilities

Home Feed Integration

  • src/pages/Home.tsx - Main integration with 4-line change
  • src/pages/Home.module.scss - Styles for "all caught up" message

Debug & Settings

  • src/components/SeenNotesSettings/ - Optional debug panel
  • DEBUGGING_GUIDE.md - Comprehensive troubleshooting guide

How It Works

Automatic Detection System

  1. MutationObserver monitors DOM for new notes with data-note-id attributes
  2. Intersection Observer tracks when notes become 50% visible
  3. Mouse events provide quick interaction feedback
  4. Smart filtering excludes sidebar elements from tracking

Note Lifecycle

Note loads → DOM detection → Intersection tracking → 
User views (5s) OR hovers (1s) → Added to bloom filter → 
Filtered from future feeds

Storage & Rotation

  • Dual filters - "old" and "new" for seamless capacity management
  • Age-based rotation - Every 7 days automatically
  • Capacity rotation - When filter reaches 99% full
  • Instant persistence - Changes saved to localStorage immediately

Performance

Metric Value Description
Filter check <0.01ms FNV-1a hash algorithm
Storage size ~4KB per filter Optimized bloom filter
False positive rate <1% 23 hash functions
Network overhead 0 bytes localStorage-only
Memory usage ~8KB max Dual filter system

Debugging Features

Console Debug Tools

// Available in browser console
window.debugSeenNotes.getStats()     // View filter statistics
window.debugSeenNotes.listStorage()  // Show localStorage data
window.debugSeenNotes.clearAll()     // Reset all filters

Comprehensive Logging

  • Filter initialization and loading
  • DOM element detection and tracking
  • Note visibility events (intersection + hover)
  • Bloom filter operations (add/check)
  • Storage operations and rotation events

User Experience

Before

  • Users see repeated notes they've already read
  • No indication of reading progress
  • Cluttered feed with duplicate content

After

  • Only fresh content appears in home feed
  • "All caught up" message shows completion
  • Cleaner browsing with automatic filtering
  • Works offline with no network requirements

Edge Cases Handled

  • Not logged in - Filter disabled, normal feed behavior
  • Filter error - Falls back to showing all notes
  • Slow network - Works entirely offline
  • Mobile devices - Optimized for touch interactions
  • Page refresh - Filters persist across sessions

Impact

This PR addresses a major user experience pain point by providing automatic content freshness filtering. Users no longer need to manually track which notes they've read - the system handles this transparently while maintaining perfect privacy through local-only storage.

Benefits

  • Improved engagement - Users see only relevant, fresh content
  • Reduced cognitive load - No need to remember what was read
  • Better performance - Fewer elements to render in feeds
  • Enhanced privacy - All data stays on user's device
  • Zero maintenance - Automatic filter management and rotation

This feature provides immediate value to users while requiring zero configuration or maintenance. The localStorage-only approach ensures reliability and privacy while the bloom filter design keeps memory usage minimal.

cloudsupper avatar Aug 15 '25 14:08 cloudsupper