* fix: hide zap reply previews for missing or non-chat events
Only show reply previews for zaps when:
1. The replied-to event exists in the event store
2. The replied-to event is a chat kind (9, 9321, or 1311)
This prevents showing loading states or reply previews for
zaps replying to events we don't have or non-chat events.
* refactor: extract CHAT_KINDS constant and include zap receipts
- Create CHAT_KINDS constant in types/chat.ts with all chat event kinds
- Include kind 9735 (zap receipts) as a chat kind
- Update ChatViewer to use the new constant for validation
- Improves maintainability and makes it easier to update chat kinds
Chat kinds now include:
- 9: NIP-29 group chat messages
- 9321: NIP-61 nutzaps
- 1311: NIP-53 live chat messages
- 9735: NIP-57 zap receipts
---------
Co-authored-by: Claude <noreply@anthropic.com>
* feat: add copy chat ID button to chat header
Add a button next to the chat title that copies the chat identifier
to clipboard. The identifier can be used with the `chat` command to
reopen the same conversation.
- For NIP-29 groups: copies relay'group-id format
- For NIP-53 live activities: copies naddr encoding
The button shows a check icon for feedback when copied.
* refactor: simplify copy chat ID button styling
- Use CopyCheck icon instead of Check for consistency with CodeCopyButton
- Remove tooltip to reduce UI noise
- Keep hover state styling (muted to foreground)
---------
Co-authored-by: Claude <noreply@anthropic.com>
* fix: improve chat UX with better scroll and symmetric padding
- Add alignToBottom prop to Virtuoso for better last message visibility
- Add small footer to prevent last message from being hidden under scroll
- Make composer padding symmetric (py-1 instead of py-1 pb-0)
* fix: restore pb-0 on composer
---------
Co-authored-by: Claude <noreply@anthropic.com>
Add right-click/long-press context menu to chat messages with event interactions:
- Reply to message
- Copy message text
- Open event detail (opens in new window)
- Copy event ID (nevent/naddr with relay hints)
- View raw JSON
Implements the same EventMenu pattern used in feed renderers, providing
consistent UX across the app. No visible UI elements added - menu appears
only on right-click or long-press (mobile).
Components added:
- src/components/ui/context-menu.tsx - Radix UI context menu primitives
- src/components/chat/ChatMessageContextMenu.tsx - Chat-specific context menu
- Integrated into MessageItem in ChatViewer.tsx
Dependencies:
- Added @radix-ui/react-context-menu
Co-authored-by: Claude <noreply@anthropic.com>
- Add controlled state for tooltip open/close
- Enable tap/click to toggle tooltip on mobile devices
- Tooltip now shows conversation metadata on tap (icon, description, protocol type, status, host)
- Maintains existing hover behavior on desktop
- Improves mobile UX by making metadata accessible via tap
Co-authored-by: Claude <noreply@anthropic.com>
* Add Blossom blob storage integration
- Add blossom-client-sdk dependency for blob storage operations
- Create blossom.ts service with upload, list, check, mirror, delete primitives
- Add kind 10063 server list fetching and parsing
- Create blossom-parser.ts for command argument parsing with subcommands
- Add BLOSSOM command to man.ts with subcommands:
- servers: Show configured Blossom servers
- check: Check server health
- upload: Upload files to user's servers
- list: List blobs for a user
- mirror: Mirror blobs between servers
- delete: Delete blobs from servers
- Create BlossomViewer component with views for each subcommand
- Wire up BlossomViewer in WindowRenderer
- Add Blossom servers dropdown to ProfileViewer header
- Upload primitives can be called programmatically for use in other components
* Enhance Blossom viewer with server selection and blob details
- Add server selection checkboxes to upload view for choosing target servers
- Add BlobDetailView with media preview (image/video/audio) and metadata display
- Add 'blob' subcommand to view individual blob details
- Remove unused 'check' subcommand
* Add Blossom upload dialog with chat integration
- Create BlossomUploadDialog component with file picker, server selection, and preview
- Create useBlossomUpload hook for easy integration in any component
- Add insertText method to MentionEditor for programmatic text insertion
- Integrate upload button (paperclip icon) in chat composer
- Supports image, video, and audio uploads with drag-and-drop
* Add rich blob attachments with imeta tags for chat
- Add BlobAttachment TipTap extension with inline preview (thumbnail for images, icons for video/audio)
- Store full blob metadata (sha256, url, mimeType, size, server) in editor nodes
- Convert blob nodes to URLs in content with NIP-92 imeta tags when sending
- Add insertBlob method to MentionEditor for programmatic blob insertion
- Update NIP-29 and NIP-53 adapters to include imeta tags with blob metadata
- Pass blob attachments through entire send flow (editor -> ChatViewer -> adapter)
* Add fallback public Blossom servers for users without server list
- Add well-known public servers as fallbacks (blossom.primal.net, nostr.download, files.v0l.io)
- Use fallbacks when user has no kind 10063 server list configured
- Show "Public Servers" label with Globe icon when using fallbacks
- Inform user that no server list was found
- Select first fallback server by default (vs all user servers)
* Fix: Don't show fallback servers when not logged in
Blossom uploads require signed auth events, so users must be logged in.
The 'Account required' message is already shown in this case.
* Remove files.v0l.io from fallback servers
* Add rich renderer for kind 10063 Blossom server list
- Create BlossomServerListRenderer.tsx with feed and detail views
- Show user's configured Blossom servers with clickable links
- Clicking a server opens the Blossom window with server info
- Register renderers for kind 10063 (BUD-03)
- Fix lint error by renaming useFallbackServers to applyFallbackServers
* Add individual server view and NIP-05 support for blossom commands
- Add 'server' subcommand to view info about a specific Blossom server
- Update BlossomServerListRenderer to open server view on click
- Make blossom parser async to support NIP-05 resolution in 'list' command
- Add kind 10063 (Blossom Server List) to EVENT_KINDS constants with BUD-03 reference
- Update command examples with NIP-05 identifier support
* Add comprehensive tests for blossom-parser
- 34 test cases covering all subcommands (servers, server, upload, list, blob, mirror, delete)
- Tests for NIP-05 resolution, npub/nprofile decoding, $me alias
- Tests for error handling and input validation
- Tests for case insensitivity and command aliases (ls, view, rm)
---------
Co-authored-by: Claude <noreply@anthropic.com>
- Focus the message input when clicking reply button for immediate typing
- Use RichText component in reply preview to render mentions and emojis
Co-authored-by: Claude <noreply@anthropic.com>
* feat: make GroupListViewer mobile-friendly with Sheet sidebar
- Add Sheet component for mobile drawer behavior
- Add Separator component for UI dividers
- Add Sidebar component with mobile/desktop variants
- Update GroupListViewer to show sheet-based sidebar on mobile (<768px)
and resizable sidebar on desktop
- Mobile view includes toggle button in header and auto-closes on selection
* refactor: integrate sidebar toggle into ChatViewer header
- Add headerPrefix prop to ChatViewer for custom header content
- Pass sidebar toggle button via headerPrefix on mobile
- Remove duplicate mobile header from GroupListViewer
- Reduces vertical space usage by reusing ChatViewer's existing header
---------
Co-authored-by: Claude <noreply@anthropic.com>
* feat: add protocol-specific actions to chat adapters
Extends the chat adapter system with support for slash commands and
protocol-specific actions without parameters.
New features:
- ChatAction type system for defining simple commands
- Base adapter getActions() and executeAction() methods
- NIP-29 /join and /leave slash commands
- Slash command parser for detecting /commands
- ChatViewer integration with toast notifications
Example usage in NIP-29 groups:
/join - Request to join the group
/leave - Leave the group
The action system is extensible and can be enhanced with parameterized
actions (e.g., /kick <user>, /ban <user>) in future iterations.
Tests: All 804 tests passing
Build: Successful
Lint: No errors
* feat: add autocomplete for slash commands
Extends the chat system with autocomplete UI for slash commands, making
actions discoverable and easier to execute.
New features:
- SlashCommandSuggestionList component with keyboard navigation
- Slash command search integrated into MentionEditor
- Autocomplete popup shows command name and description
- Arrow keys for navigation, Enter to select, Escape to close
- Filters available commands based on typed text
When typing "/" in chat, users now see:
/join Request to join the group
/leave Leave the group
The autocomplete uses the same TipTap suggestion system as @mentions
and :emoji:, providing a consistent UX across all autocomplete features.
Tests: All 804 tests passing
Build: Successful
Lint: 1 warning fixed (unused parameter)
* feat: execute slash commands immediately on autocomplete selection
When selecting a slash command from the autocomplete menu, the action
now executes immediately and clears the input, providing a more
streamlined UX.
Changes:
- Added onCommandExecute prop to MentionEditor
- Modified slash command suggestion to call onCommandExecute on selection
- Clears editor content immediately after selection
- Added handleCommandExecute callback in ChatViewer
- Executes action and shows toast notifications
Before: Type "/" → select "/join" → press Enter → executes
After: Type "/" → select "/join" → executes immediately
This matches the expected behavior for command selection - when you
choose a command from autocomplete, you want to execute it, not just
insert it into the text field.
Tests: All 804 tests passing
Build: Successful
Lint: No new errors
* feat: make chat actions context-aware and filter by membership status
Actions are now intelligently filtered based on the user's membership in
the conversation, providing a cleaner and more intuitive UX.
Changes:
- Added GetActionsOptions type with conversation and activePubkey
- Updated base adapter getActions() to accept optional context
- Modified NIP-29 adapter to filter actions based on membership:
- /join: only shown when user is NOT a member/admin
- /leave: only shown when user IS a member
- Updated ChatViewer to pass conversation and user context to searchCommands
- Moved searchCommands callback after conversation is defined
This prevents showing irrelevant commands like "/join" when you're already
a member, or "/leave" when you haven't joined yet. The autocomplete menu
now only displays actions that are actually executable in the current context.
Implementation notes:
- NIP-29 uses getAllActions() fallback when context unavailable
- Membership determined by checking conversation.participants array
- Other protocols return empty array by default (no actions yet)
Tests: All 804 tests passing
Build: Successful
Lint: No new errors
---------
Co-authored-by: Claude <noreply@anthropic.com>
* ui: make chat composer more compact
- Reduce input and button height from 2.5rem to 1.75rem (h-7)
- Reduce padding from px-3 py-1.5 to px-2 py-1
- Use text-sm for consistent sizing with chat messages
- Make Send button smaller with text-xs and smaller icon
- Tighten gap between input and button
* ui: remove bottom padding from chat composer
---------
Co-authored-by: Claude <noreply@anthropic.com>
* feat: add NIP-61 nutzap support to NIP-29 groups
Fetch and render nutzap events (kind 9321) in NIP-29 relay groups
using the same visual styling as lightning zaps. Nutzaps are P2PK
locked Cashu token transfers defined in NIP-61.
- Add nutzap filter subscription in loadMessages
- Combine chat and nutzap observables with RxJS combineLatest
- Add nutzapToMessage helper to parse NIP-61 event structure
- Extract amount by summing proof amounts from proof tag JSON
- Add nutzapUnit metadata field for future multi-currency support
* fix: improve zap/nutzap rendering in chat
- Add mb-1 margin bottom to zap messages for spacing
- Show inline reply preview for zaps that target specific messages
- Fix nutzap amount extraction to handle multiple proof tags
- Extract replyTo from e-tag for nutzaps
- Pass nutzap event to RichText for custom emoji rendering
* fix: pass event only to RichText for proper emoji rendering
* refactor: consolidate NIP-29 chat and nutzap into single REQ
- Use single filter with kinds [9, 9000, 9001, 9321] instead of
separate subscriptions with combineLatest
- Enables proper pagination for "load older" with single page fetches
- Add rounded corners to zap gradient border for consistent rendering
* refactor: consolidate NIP-53 chat and zap into single REQ
- Use single filter with kinds [1311, 9735] instead of separate
subscriptions with combineLatest
- Enables proper pagination for "load older" with single page fetches
- Filter invalid zaps inline during event mapping
* feat: add load older messages support to chat adapters
- Implement loadMoreMessages in NIP-29 adapter using pool.request
- Implement loadMoreMessages in NIP-53 adapter using pool.request
- Add "Load older messages" button to ChatViewer header
- Use firstValueFrom + toArray to convert Observable to Promise
- Track loading state and hasMore for pagination UI
---------
Co-authored-by: Claude <noreply@anthropic.com>
* feat: add NIP-53 live activity chat adapter
Add support for joining live stream chat via naddr (kind 30311):
- Create Nip53Adapter with parseIdentifier, resolveConversation, loadMessages, sendMessage
- Show live activity status badge (LIVE/UPCOMING/ENDED) in chat header
- Display host name and stream metadata from the live activity event
- Support kind 1311 live chat messages with a-tag references
- Use relays from activity's relays tag or naddr relay hints
- Add tests for adapter identifier parsing and chat-parser integration
* ui: derive live chat participants from messages, icon-only status badge
- Derive participants list from unique pubkeys in chat messages for NIP-53
- Move status badge after title with hideLabel for compact icon-only display
* feat: show zaps in NIP-53 live chat with gradient border
- Fetch kind 9735 zaps with #a tag matching the live activity
- Combine zaps and chat messages in the timeline, sorted by timestamp
- Display zap messages with gradient border (yellow → orange → purple → cyan)
- Show zapper, amount, recipient, and optional comment
- Add "zap" message type with zapAmount and zapRecipient metadata
* fix: use RichText for zap comments and remove arrow in chat
- Use RichText with zap request event for zap comments (renders emoji tags)
- Remove the arrow (→) between zapper and recipient in zap messages
* refactor: simplify zap message rendering in chat
- Put timestamp right next to recipient (removed ml-auto)
- Use RichText with content prop and event for emoji resolution
- Inline simple expressions, remove unnecessary variables
- Follow codebase patterns from ZapCompactPreview
* docs: update chat command to include NIP-53 live activity
- Update synopsis to use generic <identifier>
- Add NIP-53 live activity chat to description
- Update option description to cover both protocols
- Add naddr example for live activity chat
- Add 'live' to seeAlso references
* fix: use host outbox relays for NIP-53 live chat events
Combine activity relays, naddr hints, and host's outbox relays when
subscribing to chat messages and zaps. This ensures events are fetched
from all relevant sources where they may be published.
* ui: show host first in members list, all relays in dropdown
- derivedParticipants now puts host first with 'host' role
- Other participants from messages follow as 'member'
- RelaysDropdown shows all NIP-53 liveActivity.relays
---------
Co-authored-by: Claude <noreply@anthropic.com>
* feat: add NIP-30 emoji autocompletion to editor
Implement emoji autocomplete triggered by `:` in the MentionEditor:
- EmojiSearchService: flexsearch-based indexing for emoji shortcodes
- useEmojiSearch hook: loads Unicode emojis + user's custom emoji (kind 10030/30030)
- EmojiSuggestionList: grid-based suggestion UI with keyboard nav
- Update MentionEditor with second Mention extension for emoji
- Serialize emoji as `:shortcode:` format with NIP-30 emoji tags
- Update chat adapters to include emoji tags in messages
Sources:
- Unicode: ~300 common emojis with shortcodes
- Custom: user's emoji list (kind 10030) and referenced sets (kind 30030)
- Context: emoji tags from events being replied to
* feat: add rich emoji preview in editor
Emoji inserted via the autocomplete now display as actual images/characters
instead of :shortcode: text:
- Custom emoji: renders as inline <img> with proper sizing
- Unicode emoji: renders as text with emoji font sizing
- Both show :shortcode: on hover via title attribute
CSS styles ensure proper vertical alignment with surrounding text.
* fix: store emoji url and source attributes in node schema
The TipTap Mention extension only defines `id` and `label` by default.
Added `addAttributes()` to EmojiMention extension to also store `url`
and `source` attributes, fixing emoji tags not being included in sent
messages.
* fix: improve emoji node rendering in editor
- Remove redundant renderLabel (nodeView handles display)
- Add renderText for proper clipboard behavior
- Make nodeView more robust with null checks
- Add fallback to shortcode if image fails to load
- Unicode emoji shows character, custom shows image
* fix: serialize unicode emoji as actual characters, not shortcodes
When sending messages:
- Unicode emoji (😄, 🔥) → outputs 😄, 🔥 (the actual character)
- Custom emoji (:pepe:) → outputs :pepe: with emoji tag for rendering
---------
Co-authored-by: Claude <noreply@anthropic.com>
* feat(chat): add Ctrl+Enter shortcut to send messages
Allows users to send messages using Ctrl+Enter (or Cmd+Enter on Mac)
in addition to the existing Enter key shortcut.
* fix(chat): disable autofocus on message editor
Prevents keyboard from popping up automatically on mobile devices.
---------
Co-authored-by: Claude <noreply@anthropic.com>
Implements rich text editing with profile mentions, NIP-29 system messages,
day markers, and naddr support for a more complete chat experience.
Editor Features:
- TipTap-based rich text editor with @mention autocomplete
- FlexSearch-powered profile search (case-insensitive)
- Converts mentions to nostr:npub URIs on submission
- Keyboard navigation (Arrow keys, Enter, Escape)
- Fixed Enter key and Send button submission
NIP-29 Chat Improvements:
- System messages for join/leave events (kinds 9000, 9001, 9021, 9022)
- Styled system messages aligned left with muted text
- Shows "joined" instead of "was added" for consistency
- Accepts kind 39000 naddr (group metadata addresses)
- Day markers between messages from different days
- Day markers use locale-aware formatting (short month, no year)
Components:
- src/components/editor/MentionEditor.tsx - TipTap editor with mention support
- src/components/editor/ProfileSuggestionList.tsx - Autocomplete dropdown
- src/services/profile-search.ts - FlexSearch service for profile indexing
- src/hooks/useProfileSearch.ts - React hook for profile search
Dependencies:
- @tiptap/react, @tiptap/starter-kit, @tiptap/extension-mention
- @tiptap/extension-placeholder, @tiptap/suggestion
- flexsearch@0.7.43, tippy.js@6.3.7
Tests:
- Added 6 new tests for naddr parsing in NIP-29 adapter
- All 710 tests passing
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Reply Functionality:
- Added Reply button to each message (visible on hover)
- Button appears in message header next to timestamp
- Uses Reply icon from lucide-react
- Clicking reply sets the replyTo state with message ID
- Reply preview shows in composer when replying
Active Account Requirements:
- Check for active account using accountManager.active$
- Only show composer if user has active account
- Only enable reply buttons if user has active account
- Show "Sign in to send messages" message when no active account
- Prevent sending messages without active account
UI Improvements:
- Reply button uses opacity transition on hover (0 → 100)
- Positioned with ml-auto to align right in header
- Reply button only visible on group hover for clean UI
- Consistent styling with muted-foreground color scheme
Benefits:
- Users can reply to specific messages inline
- Clear indication when authentication is required
- Prevents errors from attempting to send without account
- Professional chat UX with hover interactions
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Message Layout Improvements:
- Added proper padding to message items (px-3 py-2)
- Added flex-1 min-w-0 to message content container to allow shrinking
- Added break-words and overflow-hidden to prevent horizontal overflow
- Ensures long URLs and unbreakable text wrap properly
Message Composer Improvements:
- Added border-t and consistent padding (px-3 py-2)
- Added border and rounded-md to textarea for better visual separation
- Added min-w-0 to textarea to prevent overflow in flex layout
- Added flex-shrink-0 to Send button to prevent squishing
- Added mb-2 to reply preview for spacing
These changes ensure:
- Long messages and URLs wrap correctly without horizontal scroll
- Consistent spacing throughout the chat interface
- Proper flex behavior prevents layout breaks
- Professional chat UI appearance
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Improvements to kind 10009 (Public Chats) renderer:
Batch Loading:
- PublicChatsRenderer now batch-loads all group metadata (kind 39000) in a single subscription
- Much more efficient than individual subscriptions per group
- Filters out "_" groups from metadata fetch (they don't have metadata)
- Creates a metadata map to pass to each GroupLink
Special "_" Group Handling:
- "_" represents the unmanaged relay top-level group
- Displays the relay name instead of group ID
- Example: "pyramid.fiatjaf.com" instead of "_"
GroupLink Updates:
- Accepts optional metadata prop (pre-loaded from parent)
- No longer fetches metadata individually (more efficient)
- Extracts group name and icon from provided metadata
- Falls back to group ID if metadata not available
Performance:
- Single subscription for all groups vs N subscriptions
- Reduces relay traffic and improves rendering speed
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Core Architecture:
- Protocol adapter pattern for chat implementations
- Base adapter interface with protocol-specific implementations
- Auto-detection of protocol from identifier format
- Reactive message loading via EventStore observables
Protocol Implementations:
- NIP-C7 adapter: Simple chat (kind 9) with npub/nprofile support
- NIP-29 adapter: Relay-based groups with member roles and moderation
- Protocol-aware reply message loading with relay hints
- Proper NIP-29 members/admins fetching using #d tags
UI Components:
- ChatViewer: Main chat interface with virtualized message timeline
- ChatMessage: Message rendering with reply preview
- ReplyPreview: Auto-loading replied-to messages from relays
- MembersDropdown: Virtualized member list with role labels
- RelaysDropdown: Connection status for chat relays
- ChatComposer: Message input with send functionality
Command System:
- chat command with identifier parsing and auto-detection
- Support for npub, nprofile, NIP-05, and relay'group-id formats
- Integration with window system and dynamic titles
NIP-29 Specific:
- Fetch kind:39000 (metadata), kind:39001 (admins), kind:39002 (members)
- Extract roles from p tags: ["p", "<pubkey>", "<role1>", "<role2>"]
- Role normalization (admin, moderator, host, member)
- Single group relay connection management
Testing:
- Comprehensive chat parser tests
- Protocol adapter test structure
- All tests passing (704 tests)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>