- 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.
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.
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>
Nostr layer:
- fetchDMConversations: fetches all kind-4 events to/from the user
(both directions in parallel), deduplicates, newest-first
- fetchDMThread: fetches both directions for a specific conversation,
sorted oldest-first for display
- sendDM: NIP-04 encrypts content via NDK signer, publishes kind 4
- decryptDM: decrypts regardless of direction (ECDH shared secret is
symmetric — always pass the other party to signer.decrypt)
UI (DMView):
- Two-panel layout: conversation list (w-56) + active thread
- ConvRow: avatar, name, time; shows "🔒 encrypted" preview to avoid
decrypting the whole inbox on load
- MessageBubble: decrypts lazily on mount; mine right-aligned,
theirs left-aligned; shows "Could not decrypt" on failure
- ThreadPanel: loads full thread, auto-scrolls to bottom, re-fetches
after send; Ctrl+Enter to send
- NewConvInput: start a new conversation by pasting an npub1 or hex
pubkey; validates and resolves before opening thread
- Read-only (npub) accounts see a clear "nsec required" message
Navigation:
- ✉ messages added to sidebar nav
- openDM(pubkey) in UI store → navigates to dm view with pending pubkey
- ProfileView: "✉ message" button in action row opens DM thread
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>