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.
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.
- 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.
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
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
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
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
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
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
- 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.
- 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