* 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>
* 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>
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>
- 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>
- 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)
- 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
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 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)