Commit Graph

57 Commits

Author SHA1 Message Date
Alejandro
8f008ddd39 feat: nip-10 chat interface (#153)
* docs: add comprehensive NIP-10 thread chat design documentation

Add detailed design documents for implementing NIP-10 thread chat feature:

- nip10-thread-chat-design.md: Full architecture, data structures, adapter
  implementation plan, relay selection strategy, UI requirements, and 7-phase
  implementation checklist
- nip10-thread-chat-examples.md: Complete code examples showing identifier
  parsing, conversation resolution, message loading, reply sending with proper
  NIP-10 tags, and ChatViewer integration
- nip10-thread-chat-summary.md: Quick reference with visual comparisons,
  architecture diagrams, protocol comparison table, data flow, and FAQ

The feature will enable "chat nevent1..." to display kind 1 threaded
conversations as chat interfaces, with the root event prominently displayed
at the top and all replies shown as chat messages below.

Key design decisions:
- Use e-tags with NIP-10 markers (root/reply) instead of q-tags
- Merge multiple relay sources (seen, hints, outbox) for coverage
- Display root event centered with full feed renderer
- Reuse existing ChatViewer infrastructure via adapter pattern
- Support both nevent (with relay hints) and note (ID only) formats

* feat: implement NIP-10 thread chat support

Add complete NIP-10 thread chat implementation enabling "chat nevent..." to
display kind 1 threaded conversations as chat interfaces.

**Type Definitions**:
- Add ThreadIdentifier for nevent/note event pointers
- Add "nip-10" to ChatProtocol type
- Extend ConversationMetadata with thread-specific fields (rootEventId,
  providedEventId, threadDepth, relays)

**NIP-10 Adapter** (src/lib/chat/adapters/nip-10-adapter.ts):
- parseIdentifier: Decode nevent/note format, reject non-kind-1 events
- resolveConversation: Fetch provided event, find root via NIP-10 refs,
  determine conversation relays (merge hints, outbox, fallbacks)
- loadMessages: Subscribe to kind 1 replies, kind 7 reactions, kind 9735 zaps
- sendMessage: Build proper NIP-10 tags (root/reply markers), add p-tags for
  all participants
- sendReaction: Send kind 7 with proper event/author references
- Smart relay selection: Merges seen relays, nevent hints, author outbox,
  user outbox (limit 7 relays for performance)

**ChatViewer Updates**:
- Detect NIP-10 threads (protocol === "nip-10")
- Fetch and display root event at top (centered with KindRenderer)
- Show visual separator ("Replies") between root and messages
- Update empty state message for threads ("No replies yet...")
- Enhanced header: Show "Author • Preview" for thread chats
- Update getAdapter to handle "nip-10" protocol

**Chat Parser**:
- Add Nip10Adapter to priority list (before other adapters to catch
  nevent/note)
- Update error message with nevent/note format examples
- Update adapter priority documentation

**Component Enhancements**:
- ReplyPreview: Show "thread root" when replying to root event (NIP-10)
- RelaysDropdown: Support conversation.metadata.relays for thread relay
  breakdown
- ChatMessageContextMenu: Add "Zap" option to context menu (opens ZapWindow)

**Features**:
- Root event displayed with full feed renderer (can interact: like, zap, etc.)
- All replies shown as chat messages with proper threading
- Reply/React/Zap options on all messages
- Relay dropdown shows breakdown of thread relays
- Participants dropdown shows all thread participants
- @ mention autocomplete works for participants
- Proper NIP-10 tag structure for nested replies
- Smart relay selection for maximum reach

**Usage**:
  chat nevent1qqsxyz...  # Thread with relay hints
  chat note1abc...       # Thread with event ID only

Root event is centered at top, all replies below as chat messages. Sending
replies creates kind 1 events with proper NIP-10 root/reply markers and
p-tags for all participants.

* fix: remove unused imports and mark intentionally unused parameter

- Remove unused getTagValues import
- Prefix providedEvent parameter with underscore to indicate intentional non-use (reserved for future relay hint extraction)

* feat: hide reply preview when directly replying to root in NIP-10 threads

In NIP-10 thread chats, the root event is already displayed prominently at
the top, so showing a reply preview when replying directly to the root is
redundant.

Changes:
- Skip reply preview for regular messages when replying to thread root
- Skip reply preview for zap messages when zapping the thread root
- Root event remains visible at top as reference point

This reduces visual clutter and makes the chat feel cleaner when replies
are directed at the already-visible root post.

* refactor: simplify NIP-10 thread chat implementation

Major simplifications based on user feedback:
- Render root event as regular message instead of special feed renderer
- Remove special-case logic for hiding reply preview when replying to root
- Include root event in messages list using RxJS combineLatest
- Change root author role from "admin" to "op" (original poster)
- Fix NIP-10 protocol link handler in chat header
- Add "Chat" option to event context menu for kind 1 events
- Fix unused parameter warnings in nip-10-adapter

This refactoring reduces complexity and makes NIP-10 threads behave
more consistently with other chat protocols.

* feat: add zap support for NIP-10 thread chat messages

Implement getZapConfig method to enable zapping messages in NIP-10 threads:
- Returns message author as recipient
- Includes event pointer for the message being zapped
- Uses conversation metadata relays for zap receipt publishing
- Integrates with new zap infrastructure from main branch

* fix: pass protocol and identifier directly to chat window

Fix 'undefined protocol' error when opening chat from event menu.
Instead of passing raw nevent as args, directly pass the parsed
protocol and identifier props that ChatViewer expects.

* debug: add logging to diagnose chat protocol issue

Add console logging to:
- chat command argParser to see input args and parsed result
- ChatViewer to see received props

This will help diagnose why 'undefined protocol' error appears
when opening chat via command or event menu.

* refactor: improve NIP-10 thread chat UX and relay/participant handling

UI Improvements:
- Remove "thread root" marker from reply previews - treat all replies uniformly
- Hide "load older messages" for NIP-10 threads (all replies loaded reactively)
- Display "Thread" with note icon instead of "Group" for NIP-10 conversations
- Remove debug logging from chat command parser and ChatViewer

Participant Management:
- Derive participants dynamically from messages (like live-chat does)
- Root author (OP) always listed first with "op" role
- All unique message authors included in member list
- Updates in real-time as new people reply

Relay Management:
- Expand relay collection to include participant outbox relays
- Fetch relays from root author, provided event author, and p-tagged participants
- Check up to 5 participants for relay diversity
- Increase max relay limit from 7 to 10 for better coverage
- Add logging for relay collection debugging

This makes NIP-10 threads feel more like proper chat conversations with
accurate participant lists and better relay coverage across the thread.

* refactor: remove debug logging from NIP-10 adapter

Remove informational console.log statements to reduce noise in production.
Keep only console.warn and console.error for actual problems.

This makes the adapter production-ready while maintaining useful error
and warning messages for debugging issues.

* fix: resolve lint errors from logging cleanup

- Fix empty block statement by simplifying next handler
- Prefix unused parameters with underscore (_response, _err)
- All lint checks now pass with 0 errors

* refactor: use AGGREGATOR_RELAYS constant and remove design docs

Replace hardcoded relay URLs with AGGREGATOR_RELAYS constant:
- Import AGGREGATOR_RELAYS from @/services/loaders
- Use constant for fallback relays in getThreadRelays()
- Use constant for default relays in getDefaultRelays()

Remove design documentation files (no longer needed):
- docs/nip10-thread-chat-design.md
- docs/nip10-thread-chat-examples.md
- docs/nip10-thread-chat-summary.md

This improves maintainability by centralizing relay configuration
and reduces repository clutter.

* refactor: remove relay.nostr.band and update AGGREGATOR_RELAYS

relay.nostr.band is no longer operational, so remove it from the codebase:

AGGREGATOR_RELAYS changes:
- Removed: wss://relay.nostr.band/
- Removed: wss://purplepag.es/
- Added: wss://relay.snort.social/
- Added: wss://relay.damus.io/
- New list: nos.lol, relay.snort.social, relay.primal.net, relay.damus.io

Updated code:
- src/services/loaders.ts: Updated AGGREGATOR_RELAYS constant
- src/lib/chat/adapters/nip-53-adapter.ts: Use AGGREGATOR_RELAYS instead of hardcoded relays

Updated tests:
- All test files updated to expect new relay URLs
- Replaced relay.nostr.band references with relay.snort.social
- Replaced purplepag.es references with relay.snort.social
- Fixed URL formats to include trailing slashes for normalization

All 980 tests passing ✓

* fix: change grimRelays from let to const in supporters.ts

Fix lint error from rebase - grimRelays is never reassigned so it should
use const instead of let.

* style: reduce padding on sign-in message to match composer

Change from px-3 py-2 to px-2 py-1 to match the horizontal and vertical
padding of the logged-in message composer (px-2 py-1), ensuring
consistent height between logged-in and logged-out states.

* feat: make sign-in message clickable to open login dialog

Add clickable 'Sign in' link to the logged-out message composer:
- Import LoginDialog component
- Add showLogin state management
- Make 'Sign in' text an underlined button that opens the login dialog
- Add LoginDialog component with controlled state

This provides a better UX by allowing users to quickly sign in
directly from the chat interface.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-19 16:47:12 +01:00
Alejandro
3f811ed072 feat: zap action for chat (#151)
* feat: add configurable zap tagging for chat messages

Implements a protocol adapter interface for configuring how zap
requests should be tagged for chat messages. This enables proper
NIP-53 live activity zapping with appropriate a-tag and goal
e-tag support.

Changes:
- Add ZapConfig interface to base-adapter for protocol-specific zap configuration
- Add getZapConfig() method to ChatProtocolAdapter (default: unsupported)
- Implement getZapConfig() in NIP-53 adapter with proper tagging:
  - Always a-tag the live activity (kind 30311)
  - If zapping host with goal, also e-tag the goal event
- Add goal tag parsing to live-activity.ts and types
- Update createZapRequest to accept custom tags parameter
- Add Zap action to ChatMessageContextMenu (shown when supported)
- Update ZapWindow to pass custom tags through to zap request
- NIP-29 groups inherit default (unsupported) behavior

* feat: add custom tags and relays to zap command

Extends the zap command to support custom tags and relay specification,
enabling full translation from chat zap config to zap command.

Changes:
- Add -T/--tag flag to specify custom tags (type, value, optional relay hint)
- Add -r/--relay flag to specify where zap receipt should be published
- Update ZapWindow to accept and pass through relays prop
- Update ChatMessageContextMenu to pass relays from zapConfig
- Update man page with new options and examples
- Add comprehensive tests for zap parser flag handling

Example usage:
  zap npub... -T a 30311:pk:id wss://relay.example.com
  zap npub... -r wss://relay1.com -r wss://relay2.com

* fix: include event pointer when zapping chat messages

Pass the message event as eventPointer when opening ZapWindow from
chat context menu. This enables:
- Event preview in the zap window
- Proper window title showing "Zap [username]"

* feat: add zap command reconstruction for Edit feature

Add zap case to command-reconstructor.ts so that clicking "Edit" on
a zap window title shows a complete command with:
- Recipient as npub
- Event pointer as nevent/naddr
- Custom tags with -T flags
- Relays with -r flags

This enables users to see and modify the full zap configuration.

* fix: separate eventPointer and addressPointer for proper zap tagging

- Refactor createZapRequest to use separate eventPointer (for e-tag)
  and addressPointer (for a-tag) instead of a union type
- Remove duplicate p-tag issue (only tag recipient, not event author)
- Remove duplicate e-tag issue (only one e-tag with relay hint if available)
- Update ZapConfig interface to include addressPointer field
- Update NIP-53 adapter to return addressPointer for live activity context
- Update ChatMessageContextMenu to pass addressPointer from zapConfig
- Update command-reconstructor to properly serialize addressPointer as -T a
- Update ZapWindow to pass addressPointer to createZapRequest

This ensures proper NIP-53 zap tagging: message author gets p-tag,
live activity gets a-tag, and message event gets e-tag (all separate).

* refactor: move eventPointer to ZapConfig for NIP-53 adapter

- Add eventPointer field to ZapConfig interface for message e-tag
- NIP-53 adapter now returns eventPointer from getZapConfig
- ChatMessageContextMenu uses eventPointer from zapConfig directly
- Remove goal logic from NIP-53 zap config (simplify for now)

This gives the adapter full control over zap configuration, including
which event to reference in the e-tag.

* fix: update zap-parser to return separate eventPointer and addressPointer

The ParsedZapCommand interface now properly separates:
- eventPointer: for regular events (nevent, note, hex ID) → e-tag
- addressPointer: for addressable events (naddr) → a-tag

This aligns with ZapWindowProps which expects separate fields,
fixing the issue where addressPointer from naddr was being
passed as eventPointer and ignored.

* feat: improve relay selection for zap requests with e+a tags

When both eventPointer and addressPointer are provided:
- Collect outbox relays from both semantic authors
- Include relay hints from both pointers
- Deduplicate and use combined relay set

Priority order:
1. Explicit params.relays (respects CLI -r flags)
2. Semantic author outbox relays + pointer relay hints
3. Sender read relays (fallback)
4. Aggregator relays (final fallback)

* fix: pass all zap props from WindowRenderer to ZapWindow

WindowRenderer was only passing recipientPubkey and eventPointer,
dropping addressPointer, customTags, and relays. This caused
CLI flags like -T (custom tags) and -r (relays) to be ignored.

Now all parsed zap command props flow through to ZapWindow
and subsequently to createZapRequest.

* refactor: let createZapRequest collect relays from both authors

Remove top-level relays from NIP-53 zapConfig so createZapRequest
can automatically collect outbox relays from both:
- eventPointer.author (message author / zap recipient)
- addressPointer.pubkey (stream host)

The relay hints in the pointers are still included via the
existing logic in createZapRequest.

* fix: deduplicate explicit relays in createZapRequest

Ensure params.relays is deduplicated before use, not just
the automatically collected relays. This handles cases where
CLI -r flags might specify duplicate relay URLs.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-19 12:16:51 +01:00
Alejandro
9f6e524ea9 feat: add tap-to-blur privacy feature for wallet balances (#143)
* feat: add tap-to-blur privacy feature for wallet balances

Implement privacy toggle for wallet balances and transaction amounts.
Tapping any balance display toggles a global blur effect across all
wallet UIs. Persisted to localStorage for consistent privacy.

- Add walletBalancesBlurred state to GrimoireState
- Add toggleWalletBalancesBlur pure function in core/logic
- Make big balance in WalletViewer clickable with eye icon indicator
- Apply blur to all transaction amounts in list and detail views
- Add blur to send/receive dialog amounts
- Make balance in user menu wallet info clickable with eye icon
- Apply blur to balance in dropdown menu item

UX matches common financial app pattern: tap balance → blur on/off

* refactor: replace blur with fixed-width placeholders for privacy

Prevent balance size information leakage by using fixed-width
placeholder characters instead of blur effect. A blurred "1000000"
would still reveal it's a large balance vs "100" even when blurred.

Changes:
- Replace blur-sm class with conditional placeholder text
- Use "••••••" for main balances
- Use "••••" for transaction amounts in lists
- Use "•••••• sats" for detailed amounts with unit
- Use "•••• sats" for smaller amounts like fees

Security improvement: No information about balance size is leaked
when privacy mode is enabled. All hidden amounts appear identical.

* refactor: improve privacy UX with stars and clearer send flow

Three UX improvements to the wallet privacy feature:

1. Don't hide amounts in send confirmation dialog
   - Users need to verify invoice amounts before sending
   - Privacy mode now only affects viewing, not sending

2. Replace bullet placeholders (••••) with stars (✦✦✦✦)
   - More visually distinct and recognizable as privacy indicator
   - Unicode BLACK FOUR POINTED STAR (U+2726)
   - Better matches common "redacted" aesthetic

3. Reduce eye icon sizes for subtler presence
   - Main balance: size-6 → size-5
   - Wallet info dialog: size-3.5 → size-3
   - Smaller icons feel less intrusive

Result: Clearer privacy state, safer payment flow, better aesthetics.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-18 23:22:00 +01:00
Alejandro
72fb47224c feat: add NIP-57 zap command foundation (#141)
* feat: add NIP-57 zap command foundation

Implements the foundational structure for sending Lightning zaps (NIP-57) to
Nostr users and events. This commit adds the command interface, UI components,
and routing logic. The actual LNURL resolution and zap request creation will
be implemented in follow-up commits.

Components Added:
- ZapWindow: Full-featured UI for zapping with amount presets, custom amounts,
  wallet integration, and QR code fallback
- parseZapCommand: Parser supporting multiple formats (npub, nprofile, nevent,
  naddr, NIP-05, $me alias)
- Command registration in man pages with examples
- Window routing and title generation

Features:
- Preset amount buttons (21, 100, 500, 1000, 5000, 10000 sats)
- Custom amount input
- Amount usage tracking (remembers most-used amounts)
- Comment field for zap messages
- Event context rendering (shows zapped event in UI)
- Dual payment methods: NWC wallet or QR code/invoice copy
- Dynamic recipient resolution (from event author if zapping event)

Usage:
  zap fiatjaf.com                  # Zap a user by NIP-05
  zap npub1...                     # Zap a user by npub
  zap nevent1...                   # Zap an event (recipient = author)
  zap npub1... nevent1...          # Zap specific user for specific event

Next Steps:
- Implement LNURL-pay resolution (fetch callback URL and nostrPubkey)
- Create kind 9734 zap request event with applesauce factory
- Implement invoice generation via LNURL callback
- Integrate NWC wallet payment
- Add zap action to event context menus
- Implement zap receipt listening (kind 9735)

* feat: implement full NIP-57 zap flow with LNURL and NWC

Completes the production-ready implementation of Lightning zaps (NIP-57) with
full LNURL-pay resolution, zap request creation, NWC wallet payment, and QR
code fallback.

Core Implementation:

1. **LNURL Resolution** (src/lib/lnurl.ts)
   - Resolve Lightning addresses (lud16) to LNURL-pay endpoints
   - Validate zap support (allowsNostr, nostrPubkey)
   - Fetch invoices from LNURL callbacks with zap requests
   - Amount validation (min/max sendable)
   - Comment length validation

2. **Zap Request Creation** (src/lib/create-zap-request.ts)
   - Build kind 9734 zap request events using applesauce EventFactory
   - Sign with user's active account
   - Include recipient (p tag), amount, relays, optional event context (e/a tags)
   - Serialize to URL-encoded JSON for LNURL callbacks
   - Smart relay selection (user's inbox relays for zap receipts)

3. **ZapWindow Complete Flow** (src/components/ZapWindow.tsx)
   - Resolve recipient's Lightning address from profile (lud16)
   - Create and sign zap request with user credentials
   - Fetch invoice from LNURL callback
   - Pay with NWC wallet OR show QR code
   - QR code generation with qrcode library
   - Success feedback with LNURL success actions
   - Comprehensive error handling and user notifications
   - Toast notifications for each step

4. **Event Menu Integration** (src/components/nostr/kinds/BaseEventRenderer.tsx)
   - Add "Zap" action to event dropdown menu
   - Automatically includes event context (e or a tag)
   - Yellow zap icon () for visual consistency
   - Opens ZapWindow with pre-filled recipient and event

Flow Diagram:
1. User clicks "Zap" on event or runs `zap` command
2. Resolve recipient's lud16 → LNURL-pay endpoint
3. Validate zap support (allowsNostr, nostrPubkey)
4. Create kind 9734 zap request (signed by sender)
5. Send to LNURL callback → get BOLT11 invoice
6. Pay via NWC wallet OR show QR code
7. Zap receipt (kind 9735) published by LNURL service

Features:
-  Full NIP-57 compliance
-  LNURL-pay support with validation
-  Applesauce EventFactory for signing
-  NWC wallet integration
-  QR code fallback for manual payment
-  Event context (zapping specific notes/articles)
-  Amount presets with usage tracking
-  Custom amounts and comments
-  Comprehensive error handling
-  Step-by-step user feedback
-  Event menu integration

Security:
- Uses user's active account signer
- Validates LNURL responses
- Validates amount ranges
- No private key exposure
- HTTPS-only LNURL endpoints

Dependencies:
- qrcode: QR code generation
- applesauce-core: EventFactory for signing
- Existing NWC wallet implementation

Related: #135 (NWC wallet viewer)
Implements: NIP-57 (Lightning Zaps)

* fix: resolve TypeScript errors in ZapWindow

- Remove unused imports (useEffect, isAddressableKind, NostrEvent)
- Fix walletInfo access: fetch from getInfo() hook instead of direct property
- Store wallet info in component state with useEffect
- All TypeScript syntax errors resolved

* fix: resolve all TypeScript build errors in zap implementation

- ZapWindow.tsx: Import useProfile from @/hooks/useProfile instead of applesauce-react/hooks
- ZapWindow.tsx: Remove Tabs import (unused)
- ZapWindow.tsx: Remove htmlFor prop from Label components (not supported)
- create-zap-request.ts: Remove non-existent EventTemplate import
- create-zap-request.ts: Use inferred type for template object
- lnurl.ts: Prefix unused lnurl parameter with underscore

All TypeScript compilation errors resolved. Code ready for production build.

* fix: correct useProfile usage and ProfileContent handling in ZapWindow

- Remove eventStore argument from useProfile (takes pubkey and optional relay hints)
- Fix recipientProfile usage: already ProfileContent, don't call getProfileContent again
- Fix authorProfile: call getProfileContent on NostrEvent, not on ProfileContent
- Fix lud16/lud06 access: use recipientProfile directly
- Fix success toast: use recipientProfile?.name instead of content?.name

All type errors resolved. ProfileContent is returned by useProfile, not NostrEvent.

* feat: refine ZapWindow UI and add dynamic window title

UI Refinements per user request:
- Remove QrCode unused import
- Simplify payment flow to single adaptive button
- Button shows "Log in to Zap" if user can't sign
- Button shows "Pay with Wallet" if NWC available, else "Pay"
- Fix activeAccount usage to use accountManager.active
- Remove unused getProfileContent import
- Remove unused eventAuthorName variable

Dynamic Title:
- Add "Zap [username]" dynamic title in DynamicWindowTitle
- Fetches recipient profile and displays name or fallback
- Shows recipient's display name, name, or truncated pubkey

Build fixes:
- Fix TypeScript errors with unused imports
- Fix activeAccount.signer property access
- All tests passing (939 passed)

* feat: add emoji autocompletion to zap comments and refine UI

Zap Comment Enhancements:
- Replace plain Input with MentionEditor for emoji autocompletion
- Add NIP-30 emoji tag support to zap requests (kind 9734)
- Emoji tags are properly serialized and included in zap events
- Support :emoji: syntax with custom emoji from emoji search

Event Preview Refinements:
- Remove Card wrapper from zapped event preview
- Remove padding and borders for cleaner display
- Event renders directly without container styling

Implementation Details:
- Add EmojiTag interface to create-zap-request.ts
- Update ZapRequestParams to include emojiTags array
- Extract emoji tags from MentionEditor in handleZap
- Pass emoji tags through zap request creation pipeline
- Add useProfileSearch and useEmojiSearch hooks to ZapWindow
- Use MentionEditor ref to get serialized content with emojis

All tests passing (939 passed)
Build successful

* feat: compact ZapWindow UI and improve debugging

UI Improvements:
- Reduce padding from p-6 to p-4 and space-y-6 to space-y-3
- Convert amount grid to single-row flex layout with gap-1.5
- Add formatAmount() helper for shortened numbers (21, 1k, 5k, 10k)
- Move custom amount input inline with preset amounts
- Reduce button size to "sm" for more compact display
- Remove separate label for custom amount
- Make comment field more compact (removed min-height)

Debugging Enhancements:
- Add console.log for recipient profile and lud16/lud06
- Add logging for LNURL resolution steps
- Add logging for zap request creation
- Add logging for invoice fetch from callback
- Add debug logging for emoji search service initialization
- Test emoji search on mount to verify it's working

Number Format:
- 21 → "21"
- 1000 → "1k"
- 5000 → "5k"
- 10000 → "10k"
- Handles decimals: 1500 → "1.5k"

The compact layout makes better use of vertical space and provides
comprehensive debug logging to help troubleshoot LNURL and emoji issues.

All tests passing (939 passed)
Build successful

* fix: open LoginDialog instead of connection window for zap login

Changes:
- Import LoginDialog component into ZapWindow
- Add showLogin state to control LoginDialog visibility
- Update handleLogin to open LoginDialog instead of connection window
- Remove unused useGrimoire import and addWindow destructuring
- Add LoginDialog component to render alongside QR dialog

User Experience:
- "Log in to Zap" button now opens proper login dialog
- Users can log in with extension, readonly, nsec, or NIP-46
- After login, user can proceed with zap flow
- More intuitive than opening relay connection window

All tests passing (939 passed)
Build successful

* feat: auto-close ZapWindow after successful wallet payment

Changes:
- Add onClose callback prop to ZapWindowProps interface
- Pass onClose from WindowRenderer to ZapWindow component
- Call onClose() with 1.5s delay after successful wallet payment
- Allow user to see success toast before window closes

User Experience:
- After zapping with wallet, window automatically closes
- 1.5 second delay allows user to see success message
- Prevents accidental double-zapping
- Cleaner flow - no manual window closing needed

Implementation:
- WindowRenderer passes onClose callback to ZapWindow
- ZapWindow calls onClose after payment success and toasts
- setTimeout(onClose, 1500) provides brief delay for UX
- QR code path unchanged (window stays open for payment)

All tests passing (939 passed)
Build successful

* feat: full-width custom amount and wallet timeout with QR fallback

Custom Amount Input:
- Change custom amount input from w-24 to flex-1
- Now takes full remaining width in flex row
- Better UX on smaller screens and more obvious input field

Wallet Payment Timeout Handling:
- Add 30 second timeout to wallet payments using Promise.race
- On timeout, automatically show QR code as fallback
- Add paymentTimedOut state to track timeout condition
- Toast warning when payment times out

Retry with Wallet Feature:
- Add handleRetryWallet function to retry timed out payment
- Show "Retry with Wallet" button in QR dialog when timed out
- Button only appears if wallet is connected and payment capable
- Retry uses same 30s timeout, shows error if fails again
- Provides loading state with spinner during retry

User Flow:
1. User attempts wallet payment
2. If timeout after 30s, shows QR code automatically
3. User can scan QR to pay manually OR
4. User can click "Retry with Wallet" to try again
5. If retry times out, stays on QR for manual payment

Implementation Details:
- Promise.race between payInvoice and 30s timeout
- Timeout throws Error("TIMEOUT") for easy detection
- QR dialog conditionally shows retry button
- Retry resets state and attempts payment again
- Console logging for debugging timeout issues

All tests passing (939 passed)
Build successful

* feat: production-ready zaps with manual close and profile integration

Payment Flow Improvements:
- Remove auto-close after successful payment
- Change "Zap Sent!" button to "Done" button that requires user click
- User must manually close window by clicking Done after payment
- Retry payment also requires manual close after success

Profile Viewer Integration:
- Add Zap icon next to lightning address in ProfileViewer
- Click zap icon to open ZapWindow for that profile
- Yellow icon with hover effect for visual feedback
- Integrates seamlessly with existing profile UI

Production Cleanup:
- Remove all debug console.log statements
- Keep console.error for production error logging
- Remove unused emojiService variable from useEmojiSearch
- Fix Loader2 className typo (animate-spin)
- Clean code ready for production deployment

User Experience:
1. View profile with lightning address
2. Click yellow zap icon to open zap window
3. Enter amount and optional comment
4. Pay with wallet (or QR code if timeout)
5. See success message
6. Click "Done" to close window (manual control)

Testing:
- All lint checks pass (no errors, only warnings)
- TypeScript build successful
- All 939 tests passing
- Production-ready code

Code Quality:
- No debug logging in production
- Proper error handling maintained
- Clean, maintainable code
- Follows project conventions

* fix: remove duplicate zapTitle condition in DynamicWindowTitle

Removed duplicate zapTitle if-else branch at line 870 that was causing
lint error. The first zapTitle condition at line 803 handles all cases,
making the second occurrence unreachable.

* feat: improve zap UX with inline QR and faster imports

- Move imports to top level instead of dynamic imports for faster resolution
- Show QR code inline in ZapWindow instead of separate dialog
- Show recipient name and address when not zapping an event
- Make Lightning address clickable in ProfileViewer with icon on left
- Use recipientName consistently throughout zap flow

This significantly reduces the "Resolving Lightning address..." delay and
provides a cleaner, more integrated UX for viewing and paying invoices.

* feat: optimize zap UX with better error handling and UI improvements

LNURL improvements:
- Add 10s timeouts to Lightning address resolution and invoice fetching
- Better error messages with more context (response status, error text)
- Handle AbortError for timeout scenarios

UI improvements:
- Bigger amount buttons (default size instead of sm)
- Custom amount on separate line for better layout
- Disable all zap UI when recipient has no Lightning address
- Show clear warning when Lightning address is missing
- Only show comment editor when Lightning address is available

Toast cleanup:
- Remove chatty info toasts ("Resolving...", "Creating...", "Fetching...")
- Only show errors and success messages
- Cleaner, less noisy UX

This addresses common issues with LNURL requests timing out and makes
the UI more responsive and informative when zaps cannot be sent.

* feat: full-width custom amount and wallet timeout with QR fallback

QR code improvements:
- Add profile picture overlay in center of QR code (25% size, circular)
- Remove redundant "Copy Invoice" button (keep icon button only)
- Show "Open in Wallet" as full-width button

UI improvements:
- Use UserName component everywhere (clickable, styled, shows Grimoire members)
- Custom amount now full-width on separate line
- Better visual hierarchy

Default amounts updated:
- Changed from [21, 100, 500, 1000, 5000, 10000]
- To [21, 420, 2100, 42000]
- More aligned with common zap amounts

The profile picture overlay helps users identify who they're zapping
while maintaining QR code scannability. UserName component provides
consistent styling and clickable profile links.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-18 23:19:11 +01:00
Alejandro
3408872811 feat: add comprehensive NWC wallet viewer with dynamic UI (#135)
* feat: add comprehensive NWC wallet viewer with dynamic UI

Implements a full-featured Lightning wallet interface using Nostr Wallet
Connect (NWC/NIP-47) with method-based UI that adapts to wallet capabilities.

**New Features:**
- WalletViewer component with tabbed interface (Overview, Send, Receive, Transactions)
- Real-time balance display with manual refresh
- Send Lightning payments via BOLT11 invoices
- Generate invoices with QR codes for receiving payments
- Transaction history viewer (when supported by wallet)
- Wallet info and capabilities display
- Enhanced useWallet hook with additional NWC methods

**Enhanced Methods:**
- listTransactions() - View recent payment history
- lookupInvoice() - Check invoice status by payment hash
- payKeysend() - Direct keysend payments to node pubkeys

**UI Features:**
- Dynamic tabs based on wallet capabilities
- QR code generation for invoices
- Copy-to-clipboard for invoices
- Error handling with user-friendly messages
- Loading states for async operations
- Empty states for no wallet connection

**Command:**
- New `wallet` command to open the wallet viewer

**Technical Details:**
- Integrates with existing NWC service singleton
- Uses reactive balance$ observable for auto-updates
- Proper TypeScript types aligned with applesauce-wallet-connect
- Follows Grimoire patterns for window system integration
- Lazy-loaded component for optimal bundle size

All tests passing. Build verified.

* refactor: redesign wallet UI to single-view layout with virtualized transactions

Converts the tabbed wallet interface to a conventional single-view layout
with improved UX and performance optimizations.

**Layout Changes:**
- Removed tabs in favor of single-page layout
- Balance header at top with wallet name and refresh button
- Side-by-side Send/Receive cards for quick access
- Transaction history below with virtualized scrolling
- Disconnect button at bottom of page

**New Features:**
- Connect Wallet button when no wallet is connected (opens dialog in-app)
- Wallet capabilities shown in tooltip on info icon
- Virtualized transaction list using react-virtuoso
- Batched transaction loading (20 per batch)
- Automatic "load more" when scrolling to bottom
- Loading states for initial load and pagination
- "No more transactions" message when exhausted

**Performance Improvements:**
- Virtualized list rendering for smooth scrolling with many transactions
- Only renders visible transactions in viewport
- Lazy loads additional batches on demand
- Reduced initial load to 20 transactions instead of 50

**UX Improvements:**
- More conventional wallet UI pattern
- Send/Receive always visible (no tab switching)
- QR code and invoice appear inline when generated
- Info icon with tooltip for capabilities (cleaner than full card)
- Disconnect option always accessible at bottom

**Technical Details:**
- Fixed transaction loading race condition with separate useEffect
- Proper dependency tracking for loadMoreTransactions callback
- Footer component in Virtuoso for loading/end states
- Responsive grid layout for Send/Receive cards

All tests passing. Build verified.

* refactor: redesign wallet UI with ReqViewer-style header and dialogs

Implements a cleaner, more consistent wallet UI matching Grimoire patterns.

**Header Changes:**
- ReqViewer-style compact header (border-b, px-4, py-2, font-mono, text-xs)
- Left side: Wallet name · Balance with info tooltip
- Right side: Icon-only action buttons (Receive, Send, Refresh)
- All actions use tooltips for labels
- Consistent spacing and typography with rest of app

**Send/Receive as Dialogs:**
- Removed always-visible Send/Receive cards
- Converted to modal dialogs opened from header icons
- Send dialog: Invoice input + optional amount override
- Receive dialog: Two-step flow (generate → show QR + invoice)
- "Generate Another" button to create multiple invoices without closing
- Cleaner main view focused on transaction list

**Transaction List Cleanup:**
- Removed Card wrapper and extra padding
- Direct virtualized list fills viewport
- Removed "Transaction History" heading (redundant)
- Added flex-shrink-0 to prevent transaction items from shrinking
- Improved truncation handling for long descriptions
- Hover effect for better interactivity
- Footer messages use consistent border-b styling

**Disconnect Button:**
- Changed to destructive variant (red appearance)
- Moved to footer with border-t
- Full width for emphasis
- Clearer visual indication of destructive action

**UX Improvements:**
- More screen space for transactions (primary content)
- Consistent icon sizing (size-3 for header actions, size-4 for tx icons)
- Better visual hierarchy with header actions
- Dialog pattern matches other Grimoire components
- Receive dialog stays open to show generated invoice

**Technical Details:**
- Proper dialog state management
- Invoice QR with max-height scrolling for long invoices
- Reset form state when closing dialogs
- Maintained all existing functionality
- Optimized for virtualized scrolling performance

All tests passing. Build verified.

* feat: final wallet UI improvements with prominent balance and enhanced UX

Redesigns wallet viewer with cleaner layout and improved user experience.

**Layout Changes:**
- Big centered balance display (4xl font, prominent)
- Large Send/Receive buttons below balance (side by side)
- Single-line transaction items with better spacing
- Info dropdown next to refresh button in header

**Transaction List:**
- Single-line compact design (description + time + amount)
- No +/- signs on amounts (cleaner look)
- Generic "Payment"/"Received" labels when description missing
- Time displayed in compact format (HH:MM)
- Day markers between days (Today/Yesterday/Jan 15)
- Virtualized scrolling with batched loading

**Info Dropdown:**
- Wallet capabilities shown in dropdown (Info icon + ChevronDown)
- Network information
- Methods displayed as compact badges
- Notifications support

**User Menu Integration:**
- Wallet option always visible (regardless of account status)
- Clicking wallet opens wallet window (not info dialog)
- Balance shown inline when connected
- "Connect Wallet" option when not connected

**Dialog Improvements:**
- Send dialog with confirmation step
- Receive dialog with payment detection
- Auto-close on payment received
- QR code with loading overlay during payment check

**Visual Hierarchy:**
- Header: Wallet name (left) | Info dropdown + Refresh (right)
- Big centered balance with "sats" label
- Prominent action buttons (Send default, Receive outline)
- Clean transaction list with hover states
- Destructive disconnect button in footer

All tests passing  Build verified 

* fix: replace AlertDialog with Dialog for disconnect confirmation

- AlertDialog component doesn't exist in UI library
- Use regular Dialog with custom footer buttons instead
- All 929 tests passing, build successful

* refine: wallet UI improvements based on feedback

- Remove "sats" text from balance display
- Swap send/receive button positions (receive left, send right)
- Remove top border from transaction list
- Remove timestamps from transaction list items
- Add relay link to wallet info dropdown with external link icon
- Change disconnect button to destructive color (always red)
- Fix imports and remove unused formatTime function

* feat: enhance send/receive flows with invoice parsing and auto-confirm

Send flow improvements:
- Parse BOLT11 invoices using light-bolt11-decoder
- Auto-proceed to confirm step when valid invoice is entered
- Show parsed amount and description in confirmation dialog
- Validate invoice before allowing confirmation

Receive flow improvements:
- Fix invoice overflow with proper truncate display
- Use nested div structure for single-line truncation

All changes preserve type safety with proper Section type guards

* feat: add Lightning address support and refine auto-confirm behavior

Send flow enhancements:
- Only auto-proceed to confirm if invoice has an amount (not for zero-amount invoices)
- Add Lightning address (LNURL-pay) support with automatic resolution
- Fetch invoice from Lightning address with amount validation
- Show "Resolving..." loading state when processing Lightning addresses
- Update UI labels and placeholders to indicate Lightning address support
- Require amount field for Lightning address payments

Lightning address flow:
1. Detect @ symbol in input (and not starting with "ln")
2. Validate amount is provided
3. Fetch LNURL-pay endpoint from .well-known/lnurlp/{username}
4. Check min/max sendable amounts
5. Request invoice from callback with specified amount
6. Parse and confirm invoice details

Error handling:
- Invalid Lightning address format
- Failed to fetch Lightning address
- Amount out of range (too small/too large)
- Failed to generate invoice from callback

* fix: UI improvements for wallet viewer and mobile receive flow

Confirmation dialog improvements:
- Remove yellow warning styles (border, background, icon)
- Fix amount calculation: show proper sats conversion with Math.floor
- Clean layout with key-value pairs instead of cluttered text
- Show either invoice amount OR override amount (not both)

Mobile receive flow improvements:
- Large prominent "Copy Invoice" button (h-12, full width)
- Invoice box now tappable to copy
- Better touch targets for mobile users
- Clearer label: "Invoice (tap to view)"

Connection status:
- Show green dot + "Connected" next to wallet name in header
- Clear visual feedback that wallet is online

Auto-launch wallet:
- After successfully connecting wallet, automatically open wallet window
- Added onConnected callback to ConnectWalletDialog
- Wire up callback in user menu to call openWallet

Code cleanup:
- Remove unused AlertTriangle import

* fix: correct amount handling and mobile invoice display

Amount input fixes:
- Changed from millisats to sats for user input
- Updated label: "Amount (sats, optional)"
- Fixed Lightning address resolution: no division, amount is already in sats
- Fixed payInvoice: multiply by 1000 to convert sats to millisats for NWC
- Fixed confirmation dialog: removed incorrect division by 1000

Before: User enters 1000 (meant as sats) → system treats as 1000000 millisats → sends 1000 sats
After: User enters 1000 sats → system converts to 1000000 millisats → sends 1000 sats ✓

Invoice parsing was correct - already converting millisats to sats.
The bug was in the confirmation display and amount submission.

Mobile receive invoice fix:
- Removed nested div with truncate (was causing overflow)
- Changed to break-all + line-clamp-2
- Invoice now wraps properly on mobile (2 lines max)
- Still tappable to copy

* fix: convert sats to millisats when generating invoices

Critical bug fix - we were generating nearly amountless invoices.

Before:
- User enters 1000 sats in UI
- We passed 1000 to makeInvoice (thinking it's millisats)
- Generated invoice for 1000 millisats = 1 sat

After:
- User enters 1000 sats in UI
- We multiply by 1000 → 1,000,000 millisats
- Generated invoice for 1,000,000 millisats = 1000 sats ✓

This matches the send flow where we also convert sats to millisats
before passing to NWC protocol.

* refactor: production-readiness improvements for WalletViewer

Critical fixes:
- Wrap loadWalletInfo and loadInitialTransactions in useCallback to prevent infinite loops
- Fix dependency arrays in useEffect hooks
- Fix early return bug in handleConfirmSend that left sending state stuck
- Add async/await error handling to handleCopyInvoice
- Change throw new Error instead of early return in Lightning address resolution

Performance optimizations:
- Removed duplicate function definitions (moved to useCallback hooks)
- Proper memoization of wallet loading functions

Code quality:
- All functions properly handle errors with try/catch
- Loading states properly reset in finally blocks
- No memory leaks from useEffect dependencies

* fix: prevent infinite transaction loading loop in WalletViewer

Previously, the transaction list would try to load constantly and fail
repeatedly due to a circular dependency in the useEffect hooks. The
listTransactions function from useWallet wasn't wrapped in useCallback,
causing loadInitialTransactions to be recreated on every render, which
triggered the useEffect infinitely.

Changes:
- Add txLoadAttempted and txLoadFailed flags to prevent repeated attempts
- Only attempt to load transactions once on wallet connection
- Fail silently on initial load (no toast spam)
- Show retry button when transaction loading fails
- Reset flags when wallet connects/disconnects or after successful payments
- Make transaction list truly optional - wallet still works if loading fails

This ensures a better UX when wallets don't support list_transactions
or when the method fails for any reason.

* fix: remove circular dependency in loadInitialTransactions

Removed txLoadFailed from the dependency array of loadInitialTransactions
callback, which was causing a circular dependency:
- loadInitialTransactions depended on txLoadFailed
- Function sets txLoadFailed, triggering recreation
- New function reference triggers useEffect again
- Infinite loop

The txLoadAttempted flag in the useEffect is sufficient to prevent
repeated loads. No need to check txLoadFailed inside the callback.

* fix: resolve React error #185 by removing callbacks from useEffect dependencies

React error #185 occurs when state updates happen during render due to
unstable function references in useEffect dependencies. The root cause
was that functions from useWallet (getInfo, listTransactions) aren't
memoized, so they create new references on every render, causing the
useEffects that depend on them to run repeatedly.

Changes:
- Removed loadWalletInfo and loadInitialTransactions callbacks
- Use refs (walletInfoLoadedRef, lastConnectionStateRef) to track state
- Call async functions directly in useEffect without depending on them
- Created reloadTransactions() helper that just resets flags
- Simplified all reload logic to use the helper

This ensures:
- No circular dependencies in useEffect
- Functions only run once when conditions are met
- No state updates during render
- Clean, predictable loading behavior

* fix: properly clear wallet state on disconnect and update copy

Changes:
- Clear all wallet state when disconnecting (transactions, walletInfo, loading flags)
- Previously only cleared state on connect, leaving stale data visible
- Remove "Mutiny" mention from connection dialog copy
- Update to generic "NWC wallet provider" text

Now when you disconnect the wallet with the window open, it properly
clears all data and returns to the "No Wallet Connected" state.

* fix: prevent wallet from auto-reconnecting after disconnect

The bug was in the useWallet hook which automatically restores the wallet
if nwcConnection exists in state but the wallet instance is null. When
disconnecting, it only cleared the wallet instance but left nwcConnection
in state, causing an immediate reconnection.

Fix:
- Call disconnectNWCFromState() to clear nwcConnection from Grimoire state
- Then call disconnect() to clear the wallet service
- This prevents the auto-restore logic from triggering

Now when you disconnect the wallet, it stays disconnected until you
manually reconnect.

* security: add critical production-ready security fixes

Invoice Validation & Expiry Checks:
- Validate BOLT11 invoice format (must start with 'ln')
- Check invoice expiry before displaying/processing
- Validate amount is reasonable (< 21M BTC)
- Surface parse errors to user with toast notifications
- Prevent processing of expired invoices

Lightning Address Security:
- Enforce HTTPS-only for LNURL-pay requests
- Add 5-second timeout to all HTTP requests
- Validate callback URLs use HTTPS
- Proper AbortController cleanup on timeout
- Better error messages for network failures

Rate Limiting:
- Balance refresh: minimum 2 seconds between calls
- Transaction reload: minimum 5 seconds between reloads
- User-friendly warning messages with countdown
- Prevents spam to wallet service providers

Storage Security Warning:
- Add prominent security notice in ConnectWalletDialog
- Warn users about browser storage implications
- Advise to only connect on trusted devices

Capability Detection:
- Hide Send button if wallet doesn't support pay_invoice
- Hide Receive button if wallet doesn't support make_invoice
- Dynamic button rendering based on wallet capabilities
- Prevents errors from unsupported operations

Error Handling:
- WindowErrorBoundary already wraps all windows (verified)
- Proper error propagation with user-friendly messages
- No silent failures on critical operations

These changes significantly improve security and production-readiness
without breaking existing functionality.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-18 20:05:19 +01:00
Alejandro
b70eb82fea feat: add @domain alias for NIP-05 domain directory resolution (#136)
Add support for @domain syntax in req and count commands to query all
users from a domain's NIP-05 directory (e.g., @habla.news).

Features:
- Fetches /.well-known/nostr.json from domain
- Extracts all pubkeys from the names object
- Works with -a (authors), -p (#p tags), and -P (#P tags) flags
- Supports mixed usage with npub, hex, NIP-05, $me, $contacts
- 5-minute caching for domain lookups
- UI display in ReqViewer query dropdown

Implementation:
- Added resolveDomainDirectory and resolveDomainDirectoryBatch to nip05.ts
- Updated req-parser and count-parser to detect @domain syntax
- Updated argParsers in man.ts to resolve domains asynchronously
- Updated ReqViewer to display queried domains in dropdown
- Added comprehensive tests for domain resolution

Examples:
- req -k 1 -a @habla.news
- req -k 7 -p @nostr.band
- count relay.damus.io -k 1 -a @getcurrent.io

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-18 14:49:37 +01:00
Alejandro
7fae344dd9 feat: add Nostr Wallet Connect (NWC) integration (#131)
* feat: add Nostr Wallet Connect (NWC) integration

Add NWC (NIP-47) support to connect Lightning wallets:
- Add NWCConnection type and state management
- Implement custom NWC client service for wallet communication
- Create ConnectWalletDialog for entering connection strings
- Add wallet button to user menu showing balance
- Display wallet info (balance, alias) in user menu dropdown
- Support get_info, get_balance, pay_invoice, make_invoice commands
- Persist wallet connection to localStorage
- Install applesauce-wallet (for future NIP-60 support)

Note: Current implementation uses custom NWC client. Can optionally
switch to @getalby/sdk for official Alby SDK support.

* refactor: use applesauce-wallet-connect and move wallet to header

Replace custom NWC client implementation with applesauce-wallet-connect:
- Install applesauce-wallet-connect for official NIP-47 support
- Create nwc.ts service wrapper for WalletConnect singleton
- Update NWCConnection type to match WalletConnectURI interface
- Use service/relays/secret properties instead of custom names

Move wallet display from user menu to header:
- Create standalone WalletButton component
- Add WalletButton to header next to UserMenu
- Remove wallet UI from user menu dropdown
- Show balance in header with yellow zap icon
- Clicking wallet button opens connect dialog

This provides better UX with wallet status visible in header
and uses the official applesauce implementation for reliability.

* feat: improve wallet button UX and add detailed info dialog

UI improvements:
- Remove border and padding from wallet button (use ghost variant)
- Remove "sats" suffix from balance display (show just the number)
- Change click behavior to show detailed wallet info dialog

Wallet info dialog:
- Show balance prominently without suffix
- Display wallet alias if available
- Show lightning address (lud16) if present
- List all supported NWC methods as badges
- Display connected relay URLs
- Add disconnect button with confirmation toast

This provides a cleaner header appearance and better wallet
management with all details accessible in one place.

* refactor: move wallet functionality to user menu

Move wallet connection and info from separate header button into
the user menu dropdown for better organization.

Changes:
- Remove standalone WalletButton component
- Add wallet section to user menu dropdown
- Show "Connect Wallet" option when no wallet is connected
- Display wallet balance and alias when connected
- Clicking wallet info opens detailed dialog with:
  - Balance (without suffix)
  - Wallet name/alias
  - Lightning address (lud16)
  - Supported NWC methods
  - Connected relay URLs
  - Disconnect button

This consolidates all user-related settings (account, relays,
blossom servers, wallet) in one consistent location.

* feat: improve wallet UX with profile-based naming and better layout

UX improvements:
- Use service pubkey username/profile as wallet name instead of alias
- Remove format hint text from connection string input
- Move wallet to single row directly below user name (remove label)
- Use RelayLink component for relay URLs in wallet info dialog
- Show wallet name from service profile with fallback to alias/pubkey

This provides better integration with Nostr profiles and a cleaner,
more compact menu layout. The wallet service provider's identity is
now shown using their actual Nostr profile name.

* feat: add persistent wallet service with auto-updating balance

Implement comprehensive NWC wallet management architecture:

**Service Layer** (`src/services/nwc.ts`):
- Maintain singleton WalletConnect instance across app lifetime
- Poll balance every 30 seconds for automatic updates
- Subscribe to NIP-47 notifications (kind 23197) for real-time updates
- Expose RxJS observable (balance$) for reactive balance changes
- Restore wallet from saved connection on app startup
- Proper cleanup on disconnect

**Hook Layer** (`src/hooks/useWallet.ts`):
- useWallet() hook exposes wallet throughout the app
- Provides methods: payInvoice, makeInvoice, getBalance, etc.
- Auto-syncs balance updates with Jotai state
- Handles wallet restoration on mount
- Type-safe wrapper around WalletConnect API

**UI Updates**:
- Add refresh button to wallet info dialog
- Auto-update balance display when transactions occur
- Proper cleanup on wallet disconnect

**Architecture**:
```
User Action → useWallet() → NWC Service → WalletConnect
                ↓                ↓
         Update State ← balance$ observable ← Polling/Notifications
```

This enables:
- Real-time balance updates when paying/receiving
- Easy wallet access: `const { payInvoice, balance } = useWallet()`
- Persistent connection across page reloads
- Automatic polling as fallback for notifications

* refactor: make NWC fully reactive with notifications and graceful balance handling

Changes:
- Remove polling mechanism in favor of pure reactive notifications$ observable
- Subscribe to wallet.notifications$ for real-time balance updates
- Make balance display conditional (only show if available)
- Fix TypeScript errors (notification.type access, unused variable)
- Remove Jotai callback mechanism for balance updates
- Use use$() directly for reactive balance subscription
- Update comments to reflect reactive architecture (no polling)

The wallet now updates balance automatically when payments are sent/received
via NIP-47 notifications, with no polling overhead.

* feat: improve wallet UX with profile-based naming and better layout

Improvements to NWC wallet UI:
- Add separator between user info and wallet section in menu
- Show wallet icon instead of zap icon for better clarity
- Display connection status indicator (green/red dot) in both menu and dialog
- Make wallet service username clickable in wallet info dialog to open profile
- Use wallet relays as hints when fetching service profile for better resolution
- Enhanced useProfile hook to accept optional relay hints parameter

The wallet now properly resolves service profiles using the NWC relay
and shows visual connection status at a glance.

* fix: remove toast descriptions for better contrast

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-18 11:14:47 +01:00
Alejandro
7a293bb41b feat: COUNT (#105)
* Add COUNT command for NIP-45 event counting

Implements the COUNT verb from NIP-45, allowing users to count events
on relays without fetching them. Features:

- New `count` command requiring at least one relay
- Filter-only flags (excludes --limit, --close-on-eose, --view)
- Single relay shows centered count result
- Multiple relays show per-relay breakdown
- Handles approximate counts, errors, and unsupported relays
- Supports $me/$contacts aliases and NIP-05 resolution

Examples:
  count relay.damus.io -k 3 -p npub1...
  count nos.lol relay.damus.io -k 1 -a fiatjaf.com

* Fix $me and $contacts alias resolution in CountViewer

- Fetch contact list (kind 3) using useNostrEvent hook
- Extract contacts from p tags to resolve $contacts alias
- Add "Account Required" message when aliases used without active account
- Match ReqViewer pattern for consistent alias resolution

* Refactor: extract FilterSummaryBadges for compact headers

- Create shared FilterSummaryBadges component (nostr/FilterSummaryBadges.tsx)
- Simplify CountViewer header to single compact line
- Use FilterSummaryBadges in both ReqViewer and CountViewer
- Remove verbose collapsible filter section from CountViewer

* Remove 'events' suffix from count result

* Update count synopsis to show relays can appear anywhere

* Refactor CountViewer to use applesauce-relay pool

Replace manual WebSocket connections with the relay pool's
count() method for NIP-45 COUNT requests. This provides:
- Proper connection reuse via the existing relay pool
- Automatic reconnection handling
- Better integration with the rest of the app

Remove the approximate property since applesauce-relay's
CountResponse type doesn't expose it yet.

* Simplify CountViewer with one-shot requests and compact UI

- Use per-relay count requests with firstValueFrom and timeout
  instead of pool.count() observable that may not complete
- Replace Collapsible-based header with icon-only DropdownMenus
  matching ReqViewer's compact style
- Add raw JSON filter view with syntax highlighting and copy button
- Show relay count and filter in dropdowns instead of expanded sections
- Requests complete after timeout (10s) instead of spinning indefinitely

* Add NIP-45 support detection via NIP-11 relay info

- Check relay's supported_nips via NIP-11 before sending COUNT request
- Return early with "unsupported" status if relay explicitly doesn't support NIP-45
- Differentiate UI between unsupported (yellow Ban icon) and error (red AlertCircle)
- Provide clearer error messages based on whether NIP-11 info was available
- Uses cached relay info when available to avoid redundant requests

* Improve CountViewer header with human-readable filter summary

- Show kinds as badges, authors ("by"), mentions ("@"), hashtags on left
- Move relay status into relay dropdown with per-relay results
- Dropdown shows count per relay, status icons, and error tooltips
- Header now shows "2/3" style relay count trigger with loading state

* Reorder CountViewer header controls and remove redundant mention prefix

- Change control order to: refresh, relays, filter (was: filter, relays, refresh)
- Remove redundant "@" prefix from mentions since UserName with isMention already shows @

* Increase COUNT timeout to 30s and improve window title

- Extend per-relay timeout from 10s to 30s for more reliable results
- Update count window title to show human-readable kind names instead of
  command-line format (e.g., "count: Short Note by abc123..." instead of
  "count -k 1 -a npub...")

* Add spell support for COUNT commands

- Extend spell system to support both REQ and COUNT commands
- Add detectCommandType() to identify command type from string
- Update encodeSpell to use ["cmd", "COUNT"] tag for count commands
- Update decodeSpell to handle COUNT spells
- Update reconstructCommand to accept cmdType parameter
- Add "Save as spell" option to COUNT windows in WindowToolbar
- Update SpellDialog to handle both REQ and COUNT commands

* Add dynamic window title for COUNT with human-readable filter summary

- Add profile fetching for COUNT authors and tagged users
- Add countTitle useMemo with human-readable kind names, authors, mentions, hashtags, and search
- Use same formatting helpers as REQ titles (getKindName, formatProfileNames, etc.)
- Add countTitle to title priority chain after reqTitle
- Title now shows "Short Note • @alice • #bitcoin" instead of "COUNT"

* Update count command documentation for production

- Add note about automatic NIP-11/NIP-45 support detection
- Mention spell saving capability in description

* Add automatic relay selection with NIP-45 filtering for COUNT

- Make relays optional in count-parser (no longer throws if none specified)
- Add useOutboxRelays for automatic relay selection based on filter criteria
- Filter selected relays by NIP-45 support via NIP-11 before querying
- Show "Selecting relays..." and "Filtering by NIP-45..." loading states
- Fall back to aggregator relays if no NIP-45 relays found
- Update man page: relays now optional, new examples showing auto-selection

* Revert automatic relay selection for COUNT command

Simplify COUNT by requiring explicit relay specification:
- Restore relay requirement validation in count-parser.ts
- Remove useOutboxRelays and NIP-45 auto-filtering from CountViewer
- Update man page documentation to reflect required relays
- Keep NIP-45 support detection for better error messages

This keeps the feature simpler for now; automatic relay selection
can be added later when the UX is better understood.

* Reduce padding and sizing in COUNT single result view

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-15 16:13:50 +01:00
Alejandro
dade9a79a6 Fix chat zap preview display logic (#106)
* 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>
2026-01-15 13:08:33 +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
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
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
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
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
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
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
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 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 Gómez
6d01ee33ef feat: implement unified chat system with NIP-C7 and NIP-29 support
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>
2026-01-11 21:38:23 +01:00
Claude
c60abe6df4 feat: implement production-grade REQ state machine with per-relay tracking
Core Infrastructure:
- Add ReqRelayState and ReqOverallState types for granular state tracking
- Implement deriveOverallState() state machine with 8 query states
- Create useReqTimelineEnhanced hook combining RelayStateManager + event tracking
- Add comprehensive unit tests (27 tests, all passing)

State Machine Logic:
- DISCOVERING: NIP-65 relay selection in progress
- CONNECTING: Waiting for first relay connection
- LOADING: Initial events loading
- LIVE: Streaming with active relays (only when actually connected!)
- PARTIAL: Some relays ok, some failed/disconnected
- OFFLINE: All relays disconnected after being live
- CLOSED: Query completed, all relays closed
- FAILED: All relays failed to connect

UI Updates:
- Single-word status indicators with detailed tooltips
- Condensed relay status into NIP-65 section (no duplicate lists)
- Per-relay subscription state badges (RECEIVING, EOSE, ERROR, OFFLINE)
- Event counts per relay
- Connection + Auth status integrated into single dropdown

Fixes Critical Bug:
- Solves "LIVE with 0 relays" issue (Scenario 5 from analysis)
- Distinguishes real EOSE from relay disconnections
- Accurate status for all 7 edge cases documented in analysis

Technical Approach:
- Hybrid: RelayStateManager for connections + event._relay for tracking
- Works around applesauce-relay catchError bug without forking
- No duplicate subscriptions
- Production-quality error handling

Tests: 27/27 passing including edge case scenarios
2025-12-22 16:18:15 +00:00
Alejandro Gómez
3346a2077d feat(req): add --view flag for list/compact display mode
- Add -v, --view <list|compact> flag to req command
- Remove UI toggle controls from ReqViewer header
- View mode is now set via command flag instead of runtime toggle
- Update man page with new flag documentation and example

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-21 20:29:32 +01:00
Alejandro Gómez
f4d0e86f09 feat: complete spellbook UX overhaul with enhanced state tracking
- Redesign SpellbookDropdown with clear status indicators (ownership, storage)
- Add SpellbookStatus component showing you/other and local/published/network
- Enhance activeSpellbook type with source, localId, isPublished fields
- Fix PublishSpellbook action to properly yield events (caller handles side-effects)
- Add k tags extraction from REQ windows for kind-based filtering/discovery
- Update terminology from "Layout" to "Spellbook" consistently
- Add comprehensive tests for k tags and source tracking

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-21 19:19:24 +01:00
Alejandro Gómez
21335a5849 WIP 2025-12-21 18:08:10 +01:00
Alejandro Gómez
d59a7aee8b feat: improve spellbook UX with active tracking and update capability
- Add activeSpellbook to GrimoireState
- Display active spellbook title in header with clear button
- Add 'Update Layout' and 'Save as new' to SpellbookDropdown
- Highlight active spellbook in dropdown list
- Add 'Manage Spells' link to dropdown
- Refine dropdown styles (muted hover, no accent color)
2025-12-21 18:08:10 +01:00
Alejandro Gómez
7d72aec83e feat: improve spellbook UX with BookHeart icon and Preview mode
- Update spellbook icon to BookHeart across the app
- Implement Preview mode with routing /:actor/:identifier
- Add Preview banner in Home component with Apply/Discard actions
- Add Preview and Share buttons to Spellbook renderers
- Clean up unused imports
2025-12-21 18:08:10 +01:00
Alejandro Gómez
5d9ff3cf56 feat: implement Nostr Wallet Connect (NWC)
- Add applesauce-wallet-connect dependency
- Create WalletService for managing NWC connection
- Create WalletViewer component for UI
- Register 'wallet' command
- Fix build errors in spell/spellbook components due to applesauce-actions upgrade
2025-12-21 18:07:55 +01:00
Alejandro Gómez
2987a37e65 feat: spells 2025-12-20 14:25:40 +01:00
Alejandro Gómez
812b719ea0 feat: debug command, simplify state 2025-12-18 23:32:00 +01:00
Alejandro Gómez
3f45cebaee feat: more flexible -e flag 2025-12-18 17:08:11 +01:00
Alejandro Gómez
f6f813d382 refactor: collapse migrations 2025-12-18 16:49:24 +01:00
Alejandro Gómez
a6650ff6e1 fix: type errors 2025-12-18 16:00:56 +01:00
Alejandro Gómez
3fba62b316 ui: simpler streams 2025-12-18 15:29:01 +01:00
Alejandro Gómez
4db7e9690c refactor(layouts): simplify to global config + preserve windows + add UI dropdown
Simplified layout system based on user feedback:

**1. Global Layout Config (Simpler)**
- Moved layoutConfig from per-workspace to global state
- One configuration applies to all workspaces (easier to understand)
- Updated state migration v8→v9 to move config to global level
- Updated WorkspaceSettings UI to edit global config
- Renamed updateWorkspaceLayoutConfig → updateLayoutConfig

**2. Preserve Extra Windows (Fixed Bug)**
- Fixed applyPresetToLayout to keep windows beyond preset slots
- When applying 4-window grid to 6 windows, windows 5-6 are preserved
- Extra windows stacked vertically on right side (70/30 split)
- No more window loss when applying presets

**3. Layout Dropdown in TabBar (Better UX)**
- Added dropdown menu next to workspace tabs
- Shows all available presets with icons (Grid2X2, Columns2, Split)
- Displays window requirements and availability
- Disables presets that need more windows than available
- One-click preset application with toast feedback
- More accessible than /layout command

All tests passing (457 passed). State migration handles v6→v7→v8→v9 correctly.

Generated with [Claude Code](https://claude.com/claude-code)
2025-12-18 12:24:00 +01:00
Alejandro Gómez
52f39a8073 feat: add layout presets system with /layout command
Phase 3 implementation:
- Created layout-presets.ts with 3 built-in presets (side-by-side, main-sidebar, grid)
- Implemented fillLayoutTemplate() for recursive template filling with window IDs
- Added collectWindowIds() for depth-first traversal of layout trees
- Created applyPresetToLayout() to reorganize existing windows

- Created layout-parser.ts for /layout command argument parsing
- Added layout command to man.ts with documentation and examples

- Built LayoutViewer component with:
  * Visual preset gallery with diagrams
  * Window count validation
  * Apply preset functionality
  * Error handling for insufficient windows
  * Command-line preset specification support

- Wired LayoutViewer into WindowRenderer with lazy loading
- Added "layout" to AppId type definition
- Exposed applyPresetLayout in useGrimoire hook

Presets allow users to quickly reorganize multiple windows into
common layouts: 50/50 splits, 70/30 main+sidebar, or 2×2 grids.

Generated with [Claude Code](https://claude.com/claude-code)
2025-12-18 12:13:28 +01:00
Alejandro Gómez
cc6f8d646b feat(layouts): Phase 1 - workspace layout configuration system
Add per-workspace layout configuration with smart auto-balancing:

**Core Changes:**
- Add LayoutConfig interface to Workspace type with insertionMode, splitPercentage, insertionPosition
- Create layout-utils.ts with smart direction algorithm that auto-balances horizontal/vertical splits
- Update addWindow() to use workspace layoutConfig instead of hardcoded values
- Migrate state from v7→v8, adding layoutConfig to all workspaces with smart defaults

**Smart Direction Algorithm:**
- Analyzes layout tree to count horizontal vs vertical splits
- Automatically balances by favoring the less-common direction
- Defaults to horizontal (row) for first split
- Provides foundation for "Balanced (auto)" insertion mode

**Testing:**
- Add 30 comprehensive tests for layout-utils.ts (tree analysis, smart direction, window insertion)
- Add 30 tests for logic.ts addWindow() with different layout configs (row/column/smart modes)
- Update migration tests to verify v6→v7→v8 and v7→v8 paths

**Migration:**
- v7→v8 adds layoutConfig with defaults: smart mode, 50% split, second position
- All existing workspaces automatically get smart auto-balancing behavior

Implements orthogonal design: layout behavior = workspace settings (not command flags)
2025-12-18 11:59:45 +01:00
Alejandro Gómez
a7dd4635dc feat: kind schemas and better man pages 2025-12-18 10:18:53 +01:00
Alejandro Gómez
97c89142ae wip: live video events 2025-12-17 11:44:12 +01:00
Alejandro Gómez
63121f6233 feat: add title command line flag 2025-12-16 20:50:03 +01:00
Alejandro Gómez
24d2640774 feat: since and until 2025-12-16 14:15:16 +01:00
Alejandro Gómez
a7059c8e8f feat: outbox relay selection 2025-12-16 12:55:07 +01:00
Alejandro Gómez
f4a0d5e669 feat: -P filter tag 2025-12-15 23:42:19 +01:00
Alejandro Gómez
e5c871617e chore: cleanup, a11y and state migrations 2025-12-14 16:32:45 +01:00
Alejandro Gómez
6f17415340 feat: NIPS command 2025-12-14 15:14:14 +01:00
Alejandro Gómez
c287cff413 refactor: remove unused commands 2025-12-13 22:58:22 +01:00
Alejandro Gómez
d877e51317 feat: editable commands 2025-12-13 22:53:27 +01:00
Alejandro Gómez
8e92a8ebfb feat: filter by arbitrary tag, relay state improvements 2025-12-13 15:49:04 +01:00
Alejandro Gómez
6568daf944 wip: relay pool view and auth 2025-12-13 15:06:05 +01:00
Alejandro Gómez
32160480e2 feat: kind links 2025-12-12 22:22:28 +01:00
Alejandro Gómez
08db0eff5a feat: dynamic titles, event footer 2025-12-11 22:50:09 +01:00
Alejandro Gómez
54ee43bdaf feat: comma-separated flags to REQ 2025-12-11 16:57:40 +01:00