Parse naddr bech32 references as a dedicated segment type with kind,
pubkey, and identifier. NaddrName component fetches the referenced event
and displays its title/name/d-tag. Clicking navigates to article view
(kind 30023) or author profile (other kinds). Supports emoji sets,
app listings, and other addressable content.
Malformed profiles with non-string fields (e.g. nip05: {}) crashed
React's entire render tree in production. Add typeof guards and
profileName() utility across all components that render profile data.
Add ErrorBoundary in main.tsx to show crash details instead of blank screen.
Named after Jurij Vega (1754-1802), Slovenian mathematician who made
knowledge accessible through his logarithm tables. Rebrand before
OpenSats application (April 1).
- Product name, window title, identifiers, binary name all renamed
- Cargo package: wrystr -> vega, wrystr_lib -> vega_lib
- PKGBUILD: wrystr -> vega (new AUR package name)
- Release workflow: artifact names, release text updated
- README: new tagline referencing Jurij Vega
- DB migration: auto-renames wrystr.db -> vega.db on first launch
- Keyring service name kept as "wrystr" for backward compatibility
- localStorage keys kept as wrystr_* to preserve existing user data
Pending manual steps:
- Rename GitHub repo hoornet/wrystr -> hoornet/vega
- Create new AUR package vega-git, orphan wrystr-git
- Update local .desktop launcher
Same pattern as profile notes and notifications: if the first relay
fetch returns empty, wait 3s and retry once. Prevents false "No X
found" messages when relays are slow to connect.
- Emoji reactions now display as grouped pills (❤️5 🤙3 🔥2) instead
of a single aggregated count. Multi-reaction per note supported.
- Throttled reaction fetch queue (max 4 concurrent) prevents relay overload.
- Searching a bare npub/nprofile navigates directly to that profile.
- Notification poller waits for relay connection before first fetch,
fixing empty results on startup.
- Dev-only debug logger (src/lib/debug.ts) — silent in production builds.
Uploaded images are now kept in a separate attachments list with
thumbnail previews and remove buttons. URLs are appended to note
content only at publish time. This keeps the textarea clean for
writing and avoids URLs appearing mid-sentence at the cursor position.
Feed header, note card header, note actions, compose box, settings
theme grid, font size presets, media feed tabs, and profile header
buttons all now wrap gracefully when CSS zoom makes content overflow
the viewport. Also add AgentDocs fetch instruction to CLAUDE.md.
Add useAutoResize hook that grows textareas to fit content up to a max.
Applied to compose box, inline replies, thread replies, and article
comments. Article comment input changed from single-line input to a
multi-row textarea with Shift+Enter support.
Add image support to InlineReplyBox (paste, file picker), paste-to-upload
in article editor, multi-select for article image toolbar. Fix emoji picker
opening off-screen (right-align), enlarge emoji/attach buttons, make note
card names clickable to open profile.
- Relay status badge in feed header: shows connected/total relay count with
color coding (green >75%, yellow 25-75%, red <25%), hover tooltip with
per-relay status
- Toast notification system: transient messages for connection lost,
reconnecting, relay reset, and back-online events
- Per-tab "last updated" relative timestamp in feed header (global,
following, trending tracked independently)
- Consolidated all relay management into RelaysView (removed duplicate
relay section from Settings); per-relay remove button on health cards
- Show all supported NIP badges on relay cards (was filtering to 11 notable)
- Tooltips on relay status dots explaining green/yellow/red/gray meaning
- Fix relay removal with trailing-slash URL normalization
- Fix stale health results lingering after relay removal
- Add acknowledgements section to README
Global feed now uses a persistent live subscription (closeOnEose: false)
so new notes stream in real-time instead of requiring manual refresh.
Inspired by Wisp's streaming architecture.
Every fetchEvents call across the entire codebase now uses
fetchWithTimeout with groupable: false — prevents NDK from
batching/reusing stale subscriptions. This fixes Articles, DMs,
Notifications, Zaps, and Trending hanging indefinitely.
Also adds since filters on global (2h) and follow (24h) feeds
to ensure relay freshness, and fixes ArticleFeed re-fetch bug
where follows changes wiped latest tab results.
The liveness probe in ensureConnected was causing a death spiral —
it force-disconnected working relays when a 3s probe timed out,
then resetNDK killed new connections before they could establish.
Now ensureConnected trusts relay.connected and only reconnects when
zero relays are connected. All fetchEvents calls have timeouts
(5-10s) so nothing hangs. resetNDK kept as background-only recovery
in the connection monitor after 30s of continuous failure.
Also adds feed diagnostics (feedDiagnostics.ts) for tracking fetch
timing, event freshness, and relay states via console helpers.
Reply boxes now open directly below the note you're replying to instead of
scrolling to a top-level composer. "Replying to" link scrolls to the parent
note when already visible in the thread instead of re-pushing the same thread.
Article view:
- Repost button in header and footer (kind-6 repost)
- Comment button opens inline input, publishes note with naddr reference
- Footer comment scrolls to top to show input
Audio cards:
- Replace inline <audio> elements with styled play cards
- Clean filename display (decode URI, strip extension, humanize separators)
- Single click loads into persistent podcast player
- Eliminates buffering spinners from slow podcast CDNs
Podcast feature:
- Podcast discovery via Podcast Index API (trending + search)
- Persistent player bar with play/pause, seek, speed (1x/1.5x/2x), volume
- Audio persists across view navigation, resumes from saved position
- Fountain.fm URL detection in feed with rich playable cards
- "Play in Wrystr" button on inline audio blocks
- V4V streaming sats via NWC (LNURL-pay, 5min accumulation, split payments)
- Share what you're listening to (publish note with confirm)
- Space key toggles play/pause globally
Notification fixes:
- Per-notification read tracking (click to mark read) instead of mark-all-on-open
- Read notifications persist at 50% opacity, unread get accent border
- Always fetches last 7 days, keeps 15 most recent
- Filter out own replies from notifications
- Sidebar badge shows only unread count
- Fix mute button having no effect in Media Feed (missing filter)
- Fix notification toggle switches overlapping labels (sizing + shrink-0)
- Fix external links not opening in system browser (use Tauri opener plugin)
- Fix trending refresh showing no visual feedback (clear list on force refresh)
- Fix emoji reactions inaccessible behind WebKitGTK context menu (visible + button)
- Add emoji picker to compose box, inline reply, and thread reply
- New shared EmojiPicker component with categorized emoji groups
- ComposeBox: remove hard 280-char post limit (Nostr has none); show counter
only after 3000 chars with yellow/red warnings at 3500/4000
- Article reader: switch from monospace to serif font (Georgia stack) at 17px
for comfortable long-form reading; article preview gets serif at 15px
- ArticleView: add 2px accent-colored reading progress bar (sticky top,
scroll-driven, smooth transition)
- Connection indicator: data-aware checking (wraps fetchEvents), 30s recent-
fetch grace period, 25s offline grace (5 checks) before marking disconnected
DMs now send via NIP-17 (kind 1059 gift-wrap) with self-copy for sent
messages. Receive supports both NIP-17 and legacy NIP-04 for backward
compat. Protocol indicator shown in conversation list and compose footer.
Note card context menu (⋯) now includes follow/unfollow option so users
can follow authors directly from the feed without visiting their profile.
Fix crash (black screen) when starting a new DM conversation — empty
event array caused undefined access on lastEvent.
Keep Feed component mounted (hidden via CSS) instead of unmounting
when navigating to threads/profiles/etc. Back button now returns to
the exact scroll position in the feed.
Add inline video/audio players for direct media URLs, rich link cards
for YouTube (with thumbnails), Vimeo, Spotify, and Tidal. Fix video
clicks navigating to thread by splitting NoteContent into inline/media
modes. Fix published notes not appearing on Following tab.
Feed tab (Global/Following) moved from local state to UI store so it
survives thread/profile navigation. Fixed hardcoded "feed" in openThread
calls to pass currentView instead.
Four features shipped in this release:
- Feed reply context: replies show "↩ replying to @name" above the
note content; clicking fetches and opens the parent thread
- NIP-65 outbox model: fetchUserRelayList + publishRelayList +
fetchUserNotesNIP65 in client.ts; profile notes fetched via the
author's write relays; "Publish relay list to Nostr" button in
Settings (kind 10002)
- Notifications: new store (notifications.ts) + NotificationsView;
🔔 sidebar nav item with unread badge; DM nav item also shows
unread conversation count; badges clear on open/select
- Keyboard shortcuts: useKeyboardShortcuts hook + HelpModal;
n=compose, /=search, j/k=feed nav with ring highlight,
Esc=back, ?=help overlay
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Critical:
- NWC wallet now stored per-account (wrystr_nwc_<pubkey>); switching
accounts loads the correct wallet automatically
- Clear NDK signer before account switch to prevent race where old
account could sign outgoing events
- LoginModal: add "New account" tab to create a fresh keypair inline
(same flow as onboarding, with nsec copy + confirmation checkbox)
- ThreadView: add like + zap action row to the root note (was missing)
UX:
- Zap button now conditional on lud16/lud06 (NoteCard, ProfileView,
RootNote) — no zap button shown for profiles without Lightning
- Remove "200 notes" counter from sidebar footer
- AccountSwitcher: larger active account avatar (w-8), name more
prominent; sign-out/remove moved into dropdown only
Quick wins:
- AboutView: add GitHub Sponsors link
- ComposeBox: paste image from clipboard → uploads via nostr.build,
inserts URL at cursor with "uploading image…" status
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- fetchNoteById(eventId): fetches a single event by ID
- NoteContent: note1 and nevent1 (kind 1) references now parsed as
"quote" segment type instead of plain mention text
- QuotePreview component: lazily fetches the referenced note, renders
a bordered card with author avatar + name + truncated content;
click navigates to the thread view
- Quote cards rendered as a block below the note text, consistent with
how images/videos are handled
- naddr1 (kind 30023) and other nevent kinds still open via the
existing article/njump.me handler
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- fetchZapCount(eventId): fetches kind 9735 receipts for an event,
parses millisat amounts from embedded zap request description tags,
returns { count, totalSats }
- useZapCount hook: session-cached, same pattern as useReactionCount
- NoteCard: zap button shows "⚡ N sats" when total > 0, falls back
to "⚡ zap" when no zaps yet; stats row shown for logged-out users
displaying ♥ and ⚡ counts when non-zero
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- fetchArticle(naddr): fetches kind 30023 by d-tag + author pubkey
- fetchAuthorArticles(pubkey): for future profile articles tab
- ArticleView: cover image, title, italic summary, author row (avatar +
name + date), tag pills, full markdown body (DOMPurify sanitized),
zap author button in header and footer, copy nostr: link, njump.me
fallback on fetch error
- prose-article CSS: reader-optimised typography (15px base, 1.8 line
height, h2 border, styled blockquote/code/pre/links/images)
- NoteContent: naddr1 clicks for kind 30023 now open ArticleView
instead of falling through to njump.me
- openArticle(naddr) added to UI store with previousView tracking
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- publishRepost: kind 6 event with stringified original event as content
and ["e", id, "", "mention"] + ["p", pubkey] tags
- publishQuote: kind 1 note with user's text + appended nostr:nevent1...
reference and ["q", id] + ["p", pubkey] tags
- QuoteModal: compose modal with live quoted-note preview (avatar, name,
truncated content); Ctrl+Enter to post, Escape to close
- NoteCard: "repost" (one-click, shows "reposted ✓") and "quote" (opens
QuoteModal) added to the actions row alongside reply/like/zap
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- fetchMuteList / publishMuteList in nostr client (kind 10000)
- mute store: mutedPubkeys persisted to localStorage + synced to relay;
mute/unmute publish kind 10000 best-effort; fetchMuteList merges relay
list with local mutes on login
- fetchMuteList called after every login (nsec + pubkey)
- Feed: muted pubkeys filtered from both Global and Following tabs
- NoteCard: ⋯ context menu (appears on hover, hidden for own notes)
with mute / unmute action; backdrop click closes menu
- ProfileView: mute / unmute button in the action row (next to follow)
- SettingsView: MuteSection lists muted accounts with name + avatar;
hover to reveal unmute button; hidden when mute list is empty
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Rust: rusqlite (bundled) with WAL mode; wrystr.db in app data dir
- db_save_notes: upsert batch of raw event JSON, prune to 500 kind-1 notes
- db_load_feed: return N most-recent kind-1 raws for instant startup display
- db_save_profile / db_load_profile: cache NDKUserProfile JSON by pubkey
- Falls back to in-memory SQLite if the on-disk open fails
- src/lib/db.ts: typed invoke wrappers; all errors silenced (cache is best-effort)
- feed store: loadCachedFeed() populates notes before relay connects;
loadFeed() merges fresh+cached (so relay returning fewer notes doesn't
erase cached ones), then saves fresh notes to SQLite
- useProfile: reads SQLite cache to show avatar/name instantly while
relay request is in-flight; saves result to SQLite after relay responds
- Feed: calls loadCachedFeed() first → notes visible before relay connects
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Mention clicks (nostr:npub1, nostr:nprofile) open internal ProfileView
- njump.me links intercepted: npub/nprofile decoded and opened internally,
note/nevent/naddr fall through to browser (no reader yet)
- Hashtag clicks navigate to SearchView and auto-run the search
- openSearch(query) action added to UIStore; pendingSearch consumed on mount
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ComposeBox, action row (reply/like/zap), and write article button
all gated on getNDK().signer in addition to loggedIn
- Prevents read-only (npub-only) users from seeing actions they can't use
- Found during browser testing of the login path
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- fetchReactionCount (kind 7 #e filter) in nostr lib
- useReactionCount hook with module-level cache to avoid refetching
- NoteCard shows count next to like button; increments optimistically on like
- Falls back to "like"/"liked" text when count is zero or still loading
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ProfileView shows edit form when viewing own profile
- publishProfile (kind 0) added to nostr lib
- Sidebar name/avatar opens own profile
- Back button in edit mode cancels form; outside edit mode navigates back
- goBack safeguard: falls back to feed if previousView === currentView
- Fix ThreadView crash when selectedNote is null
- Tighten feed filter for base64 blobs and protocol messages
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Click note content to open thread view
- ThreadView shows root note, reply composer, and replies
- fetchReplies added to nostr lib (kind 1 #e filter)
- UI store gains openThread, goBack, previousView for navigation history
- Profile back button now returns to previous view correctly
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Following tab in feed header (visible when logged in)
- Fetches kind 1 notes from followed pubkeys via NDK
- fetchFollows on login using NDK user.follows()
- fetchFollowFeed added to nostr lib
- Liked note IDs persisted in localStorage so likes survive refresh
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ProfileView shows avatar, bio, nip05, website, recent notes
- Clicking any name or avatar navigates to their profile
- Add fetchUserNotes to nostr lib (kind 1 by author)
- Add openProfile action + selectedPubkey to UI store
- Back button returns to feed
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>