- UserName and Nip05 components now receive resolvedPubkey instead of pubkey
- Fixes PROFILE $me windows not updating when account changes
- Now properly reactive to account switches
- Matches REQ window behavior for $me alias resolution
- Include explicit permission list in nostrconnect:// URI
- Request signing permissions for common event kinds (notes, reactions, zaps, etc.)
- Request NIP-04 and NIP-44 encryption/decryption permissions
- Improves compatibility with Amber and other NIP-46 signers
- Follows NIP-46 specification for permission grants
- Import relay pool singleton from services
- Pass pool to NostrConnectSigner in both QR and manual modes
- Fixes 'Missing subscriptionMethod' error when connecting to remote signer
- Enables proper relay communication for NIP-46 connections
- Install qrcode.react for QR code generation
- Default to QR code mode for easier mobile connection
- Generate nostrconnect:// URI with app metadata (Grimoire name and URL)
- Display QR code that remote signers can scan
- Auto-wait for remote signer connection with 5-minute timeout
- Add toggle to switch between QR and manual bunker:// input
- Show loading states during QR generation and connection waiting
- Include popular relays (relay.nsec.app, relay.damus.io, nos.lol)
- Support scanning from Amber, nsec.app, and other NIP-46 apps
- Add Remote tab to LoginDialog for bunker:// URI input
- Integrate NostrConnectSigner from applesauce-signers
- Support bunker:// URI format for connecting to remote signers
- Add "Remote" badge with Link2 icon for nostr-connect accounts
- Update both user menu and account manager to show remote badge
- Enable signing with mobile apps, hardware wallets, and other NIP-46 signers
- Add UserAvatar component back to user menu trigger button
- Show user's profile picture avatar when logged in
- Fall back to User icon when not logged in
- Remove space-between in AccountManager badge layout
- Keep space-between in user menu dropdown for better visual hierarchy
- Create alert-dialog UI component based on radix-ui
- Replace window.confirm in AccountManager with AlertDialog
- Install @radix-ui/react-alert-dialog dependency
- Add confirmation dialog for account removal with proper styling
- Remove login and logout commands from command palette
- Create LoginDialog component with tabbed interface (Read-only and Extension)
- Integrate LoginDialog with user menu and AccountManager
- Update badge styling to be more subtle with outline variant
- Remove avatars from user menu and account manager
- Update account layout to use space-between for name and badge
- Remove unused LoginHandler and LogoutHandler components
Account Management Improvements:
- Auto-switch to another account when logging out (instead of leaving no active account)
- If other accounts exist, automatically activates the first one before removing current
Account Type Badges:
- Added visual badges to distinguish account types in user menu
- 'Read-only' badge with eye icon for read-only accounts
- 'Extension' badge with puzzle icon for extension accounts
- Badges appear in both user menu and account manager
Auth Prompt Protection:
- Read-only accounts no longer receive relay auth prompts
- Auto-reject all auth challenges when active account is read-only
- Prevents confusing prompts for accounts that cannot sign
Type Safety:
- Proper TypeScript casting for account.constructor.type access
- Used 'as unknown as { type: string }' pattern for type narrowing
The registerCommonAccountTypes() from applesauce-accounts already
registers a ReadOnlyAccount with type 'readonly', causing a duplicate
registration error. Changed our custom ReadOnlyAccount type to
'grimoire-readonly' to avoid the conflict.
- Updated AccountCard component to use IAccount<ISigner, unknown, unknown>
- Updated switchAccount function to use proper type
- Added ISigner import to both files
- Maintains type safety while supporting all account types
Commands Added:
- /accounts: Opens account management window showing all accounts
- /logout: Removes the active account
- /logout --all: Removes all accounts
Components Created:
1. AccountManager (src/components/AccountManager.tsx)
- Lists all accounts with avatars, names, and public keys
- Shows active account with checkmark indicator
- Switch account button for inactive accounts
- Remove account button with confirmation dialog
- Add account button that opens command launcher
- Empty state with helpful instructions
2. LogoutHandler (src/components/LogoutHandler.tsx)
- Handles logout and logout-all actions
- Shows confirmation dialog for logout-all
- Displays toast notifications for success/error
- Auto-closes after execution
Type System Updates:
- Added "account-manager" and "logout-handler" to AppId type
- Integrated both components with WindowRenderer routing
Man Pages:
- Added comprehensive documentation for both commands
- Includes usage examples and related commands
- Proper categorization as System commands
- Added support for displaying all accounts in user menu
- Implemented account switching via dropdown menu
- Show active account with checkmark indicator
- List other accounts in "Switch Account" section
- Added "Add account" button that opens command launcher
- Users can now switch between multiple accounts seamlessly
- Each account displays avatar, name, and NIP-05 identifier
UI Improvements:
- Active account shown at top with green checkmark
- Other accounts listed below with avatars
- "Add account" button for adding new accounts
- Maintains existing relays and logout functionality
Technical Changes:
- Use useAppShell() hook for opening command launcher
- Use IAccount type instead of BaseAccount for type safety
- Filter accounts list to separate active from other accounts
- Observable subscriptions for reactive account updates
- Created LoginHandler component to process /login command results
- Handles three action types:
- add-account: Adds account to AccountManager and sets as active
- error: Shows error toast with descriptive message
- open-dialog: Shows info toast (dialog UI coming in next phase)
- Uses sonner for toast notifications with success/error feedback
- Integrated with WindowRenderer routing system
- Component auto-executes action on mount via useEffect
Files:
- src/components/LoginHandler.tsx (new)
- src/components/WindowRenderer.tsx (updated)
Add login command to enable account management via command palette.
Supports read-only accounts (npub, nip-05, hex, nprofile) and will
support browser extension (NIP-07) via dialog.
Changes:
- Added login command to man pages with full documentation
- Command parser uses createAccountFromInput() from login-parser
- Supports direct login: `login npub1...` or `login alice@nostr.com`
- Opens dialog when called without arguments: `login`
- Added "login-handler" to AppId type
Example usage:
- `login` - Open login dialog
- `login npub1abc...` - Add read-only account from npub
- `login alice@nostr.com` - Add read-only account from NIP-05
Build successful.
Register the ReadOnlyAccount type with the global AccountManager instance
to enable read-only account creation and management.
Changes:
- Fixed ReadOnlyAccount constructor to match IAccountConstructor signature
- Constructor now takes (pubkey, signer) instead of (pubkey, metadata)
- Added createWithMetadata() helper for factory methods
- Updated all factory methods to use createWithMetadata()
- Registered ReadOnlyAccount type in accounts service
All tests pass (48 tests), lint clean, build successful.
Update ReadOnlyAccount to extend BaseAccount from applesauce-accounts
library for proper integration with AccountManager.
Changes:
- ReadOnlyAccount now extends BaseAccount<ReadOnlySigner, void, ReadOnlyMetadata>
- Added ReadOnlySigner class that implements ISigner interface
- ReadOnlySigner throws errors on all signing operations (sign, encrypt, decrypt)
- Updated metadata structure to use ReadOnlyMetadata interface
- Updated tests to match new implementation (metadata now optional)
- Fixed TypeScript strict mode issues in tests
All tests pass (48 tests), lint clean, build successful.
Add support for read-only Nostr accounts that can be used for viewing
content without signing capability.
- ReadOnlyAccount class with factory methods for:
- npub (NIP-19 encoded public key)
- nprofile (NIP-19 encoded profile with relay hints)
- hex (64-character hexadecimal public key)
- nip05 (user@domain.com identifier)
- Login parser to detect input type and create accounts from various
formats (npub, nprofile, hex, nip-05)
- Comprehensive test coverage (47 tests) for all account creation
methods and input detection
All tests pass, lint clean, build successful.
Add detailed planning documents for multi-account, multi-login method
support in Grimoire:
- MULTI_ACCOUNT_LOGIN_PLAN.md: Complete implementation plan covering
read-only accounts (npub/nip-05/hex/nprofile), NIP-07 browser
extensions, NIP-46 remote signers (bunker URLs), and NIP-55 Android
signers. Includes current state analysis, architecture design,
login flows, commands specification, UI components, and 4-phase
implementation roadmap.
- MULTI_ACCOUNT_IMPLEMENTATION_PHASES.md: Step-by-step implementation
guide with specific files to create/modify for each phase, testing
checklists, and timeline estimates. Breaks down into Phase 1
(read-only accounts), Phase 2 (management UI), Phase 3 (NIP-46),
and Phase 4 (NIP-55).
- MULTI_ACCOUNT_ARCHITECTURE.md: Visual architecture diagrams and
data flow documentation. Covers system architecture, state
management, persistence layer, component hierarchy, security
boundaries, and performance considerations.
The plan integrates with existing applesauce-accounts AccountManager
and applesauce-signers library, with special focus on NIP-46 relay
pool integration for remote signing.
Previously, the auth dialog would appear whenever a relay sent an AUTH
challenge, even when there was no active user session. This was confusing
for users who weren't logged in.
Now the shouldPromptAuth() method checks if there's an active account
before returning true, ensuring auth dialogs only appear for logged-in users.
Co-authored-by: Claude <noreply@anthropic.com>
The MediaPlaceholder component in Link.tsx had text-sm hardcoded,
causing it to appear larger than surrounding text in compact contexts.
Now all placeholder components (Link, Gallery, Mention) consistently
inherit font size from their parent.
Co-authored-by: Claude <noreply@anthropic.com>
* feat: add copy button for NIP markdown
- Add copy button to WindowToolbar for regular NIPs (appId: "nip")
- Button appears in window toolbar next to edit button
- Uses Copy/CopyCheck icons from lucide-react
- Fetches NIP content via useNip hook
- Shows toast notification on successful copy
- Add copy button to CommunityNIPDetailRenderer for community NIPs (kind 30817)
- Button appears in header next to title
- Copies event.content (markdown) to clipboard
- Uses same Copy/CopyCheck icon pattern
- Shows toast notification on successful copy
Both implementations use the existing useCopy hook for state management
and maintain consistent styling with other toolbar buttons.
* refactor: use Button component and remove misleading shortcuts
- Replace native button elements with Button component from shadcn/ui
- Use variant="ghost" and size="icon" for consistent styling
- Apply h-8 w-8 classes for uniform button sizing
- Remove manual className styling in favor of component variants
- Remove misleading keyboard shortcut hints from button titles
- Changed "Edit command (Cmd+E)" to "Edit command"
- Changed "Close window (Cmd+W)" to "Close window"
- These shortcuts don't actually work, so they were misleading
- Clean up imports and formatting
- Format lucide-react imports across multiple lines
- Add Button component import
- Run prettier for consistent code style
* refactor: use link variant and remove size class overrides
- Change all Button components from variant="ghost" to variant="link"
- Remove className="h-8 w-8" overrides to use default Button sizing
- Maintains size="icon" for proper icon button behavior
- Applies to WindowToolbar and CommunityNIPDetailRenderer
* style: add muted color to window toolbar icon buttons
- Add className="text-muted-foreground" to all Button components in WindowToolbar
- Improves visual contrast for toolbar buttons
- Applies to Edit, Copy NIP, More actions, and Close window buttons
---------
Co-authored-by: Claude <noreply@anthropic.com>
The $contacts alias in REQ command was broken due to an object reference issue.
The contact list pointer object was being created inline on every render, causing
useNostrEvent to treat it as a new pointer each time and disrupting the subscription.
Now properly memoize the pointer object so it only changes when accountPubkey or
needsAccount actually changes, ensuring stable subscriptions and proper contact
list retrieval.
Fixes the $contacts alias resolution in REQ commands.
Co-authored-by: Claude <noreply@anthropic.com>
* feat: add clickable example commands to welcome screen
Add 4 example commands to the welcome splash screen to make the client
more friendly on cold start and to new users:
- nip 29: View relay-based groups spec
- profile verbiricha@habla.news: Explore a Nostr profile
- req -k 1 -l 20: Query recent notes
- nips: Browse all NIPs
Commands are clickable and execute directly, opening the appropriate
window without requiring users to type or use the command palette.
Changes:
- Update GrimoireWelcome component with EXAMPLE_COMMANDS constant
- Add onExecuteCommand prop to handle direct command execution
- Update WorkspaceView to pass handleExecuteCommand callback
- Use parseAndExecuteCommand from command-parser for execution
* fix: lint
---------
Co-authored-by: Claude <noreply@anthropic.com>
because you wanted to know what it feels like:
nostr:nevent1qvzqqqqqqypzqla9dawkjc4trc7dgf88trpsq2uxvhmmpkxua607nc5g6a634sv5qyd8wumn8ghj7urewfsk66ty9enxjct5dfskvtnrdakj7qgmwaehxw309a6xsetxdaex2um59ehx7um5wgcjucm0d5hsqgxwfrfg9kvh99tj6adq5v2spwm9vhnp76zljuseqcpz4nd7w9d6pg000a03
**Compact Relay Item Display:**
- Removed left-side inbox/outbox count indicators (were causing misalignment)
- Replaced "EOSE" text with checkmark icon (✓)
- Event count shown as [N] badge (only if > 0)
- Auth icon now always visible (even for unauthenticated relays)
- Clean right-side layout: [count] [✓] [auth] [wifi]
**Always-Visible Auth Status:**
- Modified getAuthIcon() to always return an icon (never null)
- Unauthenticated relays show subtle shield icon (muted-foreground/40)
- Provides at-a-glance view of auth status for all relays
- Label: "No Authentication Required" for clarity
**Rich Hover Tooltips:**
- Comprehensive tooltip shows all relay details on hover
- Displays: connection status, auth status, subscription state, event count
- Shows inbox/outbox counts when available (moved from inline display)
- Formatted as structured table for easy scanning
- Positioned on left side to avoid blocking content
**Benefits:**
✅ Perfect alignment (no variable-width counts on left)
✅ Cleaner, more scannable visual design
✅ All information still accessible via hover
✅ Consistent icon count (always 2-4 icons per relay)
✅ Easy to spot EOSE status at a glance (green checkmark)
All 639 tests passing.
**CRITICAL FIX for EOSE detection:**
**The Problem:**
- Used pool.subscription(relays, filters) which creates a RelayGroup
- RelayGroup tracks per-relay EOSE internally but only emits ONE "EOSE" when ALL relays finish
- This caused:
1. EOSE indicators taking forever to appear (waiting for slowest relay)
2. REQ stuck in LOADING state when fast relays finish but slow relays never do
3. No way to show per-relay EOSE status accurately
**The Solution:**
Subscribe to each relay individually using pool.relay(url).subscription():
- Each relay subscription emits its own EOSE immediately when that relay finishes
- We track per-relay EOSE in relayStates map with accurate timing
- Overall EOSE is derived when ALL relays reach terminal state (eose/error/disconnected)
- EOSE indicators now appear immediately as each relay finishes
**Technical Details:**
- Changed from: pool.subscription(relays, filters)
- Changed to: relays.map(url => pool.relay(url).subscription(filters))
- Added eoseReceivedRef to track overall EOSE in closures
- Mark specific relay as EOSE when that relay emits "EOSE"
- Calculate overall EOSE when all relays in terminal states
- Use url from subscription context (more reliable than event._relay)
**Benefits:**
✅ Instant per-relay EOSE indicators (no waiting for slowest relay)
✅ Accurate relay state tracking (each relay independent)
✅ REQ transitions to LIVE/CLOSED as soon as all relays finish
✅ Better user feedback (see which relays are done vs still loading)
All 639 tests passing.
**Normalize All Relay URLs:**
- Added trailing slashes to AGGREGATOR_RELAYS constants
- Ensures consistency with RelayStateManager's normalization
- Fixes fallback relay connection state tracking issue
- All hardcoded relay URLs now match normalized keys in relayStates
**Reorganize Relay Item UI:**
- Removed type indicator icons (LinkIcon/Sparkles/Inbox) from individual relay items
- Strategy type is already shown in header, no need to repeat per-item
- Moved inbox/outbox indicators from right side to left side of relay URL
- Left side now shows: inbox count (Mail icon) and/or outbox count (Send icon)
- Right side shows: event count, EOSE indicator, auth status, connection status
- Cleaner, more semantic layout with better visual hierarchy
**Why This Matters:**
The relay URL normalization fix ensures that fallback relays (AGGREGATOR_RELAYS)
now show accurate connection state in the UI. Previously, the non-normalized
URLs couldn't match keys in relayStates, making them appear disconnected even
when connected. This was the root cause of the "fallback relays not tracking"
issue.
All 639 tests passing.
Critical fixes for ReqViewer relay state accuracy:
1. **URL Normalization Fix** (fixes mismatch with CONN):
- Added normalizeRelayURL to normalize all relay URLs in finalRelays
- RelayStateManager normalizes URLs (adds trailing slash, lowercase) but
finalRelays did not, causing lookup failures in relayStates
- Now normalizedRelays is used for all state lookups and passed to
useReqTimelineEnhanced to ensure consistency
- This fixes the bug where ReqViewer showed different connected relay
counts than CONN viewer
2. **EOSE Indicator**:
- Added back EOSE indicator to relay dropdown (was removed in UI redesign)
- Shows subtle "EOSE" text when relay has sent End of Stored Events
- Includes tooltip explaining "End of stored events received"
3. **Muted Icons** (per user request for subtlety):
- Type indicators: blue-500/purple-500 → muted-foreground/60
- Strategy header icons: all → muted-foreground/60
- Section headers: green-500 → muted-foreground
- Connection icons: green-500/yellow-500/red-500 → /70 opacity variants
- Auth icons: same color reduction for consistency
- Maintains semantic meaning while reducing visual noise
All 639 tests passing.
Critical Edge Case Fix:
Previously, when all relays disconnected before sending EOSE, the state
remained stuck in LOADING because overallEoseReceived stayed false.
Solution: Check if all relays are in terminal states
- Terminal states: eose, error, or disconnected
- If all terminal AND no overall EOSE yet, derive state from events:
* No events → FAILED
* Has events, all disconnected, streaming → OFFLINE
* Has events, all disconnected, non-streaming → CLOSED
* Some active, some terminal → PARTIAL
New Test Coverage (5 tests):
1. All relays disconnect before EOSE, no events → FAILED
2. All relays disconnect before EOSE, with events (streaming) → OFFLINE
3. All relays disconnect before EOSE, with events (non-streaming) → CLOSED
4. Some EOSE, others disconnect before EOSE → PARTIAL
5. Mix of EOSE and errors, all terminal → PARTIAL
This fixes the user-reported issue where disconnected relays show LOADING
instead of transitioning to appropriate terminal state.
Tests: 639/639 passing (added 5 new edge case tests)
Replace `break-all` with `break-words overflow-x-auto` to improve
text wrapping behavior in command preview areas. This prevents
words from breaking awkwardly in the middle while still allowing
long commands to be displayed properly.
Changes:
- CreateSpellDialog.tsx: Fixed command preview overflow
- SpellDialog.tsx: Fixed command display overflow
- SpellRenderer.tsx: Fixed detail view command overflow
The new approach breaks at word boundaries when possible and
provides horizontal scrolling for exceptionally long commands.
State Tracking Fixes:
- Sync connection state for ALL relays in query, not just initialized ones
- Defensively initialize missing relay states during sync
- Handle events from unknown relays (defensive initialization)
- Add debug console logs to track state transitions
Relay Type Indicators:
- Explicit relays: Blue link icon (relays specified directly)
- Outbox relays: Purple sparkles (NIP-65 selected)
- Fallback relays: Gray inbox icon (fallback when outbox incomplete)
- Each type has tooltip explaining source
This should fix:
- "0/4 relays but events coming in" bug
- "Stuck in LOADING" when events are arriving
- Missing visibility for relay source types
Tests: 634/634 passing
Previously only showed relays from NIP-65 reasoning array, missing fallback
relays. Now always iterates over finalRelays (actual queried relays) and
looks up NIP-65 info if available.
Fixes:
- Fallback relays now visible in dropdown
- Relays show connection/subscription status regardless of source
- NIP-65 info (inbox/outbox counts) shown when available
- Works for outbox, fallback, and explicit relay configurations
Tests: 634/634 passing
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