Commit Graph

310 Commits

Author SHA1 Message Date
Alejandro
72eca99c2e Add custom parsing for relay links (#102)
* feat: Add relay link parsing and rendering to rich text

Implements custom parsing for relay URLs (wss:// and ws://) in text content,
automatically converting them to clickable links that open the relay viewer.

Changes:
- Add relay-transformer.ts with pattern matching for relay URLs
- Create Relay.tsx component for rendering relay links inline
- Register relay transformer in RichText component pipeline
- Add comprehensive test suite (26 tests) covering all URL formats

Supported formats:
- wss:// and ws:// protocols
- Domains, subdomains, and IP addresses
- Custom ports, paths, query parameters
- Multiple relay URLs in single message

All tests passing (864/864). No breaking changes.

* refactor: Update relay link styling to match other inline links

- Use muted/underline styling consistent with NIP references
- Remove icons and show only relay name (formatted)
- Display full URL in tooltip
- Match text size with surrounding content
- Simplify component by not using RelayLink wrapper

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-15 11:33:27 +01:00
Alejandro
64c181dd87 Improve chat UX with scroll and padding fixes (#99)
* 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>
2026-01-14 21:25:03 +01:00
Alejandro
6541e06b62 feat: disable pointer events on group message preview (#98)
Add pointer-events-none to the message preview div in GroupListViewer
to ensure clicks always pass through to the parent group item's onClick
handler. This prevents issues where clicks on the preview text (UserName
or RichText components) might not trigger group selection.

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-14 21:04:09 +01:00
Alejandro
f464c68bde feat: theme selector (#95)
* feat: Add reusable theme system with Plan 9 proof of concept

Implement a comprehensive theme system that:
- Defines typed Theme interface with colors, syntax highlighting, scrollbar, and gradient variables
- Creates ThemeProvider with React context for runtime theme switching
- Persists theme selection to localStorage
- Includes 3 built-in themes: dark (default), light, and plan9

Theme structure supports:
- Core UI colors (background, foreground, primary, secondary, accent, etc.)
- Status colors (success, warning, info) replacing hardcoded Tailwind colors
- Syntax highlighting variables for code blocks
- Diff highlighting colors (inserted, deleted, meta)
- Scrollbar styling variables
- Gradient colors for branding

Technical changes:
- Update CSS to use new theme variables throughout
- Update prism-theme.css to use syntax variables instead of hardcoded values
- Remove chart colors (unused)
- Add success/warning/info to tailwind.config.js
- Wire up ThemeProvider in main.tsx

For Nostr publishing (future):
- d tag: "grimoire-theme"
- name tag: theme display name

* feat: Add theme selector to user menu, remove configurable border radius

- Remove border radius from theme configuration (borders are always square)
- Add theme selector dropdown to user menu (available to all users)
- Theme selector shows active theme indicator
- Theme selection persists via localStorage

* fix: Improve theme contrast and persistence

- Fix theme persistence: properly check localStorage before using default
- Plan9: make blue subtler (reduce saturation), darken gradient colors
  for better contrast on pale yellow background
- Light theme: improve contrast with darker muted foreground and borders
- Change theme selector from flat list to dropdown submenu

* fix: Replace Plan9 yellow accent with purple, add zap/live theme colors

- Replace Plan9's bright yellow accent with purple (good contrast on pale yellow)
- Add zap and live colors to theme system (used by ZapReceiptRenderer, StatusBadge)
- Make light theme gradient orange darker for better contrast
- Update ZapReceiptRenderer to use theme zap color instead of hardcoded yellow-500
- Update StatusBadge to use theme live color instead of hardcoded red-600
- Add CSS variables and Tailwind utilities for zap/live colors

* fix: Make gradient orange darker, theme status colors

- Make gradient orange darker in light and plan9 themes for better contrast
- Make req viewer status colors themeable:
  - loading/connecting → text-warning
  - live/receiving → text-success
  - error/failed → text-destructive
  - eose → text-info
- Update relay status icons to use theme colors
- Update tests to expect theme color classes

* fix: Use themeable zap color for active user names

- Replace hardcoded text-orange-400 with text-zap in UserName component
- Replace hardcoded text-orange-400 with text-zap in SpellRenderer ($me placeholder)
- Now uses dark amber/gold with proper contrast on light/plan9 themes

* feat: Add highlight theme color for active user display

Add dedicated 'highlight' color to theme system for displaying the
logged-in user's name, replacing the use of 'zap' color which felt
semantically incorrect. The highlight color is optimized for contrast
on each theme's background.

- Add highlight to ThemeColors interface and apply.ts
- Add --highlight CSS variable to index.css (light and dark)
- Add highlight to tailwind.config.js
- Configure appropriate highlight values for dark, light, and plan9 themes
- Update UserName.tsx to use text-highlight for active account
- Update SpellRenderer.tsx MePlaceholder to use text-highlight

* fix: Restore original orange-400 highlight color for dark theme

Update dark theme highlight to match original text-orange-400 color
(27 96% 61%) for backward compatibility with existing appearance.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-14 19:24:37 +01:00
Alejandro
5fa2a1c9b8 feat: Add context menu for chat message interactions (#94)
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>
2026-01-14 18:09:19 +01:00
Alejandro
59fdfc5611 Add route for command results in popup window (#93)
* feat: Add pop-out command route for standalone window rendering

Add a new /run route that allows windows to be opened in separate
browser windows/tabs without affecting the main workspace layout.

Changes:
- Add RunCommandPage component for /run?cmd=<command> route
- Add Pop Out button to WindowToolbar (ExternalLink icon)
- Parse command from URL query parameter and render result
- Construct minimal WindowInstance for rendering
- Display command string in header with clean minimal UI

This enables users to pop out any window into a separate browser
context while maintaining the main workspace layout, useful for
multi-monitor setups or keeping reference windows visible.

* refactor: Remove header from pop-out command page

Simplify RunCommandPage to only show the window renderer without
any additional UI chrome. This provides a cleaner, more focused
experience for popped-out windows.

* refactor: Move pop-out action to window menu dropdown

Move the pop-out button from a standalone icon to the three-dot
menu dropdown to reduce toolbar clutter. The menu now always
appears since pop-out is always available.

* feat: Add AppShell header to pop-out command page

Wrap RunCommandPage with AppShell (hideBottomBar) to show the header
with user menu and command launcher, matching the behavior of NIP-19
preview pages.

When a command is launched from the /run page, it navigates to the
main dashboard (/) where the window system exists.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-14 17:30:56 +01:00
Alejandro
16764e1aca Display user's blossom servers in menu (#90)
* feat: Display blossom servers in user menu with caching

Implements caching and display of user's blossom server lists (kind 10063) in the user menu dropdown.

**Key Changes:**

1. **Database Schema (db.ts)**:
   - Added `CachedBlossomServerList` interface
   - Added `blossomServers` table to Dexie v15
   - Indexed by pubkey and updatedAt for efficient querying

2. **Caching Service (blossom-server-cache.ts)**:
   - Dual-layer caching: LRU memory cache (100 entries) + Dexie persistent cache
   - 24-hour TTL to reduce network requests
   - Auto-caches kind:10063 events from EventStore
   - Mirrors relay-list-cache pattern for consistency

3. **Sync Hooks**:
   - `useBlossomServerCacheSync`: Subscribes to EventStore for auto-caching kind:10063 events
   - `useAccountSync`: Extended to fetch and watch active account's blossom servers

4. **State Management**:
   - Added `blossomServers` field to `activeAccount` in GrimoireState
   - Created `setActiveAccountBlossomServers` pure function and callback
   - Reactive updates when user's server list changes

5. **User Menu UI (user-menu.tsx)**:
   - Added blossom servers section with HardDrive icon
   - Shows server count badge (e.g., "3 servers")
   - Clickable links open servers in new tabs
   - Displays only when user has configured servers

**Architecture:**
- Follows existing relay list caching pattern for consistency
- Reactive: UI auto-updates when kind:10063 events arrive
- Incremental sync: Fetches on login, caches for 24h
- Performance: Memory cache for <1ms lookups, Dexie for persistence

**Testing:**
- Build: ✓ No TypeScript errors
- Tests: ✓ All 838 tests passing

* feat: Open blossom server file lists directly from menus

**User Menu & Profile Viewer Improvements:**

1. **Enhanced Click Behavior**:
   - Clicking a blossom server now opens the file list for that server
   - Shows blobs uploaded by the user (user menu) or profile owner (profile viewer)
   - Pre-selects the clicked server in the dropdown

2. **UX Improvements**:
   - Removed server count from user menu label (cleaner UI)
   - Added `cursor-crosshair` to blossom server items (consistent with other clickable items)
   - Removed external link icon (not opening external URL anymore)

3. **Technical Changes**:
   - Updated `ListBlobsView` to accept optional `serverUrl` prop for pre-selection
   - User menu: Opens `blossom list` with `serverUrl` for active user
   - Profile viewer: Opens `blossom list` with both `pubkey` and `serverUrl`

**Flow:**
- User menu → Click server → Opens files for active user on that server
- Profile viewer → Click server → Opens files for viewed user on that server

* fix: Properly fetch blossom servers for any profile view

**Problem:**
Blossom servers were only visible for the logged-in user's profile,
not for other users' profiles being viewed.

**Solution:**
Enhanced ProfileViewer blossom server fetching with multi-layer approach:

1. **Cache-first loading**: Check blossomServerCache for instant display
2. **EventStore check**: Use existing cached event if available
3. **Reactive subscription**: Subscribe to EventStore for real-time updates
4. **Network fetch**: Use addressLoader to fetch latest from relays
5. **Auto-caching**: Update cache when new events arrive

**Benefits:**
- Blossom servers now display for ANY user's profile
- Instant display from cache (< 1ms)
- Reactive updates when data changes
- Proper cache hydration for future visits
- Consistent with relay list fetching pattern

**Technical:**
- Imported and integrated blossomServerCache service
- Added cache check before network fetch
- Separated EventStore subscription from network fetch
- Added cache updates on event arrival

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-14 14:52:16 +01:00
Alejandro
998944fdf7 feat: Add mobile tap support for chat name tooltip (#82)
- 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>
2026-01-14 09:49:15 +01:00
Alejandro
3117aea34f Update profile example domain name (#86)
* Update profile example from verbiricha@habla.news to fiatjaf.com

* Update remaining verbiricha@habla.news examples to fiatjaf.com in man.ts

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-14 09:47:56 +01:00
Alejandro
7036fb5539 Fix chat composer placeholder and text alignment (#84)
* Fix chat composer placeholder and text alignment

- Adjusted .ProseMirror min-height from 2rem to 1.25rem to match container
- Added flexbox layout to .ProseMirror for proper vertical centering
- Removed float:left and height:0 from placeholder causing misalignment
- Moved padding from editor props to wrapper div
- Updated EditorContent to use flex items-center for alignment

Resolves vertical alignment issues in chat composer input field.

* Fix cursor placement in chat composer placeholder

- Changed from flexbox to line-height for vertical centering
- Removed flex from .ProseMirror to fix cursor positioning
- Set line-height: 1.25rem to match min-height for proper alignment
- Removed flex items-center from EditorContent className

This ensures the cursor appears at the correct position when focusing
the input field, rather than after the placeholder text.

* Fix cursor placement on mobile devices

- Made placeholder absolutely positioned to prevent it from affecting cursor
- Added position: relative to .ProseMirror container
- This ensures cursor appears at the start of input on mobile browsers

The absolute positioning removes the placeholder from the normal layout flow,
preventing mobile browsers from placing the cursor after the pseudo-element.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-13 22:07:04 +01:00
Alejandro
20aeac2bc2 Use npub instead of NIP-05 in sharing URLs (#83)
- Replace NIP-05 preference with npub in ShareSpellbookDialog
- Replace NIP-05 preference with npub in SpellbookRenderer preview
- Remove unused useProfile imports from both components
- Update comment to reflect npub-only URL format

This ensures more reliable URL sharing since npub is always available
while NIP-05 may not be set or verified. The routing system still
supports both formats for backwards compatibility.

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-13 21:41:59 +01:00
Alejandro
1356afe9ea Fix newline rendering in chat messages (#80)
* Fix newline rendering in chat messages

The Text component was not properly rendering newlines in chat messages.
The previous implementation had buggy logic that only rendered <br /> for
empty lines and used an inconsistent mix of spans and divs for non-empty
lines, which didn't create proper line breaks between consecutive text lines.

Changes:
- Render each line in a span with <br /> between consecutive lines
- Remove unused useMemo import and fix React hooks violation
- Simplify logic for better maintainability

This ensures that multi-line messages in chat (and other text content)
display correctly with proper line breaks.

Fixes rendering of newlines in NIP-29 groups and NIP-53 live chat.

* Preserve newlines when sending chat messages

The MentionEditor's serializeContent function was not handling hardBreak
nodes created by Shift+Enter. This caused newlines within messages to be
lost during serialization, even though the editor displayed them correctly.

Changes:
- Add hardBreak node handling in serializeContent
- Preserve newlines (\n) from Shift+Enter keypresses
- Ensure multi-line messages are sent with proper line breaks

With this fix and the previous Text.tsx fix, newlines are now properly:
1. Captured when typing (Shift+Enter creates hardBreak)
2. Preserved when sending (hardBreak serialized as \n)
3. Rendered when displaying (Text component renders \n as <br />)

* Make Enter insert newline on mobile devices

On mobile devices, pressing Enter now inserts a newline (hardBreak) instead
of submitting the message. This provides better UX since mobile keyboards
don't have easy access to Shift+Enter for multiline input.

Behavior:
- Desktop: Enter submits, Shift+Enter inserts newline (unchanged)
- Mobile: Enter inserts newline, Cmd/Ctrl+Enter submits
- Mobile detection: Uses touch support API (ontouchstart or maxTouchPoints)

Users can still submit messages on mobile using:
1. The Send button (primary method)
2. Ctrl+Enter keyboard shortcut (if available)

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-13 20:50:46 +01:00
Alejandro
4078ea372a Review NIP-51 list types and rendering support (#77)
* Add comprehensive NIP-51 list rendering support

Implement rich renderers for all major NIP-51 list types with both
feed and detail views. Creates reusable list item components for
consistent UI across different list kinds.

New list renderers:
- Kind 10000: Mute List (pubkeys, hashtags, words, threads)
- Kind 10001: Pin List (pinned events/addresses)
- Kind 10003: Bookmark List (events, addresses, URLs)
- Kind 10004: Community List (community references)
- Kind 10005: Channel List (public chat channels)
- Kind 10015: Interest List (hashtags + interest sets)
- Kind 10020: Media Follow List (media creators)
- Kind 10030: User Emoji List (custom emojis)
- Kind 10101: Good Wiki Authors
- Kind 10102: Good Wiki Relays
- Kind 30000: Follow Sets (categorized follows)
- Kind 30003: Bookmark Sets (categorized bookmarks)
- Kind 30004: Article Curation Sets
- Kind 30005: Video Curation Sets
- Kind 30006: Picture Curation Sets
- Kind 30007: Kind Mute Sets
- Kind 30015: Interest Sets
- Kind 39089: Starter Packs
- Kind 39092: Media Starter Packs

Reusable components in src/components/nostr/lists/:
- PubkeyListPreview/Full: Display pubkey lists with counts
- HashtagListPreview/Full: Display hashtag pills
- EventRefList: Display event/address references
- WordList: Display muted words
- UrlList: Display URL bookmarks

* Improve NIP-51 list rendering with clickable titles and consistent styling

- Add ClickableEventTitle to all list feed renderers for detail view navigation
- Change colored icons to text-muted-foreground for consistent muted appearance
- Update HashtagListPreview to use Label component with dotted border styling
- Update EventRefList detail view to embed events using EmbeddedEvent component

* Use Label component for muted words with destructive styling

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-13 17:23:57 +01:00
Alejandro
9ef1fefd3d feat: BLOSSOM (#75)
* 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>
2026-01-13 17:16:31 +01:00
Alejandro
ed6f2fd856 Improve chat reply UX with auto-focus and rich text preview (#76)
- 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>
2026-01-13 16:26:28 +01:00
Alejandro
fd31e707fc Add window action to move windows between tabs (#74)
- Add "Move to tab" submenu in window toolbar's more actions dropdown
- Shows nested submenu with available workspace tabs (number + label)
- After moving, automatically switches to the target workspace
- Only displays the more actions menu when multiple workspaces exist
- Preserves existing REQ-specific "Save as spell" action

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-13 13:28:02 +01:00
Alejandro
797510107b Fix slash command autocomplete and add bookmark commands (#73)
* fix: slash autocomplete only at input start + add bookmark commands

- Fix slash command autocomplete to only trigger when / is at the
  beginning of input text (position 1 in TipTap), not in the middle
  of messages
- Add /bookmark command to add NIP-29 group to user's kind 10009 list
- Add /unbookmark command to remove group from user's kind 10009 list

* fix: normalize relay URLs when checking group list bookmarks

Use normalizeRelayURL for comparing relay URLs in bookmark/unbookmark
commands to handle differences in trailing slashes, casing, and protocol
prefixes between stored tags and conversation metadata.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-13 12:39:22 +01:00
Alejandro
280a395187 Make grouplist viewer mobile friendly with sidebar (#72)
* 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>
2026-01-13 11:52:52 +01:00
Alejandro
4ee385ea6c Add actions support to chat adapter protocol (#67)
* 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>
2026-01-13 11:49:32 +01:00
Alejandro Gómez
9657ec635f fix: support other user groups 2026-01-13 10:50:17 +01:00
Alejandro
c4687da3ef Add multi-room group chat support (#70)
* feat: add multi-room group chat interface (GroupListViewer)

Add a Discord/Slack-style multi-room chat interface for NIP-29 groups:

- New GroupListViewer component with split layout:
  - Left panel: List of groups from kind 10009, sorted by recency
  - Right panel: Full chat view for selected group
- Loads group metadata (kind 39000) for icons and names
- Tracks latest messages (kind 9) for activity-based sorting
2026-01-13 10:30:10 +01:00
Alejandro Gómez
5de60f17d3 ui: load older styles 2026-01-13 09:37:34 +01:00
Alejandro
cf1f384691 Make chat composer more compact (#68)
* 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>
2026-01-12 22:21:03 +01:00
Alejandro
20eb19bdbb fix: improve chat architecture robustness and error handling (#66)
* fix: improve chat architecture robustness and error handling

- Fix scroll-to-message index mismatch (was searching in wrong array)
- Fix subscription memory leaks by tracking and cleaning up subscriptions
- Add error handling for conversation resolution with retry UI
- Add error handling for send message with toast notifications
- Fix array mutation bugs in NIP-53 relay handling
- Add type guards for LiveActivityMetadata
- Fix RelaysDropdown O(n²) performance issue
- Add loading state for send button

* refactor: add stronger types and optimize message sorting

- Add discriminated union types for ProtocolIdentifier (GroupIdentifier,
  LiveActivityIdentifier, DMIdentifier, NIP05Identifier, ChannelIdentifier)
- Optimize message sorting using reverse() instead of full sort (O(n) vs O(n log n))
- Add type narrowing in adapter resolveConversation methods
- Remove unused Observable import from ChatViewer

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-12 21:12:22 +01:00
Alejandro
159d35e347 feat: add rich text rendering for live chat messages (kind 1311) (#64)
- Created LiveChatMessageRenderer component for NIP-53 live chat messages
- Displays messages with RichText component for full formatting support
- Links to parent live activity event (kind 30311) with clickable header
- Shows activity title or fallback to "Live chat with [host]"
- Registered kind 1311 in renderer registry
- Exported both human-readable name (LiveChatMessageRenderer) and kind alias (Kind1311Renderer)

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-12 19:41:20 +01:00
Alejandro Gómez
7d460352e3 ui: zap margin 2026-01-12 18:11:54 +01:00
Alejandro
73de36a544 Render NIP-29 group metadata as links (#63)
* feat: add GroupMetadataRenderer for NIP-29 group metadata (kind 39000)

Render kind 39000 events with group name, picture, description, and
an "Open Chat" link that opens the NIP-29 group in the chat viewer.

* feat: add kind names for NIP-29 group events and simplify renderer

- Add kind 39000 (Group), 39001 (Group Admins), 39002 (Group Members)
  to EVENT_KINDS so kind badge displays proper names
- Simplify GroupMetadataRenderer to show only title, description, and
  Open Chat CTA (remove group picture)

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-12 17:46:41 +01:00
Alejandro
76dd1e801d fix: reconstruct chat command for edit button (#62)
Add chat case to reconstructCommand to properly reconstruct the chat
command string when clicking the edit button in the window toolbar.

- NIP-29 groups: reconstruct as `chat relay'group-id`
- NIP-53 live activities: reconstruct as `chat naddr1...`

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-12 17:07:22 +01:00
Alejandro
b24810074d feat: add NIP-61 nutzap support to NIP-29 groups (#59)
* 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>
2026-01-12 15:26:41 +01:00
Alejandro Gómez
e50fcca386 ui: chat tooltip, better zaps 2026-01-12 13:41:40 +01:00
Alejandro Gómez
6beda51ad7 ui: don't show chat description 2026-01-12 13:31:45 +01:00
Alejandro
5bc89386ea Add NIP-53 live event chat adapter (#56)
* 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>
2026-01-12 13:05:09 +01:00
Alejandro
59e79959a6 Simplify NIP link styling in rich text (#57)
Remove icon and inline-flex layout from NIP links so they align
consistently with surrounding text and have the same size.

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-12 12:20:45 +01:00
Alejandro
035fd829d5 Add Ctrl+Enter shortcut to send messages (#55)
* fix: allow Ctrl/Cmd+Enter to submit messages when suggestion popup is open

The suggestion list components were intercepting all Enter key presses,
including Ctrl+Enter and Cmd+Enter, preventing message submission when
the autocomplete popup was visible. Now these modifier combinations
pass through to the editor's submit handler.

* fix: make Ctrl/Cmd+Enter work reliably and prevent text overflow

- Add TipTap Extension with addKeyboardShortcuts for Mod-Enter and Enter
  which has higher priority than the suggestion plugin, ensuring
  Ctrl/Cmd+Enter always submits even when autocomplete is open
- Use ref to access handleSubmit from extension without recreating it
- Add whitespace-nowrap to prevent text wrapping in single-line input
- Add overflow-hidden to container and overflow-x-auto to editor content
  to handle long text gracefully with horizontal scrolling

* fix: handle Ctrl/Cmd+Enter directly in suggestion onKeyDown handlers

The TipTap suggestion plugin intercepts key events at the plugin level,
which runs before extension keyboard shortcuts. Even returning false
from the suggestion's onKeyDown didn't properly propagate events.

Now the suggestion handlers directly handle Ctrl/Cmd+Enter by:
1. Capturing the editor reference from onStart (where it's available)
2. Checking for Ctrl/Cmd+Enter in onKeyDown
3. Closing the popup and calling handleSubmitRef.current()

Also moved handleSubmitRef to the top of the component so it can be
accessed from within the suggestion config closures.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-12 12:02:47 +01:00
Alejandro Gómez
385f599b67 ui: show 'left' in chat 2026-01-12 11:43:14 +01:00
Alejandro Gómez
a76d0fabba fix: don't shrink chat title 2026-01-12 11:43:05 +01:00
Alejandro
2bad592a3a feat: emoji autocompletion (#54)
* 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>
2026-01-12 11:30:52 +01:00
Alejandro
ae3af2d63c feat: add NIP-46 remote signer login support (#53)
* feat: add NIP-46 remote signer login support

Add a login dialog with two authentication options:
- Extension login (NIP-07): Connect via browser extensions like nos2x, Alby
- Nostr Connect (NIP-46): Login via QR code scan or bunker:// URL

The dialog allows users to generate a nostrconnect:// QR code that can be
scanned with a signer app, or paste a bunker:// URL for direct connection.

* fix: improve NIP-46 login experience

- Remove redundant signer.open() call after fromBunkerURI (it already connects)
- Increase QR code margin from 2 to 4 for better scanning
- Increase QR code width to 280px

* fix: establish WebSocket connection before showing QR code

Fixes NIP-46 QR code login by opening the relay connections BEFORE
displaying the QR code. This ensures the client is listening when
the remote signer responds.

Also adds console logging for debugging NIP-46 connection flow.

* fix: set NostrConnectSigner pool before loading accounts

Fixes crash on reload when a NIP-46 account is saved. The pool must
be configured globally before accounts are restored from localStorage,
otherwise NostrConnectSigner throws "Missing subscriptionMethod".

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-12 11:28:44 +01:00
Alejandro Gómez
a3eec2e3c9 feat: disable inline media and event embeds in chat replies
Pass options to RichText component in ReplyPreview to disable:
- All media types (images, videos, audio) via showMedia: false
- Event embeds (note/nevent/naddr mentions) via showEventEmbeds: false

Chat reply previews now only show text content for cleaner, more
focused message context display.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-12 10:59:39 +01:00
Alejandro
bff51857d6 feat: message ux improvements (#52)
* 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>
2026-01-12 10:43:59 +01:00
Alejandro Gómez
ef1032964c Merge branch 'chat' 2026-01-12 10:27:50 +01:00
Alejandro Gómez
0b20a628e2 feat: add mention editor and NIP-29 chat enhancements
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>
2026-01-12 10:26:38 +01:00
Alejandro
a7682c757b Fix custom emoji rendering in compact view (#51)
Custom emoji were not displaying in compact event previews because
DefaultCompactPreview was passing only content string to RichText,
which strips emoji tag metadata needed for :shortcode: -> URL mapping.

Now passes full event object to preserve emoji tags when rendering
content, while still using plain text rendering for specific titles.

Fixes emoji display in compact rows and reply previews.

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-12 09:07:56 +01:00
Alejandro Gómez
fc2e680afd feat: add reply functionality and require active account for composer
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>
2026-01-11 23:12:07 +01:00
Alejandro
938800c350 feat(RichText): extend parser with custom options and NIP reference support (#50)
* feat(RichText): extend parser with custom options and NIP reference support

- Add nipReferences transformer to parse NIP-xx patterns (e.g., NIP-01, nip-19)
- Create Nip component that renders NIP references as clickable links opening Grimoire's NIP viewer
- Expose parserOptions prop for customizing transformers, maxLength, and cacheKey
- Add expand/collapse functionality for truncated content with "Show more" button
- Include NIP transformer in default transformer pipeline
- Add comprehensive tests for NIP pattern matching and normalization

* feat(nip-transformer): add support for hex NIPs like NIP-C7

- Extend pattern to match hex NIP identifiers (NIP-C7, NIP-C0, NIP-A0)
- Normalize hex NIPs to uppercase (nip-c7 -> C7)
- Add tests for hex NIP parsing and normalization

* refactor(RichText): remove maxLength for now, export transformer types

* style(Nip): add icon, dotted underline, muted text for NIP links

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-11 22:21:13 +01:00
Alejandro Gómez
cfd897f96c feat: wait for EOSE before rendering messages to prevent scroll jumping
Message Loading Improvements:
- Use RxJS Subject to track EOSE (End Of Stored Events)
- Use skipUntil operator to delay timeline emissions until EOSE received
- Prevents scroll position jumping during initial message load
- Messages still update reactively after initial EOSE

NIP-29 Adapter:
- Create eoseSubject to track EOSE state
- Emit from subject when EOSE string received from relay subscription
- Apply skipUntil(eoseSubject) to eventStore.timeline() observable

NIP-C7 Adapter:
- Add relay subscription to track EOSE (was missing)
- Use same EOSE tracking pattern as NIP-29
- Apply skipUntil to prevent premature timeline emissions

Benefits:
- Smooth initial load experience without scroll jumping
- All messages appear together after EOSE
- Maintains reactive updates for new messages
- Consistent behavior across both chat protocols

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-11 22:15:04 +01:00
Alejandro Gómez
6c1c1bbf04 fix: prevent message horizontal overflow with proper text wrapping
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>
2026-01-11 22:12:53 +01:00
Alejandro Gómez
8f262e1189 feat: batch-load group metadata and handle unmanaged "_" groups
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>
2026-01-11 22:10:22 +01:00
Alejandro Gómez
6b6605ded0 fix: pass proper ProtocolIdentifier object when opening group chats
GroupLink was passing a string identifier instead of a properly structured
ProtocolIdentifier object, causing chat window to fail opening.

Fixed to pass:
{
  protocol: "nip-29",
  identifier: {
    type: "group",
    value: groupId,
    relays: [relayUrl]
  }
}

This matches the expected ChatViewer props interface and allows the
NIP-29 adapter to properly resolve the conversation with the group relay.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-11 22:06:37 +01:00
Alejandro Gómez
64b97e4926 feat: add kind 10009 (Public Chats) renderer with clickable group links
Implements rendering of NIP-51 kind 10009 (Public Chats list) events,
displaying NIP-29 groups similar to how relay lists are rendered.

Components:
- GroupLink: Clickable group component with icon and name
  - Fetches kind 39000 metadata from EventStore for group info
  - Displays group icon (if available) or chat icon fallback
  - Opens chat window on click with relay'group-id identifier

- PublicChatsRenderer: Renderer for kind 10009 events
  - Extracts "group" tags: ["group", "<group-id>", "<relay-url>"]
  - Displays each group as clickable link
  - Similar styling to relay list renderers

Integration:
- Registered kind 10009 renderer in kinds index
- Opens chat with NIP-29 protocol on click

Fixes:
- Fix missing type imports in NIP-29 adapter (Participant, ParticipantRole)
- Fix relayUrl type error in sendMessage (use validated variable)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-11 22:04:41 +01:00