Commit Graph

497 Commits

Author SHA1 Message Date
Chad Curtis db7bbb0095 Update app icon background to dark theme color
- 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
2026-02-19 06:27:00 -06:00
Chad Curtis d47f12b38d Build v2026.02.19: Update mew.apk (5.1M) 2026-02-19 06:21:41 -06:00
Chad Curtis 7e122c2118 Update build script to handle Android SDK location
- Check for ANDROID_HOME or ANDROID_SDK_ROOT environment variables
- Auto-create android/local.properties if missing
- Provide helpful error messages for SDK configuration
2026-02-19 06:21:02 -06:00
Chad Curtis 2f02e65ab6 Add Android Capacitor build system
- Install Capacitor dependencies (@capacitor/cli, @capacitor/core, @capacitor/android, @capacitor/app, @capacitor/status-bar, sharp)
- Create capacitor.config.ts with app configuration
- Update .gitignore to exclude Android build outputs and keystore files
- Generate icon-512.png from logo.svg
- Add scripts for icon generation, APK building, and keystore creation
- Initialize Capacitor Android platform
- Configure build.gradle with signing support
- Generate Android app icons in all required densities
- Add npm scripts: 'icons' and 'build:apk'
2026-02-19 06:18:02 -06:00
shakespeare.diy d519fe82bd Replace favicon and logo with new SVG design
- 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>
2026-02-19 06:11:34 -06:00
shakespeare.diy 336c761d66 Add support for custom profile fields in Edit Profile form
- 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>
2026-02-19 06:08:54 -06:00
shakespeare.diy 48a5dedb45 Fix post menu to hide mute/mention/report options on own posts
- 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>
2026-02-19 06:04:58 -06:00
shakespeare.diy bdf189481e Revert to e693589: Increase top padding for search inputs from pt-4 to pt-5
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>
2026-02-19 06:02:49 -06:00
shakespeare.diy 052aca55cb Add support for Articles and Recipes (kind 30023)
- 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>
2026-02-19 05:59:37 -06:00
shakespeare.diy e693589e6d Increase top padding for search inputs from pt-4 to pt-5
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-19 05:47:45 -06:00
shakespeare.diy 1001ea8161 Improve search filters readability with larger inputs and icons
- 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>
2026-02-19 05:46:30 -06:00
shakespeare.diy e224a33db7 Refine search filters layout with subtle improvements
- 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>
2026-02-19 05:41:22 -06:00
shakespeare.diy 92e0b0b82c Improve search filters presentation with better spacing and text hierarchy
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-19 05:40:01 -06:00
Chad Curtis 39fb1057a5 Change platform filter to checkboxes for Nostr and Mastodon
- 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
2026-02-19 05:34:26 -06:00
Chad Curtis 00c8ff6130 Add toggle to show/hide Mastodon posts on search page
- 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
2026-02-19 05:31:53 -06:00
Chad Curtis 427aa2dab4 Use FileDigit icon for Copy Event ID 2026-02-19 05:30:09 -06:00
Chad Curtis cdf38328d5 Update post detail menu: remove 'View Details', add copy event ID and JSON
- 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
2026-02-19 05:28:39 -06:00
Chad Curtis c82fc125c3 Remove debug logging bloat from useStreamPosts 2026-02-19 05:27:26 -06:00
Chad Curtis eb93d5dd09 Re-enable search filters and add logging for initial query
- 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
2026-02-19 05:26:03 -06:00
Chad Curtis c6a2f7c960 Fix streaming by bypassing pool's eoseTimeout
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
2026-02-19 05:23:07 -06:00
Chad Curtis 3593fddc87 Fix streaming on search page
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
2026-02-19 05:15:56 -06:00
Chad Curtis 0f1bdd40fb Implement Ditto relay language and media search filters
- 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
2026-02-19 05:11:59 -06:00
shakespeare.diy 0768a38796 Filter out-of-sync relay responses to prevent pagination gaps
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>
2026-02-19 05:03:54 -06:00
shakespeare.diy e8993d8ff9 Fix pagination cursor skipping events during infinite scroll
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>
2026-02-19 05:00:01 -06:00
shakespeare.diy 3a02902df2 Fix post stats clearing during pagination
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>
2026-02-19 04:55:57 -06:00
shakespeare.diy e7277c6fee Revert to 5fd4ff4: Enable NIP-85 stats mode by default
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>
2026-02-19 04:50:50 -06:00
shakespeare.diy a3d24927d4 Add three-mode stats calculation: NIP-85 Only, Manual Only, and Both
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>
2026-02-19 04:50:07 -06:00
shakespeare.diy 5fd4ff4661 Enable NIP-85 stats mode by default
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>
2026-02-19 04:46:43 -06:00
Chad Curtis 9b1c48b41d Prevent notification dot from showing until settings fully load
- 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
2026-02-19 04:39:19 -06:00
Chad Curtis 9d5c8cf292 Fix notification dot flicker by waiting for settings to load
- 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
2026-02-19 04:38:26 -06:00
Chad Curtis 2947e6685f Fix notification dot flickering during background refetch
- 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
2026-02-19 04:36:58 -06:00
Chad Curtis 4a27abb021 Add NIP-85 only mode and improve settings UX
- 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
2026-02-19 04:35:18 -06:00
Chad Curtis 7ea17e9431 Optimize NIP-85 queries to avoid contention and reduce delays
- 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
2026-02-19 04:31:03 -06:00
Chad Curtis 8e4043fe5a Add NIP-85 stats to useEventStats and useBatchEventStats
- 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
2026-02-19 04:27:51 -06:00
Chad Curtis 8a4ea45035 Add NIP-85 stats integration with fallback to manual calculation
- 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
2026-02-19 04:25:30 -06:00
Chad Curtis 16e20df0cc Optimize feed loading performance from 8s to 1.3s
- Delay non-critical queries (notifications, sidebar, sync) to prioritize feed
- Wait for batch author/stats queries before rendering NoteCards
- Disable page 2 auto-fetch to reduce initial query contention
- Fix notification badge flicker and sidebar 'No trends' flash
- Sequential loading of authors then stats to avoid relay saturation

