* feat: add NIP-85 Trusted Assertions feed & detail renderers
Add support for NIP-85 Trusted Assertion events (kinds 30382-30385) and
the Trusted Provider Declaration (kind 10040) with kind constants,
helper library, and feed + detail renderers.
- Add kind entries for 10040, 30382, 30383, 30384, 30385 to EVENT_KINDS
- Create src/lib/nip85-helpers.ts with cached helpers for parsing
assertion data (user, event, address, external) and provider lists
- Create shared TrustedAssertionRenderer for all 4 assertion kinds with
rank bar, subject display, and compact metrics preview
- Create TrustedAssertionDetailRenderer with full metrics table,
rank visualization, topics, and raw tag fallback
- Create TrustedProviderListRenderer/DetailRenderer for kind 10040
with provider table and encrypted entries indicator
- Register all renderers in kinds/index.tsx
https://claude.ai/code/session_01XjwLaShFSVPR5gNA7iUjuB
* refactor: extract NIP-73 helpers and shared ExternalIdentifierDisplay
Move getExternalIdentifierIcon() and getExternalIdentifierLabel() from
nip22-helpers.ts into a new nip73-helpers.ts since they are NIP-73
utilities, not NIP-22 specific. Add inferExternalIdentifierType() and
getExternalIdentifierHref() helpers.
Create shared ExternalIdentifierDisplay components (inline + block
variants) that use proper NIP-73 type-specific icons (Globe for web,
BookOpen for ISBN, Podcast for podcasts, Film for ISAN, etc.) instead
of a generic ExternalLink icon.
- Kind 1111 renderer now uses ExternalIdentifierInline for root scope
- Kind 30385 assertion renderer uses ExternalIdentifierInline (feed)
and ExternalIdentifierBlock (detail) for NIP-73 subjects
- nip22-helpers.ts re-exports from nip73-helpers for compatibility
https://claude.ai/code/session_01XjwLaShFSVPR5gNA7iUjuB
* fix: UI polish for NIP-85 renderers
Address review feedback across all NIP-85 components:
Icon: Replace BarChart3 with ShieldCheck for assertion kind constants
(30382-30385) — communicates "verified trust data" vs generic analytics.
Feed renderer: Subject is now the visual anchor (ClickableEventTitle)
with kind label as a small outline Badge above it. Rank bar widened to
w-32, color-coded green/yellow/red by score threshold. Fix "Zaps Recd"
abbreviation to "Zaps In".
Detail renderer: Metrics grouped into Activity, Zaps, Moderation
sections with uppercase section headers. Kind 30384 addresses now
show "Kind X by <UserName> / d-tag" instead of raw hex. ExternalMetrics
type badges now show NIP-73 icons + friendly labels (getExternalTypeLabel)
instead of raw k-tag values like "podcast:item:guid".
Provider list: Feed uses compact Badge for kind:tag + count summary.
Detail uses stacked cards instead of 3-column table for narrow panels.
ExternalIdentifierBlock: Linked blocks now show dotted underline on
label + ExternalLink icon for clear click affordance.
Add getExternalTypeLabel() to nip73-helpers for k-value display names.
https://claude.ai/code/session_01XjwLaShFSVPR5gNA7iUjuB
* fix: use Progress component for rank bars, consistent sats formatting
Replace custom rank bar divs with the existing Progress UI component
(with new indicatorClassName prop for color-coded fills).
Make sat amount displays consistent with zap receipt renderer pattern:
value and "sats" unit are rendered as separate elements — numeric value
in font-medium, unit in smaller muted text — matching how Kind9735
displays amounts across the app.
https://claude.ai/code/session_01XjwLaShFSVPR5gNA7iUjuB
* fix: use RelayLink for relay URLs, remove redundant kindTag, add relay hints
Provider list renderers now use RelayLink instead of raw relay URL
strings — shows favicon, insecure ws:// warning, opens relay detail
on click.
Remove kindTag display from provider cards — it's an internal
protocol detail redundant in the UI context.
Pass relay hints from provider entries to UserName so profiles can
be fetched from the relay the provider actually publishes to.
Add UserName relayHints prop (forwarded to useProfile).
Add RelayLink and UserName to Shared Components section in CLAUDE.md
so they're consistently used across the codebase.
https://claude.ai/code/session_01XjwLaShFSVPR5gNA7iUjuB
* docs: expand shared components section in CLAUDE.md
Add BaseEventContainer, ClickableEventTitle, RichText, QuotedEvent,
and CustomEmoji to the shared components reference. These are the
core building blocks used across all kind renderers — documenting
them prevents re-implementation and ensures consistent patterns.
https://claude.ai/code/session_01XjwLaShFSVPR5gNA7iUjuB
* docs: trim shared components list in CLAUDE.md
Remove BaseEventContainer and QuotedEvent — these are internal
patterns that kind renderer authors already know from context,
not general-purpose components that get misused or forgotten.
https://claude.ai/code/session_01XjwLaShFSVPR5gNA7iUjuB
* feat: show metric type labels in trusted assertion feed view
Add Label components to the assertion feed renderer so you can see at a
glance which metrics an assertion carries (Followers, Posts, Zaps, etc.)
instead of just numeric values. Also swap Badge → Label for the kind
indicator for visual consistency.
Replace hardcoded green/yellow/red rank colors with theme variables
(success/warning/destructive) in both feed and detail renderers so the
rank bar works correctly across all themes.
Add Label to CLAUDE.md shared components list (22 imports across the
codebase).
https://claude.ai/code/session_01XjwLaShFSVPR5gNA7iUjuB
* feat: show provider kind tag in trusted provider list renderers
Add Label with formatKindTag() to both feed and detail views so each
provider row shows what it provides (e.g. "User Assertion: Rank").
Also swap Badge → Label for consistency with the assertion renderers.
https://claude.ai/code/session_01XjwLaShFSVPR5gNA7iUjuB
* fix: stabilize relayHints in useProfile to prevent fetch abort loop
relayHints was used directly in the useEffect dependency array, so
callers passing a new array literal (e.g. [p.relay]) on every render
caused the effect to re-run each cycle — aborting the previous network
fetch before it could complete. The IndexedDB fast-path masked this in
the feed view (profiles already cached), but the detail view showed
raw pubkeys because profiles were never fetched from the network.
Wrap relayHints in a JSON.stringify-based useMemo (same pattern as
useStableArray) so the effect only re-runs when the actual relay
values change.
https://claude.ai/code/session_01XjwLaShFSVPR5gNA7iUjuB
---------
Co-authored-by: Claude <noreply@anthropic.com>
* 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>
- Replace mounted boolean flag with AbortController pattern
- Check abort signal before initiating database writes
- Proper cleanup on unmount/pubkey change
This prevents stale data from being written to IndexedDB when:
- Component unmounts during async operations
- Pubkey changes while a fetch is in progress