feat: seen notes filters
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
-
MutationObserver monitors DOM for new notes with
data-note-idattributes - Intersection Observer tracks when notes become 50% visible
- Mouse events provide quick interaction feedback
- 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.