3.6 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Overview
Grimoire is a Nostr protocol explorer and developer tool. It's a tiling window manager interface where each window is a Nostr "app" (profile viewer, event feed, NIP documentation, etc.). Commands are launched Unix-style via Cmd+K palette.
Stack: React 19 + TypeScript + Vite + TailwindCSS + Jotai + Dexie + Applesauce
Core Architecture
Dual State System
UI State (src/core/state.ts + src/core/logic.ts):
- Jotai atom persisted to localStorage
- Pure functions for all mutations:
(state, payload) => newState - Manages workspaces, windows, layout tree, active account
Nostr State (src/services/event-store.ts):
- Singleton
EventStorefrom applesauce-core - Single source of truth for all Nostr events
- Reactive: components subscribe via hooks, auto-update on new events
- Handles replaceable events automatically (profiles, contact lists, etc.)
Critical: Don't create new EventStore or RelayPool instances - use the singletons in src/services/
Window System
Windows are rendered in a recursive binary split layout (via react-mosaic-component):
- Each window has:
id(UUID),appId(type identifier),title,props - Layout is a tree: leaf nodes are window IDs, branch nodes split space
- Never manipulate layout tree directly - use callbacks from mosaic
Workspaces are virtual desktops, each with its own layout tree.
Command System
src/types/man.ts defines all commands as Unix man pages:
- Each command has an
appId(which app to open) andargParser(CLI → props) - Parsers can be async (e.g., resolving NIP-05 addresses)
- Command pattern: user types
profile alice@example.com→ parser resolves → opens ProfileViewer with props
Reactive Nostr Pattern
Applesauce uses RxJS observables for reactive data flow:
- Events arrive from relays → added to EventStore
- Queries/hooks subscribe to EventStore observables
- Components re-render automatically when events update
- Replaceable events (kind 0, 3, 10000-19999, 30000-39999) auto-replace older versions
Use hooks like useProfile(), useNostrEvent(), useTimeline() - they handle subscriptions.
Key Conventions
- Path Alias:
@/=./src/ - Styling: Tailwind + HSL CSS variables (theme tokens defined in
index.css) - Types: Prefer types from
applesauce-core, extend insrc/types/when needed - File Organization: By domain (
nostr/,ui/,services/,hooks/,lib/) - State Logic: All UI state mutations go through
src/core/logic.tspure functions
Important Patterns
Adding New Commands:
- Add entry to
manPagesinsrc/types/man.ts - Create parser in
src/lib/*-parser.tsif argument parsing needed - Create viewer component for the
appId - Wire viewer into window rendering (
WindowTitle.tsx)
Working with Nostr Data:
- Event data comes from singleton EventStore (reactive)
- Metadata cached in Dexie (
src/services/db.ts) for offline access - Active account stored in Jotai state, synced via
useAccountSynchook - Use inbox/outbox relay pattern for user relay lists
Mosaic Layout:
- Layout mutations via
updateLayout()callback only - Don't traverse or modify layout tree manually
- Adding/removing windows handled by
logic.tsfunctions
Critical Notes
- React 19 features in use (ensure compatibility)
- LocalStorage persistence has quota handling built-in
- Dark mode is default (controlled via HTML class)
- EventStore handles event deduplication and replaceability automatically