- Add handlers for loading my writings, friends writings, and nostrverse writings
- Display writings with title, summary, author, and d-tag
- Show timing metrics (total load time and first event time)
- Use writingsController for own writings to test controller functionality
- Remove cachedHighlights, cachedWritings, myHighlights from useEffect deps
- These are derived from eventStore and caused infinite refetch loop
- Content is still seeded from cache but doesn't trigger re-fetches
- Allow exploring nostrverse writings and highlights without account
- Default to nostrverse visibility when logged out
- Update visibility settings when login state changes
- Add useStoreTimeline hook for reactive EventStore queries
- Add dedupe helpers for highlights and writings
- Explore: seed highlights and writings from store instantly
- Article sidebar: seed article-specific highlights from store
- External URLs: seed URL-specific highlights from store
- Profile pages: seed other-profile highlights and writings from store
- Remove debug logging
- All data loads from cache first, then updates with fresh data
- Follows DRY principles with single reusable hook
- Use eventStore.timeline() to query cached highlights
- Seed Explore page with cached highlights immediately
- Provides instant display of nostrverse highlights from store
- Fresh data still fetched in background and merged
- Follows applesauce pattern with useObservableMemo
- Add key prop based on activeTab to wrapper div
- Forces complete unmount/remount of content when switching tabs
- Prevents DOM element reuse that was causing blog posts to bleed into highlights tab
- Use consistent deduplication key (author:d-tag) for replaceable events
- Prevents duplicate blog posts when same article has multiple event IDs
- Streaming updates now properly replace older versions with newer ones
- Fixes issue where same blog post card appeared multiple times
- Add eventStore parameter to fetchNostrverseBlogPosts
- Add eventStore parameter to fetchNostrverseHighlights
- Pass eventStore from Explore component to nostrverse fetchers
- Store all nostrverse blog posts and highlights in event store
- Enables offline access to nostrverse content
- Pass eventStore to fetchHighlightsForArticle in useBookmarksData
- Pass eventStore to fetchHighlightsForUrl in useExternalUrlLoader
- All fetched highlights now persist in the centralized event store
- Enables offline access and consistent state management
- Subscribe to highlightsController for user's own highlights
- Subscribe to contactsController for followed pubkeys
- Merge controller highlights with article-specific highlights
- Remove duplicate fetching logic for contacts and own highlights
- Maintain article-specific highlight fetching for context-aware display
The subscription pattern only fires on *changes*, not initial state.
When Me component mounts, we need to immediately get the current
highlights from the controller, not wait for a change event.
Before:
- Subscribe to controller
- Wait for controller to emit (only happens on changes)
- Meanwhile, myHighlights stays []
After:
- Get initial state immediately: highlightsController.getHighlights()
- Then subscribe to future updates
- myHighlights is populated right away
This ensures highlights are always available when navigating to
/me/highlights, even if the controller hasn't emitted any new events.
The real issue: loadHighlightsTab was calling setHighlights(myHighlights)
before the controller subscription had populated myHighlights, resulting
in setting highlights to an empty array.
Solution: For own profile, let the sync effect handle setting highlights.
The controller subscription + sync effect is the single source of truth.
Only fetch highlights manually when viewing other users' profiles.
Flow for own profile:
1. Controller subscription populates myHighlights
2. Sync effect (useEffect) updates local highlights state
3. No manual setting needed in loadHighlightsTab
This ensures highlights are always synced from the controller, never
from a stale/empty initial value.
Fix issue where "No highlights yet" message would show briefly when
navigating to /me/highlights even when user has many highlights.
Root cause:
- Sync effect only ran when myHighlights.length > 0
- Local highlights state could be empty during navigation
- "No highlights yet" condition didn't check myHighlightsLoading
Changes:
- Remove length check from sync effect (always sync myHighlights)
- Add myHighlightsLoading check to "No highlights yet" condition
- Now shows skeleton or content, never false empty state
The controller always has the highlights loaded, so we should always
sync them to local state regardless of length.
Add user setting to control default visibility scope in /explore page.
Changes:
- Add defaultExploreScopeNostrverse/Friends/Mine to UserSettings type
- Add "Default Explore Scope" setting in ReadingDisplaySettings UI
- Update Explore component to use defaultExploreScope settings
- Set default to friends-only (nostrverse: false, friends: true, mine: false)
Users can now configure which content types (nostrverse/friends/mine)
are visible by default when visiting the explore page, separate from
the highlight visibility settings.
Remove background color from .highlight-level-toggles bar in /explore page.
The visibility filter buttons (nostrverse, friends, mine) now have no
background, making the UI cleaner.
Remove unnecessary prop drilling of myHighlights/myHighlightsLoading.
Components now subscribe directly to highlightsController (DRY principle).
Changes:
- Explore: Subscribe to controller directly, no props needed
- Me: Subscribe to controller directly, no props needed
- Bookmarks: Remove myHighlights props (no longer passes through)
- App: Remove highlights state, controller manages it internally
Benefits:
- ✅ Simpler code (no prop drilling through 3 layers)
- ✅ More DRY (single source of truth in controller)
- ✅ Consistent with applesauce patterns (like useActiveAccount)
- ✅ Less boilerplate (removed ~30 lines of prop passing)
- ✅ Controller encapsulates all state management
Pattern: Components import and subscribe to controller directly,
just like they use Hooks.useActiveAccount() or other applesauce hooks.