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>
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>
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>
- 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>
- 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>
- 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>
- 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>
- 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>
- 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>
- 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>
- 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>
- 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>
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>
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>
- 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>
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>
- 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>
- 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>
- 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>
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>
- 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>
- 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>
- 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>
- 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>
- 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>
- 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>
- 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>
- 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>
- 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>
- 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>
- 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>
- 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>
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>
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>
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>
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>
- 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>
- 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>
- 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>