Files
vega/AGENTS.md
Jure 5e20e5a128 Compose, reactions, replies + feed filtering
- Add ComposeBox with Ctrl+Enter shortcut and char limit
- Add reply/like actions to NoteCard with inline reply box
- Add publishNote, publishReaction, publishReply to nostr lib
- Filter bot JSON blobs from feed (content starting with { or [)
- Fix sidebar login button always visible (shrink-0 + nav overflow)
- Restore pubkey sessions from localStorage on startup
- Add CLAUDE.md with project guidance
- Add thread view and onboarding notes to AGENTS.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 18:44:04 +01:00

8.3 KiB

AGENTS.md — Wrystr

This file guides AI coding agents (Claude Code, Copilot, etc.) working on this codebase. Read it fully before making changes. Follow the conventions here strictly.


What is Wrystr?

Wrystr is a cross-platform desktop Nostr client built for Mac, Windows, and Linux. The goal is to be the best Nostr desktop experience — polished UI, deep Lightning integration, full NIP coverage, and performance that feels native, not webby.

Named as a nod to Nostr with a wry twist. The -str suffix is intentional.


Tech Stack

Layer Technology
Desktop shell Tauri 2.0 (Rust backend)
Frontend React + TypeScript (Vite)
Nostr protocol NDK (Nostr Dev Kit for JS)
Lightning Nostr Wallet Connect (NIP-47) primary; LND/CLN gRPC optional
Styling Tailwind CSS
State management Zustand
Local storage SQLite via Tauri plugin (tauri-plugin-sql)
Key storage OS keychain via Tauri plugin (tauri-plugin-keychain)

Rust is used for: key management, secure storage, native node connections, system tray, OS notifications. TypeScript/React handles all UI and Nostr logic via NDK.


Project Structure

wrystr/
├── src-tauri/          # Rust backend (Tauri)
│   ├── src/
│   │   ├── main.rs
│   │   ├── commands/   # Tauri commands exposed to frontend
│   │   ├── keys/       # Key management, signing
│   │   ├── lightning/  # LND/CLN direct node connections
│   │   └── storage/    # SQLite, keychain wrappers
│   └── Cargo.toml
├── src/                # React frontend
│   ├── main.tsx
│   ├── App.tsx
│   ├── components/     # Reusable UI components
│   │   ├── feed/
│   │   ├── notes/
│   │   ├── profile/
│   │   ├── lightning/
│   │   └── shared/
│   ├── views/          # Full page/screen views
│   ├── stores/         # Zustand state stores
│   ├── hooks/          # Custom React hooks
│   ├── lib/
│   │   ├── nostr/      # NDK setup, relay management, NIP helpers
│   │   └── lightning/  # NWC client, zap flows, LNURL
│   └── types/          # Shared TypeScript types
├── AGENTS.md
├── README.md
└── package.json

Nostr Protocol Coverage

Priority 1 — Core (must be solid before anything else)

  • NIP-01 Basic protocol (events, subscriptions, relay communication)
  • NIP-02 Follow lists
  • NIP-03 OpenTimestamps (optional but nice)
  • NIP-10 Reply threading
  • NIP-11 Relay metadata (used for relay health display)
  • NIP-19 bech32 encoding (npub, nsec, note, nprofile, nevent)
  • NIP-21 nostr: URI scheme
  • NIP-25 Reactions
  • NIP-27 Text note references
  • NIP-50 Full-text search

Priority 2 — Lightning & Social

  • NIP-47 Nostr Wallet Connect (primary Lightning integration)
  • NIP-57 Lightning Zaps
  • NIP-65 Relay list metadata (outbox model)

Priority 3 — Rich features

  • NIP-04 Legacy encrypted DMs (keep for compatibility)
  • NIP-44 Encrypted payloads (new DM standard)
  • NIP-17 Private Direct Messages
  • NIP-23 Long-form content (articles)
  • NIP-36 Sensitive content warnings
  • NIP-51 Lists (mute lists, bookmarks, etc.)
  • NIP-72 Moderated communities
  • NIP-94 File metadata
  • NIP-96 File storage (media uploads)
  • NIP-98 HTTP Auth

Lightning Integration

Primary path: NWC (NIP-47)

  • Widest wallet compatibility (Alby, Mutiny-compatible, etc.)
  • Handles: pay invoice, make invoice, get balance, list transactions
  • UI: inline zap buttons that feel instant, not modal-heavy

Secondary path: Direct node connection

  • LND via gRPC (Rust backend)
  • Core Lightning via REST
  • Handled entirely in src-tauri/src/lightning/
  • Never expose node credentials to the frontend layer

LNURL support

  • lnurl-pay, lnurl-auth, lightning address resolution
  • Lightning address shown on profiles when available

Zap flow UX goal: Zapping should feel as fast and frictionless as a like button. No unnecessary confirmation dialogs for small amounts. Custom amounts and messages available but not forced.


Key Management

  • nsec stored encrypted in OS keychain (never in localStorage or SQLite)
  • Support multiple accounts
  • NIP-07 browser extension passthrough for users who prefer that flow
  • Hardware signer support (NIP-07-compatible) — future
  • Key generation uses Rust (cryptographically secure)
  • Signing can be delegated to Rust backend via Tauri commands to avoid exposing private keys to the JS layer

UI / UX Principles

  • Target quality bar: Telegram Desktop. Fast, keyboard-navigable, no loading spinners where possible, snappy transitions.
  • Feels like a native app, not a web page in a frame.
  • NOT a "crypto app" — no unnecessary blockchain/wallet jargon in the UI. Use plain language: "Send tip" not "Broadcast zap transaction".
  • Dark mode by default. Light mode supported.
  • Responsive within desktop constraints (min window ~900px wide).
  • Keyboard shortcuts for all primary actions.

Coding Conventions

TypeScript / React

  • Functional components only, no class components
  • Hooks for all logic — no business logic directly in components
  • Zustand stores per domain (feed, profile, relays, lightning, ui)
  • Types defined in src/types/ — never use any
  • NDK interactions go through src/lib/nostr/ — not called directly in components
  • Use async/await over .then() chains
  • Error boundaries around major UI sections

Rust

  • All Tauri commands in src-tauri/src/commands/
  • Return Result<T, String> from Tauri commands for consistent error handling on the JS side
  • Sensitive operations (key access, node credentials) must never log to console in production
  • Use serde for all JSON serialization

General

  • No secrets, keys, or credentials ever committed to git
  • .env for dev config, .env.example committed as template
  • Prefer explicit over clever — this codebase should be readable by someone new

Relay Management

  • Connect to multiple relays simultaneously
  • Display relay health (latency, NIP support from NIP-11)
  • Outbox model (NIP-65): write to user's own relays, read from followed users' relays
  • Default relay list for new users
  • Relay list persisted locally and synced to Nostr (NIP-65 events)

Development Setup

# Prerequisites: Node.js 20+, Rust stable, Tauri CLI
npm install
npm run tauri dev       # starts dev build with hot reload
npm run tauri build     # production build

Thread View (next major feature after reactions/replies)

Clicking a note should open a thread view showing:

  • The root note
  • All replies in chronological order (or threaded if nested)
  • An inline reply composer at the bottom

This is essential for replies to feel complete — right now replies are posted to the network but there's no way to read them in context within the app.

Implementation notes:

  • Fetch replies via kinds: [1], #e: [noteId] NDK filter
  • Thread view can be a new currentView in the UI store, with a selectedNote state
  • Back navigation returns to the feed

Onboarding

Nostr onboarding is notoriously bad across most clients. Wrystr should be the exception. Goals:

  • Key generation built-in (no "go get a browser extension first")
  • Human-readable explanation of what a key is, without crypto jargon
  • One-click backup flow (show nsec, prompt to save it somewhere)
  • Optional: sign up with email/password via a custodial key service for non-technical users, with a clear path to self-custody later
  • New users should see interesting content immediately, not a blank feed

This is a first-class feature, not an afterthought.


What to Avoid

  • Do NOT add new dependencies without checking if something in the existing stack covers it
  • Do NOT store private keys anywhere except the OS keychain via Tauri plugin
  • Do NOT add paywalls or restrictions to core social/protocol features
  • Do NOT expose Lightning node credentials to the frontend/JS layer
  • Do NOT break the single-shot zap flow with unnecessary confirmation modals
  • Do NOT use inline styles — use Tailwind classes

Monetization Context

Wrystr will support monetization through:

  • Built-in developer Lightning address (tip jar, visible in About)
  • Optional paid relay tier (run separately)
  • One-time purchase / sponsorship tier for power features

Keep core social features fully free and open. Monetization lives at the edges.