This reverts the codebase back to the state at commit 5babdf6.
Reverted 2 commit(s):
- 64e1273: Race relays on first EOSE with grace timer for remaining
- cf193ab: Route reads to a single relay (round-robin) for fast EOSE resolution
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Replace the round-robin single-relay approach with proper racing:
- Queries are sent to ALL read relays simultaneously
- First relay to return EOSE starts a 1.5s grace timer
- Remaining relays that respond within the grace window have their
events merged into the result set
- If all relays finish before the timer, returns immediately
- Events are deduplicated via NSet (handles replaceable events correctly)
- Publishing and req() still delegate to the pool unchanged
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Previously reqRouter sent every query to all 3 read relays, meaning
NPool.query() had to wait for all relays to coordinate before resolving.
The slowest relay dictated the feed speed.
Now reads go to one relay at a time, rotating round-robin across the
configured read relays. Each query resolves as soon as that single relay
sends EOSE — no waiting on the other two. Publishing still fans out to
all write relays.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- 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 NoteCardSkeleton: restructured to match NoteCard's avatar + stacked
name/handle header with full-width content below (was incorrectly nested)
- VinesPage VineSkeleton: restructured to match vine card layout with
header, title, video placeholder, hashtags, and actions
- SearchPage PostSkeleton: aligned with Feed skeleton for consistency
- NoteCard: show skeleton for avatar and name while useAuthor is loading,
resolves to real data or fallback when relay responds/gives up
- RepostHeader: skeleton for reposter name while author loads
- ReplyContext: skeleton for reply-to name while author loads
- PostDetailPage: skeleton for author row in both main post and parent note
- EmbeddedNote: skeleton for author row in embedded note card
- EmbeddedNaddr: skeleton for author row in embedded naddr card
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>
- Moved volume toggle to the right of the play/pause button
- Progress bar fill and scrub dot now use bg-primary accent color
- Replaced Maximize icon with Expand (lucide) for fullscreen button
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Created a reusable VideoPlayer component with custom controls:
- Big centered play button overlay before first play
- Bottom control bar with gradient background that auto-hides during playback
- Play/pause toggle, seek bar with hover scrub dot, current/total time display
- Volume mute toggle and fullscreen button
- Controls reappear on mouse move and auto-hide after 2.5s
- Used in both NoteCard and PostDetailPage
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Extract video URLs (.mp4, .webm, .mov) from note content
- Parse all imeta tags into a URL-keyed map to get thumbnails for videos
- NoteCard renders videos with the same play/pause UI as vine events,
using imeta thumbnail as the poster image
- PostDetailPage also renders videos with the same player
- Videos are already stripped from NoteContent text by the MEDIA_URL_REGEX
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
The whitespace collapsing logic was stripping newlines before naddr-embed
tokens that have a URL, causing text and the URL to smash together.
Now only pure block embeds (no URL) trim preceding whitespace.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- NoteContent now shows the original URL as a clickable link above the
embedded naddr card when the naddr was extracted from a regular URL
- Replaced "Addressable event view coming soon" placeholder in NIP19Page
with a full AddrDetailPage that shows hero image, author, title,
description, image gallery, metadata fields, and tags
- AddrDetailPage extracts metadata from both event tags and JSON content
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>
Changed `lg:border-r` to `xl:border-r` on the main content area across
all 12 pages so the right border only appears when the right sidebar is
visible (at xl/1280px+).
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
The right sidebar was showing at lg (1024px) which made the three-column
layout too cramped. Changed from `hidden lg:flex` to `hidden xl:flex` so
the sidebar only appears when there's enough horizontal space.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
When viewing a reply's post detail page, the event being replied to
is now rendered above the main post with a thread connector line.
- Added getParentEventId() helper that extracts the parent event ID
from e-tags following NIP-10 conventions (marked and positional)
- Added ParentNote component with avatar, author info, content preview,
and a vertical thread connector line linking parent to child
- Parent note is clickable and navigates to its own post detail page
- Includes skeleton loading state while parent event is being fetched
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Add All/Mentions tab bar with underline indicator
- Like notifications: show heart icon + actor name + original post with full content, images, link previews, and action buttons
- Repost notifications: show repost icon + actor name + original post
- Zap notifications: show zap icon + actor name + amount + original post
- Mention notifications: show @ icon + actor name + full note card with avatar, nip05, reply context, content, and action buttons
- Fetch referenced events for likes/reposts/zaps to show the original post
- Include reply, repost, react, zap, and more action buttons on all notification posts
- Skeleton loading states matching the new layout
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Display name on first line (bold), @handle · timestamp on second
line (muted), matching the Ditto-style two-line author layout.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Restructure NoteCard so avatar+name is a header row, and all
content (text, images, videos, action buttons) renders at full
card width below it — no more indentation from the avatar column.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Move images and vine videos outside the avatar flex row so they
render at full card width instead of being indented by the avatar
gap. Text content stays inline with the avatar. Action buttons
are aligned with the text column.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Guard against undefined pubkey in replyTo[1] before passing to
ReplyContext, preventing genUserName from crashing on undefined input.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- NoteCard now handles both kind 1 (text notes) and kind 34236 (vines)
- Extracted NoteBody (text + images) and VineBody (title + video + hashtags)
as internal sub-components within NoteCard
- Same avatar, header, action buttons (reply, repost, react, zap, more),
more menu, and reply modal shared across all kinds
- Addressable events (kind 30000+) use naddr encoding for navigation
- Deleted standalone VineCard.tsx — no longer needed
- Updated SearchPage and VinesPage to use NoteCard for everything
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>
- Tabs (Posts/Trends/Accounts) at the very top, sticky
- Search input below tabs inside content area (icon on right)
- Search filters open by default with circled chevron toggle
- "Including replies" label + switch inline on same row
- Radio buttons laid out horizontally with flex-wrap
- "In the language:" label inline with dropdown
- Filter labels are bold (matching Ditto's white/bold style)
- Removed back arrow from header (Ditto doesn't have one)
- Accounts tab gets its own search input
- Posts stream live below filters
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>
- Changed min-h from 84px to 75px to match actual ComposeBox height
- Both headers now have identical 75px height
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Changed profile header padding from py-2 to py-3
- Now both headers have consistent 12px vertical padding
- Provides better visual alignment across pages
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Show NIP-05 username if available
- Otherwise show npub with monospace font
- Removed the previous behavior of showing both username and NIP-05 separated by a dot
- Made npub slightly smaller (11px) with monospace font for better readability
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>