84% performance improvement. Requires NPool patch: comment out
Promise.allSettled in node_modules/@nostrify/nostrify/dist/NPool.js:101
2026-02-19 04:13:02 -06:00
shakespeare.diy 1d1286b331 Detect and reject Google's default favicon placeholder
- 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>
2026-02-19 02:42:19 -06:00
shakespeare.diy ff1dfd8ae4 Use CORS proxy for intelligent favicon discovery with Google fallback
- 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>
2026-02-19 02:41:05 -06:00
shakespeare.diy 3462771fea Add Google favicon service as final fallback
- 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>
2026-02-19 02:39:28 -06:00
shakespeare.diy 29ee4db037 Add intelligent favicon discovery by parsing HTML link tags
- 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>
2026-02-19 02:38:25 -06:00
shakespeare.diy 11e983563b Create shared DomainFavicon component for DRY favicon handling
- 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>
2026-02-19 02:36:12 -06:00
shakespeare.diy 36bb8b5be3 Load favicons directly from domain with transparent background
- 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>
2026-02-19 02:33:39 -06:00
shakespeare.diy b00263bddc Fix favicon loading by using Google service with validation
- 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>
2026-02-19 01:38:21 -06:00
shakespeare.diy 9f17ec1a92 Only show favicons that actually exist - fetch directly from domain
- 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>
2026-02-19 01:37:29 -06:00
shakespeare.diy 02014b3dc4 Fix favicon display issues: use DDG service and reduce background opacity
- 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>
2026-02-19 01:36:25 -06:00
shakespeare.diy 5f6a2a8984 Refactor NIP-05 favicon display to use DRY component
- 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>
2026-02-19 01:34:32 -06:00
shakespeare.diy 7d0b45f0f8 Add NIP-05 domain favicon display in posts
- 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>
2026-02-19 01:32:31 -06:00
shakespeare.diy e411116983 Remove redundant WalletPage component
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>
2026-02-19 01:28:28 -06:00
shakespeare.diy 507fbf3bb1 Update section headers to use mt-4 mb-5 instead of my-4
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>
2026-02-19 01:27:56 -06:00
shakespeare.diy 32d01c6af0 Fix feed skeleton loading states
- 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>
2026-02-19 01:25:17 -06:00