Extend relay filter chunking to route #p tags to inbox/read relays,
matching the existing outbox/write routing for authors. Remove debug
console.log statements across the codebase while preserving error-level
logging. Delete unused logger utility. Expand test coverage for all
chunking scenarios.
* refactor: remove unused compactModeKinds from app state
The compactModeKinds feature was partially implemented but never used:
- Settings dialog allowed configuration but the value was never consumed
- REQ viewer uses --view flag for compact mode, not this state value
Removed:
- compactModeKinds from GrimoireState type and initial state
- setCompactModeKinds logic function and state hook callback
- Compact Events section from SettingsDialog (now shows placeholder)
- compactModeKinds from AppearanceSettings in settings service
- Migration now strips compactModeKinds if present instead of adding it
https://claude.ai/code/session_01DiWUxiS5BAzU9mrKvCUuMW
* chore: delete unused SettingsDialog component
The SettingsDialog was imported but never used - the Settings menu
item opens a window via addWindow("settings", ...) instead.
https://claude.ai/code/session_01DiWUxiS5BAzU9mrKvCUuMW
* refactor: simplify settings service to only used settings
Removed unused settings that were defined but never consumed:
- RelaySettings (fallbackRelays, discoveryRelays, outbox*, etc.)
- PrivacySettings (shareReadReceipts, blurWalletBalances, etc.)
- DatabaseSettings (maxEventsCached, autoCleanupDays, etc.)
- NotificationSettings (enabled, notifyOnMention, etc.)
- DeveloperSettings (debugMode, showEventIds, logLevel, etc.)
- Most of AppearanceSettings (theme, fontSizeMultiplier, etc.)
- Most of PostSettings (defaultRelayMode, customPostRelays)
Only kept settings that are actually used:
- post.includeClientTag
- appearance.showClientTags
Also simplified useSettings hook to match.
https://claude.ai/code/session_01DiWUxiS5BAzU9mrKvCUuMW
---------
Co-authored-by: Claude <noreply@anthropic.com>
* feat: add "+ new tab" option to move window menu
Adds a "+ New tab" option at the top of the "Move to tab" submenu in the
window toolbar. This allows users to create a new tab and move the current
window to it in a single action, streamlining the workflow.
Changes:
- Add moveWindowToNewWorkspace function in logic.ts
- Expose moveWindowToNewWorkspace through useGrimoire hook
- Always show "Move to tab" menu (not just when multiple tabs exist)
- Add "+ New tab" option with Plus icon at top of submenu
https://claude.ai/code/session_01Hy2vYBooPyrF7ZJCodcCav
* refactor: simplify new tab menu item copy
- Change menu text from "+ New tab" to "New" (icon already shows +)
- Simplify toast to just "Moved to new tab"
https://claude.ai/code/session_01Hy2vYBooPyrF7ZJCodcCav
---------
Co-authored-by: Claude <noreply@anthropic.com>
* 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>
* 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>
* 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>
- Introduce activeGrimoireStateAtom to handle switching between persistent and temporary state
- Ensure setState and all logic callbacks are stable across re-renders
- Correctly type Jotai updaters to avoid implicit any errors
- Add temporaryStateAtom to track in-memory sessions
- Update useGrimoire to handle conditional state management
- Previews and Direct links now use temporary state, preserving dashboard
- Add Apply and Discard logic to persist or revert temporary sessions
- 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)
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)
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)
Add per-workspace layout configuration UI with visual controls:
**Core Changes:**
- Add updateWorkspaceLayoutConfig() function to logic.ts for updating workspace layout settings
- Expose updateWorkspaceLayoutConfig in useGrimoire hook
**UI Components:**
- Create WorkspaceSettings dialog with three sections:
* Insertion Mode selector (Balanced/Horizontal/Vertical) with icons
* Split Percentage slider (10-90%) with real-time preview
* Insertion Position toggle (Left-Top/Right-Bottom)
- Add settings icon (SlidersHorizontal) to workspace tabs that appears on hover
- Settings button opens configuration dialog for that workspace
**UX Details:**
- Settings icon only visible on hover to reduce visual clutter
- Clear visual feedback for selected options with primary color highlights
- Preview section shows current configuration in plain language
- Reset to Defaults button restores smart mode defaults
- Prevents workspace switch when clicking settings icon
**Icons Used:**
- Sparkles: Balanced (smart auto-balancing)
- SplitSquareHorizontal: Horizontal splits
- SplitSquareVertical: Vertical splits
- SlidersHorizontal: Settings access
Each workspace can now have independent layout behavior configured through an intuitive UI.
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)