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,6 +2,7 @@ import { useEffect, useRef, useState } from "react";
import { NDKEvent, nip19 } from "@nostr-dev-kit/ndk";
import { useUserStore } from "../../stores/user";
import { useUIStore } from "../../stores/ui";
import { useNotificationsStore } from "../../stores/notifications";
import { fetchDMConversations, fetchDMThread, sendDM, decryptDM, getNDK } from "../../lib/nostr";
import { useProfile } from "../../hooks/useProfile";
import { timeAgo, shortenPubkey } from "../../lib/utils";
@@ -306,6 +307,12 @@ export function DMView() {
if (!selectedPubkey && !pendingDMPubkey && grouped.size > 0) {
setSelectedPubkey(Array.from(grouped.keys())[0]);
}
// Compute DM unread counts
const convList = Array.from(grouped.entries()).map(([partnerPubkey, msgs]) => ({
partnerPubkey,
lastAt: msgs[0]?.created_at ?? 0,
}));
useNotificationsStore.getState().computeDMUnread(convList);
})
.finally(() => setLoading(false));
}, [pubkey, hasSigner]);
@@ -354,7 +361,10 @@ export function DMView() {
partnerPubkey={partner}
lastEvent={events[0]}
selected={selectedPubkey === partner}
onSelect={() => setSelectedPubkey(partner)}
onSelect={() => {
setSelectedPubkey(partner);
useNotificationsStore.getState().markDMRead(partner);
}}
/>
))}
</div>