Bump to v0.2.0 — Phase 2: Engagement & Reach

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>
This commit is contained in:
Jure
2026-03-11 20:39:30 +01:00
parent 181233796b
commit 3a196cb9a0
22 changed files with 479 additions and 63 deletions

View File

@@ -2,7 +2,7 @@ import { create } from "zustand";
import { NDKEvent } from "@nostr-dev-kit/ndk";
type View = "feed" | "search" | "relays" | "settings" | "profile" | "thread" | "article-editor" | "article" | "about" | "zaps" | "dm";
type View = "feed" | "search" | "relays" | "settings" | "profile" | "thread" | "article-editor" | "article" | "about" | "zaps" | "dm" | "notifications";
interface UIState {
currentView: View;
@@ -13,6 +13,7 @@ interface UIState {
pendingSearch: string | null;
pendingDMPubkey: string | null;
pendingArticleNaddr: string | null;
showHelp: boolean;
setView: (view: View) => void;
openProfile: (pubkey: string) => void;
openThread: (note: NDKEvent, from: View) => void;
@@ -21,6 +22,7 @@ interface UIState {
openArticle: (naddr: string) => void;
goBack: () => void;
toggleSidebar: () => void;
toggleHelp: () => void;
}
const SIDEBAR_KEY = "wrystr_sidebar_collapsed";
@@ -34,6 +36,7 @@ export const useUIStore = create<UIState>((set, _get) => ({
pendingSearch: null,
pendingDMPubkey: null,
pendingArticleNaddr: null,
showHelp: false,
setView: (currentView) => set({ currentView }),
openProfile: (pubkey) => set((s) => ({ currentView: "profile", selectedPubkey: pubkey, previousView: s.currentView as View })),
openThread: (note, from) => set({ currentView: "thread", selectedNote: note, previousView: from }),
@@ -41,6 +44,7 @@ export const useUIStore = create<UIState>((set, _get) => ({
openDM: (pubkey) => set({ currentView: "dm", pendingDMPubkey: pubkey }),
openArticle: (naddr) => set((s) => ({ currentView: "article", pendingArticleNaddr: naddr, previousView: s.currentView as View })),
goBack: () => set((s) => ({
showHelp: false,
currentView: s.previousView !== s.currentView ? s.previousView : "feed",
selectedNote: null,
})),
@@ -49,4 +53,5 @@ export const useUIStore = create<UIState>((set, _get) => ({
localStorage.setItem(SIDEBAR_KEY, String(next));
return { sidebarCollapsed: next };
}),
toggleHelp: () => set((s) => ({ showHelp: !s.showHelp })),
}));