The issue was that when new pages were loaded during infinite scroll, the batch stats query key would change (since it includes all event IDs), causing React Query to treat it as a completely new query. This would briefly show no stats until the new query completed.
Fixed by improving the placeholderData function in useBatchEventStats to pull cached stats from individual event-stats queries. This ensures that previously loaded stats remain visible while new stats are being fetched during pagination.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
This reverts the codebase back to the state at commit 5fd4ff4.
Reverted 1 commit(s):
- a3d2492: Add three-mode stats calculation: NIP-85 Only, Manual Only, and Both
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Replace the binary nip85OnlyMode boolean with a statsMode enum that supports three options:
- "nip85-only": Show only pre-computed NIP-85 stats (fastest, may be empty)
- "manual-only": Always calculate stats from relay queries (slower, guaranteed)
- "both": Use NIP-85 when available with manual fallback (recommended default)
Updated components:
- AppContext: Changed nip85OnlyMode to statsMode with StatsMode type
- AppProvider: Updated Zod schema to validate three-mode enum
- useTrending.ts: Implemented logic for all three modes in useEventStats and useBatchEventStats
- AdvancedSettings: Replaced switch with radio group for better UX
Default remains "nip85-only" for optimal performance.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Set nip85OnlyMode to true in the default configuration. This makes the app use NIP-85 pre-computed engagement stats by default instead of calculating them manually, providing faster performance and reduced relay load.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Use null cursor instead of default 0 when settings haven't loaded yet
- Only calculate newNotifications when cursor is available (not null)
- Ensures hasUnread is always false until settings load with actual cursor value
- Eliminates all flickering by never showing dot with incomplete data
- Don't show notification dot until encrypted settings are loaded
- Prevents flicker when notifications load (3s) before settings (5s)
- Without settings, cursor defaults to 0 causing false positives for hasUnread
- Now waits for both notifications query AND settings query to complete
- Add placeholderData to useNotifications query to keep previous data during refetch
- Prevents notification indicator from briefly disappearing when query refetches every 60s
- Maintains smooth user experience without visual flicker
- Add nip85OnlyMode setting to disable manual stat calculation fallback
- When enabled, stats only show if NIP-85 pubkey provides them (no fallback queries)
- Allow clearing NIP-85 pubkey to disable it entirely (empty string now valid)
- Update Zod schema to accept empty string or 64-char hex pubkey
- Match ContentSettings presentation style (no boxes, simple switches)
- Show toast notifications when enabling/disabling NIP-85 only mode
- Disable NIP-85 only mode toggle when no pubkey is configured
- Update useEventStats and useBatchEventStats to respect nip85OnlyMode
- Use sequential queries (NIP-85 first, then manual fallback) to avoid network contention
- Reduce NIP-85 timeout from 2000ms to 500ms for faster fallback
- When NIP-85 succeeds: only fetch reactions/zaps/quotes (70% less data)
- When NIP-85 fails: fetch full stats as before (no degradation)
- useEventStats: 10 events instead of 50 when NIP-85 available
- useBatchEventStats: 3x per event instead of 10x when NIP-85 available
- Eliminates waterfall delay while avoiding parallel query contention
- Update useEventStats to query NIP-85 stats first, reducing manual query limits when available
- Update useBatchEventStats to batch-query NIP-85 stats for all events in parallel
- Merge NIP-85 counts with computed data (keep emojis, quotes, and zap amounts from manual calculation)
- Reduce relay query limits significantly when NIP-85 stats are available (3x-5x fewer events fetched)
- Falls back seamlessly to manual calculation when NIP-85 unavailable
- Add nip85StatsPubkey to AppConfig (default: 5f68e85ee174102ca8978eef302129f081f03456c884185d5ec1c1224ab633ea)
- Create useNip85Stats hook for querying NIP-85 events (kinds 30382, 30383, 30384)
- Update useEventInteractions to fetch NIP-85 event stats and reduce query limits when available
- Update useComments to fetch NIP-85 stats for events and addressable events
- Add NIP-85 Stats Source section to Advanced Settings with pubkey input field
- Falls back to manual stats calculation when NIP-85 events are not available
- Added onLoad handler to validate Google favicon responses
- Checks if returned image is 16x16 (Google's default globe indicator)
- Hides favicon if Google returns default placeholder instead of real icon
- Only shows favicons that actually exist, no generic fallbacks
Now domains without real favicons (like turdsoup.com) won't show anything.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Use Shakespeare CORS proxy to actually fetch and parse HTML
- Strategy: Scrape HTML → try .svg/.ico/.png → Google service → hide if all fail
- Tracks which fallbacks have been tried to avoid loops
- Only shows favicon if something actually loads successfully
- Google service is last resort before giving up entirely
This ensures we get pure favicons when possible, with intelligent fallback.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Added Google's favicon service as last fallback option
- Tries: HTML discovery → .svg → .ico → .png → Google service
- Google's service intelligently finds favicons even at non-standard paths
- Ensures favicons show for sites like primal.net with versioned paths
This provides maximum coverage while still trying direct paths first.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Fetches domain HTML and parses <link rel="icon"> tags to find actual favicon path
- Discovers versioned/hashed favicons like /assets/favicon-51789dff.ico
- Falls back to common paths (.svg, .ico, .png) if discovery fails
- Handles both absolute and relative favicon URLs in link tags
- Supports shortcut icon, icon, and apple-touch-icon variations
This intelligently finds favicons even when they're not at standard paths.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Created DomainFavicon component that handles all favicon loading logic
- Tries .svg, .ico, .png formats in order (SVG first for best quality)
- Works with both full URLs and plain domains
- Updated Nip05Badge to use DomainFavicon instead of custom logic
- Updated ProfilePage to use DomainFavicon for website links
- Removed duplicate Favicon function from ProfilePage
Benefits:
- Single source of truth for favicon loading logic
- Consistent behavior across NIP-05 badges and profile website links
- Easier to maintain and update favicon handling in one place
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Fetch favicons directly from domain (tries .svg, .ico, .png in order)
- Removed background masking circle (no bg-white/5, no rounded-sm)
- Transparent favicons now render properly with no background
- Falls back through common favicon formats on error
- Hides icon only if all format attempts fail
This gives clean, transparent favicon display without any background artifacts.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Reverted to Google's favicon service (direct domain fetching causes CORS errors)
- Request larger size (32px) to differentiate from default 16x16 globe
- Hide any 16x16 favicons as they're likely the default placeholder
- Real domain favicons will show, default globe will be hidden
This fixes the issue where no favicons were showing due to CORS errors.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Changed to fetch favicon directly from domain (https://domain.com/favicon.ico)
- Added size validation: hide favicons smaller than 16x16 (likely placeholders)
- Hide favicon immediately on any error (404, CORS, etc)
- Added opacity-0 until image loads to prevent flash of broken images
- No more fallback icons or default placeholders - real favicons only
This ensures users only see actual domain favicons, never generic placeholders.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Switched from Google favicon service to DuckDuckGo's service
- DuckDuckGo returns 404 for missing favicons instead of default globe icon
- Reduced background opacity from bg-muted/50 to bg-white/5 for nearly transparent look
- Moved getNip05Domain function into Nip05Badge component for better encapsulation
- Removed unused utility functions from lib/utils.ts
Fixes:
- No more default globe icons showing for domains without favicons
- Much more subtle/transparent background that doesn't look "quite" visible
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Created reusable Nip05Badge component to eliminate code duplication
- Added error handling to hide favicon if image fails to load
- Added subtle background (bg-muted/50) to handle favicon transparency
- Increased spacing from gap-1 to gap-1.5 for better visual separation
- Updated NoteCard, PostDetailPage, and NotificationsPage to use new component
- Made favicon size configurable via iconSize prop (defaults to 16px)
Fixes issues:
- DRY violation: now using single component instead of duplicated code
- Missing favicon handling: onError handler hides broken images
- Transparency: added rounded background for better contrast
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Created utility functions getNip05Domain() and getDomainFavicon() in lib/utils.ts
- Updated NoteCard component to show domain favicon after NIP-05 identifier
- Updated PostDetailPage to display favicons in all NIP-05 displays
- Updated NotificationsPage to include favicons in ReferencedPostCard and FullNoteCard
- Uses Google's favicon service for reliable favicon retrieval
- Favicons are displayed as 16x16px (size-4) inline images with lazy loading
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
WalletPage was unused - wallet functionality is already available in Settings (Settings > Wallet tab). Removed the duplicate page to keep the codebase clean.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Changed spacing on page headers from my-4 (vertical margin) to mt-4 mb-5 (separate top and bottom margins) for better visual consistency across BookmarksPage, HashtagPage, KindFeedPage, PlaceholderPage, WalletPage, and PostDetailPage.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Show skeleton when switching feed tabs without cached data
- Fix infinite skeleton loading when follow list is empty
- Remove placeholderData to ensure proper loading states during tab switches
- Allow follows feed query to run even with empty follow list (shows own posts)
- Use isLoading check to show skeleton during initial data fetch
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Changed tab order to show Community before Global:
- Follows (always first when logged in)
- Community (when enabled - e.g., "Ditto")
- Global (when enabled)
Rationale: Community is more curated/relevant than Global, so it should appear before the global firehose.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
When downloading a community:
- Extract label from hostname (ditto.pub → "Ditto")
- Capitalize first letter of hostname
- Store label in community object
- Auto-enable Community Feed toggle
- Display label in feed tab instead of generic "Community"
Community card display:
- Show label as primary text (e.g., "Ditto")
- Show domain + user count in secondary text
- Format: "Ditto" with "ditto.pub • 247 users"
Feed tab:
- Load community label from localStorage
- Display as tab name (e.g., "Ditto" instead of "Community")
- Falls back to "Community" if no label found
Removing community:
- Auto-disables Community Feed toggle
- Clears all community data
- Returns to input state
Examples:
- ditto.pub → "Ditto" tab
- spinster.xyz → "Spinster" tab
- bitcoinhackers.org → "Bitcoinhackers" tab
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Added proper repost handling to Community feed (was missing):
- Process kind 6 (repost) events same as Follows feed
- Parse embedded JSON content from repost
- Fetch missing reposted events in batch query
- Deduplicate events (prefer original over repost)
- Sort by repost timestamp when displaying reposts
- Maintain repostedBy context for UI display
Community feed now handles:
- Direct posts (kind 1)
- Reposts (kind 6) with embedded content
- Reposts (kind 6) with e-tag reference
- Extra content types (vines, polls, etc.)
- All with NIP-05 domain verification
Fixes the broken repost display showing raw JSON instead of parsed content.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Now only shows posts from users with matching NIP-05 domain in their profile:
1. Query events from all pubkeys in the community NIP-05 JSON
2. Fetch kind 0 metadata for all authors in the results
3. Extract nip05 field from each author's profile
4. Filter events to only include authors whose nip05 ends with @domain
5. Example: ditto.pub community only shows users with nip05 ending in "@ditto.pub"
This ensures the Community feed respects current NIP-05 verification status:
- Users must have the matching domain in their current profile
- If a user changes their NIP-05 to a different domain, they're filtered out
- Prevents showing posts from users who left the community
- Maintains trust model based on current verification state
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Changes to support only one community at a time:
Settings (ContentSettings):
- Changed from communities array to single community object
- Storage: mew:community (single object) instead of mew:communities (array)
- Storage: mew:communityData (NIP-05 JSON) instead of mew:community:{domain}
- Changed "Communities Feed" → "Community Feed" in UI
- Changed showCommunitiesFeed → showCommunityFeed in localStorage
- Show input when no community set, show card when community exists
- Remove button clears the single community
- Updated placeholder to "ditto.pub"
Feed component:
- Changed showCommunitiesFeed → showCommunityFeed
- Changed tab label "Communities" → "Community"
useFeed hook:
- Simplified to load single community data from mew:communityData
- Extracts pubkeys directly from NIP-05 names object
- No longer loops through multiple communities
Benefits:
- Simpler UX - one community at a time
- Cleaner data model
- Less storage complexity
- Clear visual state (input vs community card)
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Feed component changes:
- Added "communities" as a feed tab type
- Load showGlobalFeed and showCommunitiesFeed from localStorage
- Conditionally render Global tab based on setting
- Conditionally render Communities tab based on setting
- Tab buttons dynamically appear/disappear based on user preferences
useFeed hook changes:
- Extended to support 'communities' tab type
- Loads community list from localStorage (mew:communities)
- Fetches NIP-05 JSON data for each community
- Extracts all pubkeys from community NIP-05 data
- Queries posts from all community members
- Returns feed filtered to community pubkeys
- Includes all enabled content types (vines, polls, etc.)
How it works:
1. User adds communities in Settings > Content > Feed Tabs
2. User enables "Communities Feed" toggle
3. Communities tab appears in main feed navigation
4. Shows posts from all users in added communities
5. Respects NIP-05 verification (only shows users in the JSON)
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Added toggle to enable/disable Communities feed tab
- Stored in localStorage (mew:showCommunitiesFeed)
- Defaults to disabled (false)
- Toggle is disabled when no communities are added
- Description updates dynamically showing community count
- Toast notifications on toggle
- Positioned next to Global feed toggle for consistency
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Moved border-b-2 from wrapping div to CollapsibleTrigger button
- Border now appears at section title instead of bottom of content
- Consistent with Other Stuff section styling
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Added back the feed-intro.png image
- Added back "Other Stuff" heading and description inside collapsible
- Matches styling of Feed Tabs and Muted Content sections
- Consistent visual presentation across all collapsible sections
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Major additions to Content settings:
1. Renamed "Content Types" → "Other Stuff" (now collapsible)
- Moved intro image and description into collapsible section
- Defaults to OPEN for easy access
2. Added new "Feed Tabs" section (collapsible, defaults to CLOSED)
- Community management feature
- Download NIP-05 JSON from any domain
- Fetches https://domain/.well-known/nostr.json
- Parses user list and stores locally
- Shows user count for each community
- Remove communities with X button
- Communities stored in localStorage (mew:communities + mew:community:{domain})
- Placeholder for feed tab toggles (Global, Communities, etc.)
Community feature details:
- Input validates and cleans domain (removes protocol, trailing slash)
- Prevents duplicate communities
- Shows loading state during fetch
- Error handling for invalid domains or JSON format
- Stores both community list and raw JSON data for feed filtering
- Clean, consistent UI matching existing patterns
Structure:
- Feed Tabs (new)
- Other Stuff (renamed from Content Types)
- Muted Content (existing)
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Changed section headers from text-sm to text-base font-semibold
- Changed border-bottom from border-border to border-b-2 border-primary (thicker accent color)
- Increased vertical padding on collapsible triggers (py-3.5 instead of py-3)
- Added "Other Stuff" as a static section header above Content Types
- Moved "Other Stuff" description to static section instead of collapsible content
- Removed duplicate description from Muted Content internals
Visual improvements:
- Section headers now stand out more with larger, bolder text
- Accent-colored borders create clearer visual hierarchy
- Static "Other Stuff" section provides context before collapsible sections
- More consistent with modern settings UI patterns
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Major restructure to improve UX for less technical users:
- Consolidated "Feed" + "Mutes" → "Content" tab
- Moved "Wallet" + "Relays" → "Advanced" tab
- Reduced from 5 tabs to 3 tabs with collapsible sections
New structure:
- Profile: Identity settings (unchanged)
- Content: Content Types, Muted Content (with placeholders for Communities, Sensitive Content)
- Advanced: Wallet, Network (relays)
Implementation details:
- Created ContentSettings component with collapsible sections
- Created AdvancedSettings component with collapsible sections
- Content Types section defaults to OPEN
- Muted Content section defaults to CLOSED
- Wallet/Network sections default to CLOSED
- All sections use consistent collapsible UI pattern
- Updated settings page description
- Maintains all existing functionality
Benefits:
- Cleaner mental model for new users
- Power user features hidden but accessible
- Room to grow (Communities, Sensitive Content placeholders)
- Reduced cognitive load with fewer top-level tabs
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Added intro section with relay-intro.png image and description
- Reorganized layout with cleaner visual hierarchy and border separators
- Reduced font sizes for consistency (text-xs, text-sm labels)
- Removed rounded borders and card-style backgrounds
- Added hover states to relay rows (hover:bg-muted/20)
- Improved spacing and padding throughout
- Smaller icon sizes (h-4 w-4 for relays, h-5 w-5 for section headers)
- Reduced button sizes and input heights (h-9) to match other tabs
- Simplified "Add Relay" button text and styling
- Updated popover label sizes to text-xs for consistency
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Added intro section with profile-intro.png image and description
- Reorganized form layout with cleaner visual hierarchy and border separators
- Reduced font sizes and spacing to match feed tab style (text-xs labels)
- Moved "Bot Account" setting under collapsible "Advanced Settings" section
- Improved input heights (h-9) and button sizes for consistency
- Enhanced image upload field styling with smaller previews and buttons
- Updated form descriptions to be more concise
- Added proper spacing with border-b separators between sections
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>