Commit Graph

327 Commits

Author SHA1 Message Date
shakespeare.diy 3e3d7e3dcf fix: parallelize author/stats prefetch and stop seeding empty author entries
Two bugs in the useFeed prefetch approach:

1. Empty author seeding was blocking mentions from resolving.
   We were calling setQueryData(['author', pk], {}) for any feed post
   author with no profile on our relay. If that same pubkey appeared
   as a @mention inside another post, useAuthor found the empty {} in
   cache, considered it fresh, and never queried — so the mention showed
   a generated name forever. Fix: only seed cache when we actually got a
   profile event back. Let useAuthor handle the no-profile case itself.

2. Author and stats prefetches were sequential (await author, then await
   stats) when they're completely independent. Now run in parallel with
   Promise.all — page load time roughly halved for the prefetch step.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 10:03:03 -06:00
shakespeare.diy 1dfe209690 fix: move author/stats prefetch inside useFeed queryFn to eliminate race
Previously the feed had two separate systems (batch hooks in Feed.tsx +
individual hooks in NoteCard) that raced each other. The batch ran as a
useQuery at the component level, but NoteCards mounted in the same render
cycle and fired their own per-card queries before the batch resolved.
This got worse with each page as both systems scaled up.

The fix: prefetch authors and stats *inside* useFeed's queryFn, after the
feed events are fetched but before the queryFn returns. By the time React
re-renders with the new page data, the ['author', pubkey] and
['event-stats', id] caches are already populated. NoteCard's individual
useAuthor and useEventStats hooks then resolve instantly from cache
without firing any extra network requests.

New page loads only fetch authors/stats for IDs not already in cache, so
page 1's data is never re-requested when page 2 loads.

- Remove useAuthors + useBatchEventStats calls from Feed.tsx entirely
- Export computePageStats from useTrending for use in useFeed
- useBatchEventStats / useAuthors still exist for non-feed use cases

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 09:58:16 -06:00
shakespeare.diy 0d8176834a fix: proper mobile action bar layout and pagination stats/author stability
Action bar (NoteCard):
- Left-aligned buttons with consistent ml-2 spacing between them
- More button pushed to the far right with ml-auto
- Stat counts use text-xs so they never blow out the row width
- No artificial max-width cap — works naturally on any screen size

Pagination stats/authors (useAuthors + useBatchEventStats):
- Both batch hooks now filter out already-cached IDs before building
  the query key and firing the request
- When page 2 loads, only genuinely new pubkeys/event IDs are fetched;
  page 1's data is untouched in cache
- The query key stabilises to an empty string once all items are cached,
  so no more cascading re-fetches as the feed grows
- useAuthor: return {} instead of throwing when no kind-0 event is found,
  eliminating the retry storm when many cards mount simultaneously

Also removes the usePageBatch hack introduced in the previous attempt.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 09:49:34 -06:00
shakespeare.diy f1778babb6 fix: mobile action bar layout + infinite scroll stats/author drop
- NoteCard: replace fixed gap-6 action bar with justify-between + max-w-xs
  on mobile so stats never overflow or wrap on narrow screens

- usePageBatch: new hook that batches author/stats prefetches per page
  instead of across the whole accumulated feed. The old approach used a
  single query key built from ALL loaded event IDs; adding page 2 changed
  the key, abandoned the old cache entry, and fired a fresh request for
  every item — causing stats and author data to visually disappear on
  pages 2 and 3. Now each page gets its own stable cache entry that is
  never invalidated by subsequent page loads.

