Last stabilization release before the podcast playback sprint. - Add Supported NIPs table to README (20 NIPs documented) - Update README features with everything since v0.8.0: NIP-46, media feed, trending, emoji picker, keyword muting, hashtag pages, profile media gallery, relay recommendations, data export, reading list tracking, syntax highlighting, OS notifications - Update ROADMAP: NIP-46 marked shipped, v0.8.x entries added, podcast playback listed as next milestone - Update CLAUDE.md architecture to reflect refactored file structure - Update release notes in CI workflow - Version bump in all 4 files
9.5 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 split into domain modules (core.ts,notes.ts,social.ts,articles.ts,engagement.ts,dms.ts,bookmarks.ts,muting.ts,search.ts,relays.ts,trending.ts); barrelindex.tsre-exports all; 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, NoteActions, InlineReplyBox, TextSegments, MediaCards, ComposeBoxsrc/components/profile/— ProfileView, EditProfileForm, ImageField, Nip05Field, ProfileMediaGallerysrc/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/media/— MediaFeed (media discovery with tab filtering)src/components/zap/— ZapModalsrc/components/onboarding/— OnboardingFlow (welcome, create key, backup, login)src/components/shared/— RelaysView (relay health dashboard + recommendations), SettingsView (NWC + identity + data export), EmojiPicker (categorized emoji insertion)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
- NIP-05 verification badges — cached verification with green checkmark on note cards
- Dedicated hashtag pages — clicking #tag opens a live feed, not generic search
- Keyword muting — word/phrase mute list, client-side filtering across all views
- Follow suggestion dismissal — persistent "don't suggest again" per person
- Background notification poller — 60s polling for mentions, zaps, new followers; each type independently toggleable
- Trending feed polish — 24h time window, time decay scoring, articles mixed with notes
- NIP-46 remote signer — bunker:// URI login, session persistence via toPayload/fromPayload, account switching
- Media feed — dedicated "Media" view with All/Videos/Images/Audio tabs; filters notes by embedded media type
- Profile media gallery — "Media" tab on profiles with grid layout; images open lightbox, videos/audio navigate to thread
- Emoji picker — shared categorized emoji picker (Frequent/Faces/Gestures/Objects/Symbols) in compose box, inline reply, thread reply; emoji reaction picker on note cards via visible + button
- External link opener — global click handler intercepts http(s) links and opens in system browser via
@tauri-apps/plugin-opener
Not yet implemented:
- Web of Trust scoring
- NIP-96 file storage
- Custom feeds / lists