diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5e12e50..bcd5ca4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -69,6 +69,15 @@ jobs: > **Windows note:** The installer is not yet code-signed. Windows SmartScreen will show an "Unknown publisher" warning — click "More info → Run anyway" to install. + ### v0.12.11 — Read-only mode polish + + If you haven't created a key yet, or you're signed in with just an `npub` (view-only), the app now behaves correctly throughout: no broken Publish buttons, no dead-end "Not logged in" errors. + + - **Sidebar shows only the views that work without a signer** — Feed, Articles, Media, Podcasts, Search, Follows, Relays, Settings, Support. Account-bound entries (Bookmarks, Messages, Notifications, Zaps, V4V) are hidden until you sign in. + - **Write surfaces are guarded everywhere.** Compose box, reply box, reactions, repost, quote, zap, bookmark, follow/unfollow, mute, profile edit, article publish, poll vote, relay-list publish — all properly hidden or replaced with a "Sign in" CTA that opens the login dialog. + - **Read-only banner now fires for npub-only logins too**, not just fully logged-out state. + - **The original bug:** in v0.12.10 and earlier, opening the article editor without a signer and clicking Publish raised `Failed to publish: Error: Not logged in`. That path no longer exists. + ### v0.12.10 — Security update Dependency security bumps only. No functional changes. diff --git a/PKGBUILD b/PKGBUILD index d943525..57b5cbc 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -1,6 +1,6 @@ # Maintainer: hoornet pkgname=vega-nostr -pkgver=0.12.10 +pkgver=0.12.11 pkgrel=1 pkgdesc="Cross-platform Nostr desktop client with Lightning integration" arch=('x86_64') diff --git a/package-lock.json b/package-lock.json index 66b5802..0f3871a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "vega", - "version": "0.12.10", + "version": "0.12.11", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "vega", - "version": "0.12.10", + "version": "0.12.11", "dependencies": { "@nostr-dev-kit/ndk": "^3.0.3", "@tailwindcss/vite": "^4.2.1", diff --git a/package.json b/package.json index 8d36829..ee1ba98 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vega", "private": true, - "version": "0.12.10", + "version": "0.12.11", "type": "module", "scripts": { "dev": "vite", diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index ae8af8b..9f77184 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -5429,7 +5429,7 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "vega" -version = "0.12.10" +version = "0.12.11" dependencies = [ "futures-util", "hex", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index cd001ba..1371824 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "vega" -version = "0.12.10" +version = "0.12.11" description = "Cross-platform Nostr desktop client with Lightning integration" authors = ["hoornet"] edition = "2021" diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 28f7460..4dc4a33 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": "Vega", - "version": "0.12.10", + "version": "0.12.11", "identifier": "com.hoornet.vega", "build": { "beforeDevCommand": "npm run dev", diff --git a/src/App.tsx b/src/App.tsx index b12b711..0a29a85 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -28,7 +28,7 @@ const V4VView = lazy(() => import("./components/v4v/V4VView").then(m => ({ defau const DebugPanel = lazy(() => import("./components/shared/DebugPanel").then(m => ({ default: m.DebugPanel }))); const HelpModal = lazy(() => import("./components/shared/HelpModal").then(m => ({ default: m.HelpModal }))); import { useUIStore } from "./stores/ui"; -import { useUserStore } from "./stores/user"; +import { useCanSign } from "./stores/user"; import { getTheme, applyTheme } from "./lib/themes"; import { useUpdater } from "./hooks/useUpdater"; import { useKeyboardShortcuts } from "./hooks/useKeyboardShortcuts"; @@ -57,8 +57,8 @@ function UpdateBanner() { } function ReadOnlyBanner() { - const loggedIn = useUserStore((s) => s.loggedIn); - if (loggedIn) return null; + const canSign = useCanSign(); + if (canSign) return null; return (
Read-only mode — sign in to post, react, and zap diff --git a/src/components/article/ArticleEditor.tsx b/src/components/article/ArticleEditor.tsx index 655778d..d62c175 100644 --- a/src/components/article/ArticleEditor.tsx +++ b/src/components/article/ArticleEditor.tsx @@ -2,8 +2,10 @@ import { useState, useEffect, useRef, useCallback, useMemo } from "react"; import { renderMarkdown } from "../../lib/markdown"; import { publishArticle } from "../../lib/nostr"; import { useUIStore } from "../../stores/ui"; +import { useCanSign } from "../../stores/user"; import { MarkdownToolbar, handleEditorKeyDown } from "./MarkdownToolbar"; import { useDraftStore, type ArticleDraft } from "../../stores/drafts"; +import { LoginModal } from "../shared/LoginModal"; import { open } from "@tauri-apps/plugin-dialog"; import { readFile } from "@tauri-apps/plugin-fs"; import { uploadBytes, uploadImage } from "../../lib/upload"; @@ -31,9 +33,11 @@ function formatSavedAgo(elapsedMs: number): string { } export function ArticleEditor() { + const canSign = useCanSign(); const { goBack } = useUIStore(); const { activeDraftId, drafts, updateDraft, deleteDraft, setActiveDraft, createDraft } = useDraftStore(); const textareaRef = useRef(null); + const [showLoginModal, setShowLoginModal] = useState(false); // If no active draft, show draft list const activeDraft = activeDraftId ? drafts.find((d) => d.id === activeDraftId) : null; @@ -336,13 +340,22 @@ export function ArticleEditor() { Meta - + {canSign ? ( + + ) : ( + + )}
@@ -498,6 +511,7 @@ export function ArticleEditor() { )} + {showLoginModal && setShowLoginModal(false)} />} ); } diff --git a/src/components/article/ArticleFeed.tsx b/src/components/article/ArticleFeed.tsx index 5049257..4827ac7 100644 --- a/src/components/article/ArticleFeed.tsx +++ b/src/components/article/ArticleFeed.tsx @@ -1,7 +1,7 @@ import { useState, useEffect } from "react"; import { NDKEvent } from "@nostr-dev-kit/ndk"; import { fetchArticleFeed, getNDK } from "../../lib/nostr"; -import { useUserStore } from "../../stores/user"; +import { useUserStore, useCanSign } from "../../stores/user"; import { useUIStore } from "../../stores/ui"; import { dbLoadArticles, dbSaveNotes } from "../../lib/db"; import { ArticleCard } from "./ArticleCard"; @@ -9,6 +9,7 @@ import { ArticleCard } from "./ArticleCard"; type ArticleTab = "latest" | "following"; export function ArticleFeed() { + const canSign = useCanSign(); const { loggedIn, follows } = useUserStore(); const { setView } = useUIStore(); const [tab, setTab] = useState("latest"); @@ -66,12 +67,14 @@ export function ArticleFeed() { {/* Header */}

Articles

- + {canSign && ( + + )}
{/* Tabs */} diff --git a/src/components/article/ArticleView.tsx b/src/components/article/ArticleView.tsx index 52e5157..d471304 100644 --- a/src/components/article/ArticleView.tsx +++ b/src/components/article/ArticleView.tsx @@ -3,7 +3,7 @@ import { NDKEvent } from "@nostr-dev-kit/ndk"; import { renderMarkdown } from "../../lib/markdown"; import { useAutoResize } from "../../hooks/useAutoResize"; import { useUIStore } from "../../stores/ui"; -import { useUserStore } from "../../stores/user"; +import { useCanSign } from "../../stores/user"; import { useBookmarkStore } from "../../stores/bookmark"; import { fetchArticle, publishReaction, publishRepost, publishNote } from "../../lib/nostr"; import { nip19 } from "@nostr-dev-kit/ndk"; @@ -69,7 +69,7 @@ function AuthorRow({ pubkey, publishedAt, readingTime }: { pubkey: string; publi export function ArticleView() { const { pendingArticleNaddr, pendingArticleEvent, goBack } = useUIStore(); - const { loggedIn } = useUserStore(); + const canSign = useCanSign(); const [event, setEvent] = useState(null); const [loading, setLoading] = useState(true); @@ -241,7 +241,7 @@ export function ArticleView() { ← Back
- {event && loggedIn && ( + {event && canSign && ( )} - {event && loggedIn && ( + {event && canSign && (
- {loggedIn && ( + {canSign && ( )} - {loggedIn && ( + {canSign && (
- · - - - - · - - - - {(profile?.lud16 || profile?.lud06) && ( + {canSign && ( <> · + + + · + + + + {(profile?.lud16 || profile?.lud06) && ( + <> + · + + + )} + + · + + + + · )} - · - - - - · - + {canSign ? "Ctrl+Enter to post" : ""} + {canSign ? ( + + ) : ( + + )}
+ {showLogin && setShowLogin(false)} />} ); } diff --git a/src/components/follows/FollowsView.tsx b/src/components/follows/FollowsView.tsx index 7f7905d..7cc8cd4 100644 --- a/src/components/follows/FollowsView.tsx +++ b/src/components/follows/FollowsView.tsx @@ -1,6 +1,6 @@ import { useEffect, useState } from "react"; import { useUIStore } from "../../stores/ui"; -import { useUserStore } from "../../stores/user"; +import { useUserStore, useCanSign } from "../../stores/user"; import { useNotificationsStore } from "../../stores/notifications"; import { useProfile } from "../../hooks/useProfile"; import { useNip05Verified } from "../../hooks/useNip05Verified"; @@ -23,6 +23,7 @@ function FollowRow({ const nip05 = typeof profile?.nip05 === "string" ? profile.nip05 : undefined; const verified = useNip05Verified(pubkey, nip05); + const canSign = useCanSign(); const { follows, follow, unfollow, pubkey: ownPubkey } = useUserStore(); const { openProfile } = useUIStore(); const isFollowing = follows.includes(pubkey); @@ -68,7 +69,7 @@ function FollowRow({ - {!isSelf && ( + {!isSelf && canSign && ( + {canSign ? ( + + ) : ( + + )} + {showLoginModal && setShowLoginModal(false)} />} ); } diff --git a/src/components/profile/ProfileView.tsx b/src/components/profile/ProfileView.tsx index f5eaba9..dcd3e72 100644 --- a/src/components/profile/ProfileView.tsx +++ b/src/components/profile/ProfileView.tsx @@ -1,11 +1,11 @@ import { useEffect, useMemo, useState } from "react"; import { NDKEvent, nip19 } from "@nostr-dev-kit/ndk"; import { useUIStore } from "../../stores/ui"; -import { useUserStore } from "../../stores/user"; +import { useUserStore, useCanSign } from "../../stores/user"; import { useMuteStore } from "../../stores/mute"; import { useProfile } from "../../hooks/useProfile"; import { useReputation } from "../../hooks/useReputation"; -import { fetchUserNotesNIP65, fetchAuthorArticles, getNDK } from "../../lib/nostr"; +import { fetchUserNotesNIP65, fetchAuthorArticles } from "../../lib/nostr"; import { shortenPubkey, profileName } from "../../lib/utils"; import { NoteCard } from "../feed/NoteCard"; import { ArticleCard } from "../article/ArticleCard"; @@ -47,7 +47,8 @@ function TopFollowerAvatar({ pubkey }: { pubkey: string }) { export function ProfileView() { const { selectedPubkey, goBack, openDM } = useUIStore(); - const { pubkey: ownPubkey, profile: ownProfile, loggedIn, follows, follow, unfollow } = useUserStore(); + const canSign = useCanSign(); + const { pubkey: ownPubkey, profile: ownProfile, follows, follow, unfollow } = useUserStore(); const pubkey = selectedPubkey!; const isOwn = pubkey === ownPubkey; @@ -150,13 +151,13 @@ export function ProfileView() { ← {editing ? "Cancel" : "Back"}

{isOwn ? "Your Profile" : "Profile"}

- {isOwn && !getNDK().signer && ( + {isOwn && !canSign && ( read-only )} - {isOwn && !editing && !!getNDK().signer && ( + {isOwn && !editing && canSign && ( )} - {!isOwn && loggedIn && ( + {!isOwn && canSign && (
{(lud16 || profile?.lud06) && ( - {loggedIn && !!getNDK().signer && ( + {canSign && ( )} - {NAV_ITEMS.map((item) => { + {visibleNav.map((item) => { const badge = item.id === "dm" ? dmUnreadCount : item.id === "notifications" ? notifUnread : item.id === "bookmarks" ? bookmarkUnread : item.id === "follows" ? newFollowersCount : 0; return (
{/* Root reply box (inline, right below root) */} - {showRootReply && loggedIn && !!getNDK().signer && ( + {showRootReply && canSign && (