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>
- Updated CTA text to "Follow accounts you care about on Mew"
- Reinforces brand identity in the call-to-action
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Reduced padding from py-6 to py-3 for more compact design
- Horizontal layout instead of vertical stacking
- Better CTA copy: "Follow accounts you care about"
- Smaller button size (sm) and simpler "Join" text
- Keeps gradient background but with better proportions
- More balanced and less overwhelming
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Center-aligned design for better visual balance
- Gradient background (primary/5 to primary/10) for subtle depth
- Larger, more prominent "Join Mew" button with shadow effects
- Engaging copy: "Join to unlock your personalized feed ✨"
- Increased padding and spacing for breathing room
- More visually appealing and less boring design
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Change to "Join Mew to follow accounts & personalize your feed"
- Add emphasis with bold "Join Mew" in foreground color
- Use medium font weight for better visual hierarchy
- Make Join button smaller (sm size) for better balance
- More engaging and visually distinct CTA
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Changed "Join Mew to personalize your feed and connect with others" to "Join to personalize your feed"
- More concise and direct call-to-action
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Show Follow/Global tabs only when user is logged in
- Display CTA banner with Join button when logged out
- CTA message: "Join Mew to personalize your feed and connect with others"
- Join button triggers login dialog in Feed component
- Added LoginDialog and SignupDialog to Feed component
- Improves UX with clear call-to-action instead of empty tab bar
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Import useCurrentUser hook to check login status
- Conditionally render tabs only when user is logged in
- Change default tab to 'global' for logged-out users
- Improves UX by showing only relevant UI elements based on auth state
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Added onSignupClick prop to LoginDialog interface
- Added "New here? Create account" link in LoginDialog
- Link appears after extension login button (if available) or with other options
- Matches borkstr's login dialog pattern for better UX
- Users can easily switch between login and signup flows
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Changed Join button onClick handler to open LoginDialog instead of SignupDialog
- Follows the same pattern as borkstr project where Join opens the login modal
- Users can still access signup through the login modal's "More Options" section
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Moved early return to after all hooks to comply with Rules of Hooks
- Changed handleLogout to async/await pattern for better error handling
- Prevents "Rendered more hooks than during the previous render" error
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Simplified code to inline filters in className
- Light theme: dark grey filter
- Pink theme: dark pink filter with medium brightness
- Dark/Black themes: no filter (original logo)
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Light theme: neutral dark grey filter (no color tint)
- Pink theme: vibrant dark pink filter with high saturation
- Improved contrast and readability on light backgrounds
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Apply dark blue-purple filter for light theme to make logo readable
- Apply dark pink filter for pink theme to match theme and improve visibility
- Replace opacity with color filters for better contrast
- Logo remains unfiltered in dark and black themes
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Show current theme name and icon in dropdown trigger button
- Fix light theme not working (update class removal from 'mew' to 'dark')
- Remove opacity from logo in pink theme (keep full visibility)
- Logo now only uses opacity in light theme (70%)
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Use opacity (60% pink, 70% light) for MewLogo instead of color filters
- Change Mew theme icon to Cat, Black theme icon to Moon
- Convert theme selector to proper dropdown menu with chevron indicator
- Add DropdownMenuLabel and separator for better UX
- Maintain theme icons: Cat (Mew), Sun (Light), Moon (Black), Heart (Pink)
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Rename "mew" theme to "dark" (Mew=dark, Light=light remain)
- Make pink theme more vibrant with increased saturation and adjusted hues
- Convert theme selector to dropdown menu with icons (Moon, Sun, Zap, Heart)
- Add CSS filters to MewLogo for pink and light themes with smooth transitions
- Keep Black and Pink themes as requested
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Add showEmojis prop to ReactionButton (default false). The emoji
indicators next to the count now only render when showEmojis is
true, which is only passed from PostDetailPage.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
The optimistic +1 delta was stacking on top of the refetched stats
(which already included the new reaction), causing the count to
jump by 2. Now we reset the delta to 0 on success so only the
real server count is displayed.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Create ReactionButton component with Popover-based emoji picker
- Clicking the reaction button opens the full emoji picker (EmojiPicker)
- Selecting an emoji publishes a NIP-25 kind 7 reaction event with proper e/p/k tags
- Optimistically increments the reaction count and shows the user's chosen emoji
- Reverts optimistic update on publish failure
- Invalidates event-stats and event-interactions queries on success
- Replace static heart/like buttons in NoteCard and PostDetailPage with ReactionButton
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Parse nostr:npub1… and nostr:nprofile1… references within embedded note
card text and render them as clickable @username mentions (resolving the
display name via useAuthor), matching the style used in NoteContent.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Embedded note cards now strip out nostr:nevent1... and nostr:note1...
references from the content preview text, preventing recursive embedding
and keeping the preview clean.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Create EmbeddedNote component with avatar, author name, timestamp,
truncated content preview, and optional image thumbnail
- Update NoteContent tokenizer to detect note1 and nevent1 references
and render them as embedded cards instead of plain text links
- Collapse whitespace around embedded note cards like link previews
- Clicking an embedded card navigates to the full post detail page
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>