- Feed: swap useAuthors + useBatchEventStats calls for usePageBatch

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 09:40:18 -06:00
shakespeare.diy e3b8a65fce Fix abort signal handling for nostrconnect to prevent immediate timeout
- Add optional abortSignal parameter to nostrconnect method
- Pass abort controller signal from LoginDialog to nostrconnect
- Properly handle AbortError when dialog is closed
- Prevent "signal has been aborted" error on dialog close
- Use custom abort controller instead of only relying on timeout signal

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 04:35:13 -06:00
shakespeare.diy 606f75417b Move remote signer option under More Options alongside secret key
- Change tab order: Secret Key first, Remote Signer second
- Remove "Use remote signer" standalone button
- Add clickable "More Options" button that expands collapsible
- Remote signer is now at the same level as secret key in the tabs
- Don't auto-generate nostrconnect session on dialog open

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 04:33:51 -06:00
shakespeare.diy f0728d1737 Implement remote signer login option with nostrconnect
- Add nostrconnect method to useLoginActions for NIP-46 client-initiated connections
- Add helper functions generateNostrConnectParams and generateNostrConnectURI
- Create QRCodeCanvas component wrapper for displaying QR codes
- Update LoginDialog to support remote signer login with QR code scanning
- Add "Use remote signer" button when extension is available
- Display QR code on desktop, "Open Signer App" button on mobile
- Support manual bunker URI input as fallback option
- Add connection retry on error
- Use existing useIsMobile hook for responsive behavior

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 04:31:51 -06:00
shakespeare.diy 3c4c84d255 Fix URL parsing with trailing punctuation and preserve whitespace formatting
- Strip trailing punctuation from URLs (.,;:!?)])  that are likely not part of the URL
- Fixes issue where (https://example.com) was parsed incorrectly
- Preserve newlines and formatting after embedded content
- Only collapse excessive whitespace (3+ newlines) instead of removing all whitespace
- Maintains proper text flow after link previews and embedded events

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 04:27:33 -06:00
shakespeare.diy 973064af99 Fix whitespace handling for naddr URLs with embedded events
- Preserve one newline after naddr-embed tokens when present
- Prevents text from flowing directly after URL without spacing
- Embedded event now properly displays with preserved formatting

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 04:25:38 -06:00
shakespeare.diy 724663f7dd Support mentions in all contexts including inside quotes and parentheses
- Removed lookahead that was preventing matches in certain contexts
- NIP-19 identifiers now render as mentions regardless of surrounding characters
- Maintains single @ display by consuming optional @ prefix

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 04:23:08 -06:00
shakespeare.diy d3d98a00bf Fix double @@ rendering for mentions and support mentions inside quotes
- Updated regex to optionally match @ before NIP-19 identifiers
- Removed word boundary requirement to allow mentions in quotes
- Mentions now render correctly with single @ symbol in all contexts

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 04:22:04 -06:00
shakespeare.diy 625dd91016 Adjust ComposeBox styling: increase avatar size and add text opacity
- Change avatar from size-11 to size-12 for better visual balance
- Increase textarea top padding from pt-2 to pt-2.5
- Add opacity-85 to textarea text for softer appearance

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 04:20:42 -06:00
shakespeare.diy bac9609b0d Right sidebars pt-5 → pt-4
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 04:12:04 -06:00
shakespeare.diy 26508210f7 Right sidebars pt-6 → pt-5
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 04:11:30 -06:00
shakespeare.diy e7821e0bc1 Remove Notifications title bar entirely, increase tab padding to py-5
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 04:09:16 -06:00
shakespeare.diy 6ebec34eae Make Follows/Global and All/Mentions tabs sticky on mobile (top-12)
Tab bars should stick below the mobile top bar (h-12 = top-12) on mobile
and at top-0 on desktop. These are navigation tabs, not page title headers.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 04:07:49 -06:00
shakespeare.diy fd1e6fb64a Hide Notifications title header on mobile, keep All/Mentions tabs
The "Notifications" header with bell icon is redundant with the bottom nav
on mobile, so it's now hidden below the sidebar breakpoint. The All/Mentions
tab bar remains visible as the top element on mobile.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 04:06:52 -06:00
shakespeare.diy 8532d5a45e Make mobile top bar slightly taller (h-10→h-12) and unstick page headers on mobile
- MobileTopBar: bump height from h-10 (40px) to h-12 (48px)
- Page headers (STICKY_HEADER_CLASS): only sticky on desktop (sidebar:sticky sidebar:top-0), scroll naturally on mobile
- Updated inline sticky classes in Feed.tsx and PostDetailPage.tsx to match

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 04:03:04 -06:00
shakespeare.diy 6d7375bb19 Scope overscroll-behavior to PullToRefresh component only
Remove global overscroll-behavior-y: contain from CSS. Instead, set it
on document.documentElement via JS when the PullToRefresh component
mounts and restore the previous value on unmount. This way other pages
keep the browser's native pull-to-refresh behavior.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 04:00:44 -06:00
shakespeare.diy e87ae03805 Fix pull-to-refresh hijacked by browser native gesture
- Add overscroll-behavior-y: contain on html/body to disable browser's
  native pull-to-refresh
- Switch from React synthetic touch events to native addEventListener
  with { passive: false } so e.preventDefault() actually blocks the
  browser gesture during pull-down
- Use a mirrored ref (currentPull) for synchronous reads inside native
  event handlers

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 03:57:53 -06:00
shakespeare.diy 69a05f690a Add pull-to-refresh on mobile feed
- Create PullToRefresh component with touch drag detection
- Rubber-band resistance effect for natural feel
- Spinner indicator appears below tabs when pulling down
- Triggers feed data refetch on release past threshold
- Only active on mobile (touch events + indicator hidden on sidebar breakpoint)

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 03:55:23 -06:00
shakespeare.diy 950b11f39e fix: skeleton load sparklines while activity data is fetching
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 03:52:14 -06:00
shakespeare.diy cef2693cdf feat: replace fake sparklines with real hashtag activity data
- Add useTagSparklines hook that queries kind-1 posts for each trending
  tag over the last 24 hours, buckets them into 10 time intervals, and
  returns actual post counts per bucket.
- All tags are fetched in a single relay query for efficiency.
- TrendSparkline component now accepts a `data: number[]` prop and
  renders the real activity curve instead of random noise.
- Remove all seeded PRNG code that was generating fake trend lines.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 03:51:16 -06:00
shakespeare.diy 9213da9d27 fix: make trend sparklines deterministic per tag name
Replace Math.random() with a seeded PRNG (mulberry32) keyed on the
hashtag name so the sparkline shape is always the same for a given tag
across page loads.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 03:48:08 -06:00
shakespeare.diy 883bf893cf Fix settings spasm by skipping NostrSync after local writes
- Add write timestamp guard to prevent NostrSync from overwriting
  settings we just changed locally
- 10 second cooldown window after any local settings write
- Eliminates back-and-forth between local state and stale encrypted data

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 03:44:06 -06:00
shakespeare.diy abf6be233f Fix settings glitch by updating cache in-place instead of refetching
- Replace invalidateQueries with setQueryData on mutation success
- Prevents refetch -> NostrSync -> updateConfig -> re-render loop
- Local settings change is now the only render, no secondary flash

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 03:42:32 -06:00
shakespeare.diy dd5021788f Publish encrypted settings in background without triggering re-renders
- Use nostr.event() directly instead of useNostrPublish mutation
- Sign and publish happens without React Query mutation lifecycle
- Prevents signer prompt from causing component re-renders and glitches
- Settings still save properly but UI stays stable during theme changes

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 03:40:01 -06:00
shakespeare.diy 41cdf72bab Remove all theme transitions - apply instantly
- Strip out View Transitions API and RAF complexity
- Just apply theme class change synchronously
- Debounce still prevents signer spam
- Let browser handle the paint naturally

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 03:38:49 -06:00
shakespeare.diy e862331f38 Debounce theme sync to only save after user stops changing
- Use debounced timer with 1 second delay
- Only triggers signer prompt after user settles on a theme
- Prevents repeated prompts when clicking through themes
- Reduces glitching by avoiding prompt during active theme switching

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 03:37:53 -06:00
shakespeare.diy 62d5625adb Use View Transitions API and double RAF for smooth theme changes
- Use native View Transitions API when available for smooth theme switch
- Fall back to manual transitions with double requestAnimationFrame
- Double RAF ensures theme change happens after browser paint cycle
- Prevents glitching even when React re-renders from signer prompt

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 03:37:18 -06:00
shakespeare.diy faef42481d Add smooth CSS transition for theme changes
- Apply 300ms transition to background and text colors during theme switch
- Prevents jarring flash when signer prompt appears
- Transition removed after completion to avoid affecting other animations

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 03:36:35 -06:00
shakespeare.diy e4cb3fd10e Increase theme sync delay to 2 seconds
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 03:35:25 -06:00
shakespeare.diy 1079799eb7 Increase theme sync delay to 500ms to prevent glitch
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 03:34:24 -06:00
shakespeare.diy c4f65a3dde Add 100ms delay before syncing theme to prevent glitch
- Delay encrypted settings update to let theme apply to DOM first
- Prevents visual glitch when signer prompt appears

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 03:33:14 -06:00
shakespeare.diy fa36aa9d94 Fix theme change glitch - make setTheme synchronous
- Remove async from setTheme to prevent UI glitches
- Fire-and-forget encrypted settings update
- Theme changes apply instantly without waiting for signer

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 03:32:44 -06:00
shakespeare.diy c0f6bd6bbb Sync theme changes to encrypted settings
- Update useTheme to save theme to encrypted storage when changed
- Theme now syncs across devices like other settings
- Prompts signer when changing theme while logged in

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 03:31:51 -06:00
shakespeare.diy f46e7facf1 Change encrypted settings stale time to 30 minutes
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 03:30:15 -06:00
shakespeare.diy 93e6848622 Add encrypted settings sync on login with stale-while-revalidate
- NostrSync now loads encrypted settings from Nostr on login
- Settings sync to local AppContext (theme, feedSettings, useAppRelays)
- Use 5-minute staleTime for fresh page loads to pick up changes
- Settings refetch automatically after 5 minutes on new page load
- No expensive real-time subscriptions - just periodic refresh

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 03:29:21 -06:00
shakespeare.diy 9c79578d7a Sync feed and relay settings to encrypted Nostr storage
- Update FeedSettingsForm to save to encrypted settings when toggling
- Update RelayListManager to sync app relay toggle to encrypted storage
- Feed preferences and relay toggle now prompt for signer when changed
- Settings sync across devices via NIP-78 encrypted events
- Local storage still used for immediate UI updates (dual-write pattern)

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 03:23:42 -06:00
shakespeare.diy 9400b0392a Refactor mute settings to always use encryption
- Remove silly public/private toggle - all mutes are now encrypted by default
- Update useMuteList to store everything in encrypted content field
- Remove isPrivate field from MuteListItem interface
- Simplify MuteSettings UI by removing encryption toggle
- Update muteHelpers to remove hasPrivate from summary
- Add useEncryptedSettings hook for unified app settings storage
- Integrate MuteSettings component into SettingsPage /settings/mutes tab
- Refactor useContentFilters to use unified encrypted settings
- All sensitive user preferences now encrypted with NIP-44

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 03:22:05 -06:00
shakespeare.diy 149a1cf6e5 Add comprehensive mute settings with NIP-51 and encrypted content filters
- Implement useMuteList hook for NIP-51 mute lists (kind 10000)
  - Support for muting users, hashtags, words, and threads
  - Public and encrypted private mute items using NIP-44
  - Automatic normalization of npub/note identifiers
  
- Implement useContentFilters hook for encrypted client-side filters (NIP-78)
  - Advanced filtering with custom rules (kind, content, tags)
  - Multiple operators (equals, contains, regex, etc.)
  - Stored as encrypted kind 30078 events
  
- Add MuteSettingsPage with full UI
  - Tabbed interface for mute lists and content filters
  - Add/remove mutes with public/private toggle
  - Create custom filters with multiple rules
  - Collapsible sections for organized viewing
  
- Add utility functions for applying mutes
  - isEventMuted() checks if event should be filtered
  - filterMutedEvents() applies mutes to event arrays
  - getMuteSummary() provides mute statistics
  
- Add /mutes route to AppRouter

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 03:16:53 -06:00
shakespeare.diy 3310c25e7f Move Mutes into Settings page as a tab
Mutes is now a tab in the Settings page (Settings > Mutes) instead of a
standalone route. Removed the /mutes route and PlaceholderPage import
from the router. The mutes tab shows a placeholder state for now until
the full mute list management UI is built.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 03:12:03 -06:00
shakespeare.diy 8a28742f11 Remove Vines from mobile bottom nav
Vines is now accessible through the mobile drawer alongside the other
custom kinds, so it no longer needs a dedicated bottom nav tab. Bottom
nav is now just Home, Notifications, Search.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 03:09:50 -06:00
shakespeare.diy 1e19ca255f Reorder mobile drawer to match sidebar; remove Mutes link
Drawer now shows extra kinds first, then Profile, Bookmarks, Settings —
matching the desktop sidebar order (minus Home/Notifications/Search
which live in the bottom nav). Mutes link removed from the drawer since
it belongs in Settings.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 03:09:11 -06:00
shakespeare.diy 416de22ff5 Add custom kind pages to mobile drawer navigation
Show enabled extra kinds (Vines, Polls, Treasures, Colors, Follow Packs)
in the mobile slide-out drawer under an "Other Stuff" section, matching
the desktop sidebar behavior. Items are driven by the same feedSettings
config so toggling them in settings updates both desktop and mobile nav.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 03:07:25 -06:00
shakespeare.diy 9918ca4dfe Simplify image blend to mix-blend-difference, remove custom CSS
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 03:02:54 -06:00
shakespeare.diy 01bdb9322d Replace intro art with custom mew_boy illustration, update blend logic
- Swapped to provided gray-on-white line art
- Light/pink: darken blend (gray lines show naturally)
- Dark/black: lighten + invert (lines become light on dark bg)

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 03:01:19 -06:00
shakespeare.diy c60768215d Theme-aware blend for intro art: darken on light/pink, lighten on dark/black
- Added .blend-art CSS utility that inverts + darkens on light backgrounds
  and lightens on dark backgrounds
- Works across all four themes (light, dark, black, pink)

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 02:58:25 -06:00
shakespeare.diy 31da678784 Update intro image: w-40, 80% opacity, lighten blend mode
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 02:54:56 -06:00
shakespeare.diy e379b03a7a Replace intro illustration with charming chibi-style Nintendo manual art
- Polished white line illustration with cute character and detailed icons
- Clapperboard, bar chart, treasure chest, palette, party popper floating above
- Clean confident linework that reads well at small sizes

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 02:47:59 -06:00