- Add `enabled` parameter to `useTrendingTags` and `useLatestAccounts` hooks
- RightSidebar now detects xl breakpoint visibility with matchMedia
- Sidebar queries only fire after the feed finishes its initial fetch
- On smaller screens where the sidebar is hidden, queries are skipped entirely
- Gives the feed full relay bandwidth priority on page load
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Rewrite useFeed to use useInfiniteQuery with timestamp-based pagination
- Reduce page size from 60/40 to 30 for both follows and global tabs
- Add intersection observer sentinel in Feed component for auto-loading
- Deduplicate feed items across pages
- Show spinner while fetching next page
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Feed.tsx: initialize activeTab to 'follows' when user is logged in, 'global' otherwise
- useFeed.ts: filter out events with created_at > now in both follows and global feeds, including embedded repost content and fetched originals
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Added useAddrEvent hook for fetching addressable events by kind+pubkey+d-tag
- Created EmbeddedNaddr component that displays title, description, image, and author
from addressable events (extracting metadata from tags and JSON content)
- Updated NoteContent to detect naddr1 identifiers in regular URLs (e.g.
bitpopart.com/art/naddr1...) and render them as Nostr embeds
- Added naddr1 to the nostr: URI regex so nostr:naddr1... is also handled
- Added naddr-embed token type with proper whitespace collapsing
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Updated useFeed hook to query kinds [1, 6] for the follows tab
- Reposts are resolved from embedded content JSON or fetched by event ID
- Added FeedItem type with repostedBy and sortTimestamp fields
- Deduplication prefers direct posts over reposts of the same event
- NoteCard now shows a "reposted by" header with the reposter's name
- Feed renders repost items with unique keys
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Created VinesPage with streaming feed of kind 34236 events
- Created VineCard component: renders video with thumbnail, play overlay,
author header, title, and hashtag links
- Updated useStreamPosts: 'vines' filter switches to kind 34236 query
(restarts stream since it's a different kind)
- Search page renders VineCard for kind 34236 events, NoteCard for kind 1
- Updated AppRouter: /vines route now uses VinesPage instead of placeholder
- Created useStreamVines hook for dedicated Vines page streaming
- Validates vine events: requires d tag, imeta with url
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Stream effect only depends on [nostr, query] — filter toggles don't
restart the stream or re-fetch from the relay
- Store all raw events in allEvents state (unfiltered)
- Apply includeReplies/mediaType filters via useMemo on the raw events
- Toggling a filter instantly shows/hides posts from the existing set
instead of fetching a new batch and getting "no posts"
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Added options.includeReplies and options.mediaType to effect deps
- Changing a radio button or toggle now re-fetches and re-streams
- Removed refs that were preventing filter changes from taking effect
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Call nostr.relay('wss://relay.damus.io').req() directly on a single relay
instead of passing relays option to pool.req()
- Use refs for filter options to avoid restarting the stream on filter changes
- Minimal effect deps: only [nostr, query] trigger stream restart
- Filter changes apply via optionsRef without killing the subscription
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
The debug logs confirmed the stream IS working correctly:
- req() subscription opens successfully
- EOSE is received and subscription stays open
- New events arrive after EOSE
- Cleanup only happens when the preview environment reloads (replaceDocument)
Removed eoseTimeout from NPool in previous commit which was the
main fix for subscription lifecycle issues.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Pass `relays` array directly to nostr.req() and nostr.query()
to bypass pool's reqRouter/eoseTimeout that was killing streams
- This is the pattern clawchat uses successfully for live subscriptions
- Default stream uses multiple relays, NIP-50 search uses relay.ditto.pub only
- Filter out future-dated events (created_at > now)
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Always use relay.ditto.pub directly for req() streaming
(NPool's eoseTimeout: 200 was killing subscriptions prematurely)
- Filter out events with created_at in the future
- Matches clawchat pattern: fetchInitial + streamNew in parallel
- Both query() and req() go to same single relay
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Match clawchat's proven streaming pattern
- fetchInitial() and streamNew() run in parallel, not sequentially
- req() uses limit: 100 (not limit: 0 which was wrong)
- Added isSubscribed guard for safe state updates
- Proper AbortError handling on both fetch and stream paths
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Rewrote useStreamPosts to use Nostrify's correct req() API which
returns AsyncIterable, not a subscription object with .close()
- Uses AbortController to cancel streams on cleanup (fixes sub.close error)
- Streams posts by default with no search query (global kind:1 feed)
- When search query is provided, uses NIP-50 search on relay.ditto.pub
- Properly handles EOSE, CLOSED, and EVENT messages from the stream
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Removed broken sidebar-inside-main-column layout that squished content
- Filters now collapse/expand above the post results (matching Ditto's pattern)
- Smooth animated expand/collapse with chevron indicator
- Simplified empty states (plain text, no cards)
- Cleaned up unused useSearchPosts.ts (replaced by useStreamPosts)
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Added Posts, Trends, and Accounts tabs
- Implemented sidebar search filters for Posts tab (replies, media type, language)
- Created useStreamPosts hook for real-time streaming subscriptions
- Search uses relay.ditto.pub for NIP-50 search functionality
- Posts stream in real-time with REQ subscription instead of static queries
- Layout matches Ditto design with filters in left sidebar on desktop
- All tabs functional with proper empty states
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Spacing:
- Reduce link preview margins from my-2 to mt-1.5 mb-0.5
- Collapse whitespace on text tokens adjacent to block-level tokens
(link-preview, youtube-embed) so newlines from the original content
don't stack with the card's own spacing
- Filter out empty text tokens after trimming
Bot headers:
- Send proper User-Agent header ("Mew/1.0 Link Preview Bot") when
fetching OG data so dynamic sites (SPAs, Twitter, etc.) return
server-rendered HTML with OG meta tags instead of a JS shell
- Accept application/xhtml+xml in addition to text/html
YouTube embeds:
- Detect YouTube URLs (youtube.com/watch, youtu.be, /embed/, /shorts/)
and extract video ID
- New YouTubeEmbed component renders a privacy-enhanced iframe player
(youtube-nocookie.com) with proper 16:9 aspect ratio
- YouTube links render as playable inline embeds instead of OG cards
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Move LinkPreview rendering from a fixed position below the post into
NoteContent itself, so previews appear exactly where the URL was in
the original text.
- Rewrite NoteContent to use a token-based architecture: useMemo
produces typed tokens (text, link-preview, mention, nostr-link,
hashtag), then the render phase maps tokens to components
- Non-media URLs produce a `link-preview` token that renders an
inline <LinkPreview> card at that position in the content flow
- Remove separate LinkPreview blocks from NoteCard and PostDetailPage
since previews are now handled by NoteContent
- Remove unused extractPreviewUrl helper from useLinkPreview hook
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Add useLinkPreview hook that fetches Open Graph metadata (title,
description, image, favicon, site name) via CORS proxy with 1hr
stale time caching
- Add extractPreviewUrl helper to find the first non-media URL in
note content
- Add LinkPreview component with a polished card UI showing OG image,
title, description, domain favicon, and external link indicator
- Strip all URLs (both media and non-media) from NoteContent inline
text — media URLs are rendered as image embeds, non-media URLs are
rendered as LinkPreview cards
- Integrate LinkPreview into NoteCard and PostDetailPage between
image attachments and action buttons
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Create useSearchProfiles hook that queries wss://relay.ditto.pub
with NIP-50 search filter for kind 0 (profile) events
- Create ProfileSearchDropdown component with debounced input,
keyboard navigation, and animated dropdown of matching profiles
- Update SearchPage to use the new profile autocomplete
- Update LeftSidebar search bar to use the profile autocomplete
- Selecting a profile navigates to their npub profile page
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
The query already had limit: 5 at the relay level, but relays may
return more results than requested. Added .slice(0, 5) after sorting
to guarantee only the 5 most recent accounts are shown.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Created useEventInteractions hook that fetches kind 6 (reposts),
kind 7 (reactions), and kind 9735 (zap receipts) for a given event ID
- Created InteractionsModal component with three tabs:
- Reposts: shows who reposted with avatar, name, and timestamp
- Reactions: groups reactions by emoji, showing each user under the
emoji header with counts
- Zaps: shows who zapped with amount badges (sorted by largest first),
zap messages, and a total sats summary at top
- Stats row text (e.g. "2 Reposts", "1 👍", "500 sats") is now clickable
and opens the modal to the relevant tab
- Action buttons (Repost, Reactions, Zaps) in the post detail view also
open the modal to their respective tabs
- All user rows link to the user's profile page
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Map "+" and empty reactions to 👍 emoji so they appear in the stats row
- Remove border-top from stats row and date-only row to match Ditto
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Create PostDetailPage with expanded post view showing author info,
full content, images, stats (reposts, likes, zap amount), full date,
action buttons, and inline reply composer
- Create useEvent hook for fetching single events by hex ID
- Create useReplies hook for fetching kind:1 replies to an event
- Update NIP19Page to render PostDetailPage for note1/nevent1 identifiers
- Make NoteCard clickable to navigate to post detail via nevent1 URL
- Fix NoteMoreMenu to use React Router navigate instead of window.location.href
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Fixed early return using old `zaps` key instead of `zapAmount`
- Added robust amount extraction with 3 fallback strategies:
1. Top-level `amount` tag on the zap receipt
2. `amount` tag inside the embedded zap request (description tag)
3. Parsing the bolt11 invoice string
- This ensures zap amounts display even when relays omit the amount tag
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- useEventStats now sums the `amount` tag (millisatoshis) from kind 9735
zap receipts and returns `zapAmount` in sats instead of a zap count
- NoteCard displays the total with compact formatting (e.g. 1.2K, 3.5M)
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Created useBookmarks hook for NIP-51 kind 10003 bookmark list management
(query, add, remove bookmarks)
- Created BookmarksPage with login prompt, loading skeletons, and empty state
- Replaced "More" nav item with "Bookmarks" in LeftSidebar
- Added bookmark toggle button to NoteCard action bar (replaces three-dot menu)
- Removed /more route from AppRouter
- Bookmarks are stored per NIP-51 as replaceable kind 10003 events
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>