diff --git a/README.md b/README.md index 3515ad5..b0c1bdd 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ Grab the latest release from the [Releases page](https://github.com/hoornet/wrys - Global and following feeds with live relay connection - Compose notes, inline replies, full thread view - **Image paste in compose** — paste an image from clipboard → auto-uploads and inserts the URL +- **Feed reply context** — replies show "↩ replying to @name"; click to jump to the parent thread - Reactions (NIP-25) with live network counts - Follow / unfollow (NIP-02) with contact list publishing - **Quote & Repost** (NIP-18) — one-click repost or quote with compose modal @@ -38,7 +39,12 @@ Grab the latest release from the [Releases page](https://github.com/hoornet/wrys - Long-form article editor + reader (NIP-23) — write with title, tags, cover image, auto-save; click any `nostr:naddr1…` link to open in the in-app reader - **Quoted note inline preview** — `nostr:note1…` / `nostr:nevent1…` renders as an inline card - Note rendering: images, video, mentions, hashtags, njump.me link interception -- **Direct Messages** (NIP-04) — conversation list, thread view, per-message decryption +- **Direct Messages** (NIP-04) — conversation list, thread view, per-message decryption; unread badge in sidebar +- **Notifications** — mentions view (🔔 in sidebar) with unread badge; clears on open + +**Relay & network** +- Relay management: add/remove relays with live connection status +- **NIP-65 outbox model** — reads user relay lists (kind 10002) so you see notes from people who publish to their own relays; publish your own relay list to Nostr from Settings **Lightning & zaps** - **Per-account NWC wallet** — each account remembers its own Lightning wallet; switching accounts loads the correct one automatically @@ -53,8 +59,8 @@ Grab the latest release from the [Releases page](https://github.com/hoornet/wrys - **SQLite note cache** — feed loads instantly from local cache on startup; profiles cached for immediate avatar display - **System tray** — close button hides to tray; "Quit" in tray menu to fully exit - Collapsible sidebar (icon-only mode) +- **Keyboard shortcuts** — `n` compose, `/` search, `j`/`k` navigate feed, `Esc` back, `?` help overlay - Search: NIP-50 full-text, `#hashtag`, people search with inline follow -- Relay management: add/remove relays with live connection status ## Stack @@ -82,11 +88,12 @@ npm run tauri build # production binary See [ROADMAP.md](./ROADMAP.md) for the full prioritised next steps. -Up next (Phase 2): -- Notifications — mentions, replies, DM badge, OS native alerts -- NIP-65 outbox model — fetch notes from the right relay set per author -- Feed reply context — show "↩ replying to @name" for replies in the feed -- Keyboard shortcuts — N, R, /, J/K, Escape, ? help overlay +Up next (Phase 3): +- NIP-17 DMs (gift wrap) — proper sender/recipient privacy, replacing NIP-04 +- Image lightbox — click to expand images full-size +- Bookmark list (NIP-51 kind 10003) +- Follow suggestions / discovery +- UI polish pass ## Support diff --git a/ROADMAP.md b/ROADMAP.md index 951b27b..5df4682 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -31,31 +31,15 @@ Bugs found during testing are fixed before Phase N+1 starts. A release is cut be --- -## Phase 2 — Engagement & reach -*Test v0.1.7 on Windows first. Fix any issues before starting.* +## Phase 2 — Engagement & reach ✓ COMPLETE -### 5. Notifications -- No way to see mentions, replies to own notes, or incoming DMs without manually checking -- Badge on the messages nav item for unread DMs -- Notifications view: mentions of your pubkey, replies to your notes, new DMs -- System notification (OS native) for DMs and mentions — Tauri has a notification plugin +*Shipped in v0.1.11.* -### 6. NIP-65 outbox model (relay lists, kind 10002) -- Without NIP-65, we miss notes from people who publish to their own relay set -- On profile open: fetch their kind 10002 relay list, query those relays for their notes -- On publish: write to own relay list (configurable in settings) -- Dramatically improves note discovery and reach - -### 7. Feed reply context -- In the feed, replies look identical to top-level posts — no visual distinction -- Show "↩ replying to @name" above the note content for kind-1 events with `e` tags -- Clicking the context navigates to the parent note thread - -### 8. Keyboard shortcuts -- A writing-focused desktop app should be keyboard-navigable -- N — compose new note, R — reply to focused note, / — focus search -- J/K — navigate feed up/down, Escape — close modal/back -- Show shortcuts in a `?` help overlay +- ✓ **Feed reply context** — "↩ replying to @name" shown above reply notes; click to open parent thread +- ✓ **NIP-65 outbox model** — fetch user relay lists (kind 10002) for better note discovery; "Publish relay list" button in Settings; profile notes fetched via write relays +- ✓ **Notifications** — mentions view with unread badge; 🔔 nav item in sidebar; badge clears on view +- ✓ **DM unread badge** — messages nav item shows badge count; clears when conversation opened +- ✓ **Keyboard shortcuts** — n (compose), / (search), j/k (feed nav), Esc (back), ? (help modal) --- @@ -116,6 +100,13 @@ Bugs found during testing are fixed before Phase N+1 starts. A release is cut be ## What's already shipped +### v0.2.0 — Phase 2: Engagement & Reach +- **Feed reply context** — replies show "↩ replying to @name" above the note; click to open the parent thread +- **NIP-65 outbox model** — reads kind 10002 relay lists so you see notes from people who publish to their own relays; profile notes fetched via their write relays; "Publish relay list to Nostr" button in Settings +- **Notifications view** — 🔔 sidebar nav item; lists recent mentions with unread badge; badge clears on open +- **DM unread badge** — messages nav item shows count of conversations with new messages; clears when conversation is opened +- **Keyboard shortcuts** — `n` focus compose, `/` focus search, `j`/`k` navigate feed with ring highlight, `Esc` go back, `?` help overlay + ### v0.1.10 - **Fix: Bitcoin QR to right edge** — Support page QR section uses `justify-between` so Lightning sits left, Bitcoin sits right diff --git a/package.json b/package.json index 10bfa5e..85ed934 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "wrystr", "private": true, - "version": "0.1.10", + "version": "0.2.0", "type": "module", "scripts": { "dev": "vite", diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index f922d9a..85dc717 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "Wrystr", - "version": "0.1.10", + "version": "0.2.0", "identifier": "com.hoornet.wrystr", "build": { "beforeDevCommand": "npm run dev", diff --git a/src/App.tsx b/src/App.tsx index 4fd96df..f010f5d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -12,8 +12,11 @@ import { OnboardingFlow } from "./components/onboarding/OnboardingFlow"; import { AboutView } from "./components/shared/AboutView"; import { ZapHistoryView } from "./components/zap/ZapHistoryView"; import { DMView } from "./components/dm/DMView"; +import { NotificationsView } from "./components/notifications/NotificationsView"; +import { HelpModal } from "./components/shared/HelpModal"; import { useUIStore } from "./stores/ui"; import { useUpdater } from "./hooks/useUpdater"; +import { useKeyboardShortcuts } from "./hooks/useKeyboardShortcuts"; function UpdateBanner() { const { available, version, installing, error, install, dismiss } = useUpdater(); @@ -40,10 +43,14 @@ function UpdateBanner() { function App() { const currentView = useUIStore((s) => s.currentView); + const showHelp = useUIStore((s) => s.showHelp); + const toggleHelp = useUIStore((s) => s.toggleHelp); const [onboardingDone, setOnboardingDone] = useState( () => !!localStorage.getItem("wrystr_pubkey") ); + useKeyboardShortcuts(); + if (!onboardingDone) { return setOnboardingDone(true)} />; } @@ -65,8 +72,10 @@ function App() { {currentView === "about" && } {currentView === "zaps" && } {currentView === "dm" && } + {currentView === "notifications" && } + {showHelp && } ); } diff --git a/src/components/dm/DMView.tsx b/src/components/dm/DMView.tsx index 0638bb8..220e466 100644 --- a/src/components/dm/DMView.tsx +++ b/src/components/dm/DMView.tsx @@ -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); + }} /> ))} diff --git a/src/components/feed/ComposeBox.tsx b/src/components/feed/ComposeBox.tsx index dcfbd01..ce082af 100644 --- a/src/components/feed/ComposeBox.tsx +++ b/src/components/feed/ComposeBox.tsx @@ -93,6 +93,7 @@ export function ComposeBox({ onPublished }: { onPublished?: () => void }) {