Profile banner polish (hero height, click-to-lightbox, avatar overlap), data export (bookmarks/follows/relays as JSON), relay recommendations (discover from follows' NIP-65 lists), reading list tracking (read/unread on bookmarked articles with sidebar badge), trending hashtags (clickable pills on search idle screen). Updated CLAUDE.md and release notes.
7.9 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
What This Is
Wrystr is a cross-platform Nostr desktop client built with Tauri 2.0 (Rust) + React + TypeScript. It connects to Nostr relays via NDK (Nostr Dev Kit) and aims for Telegram Desktop-quality UX. Long-form content (NIP-23) is a first-class, distinguishing feature — not an afterthought.
Commands
npm run tauri dev # Run full app with hot reload (recommended for development)
npm run dev # Vite-only dev server (no Tauri window)
npm run build # TypeScript compile + Vite build
npm run tauri build # Production binary
Prerequisites: Node.js 20+, Rust stable, @tauri-apps/cli
Releasing a New Version
Order matters — do not tag before bumping versions.
- Bump version to
X.Y.Zin all four files (they must stay in sync):src-tauri/tauri.conf.json→"version": "X.Y.Z"package.json→"version": "X.Y.Z"src-tauri/Cargo.toml→version = "X.Y.Z"PKGBUILD→pkgver=X.Y.Z
- Update the release notes in
.github/workflows/release.yml - Commit:
git commit -m "Bump to vX.Y.Z — <summary>" - Tag:
git tag vX.Y.Z - Push:
git push origin main vX.Y.Z
CI triggers on the tag and builds all three platforms (Ubuntu, Windows, macOS ARM). All jobs must complete for latest.json to be assembled.
Hard-won CI rules:
includeUpdaterJson: truemust be set in tauri-action — without itlatest.jsonis never uploaded and the auto-updater silently does nothingbundle.createUpdaterArtifacts: truemust be set intauri.conf.json— without it.sigfiles are never generated even if the signing key is set (Tauri 2 requirement)- Valid
bundle.targets:"deb","rpm","nsis","msi","dmg"— do NOT add"updater"(that's a plugin, not a bundle format) - macOS runner is
macos-latest(ARM only) —macos-12/macos-13are gone - Verify after CI:
https://api.github.com/repos/hoornet/wrystr/releases/latest(check for.sigassets +latest.json)
Architecture
Frontend (src/): React 19 + TypeScript + Vite + Tailwind CSS 4
src/App.tsx— root component; showsOnboardingFlowfor new users, then view routing via UI storesrc/stores/— Zustand stores per domain:feed.ts,user.ts,ui.ts,lightning.ts,drafts.ts,relayHealth.ts,bookmark.tssrc/lib/nostr/— NDK wrapper (client.ts+index.ts); all Nostr calls go through heresrc/lib/lightning/— NWC client (nwc.ts); Lightning payment logicsrc/hooks/—useProfile.ts,useReactionCount.tssrc/components/feed/— Feed, NoteCard, NoteContent, ComposeBoxsrc/components/profile/— ProfileView (own + others, edit form)src/components/thread/— ThreadViewsrc/components/search/— SearchView (advanced search with modifiers, NIP-50, hashtag, people, articles)src/lib/search.ts— Advanced search query parser (by:, has:, is:, kind:, since:, until:, OR)src/lib/nostr/relayHealth.ts— Relay health checker (NIP-11, latency probing, status classification)src/components/article/— ArticleEditor, ArticleView, ArticleFeed, ArticleCard, MarkdownToolbar (NIP-23)src/components/bookmark/— BookmarkViewsrc/components/zap/— ZapModalsrc/components/onboarding/— OnboardingFlow (welcome, create key, backup, login)src/components/shared/— RelaysView (relay health dashboard + recommendations), SettingsView (NWC + identity + data export)src/components/sidebar/— Sidebar navigation
Backend (src-tauri/): Rust + Tauri 2.0
src-tauri/src/lib.rs— Tauri app init and command registration- Rust commands must return
Result<T, String> - OS keychain via
keyringcrate —store_nsec,load_nsec,delete_nseccommands - SQLite note/profile cache via
rusqlite - File uploads handled entirely in TypeScript with NIP-98 auth (Rust upload_file removed in v0.7.0)
- Future: lightning node integration
Key Conventions (from AGENTS.md)
- Functional React components only — no class components
- Never use
any— define types insrc/types/ - Tailwind classes only — no inline styles, except unavoidable WebkitUserSelect
- Private keys stored in OS keychain via Rust
keyringcrate; nsec persists across restarts - New Zustand stores per domain when adding features
- NDK interactions only through
src/lib/nostr/wrapper - Lightning/NWC only through
src/lib/lightning/wrapper
NIP Priority Reference
- P1 (core): NIP-01, 02, 03, 10, 11, 19, 21, 25, 27, 50
- P2 (monetization): NIP-47 (NWC/Lightning), NIP-57 (zaps), NIP-65 (relay lists)
- P3 (advanced): NIP-04/44 (DMs), NIP-11 (relay info — used by health checker), NIP-23 (articles), NIP-96 (file storage), NIP-98 (HTTP Auth — implemented for uploads)
Current State
Implemented:
- Onboarding: key generation, nsec backup flow, login with nsec/npub
- Global + following feed, compose, reply, thread view
- Reactions (NIP-25) with live network counts
- Follow/unfollow (NIP-02), contact list publishing
- Profile view + edit (kind 0) with Notes/Articles tab toggle
- Long-form article editor (NIP-23) with markdown toolbar (bold, italic, heading, link, image, quote, code, list), keyboard shortcuts (Ctrl+B/I/K), multi-draft management, cover image file picker
- Article discovery feed — dedicated "Articles" view in sidebar; Latest/Following tabs
- Article reader — markdown rendering, reading time, bookmark, like, zap
- Article search — NIP-50 + hashtag search for kind 30023 articles
- Article cards — reusable component with title, summary, author, cover thumbnail, reading time, tags
- NIP-98 HTTP Auth for image uploads with fallback services (nostr.build, void.cat, nostrimg.com)
- Zaps: NWC wallet connect (NIP-47) + NIP-57 via NDKZapper
- Advanced search — query parser with modifiers:
by:author,mentions:npub,kind:N,is:article,has:image,since:date,until:date,#hashtag,"phrase", booleanOR; NIP-05 resolution; client-side content filters; search help panel - Search: NIP-50 full-text, hashtag (#t filter), people, articles
- Settings: relay add/remove (persisted to localStorage), NWC URI, npub copy
- Relay health checker — NIP-11 info fetch, WebSocket latency probing, online/slow/offline status; expandable cards with supported NIPs, software info; "Remove dead" + "Republish list" workflow
- Relay recommendations — suggest relays based on follows' NIP-65 relay lists; "Discover relays" button with follow count, one-click "Add"
- Data export — export bookmarks, follows, and relay list as JSON via native save dialog (Tauri plugin-dialog + plugin-fs)
- Profile banner polish — hero-height banner (h-36), click-to-lightbox, avatar overlaps banner edge with ring, loading shimmer
- Reading list tracking — read/unread state on bookmarked articles (localStorage-backed), unread dot indicators, sidebar badge, auto-mark-read on open
- Trending hashtags — #t tag frequency analysis from recent events; clickable tag pills on search idle screen
- OS keychain integration — nsec persists across restarts via
keyringcrate - SQLite note + profile cache
- Direct messages (NIP-04 + NIP-17 gift wrap)
- NIP-65 outbox model
- Image lightbox (click to expand, arrow key navigation)
- Bookmark list (NIP-51 kind 10003) with sidebar nav, Notes/Articles tabs, article
atag support, read/unread tracking - Follow suggestions / discovery (follows-of-follows algorithm)
- Language/script feed filter (Unicode script detection + NIP-32 tags)
- Skeleton loading states, view fade transitions
- Note sharing (nevent URI to clipboard)
- Reply counts on notes
- Media players (video/audio inline, YouTube/Vimeo/Spotify cards)
- Multi-account switcher with keychain-backed session restore
- System tray, keyboard shortcuts, auto-updater
Not yet implemented:
- Web of Trust scoring
- NIP-46 remote signer
- NIP-96 file storage
- Custom feeds / lists