- Change icon background from white (#ffffff) to dark theme (#14161f)
- Update capacitor.config.ts backgroundColor to match
- Update generate-icons.sh to use dark theme color
- Regenerate all Android icons with new background
- Prevents white-on-white icon appearance
- Check for ANDROID_HOME or ANDROID_SDK_ROOT environment variables
- Auto-create android/local.properties if missing
- Provide helpful error messages for SDK configuration
- Added new mew_logo_2.svg to public directory
- Updated MewLogo component to use SVG instead of PNG
- Updated index.html favicon references to use SVG format
- Updated manifest.webmanifest icon references to use SVG with 'any' size
- SVG provides better scalability and smaller file size (3.5KB vs 4.2KB PNG)
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Added dynamic field array management with add/remove functionality
- Extended form schema to support custom fields array
- Parse existing fields from kind 0 event content
- Store fields in NIP-compliant format: [["label", "value"], ...]
- UI includes label/value input pairs with trash icon to remove
- Fields are properly saved and loaded from metadata
- Integrates with existing ProfileRightSidebar display
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Wrapped mute conversation, mention, mute user, and report options with !isOwnPost checks
- Only show these options when viewing someone else's post
- Pin to profile option remains available only on own posts
- Fixes issue where inappropriate moderation options were shown on user's own content
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
This reverts the codebase back to the state at commit e693589.
Reverted 1 commit(s):
- 052aca5: Add support for Articles and Recipes (kind 30023)
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Added ArticleCard component for displaying long-form content in feeds
- Added ArticleDetail component with markdown rendering for article detail pages
- Created ArticlesFeedPage with optional tag filtering for recipes
- Added /articles and /recipes routes
- Updated PostDetailPage to use ArticleDetail for kind 30023 events
- Added Articles and Recipes to settings toggles (sidebar & feed visibility)
- Integrated articles into Feed component - kind 30023 events now use ArticleCard
- Added react-markdown and remark-gfm for proper markdown rendering
- Articles and Recipes now visible by default in sidebar navigation
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Add icons to media type filters (Images, Videos, Vines)
- Add Languages icon to language filter
- Remove icons from "All media", "No media", and platform checkboxes
- Increase radio button and checkbox size from 16px to 18px
- Remove text-sm classes for better readability
- Increase icon sizes from 3.5px to 4px
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Use font-medium instead of font-semibold for cleaner hierarchy
- Restore original spacing (space-y-4, gap-3)
- Remove explicit text-foreground to let theme handle it naturally
- Keep consistent label spacing
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Changed from single toggle to dual checkboxes
- Users can select Nostr only, Mastodon only, or both platforms
- Nostr is checked by default, Mastodon unchecked by default
- Both checkboxes can be unchecked (shows no results)
- More flexible filtering with AND/OR logic
- Added 'Show Mastodon posts' toggle in search filters
- Detects Mastodon/ActivityPub bridge posts via proxy tag
- Posts with proxy tag containing 'activitypub' are filtered out by default
- Toggle allows users to include or exclude bridged posts from results
- Removed 'Show Post Details' option (redundant on detail page)
- Added 'Copy Event ID' to copy hex event ID
- Added 'Copy Event JSON' to copy full event as formatted JSON
- Replaced ArrowUpDown icon with FileJson for JSON copy option
- Re-enabled language and media search filters for initial query
- Added console logging to track initial batch fetch
- Initial query fetches 40 events with NIP-50 filters applied
- Streaming subscription then continues from relay.ditto.pub for new events
The root cause: NPool.req() aborts subscriptions 500ms after the first EOSE
due to eoseTimeout configuration. This is intended for queries but breaks
streaming subscriptions.
Solution: Use relay() directly for streaming instead of the pool. This
bypasses the pool's eoseTimeout logic and allows subscriptions to stay
open indefinitely after EOSE.
- Use nostr.relay('wss://relay.ditto.pub') for streaming subscription
- Keep using the pool for initial query (benefits from multi-relay speed)
- Added detailed logging to track subscription lifecycle
- Re-enabled search filters for initial query only
The search page was broken because it tried to use NIP-50 search filters
in the streaming subscription. Relays don't support streaming search queries
because search requires full-text indexing.
Changes:
- Split filters into initialFilter (with search) and streamFilter (kinds only)
- Initial query uses relay-level NIP-50 search extensions (language, media, video)
- Streaming subscription only filters by kinds
- Re-added client-side filtering for streaming events (search text, media type)
- Language filtering remains relay-level only (can't detect language client-side)
Now the search page correctly:
1. Fetches initial results using relay search filters
2. Streams new events by kind
3. Filters streaming events client-side as they arrive
- Add language filtering using NIP-50 language: extension
- Add media filtering using NIP-50 media: and video: extensions
- Move media filtering from client-side to relay-level for better performance
- Support filtering by images (media:true video:false), videos (video:true), or no media (media:false)
- Language filter supports all existing language options (en, es, fr, de, ja, zh, etc.)
- Filters are now applied at the relay level via search query extensions rather than client-side filtering
When querying multiple relays, if one relay is missing events or has gaps in its data, it can return events from much earlier timestamps (e.g., jumping from 10h ago to 4d ago). This causes the entire feed to skip forward because the pagination cursor is based on the oldest event returned.
Solution: Filter out events that are too old relative to the newest event in the response. If the time span between newest and oldest events exceeds MAX_EVENT_SPAN_SECONDS (6 hours), we exclude the outliers.
This approach:
- Ignores relays that are out of sync until they're back in range
- Prevents single bad relay from punishing the others
- Maintains smooth timeline progression
- Logs warnings when significant filtering occurs for debugging
The 6-hour threshold allows for normal timeline variation while catching relays with large gaps that would otherwise skip days of content.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
The issue was that deduplication in the feed query was reducing the page size from the requested PAGE_SIZE to fewer items. When the pagination cursor was calculated from the deduplicated results, it would use the oldest item's timestamp, which could skip over events that should have been included.
For example:
- Request 15 events from relay
- Receive 15 events including duplicates
- After deduplication, only 12 unique items remain
- Pagination cursor set to oldest of those 12 items
- Missing 3 events worth of timeline between pages
Fixed by tracking the oldest timestamp from the raw relay query (before deduplication) and using that for the pagination cursor. This ensures we never skip events, even when deduplication reduces the page size.
Changed return type of useFeed queryFn from FeedItem[] to FeedPage object containing both the deduplicated items and the oldestQueryTimestamp for accurate pagination.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
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>