- React.memo on NoteCard and ArticleCard to skip re-renders
- Granular Zustand selectors in Feed.tsx and NoteCard.tsx
- Lazy-load 19 views with React.lazy + Suspense boundary
- Replace clickable divs with semantic <button> elements (NoteCard, SearchView)
- Add role="dialog", aria-modal, aria-labelledby on all modals (Login, Quote, Zap, Lightbox)
- Add role="presentation" on overlay backdrops (NoteCard menu, emoji pickers)
- Add aria-label to all icon-only buttons (close, nav arrows, context menu)
- Add aria-expanded to context menu toggle
- Add meaningful alt text to all 35+ images (avatars, covers, thumbnails, media)
- Add aria-label to form inputs (search, onboarding login)
Malformed profiles with non-string fields (e.g. nip05: {}) crashed
React's entire render tree in production. Add typeof guards and
profileName() utility across all components that render profile data.
Add ErrorBoundary in main.tsx to show crash details instead of blank screen.
Feed header, note card header, note actions, compose box, settings
theme grid, font size presets, media feed tabs, and profile header
buttons all now wrap gracefully when CSS zoom makes content overflow
the viewport. Also add AgentDocs fetch instruction to CLAUDE.md.
Add image support to InlineReplyBox (paste, file picker), paste-to-upload
in article editor, multi-select for article image toolbar. Fix emoji picker
opening off-screen (right-align), enlarge emoji/attach buttons, make note
card names clickable to open profile.
Global feed now uses a persistent live subscription (closeOnEose: false)
so new notes stream in real-time instead of requiring manual refresh.
Inspired by Wisp's streaming architecture.
Every fetchEvents call across the entire codebase now uses
fetchWithTimeout with groupable: false — prevents NDK from
batching/reusing stale subscriptions. This fixes Articles, DMs,
Notifications, Zaps, and Trending hanging indefinitely.
Also adds since filters on global (2h) and follow (24h) feeds
to ensure relay freshness, and fixes ArticleFeed re-fetch bug
where follows changes wiped latest tab results.
Reply boxes now open directly below the note you're replying to instead of
scrolling to a top-level composer. "Replying to" link scrolls to the parent
note when already visible in the thread instead of re-pushing the same thread.
- Fix mute button having no effect in Media Feed (missing filter)
- Fix notification toggle switches overlapping labels (sizing + shrink-0)
- Fix external links not opening in system browser (use Tauri opener plugin)
- Fix trending refresh showing no visual feedback (clear list on force refresh)
- Fix emoji reactions inaccessible behind WebKitGTK context menu (visible + button)
- Add emoji picker to compose box, inline reply, and thread reply
- New shared EmojiPicker component with categorized emoji groups
DMs now send via NIP-17 (kind 1059 gift-wrap) with self-copy for sent
messages. Receive supports both NIP-17 and legacy NIP-04 for backward
compat. Protocol indicator shown in conversation list and compose footer.
Note card context menu (⋯) now includes follow/unfollow option so users
can follow authors directly from the feed without visiting their profile.
Fix crash (black screen) when starting a new DM conversation — empty
event array caused undefined access on lastEvent.
Add inline video/audio players for direct media URLs, rich link cards
for YouTube (with thumbnails), Vimeo, Spotify, and Tidal. Fix video
clicks navigating to thread by splitting NoteContent into inline/media
modes. Fix published notes not appearing on Following tab.
Feed tab (Global/Following) moved from local state to UI store so it
survives thread/profile navigation. Fixed hardcoded "feed" in openThread
calls to pass currentView instead.
Four features shipped in this release:
- Feed reply context: replies show "↩ replying to @name" above the
note content; clicking fetches and opens the parent thread
- NIP-65 outbox model: fetchUserRelayList + publishRelayList +
fetchUserNotesNIP65 in client.ts; profile notes fetched via the
author's write relays; "Publish relay list to Nostr" button in
Settings (kind 10002)
- Notifications: new store (notifications.ts) + NotificationsView;
🔔 sidebar nav item with unread badge; DM nav item also shows
unread conversation count; badges clear on open/select
- Keyboard shortcuts: useKeyboardShortcuts hook + HelpModal;
n=compose, /=search, j/k=feed nav with ring highlight,
Esc=back, ?=help overlay
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Critical:
- NWC wallet now stored per-account (wrystr_nwc_<pubkey>); switching
accounts loads the correct wallet automatically
- Clear NDK signer before account switch to prevent race where old
account could sign outgoing events
- LoginModal: add "New account" tab to create a fresh keypair inline
(same flow as onboarding, with nsec copy + confirmation checkbox)
- ThreadView: add like + zap action row to the root note (was missing)
UX:
- Zap button now conditional on lud16/lud06 (NoteCard, ProfileView,
RootNote) — no zap button shown for profiles without Lightning
- Remove "200 notes" counter from sidebar footer
- AccountSwitcher: larger active account avatar (w-8), name more
prominent; sign-out/remove moved into dropdown only
Quick wins:
- AboutView: add GitHub Sponsors link
- ComposeBox: paste image from clipboard → uploads via nostr.build,
inserts URL at cursor with "uploading image…" status
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- fetchZapCount(eventId): fetches kind 9735 receipts for an event,
parses millisat amounts from embedded zap request description tags,
returns { count, totalSats }
- useZapCount hook: session-cached, same pattern as useReactionCount
- NoteCard: zap button shows "⚡ N sats" when total > 0, falls back
to "⚡ zap" when no zaps yet; stats row shown for logged-out users
displaying ♥ and ⚡ counts when non-zero
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- publishRepost: kind 6 event with stringified original event as content
and ["e", id, "", "mention"] + ["p", pubkey] tags
- publishQuote: kind 1 note with user's text + appended nostr:nevent1...
reference and ["q", id] + ["p", pubkey] tags
- QuoteModal: compose modal with live quoted-note preview (avatar, name,
truncated content); Ctrl+Enter to post, Escape to close
- NoteCard: "repost" (one-click, shows "reposted ✓") and "quote" (opens
QuoteModal) added to the actions row alongside reply/like/zap
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- fetchMuteList / publishMuteList in nostr client (kind 10000)
- mute store: mutedPubkeys persisted to localStorage + synced to relay;
mute/unmute publish kind 10000 best-effort; fetchMuteList merges relay
list with local mutes on login
- fetchMuteList called after every login (nsec + pubkey)
- Feed: muted pubkeys filtered from both Global and Following tabs
- NoteCard: ⋯ context menu (appears on hover, hidden for own notes)
with mute / unmute action; backdrop click closes menu
- ProfileView: mute / unmute button in the action row (next to follow)
- SettingsView: MuteSection lists muted accounts with name + avatar;
hover to reveal unmute button; hidden when mute list is empty
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ComposeBox, action row (reply/like/zap), and write article button
all gated on getNDK().signer in addition to loggedIn
- Prevents read-only (npub-only) users from seeing actions they can't use
- Found during browser testing of the login path
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- fetchReactionCount (kind 7 #e filter) in nostr lib
- useReactionCount hook with module-level cache to avoid refetching
- NoteCard shows count next to like button; increments optimistically on like
- Falls back to "like"/"liked" text when count is zero or still loading
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Click note content to open thread view
- ThreadView shows root note, reply composer, and replies
- fetchReplies added to nostr lib (kind 1 #e filter)
- UI store gains openThread, goBack, previousView for navigation history
- Profile back button now returns to previous view correctly
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Following tab in feed header (visible when logged in)
- Fetches kind 1 notes from followed pubkeys via NDK
- fetchFollows on login using NDK user.follows()
- fetchFollowFeed added to nostr lib
- Liked note IDs persisted in localStorage so likes survive refresh
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ProfileView shows avatar, bio, nip05, website, recent notes
- Clicking any name or avatar navigates to their profile
- Add fetchUserNotes to nostr lib (kind 1 by author)
- Add openProfile action + selectedPubkey to UI store
- Back button returns to feed
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Tailwind CSS + Zustand + NDK installed and configured
- Sidebar with feed/relays/settings navigation
- Global feed view with live notes from relays
- Profile fetching with caching and deduplication
- Relay connection with timeout handling
- Note cards with avatar, name, timestamp, content
- Dark theme, monospace, no-slop UI
- Devtools enabled for debugging