setExpanded((v) => !v)}
>
-
+
{result.url}
@@ -72,6 +74,12 @@ function RelayHealthCard({ result, poolConnected }: { result: RelayHealthResult;
{poolConnected && (
)}
+
{expanded ? "▾" : "▸"}
@@ -105,9 +113,6 @@ function RelayHealthCard({ result, poolConnected }: { result: RelayHealthResult;
{nip11.supported_nips && nip11.supported_nips.length > 0 && (
-
- {nip11.supported_nips.length} NIPs supported
-
)}
@@ -125,12 +130,21 @@ function RelayHealthCard({ result, poolConnected }: { result: RelayHealthResult;
}
/** Fallback row for relays not yet health-checked */
-function RelayPoolRow({ url, connected }: { url: string; connected: boolean }) {
+function RelayPoolRow({ url, connected, onRemove }: { url: string; connected: boolean; onRemove: () => void }) {
return (
-
-
+
+
{url}
{connected ? "connected" : "—"}
+
);
}
@@ -142,6 +156,11 @@ export function RelaysView() {
const poolRelays = Array.from(ndk.pool?.relays?.values() ?? []);
const poolConnectedUrls = new Set(poolRelays.filter((r) => r.connected).map((r) => r.url));
+ const [input, setInput] = useState("");
+ const [addError, setAddError] = useState
(null);
+ const [removing, setRemoving] = useState(false);
+ const [republishing, setRepublishing] = useState(false);
+
// Auto-check on first mount if no results yet
useEffect(() => {
if (results.length === 0 && !checking) {
@@ -154,15 +173,33 @@ export function RelaysView() {
const offlineCount = results.filter((r) => r.status === "offline").length;
const deadRelays = results.filter((r) => r.status === "offline");
- const [removing, setRemoving] = useState(false);
- const [republishing, setRepublishing] = useState(false);
+ const handleAddRelay = () => {
+ const url = input.trim();
+ if (!url) return;
+ if (!url.startsWith("ws://") && !url.startsWith("wss://")) {
+ setAddError("URL must start with ws:// or wss://");
+ return;
+ }
+ if (getStoredRelayUrls().includes(url)) {
+ setAddError("Already in list");
+ return;
+ }
+ addRelay(url);
+ setInput("");
+ setAddError(null);
+ checkAll();
+ };
+
+ const handleRemoveRelay = (url: string) => {
+ removeRelay(url);
+ checkAll();
+ };
const handleRemoveDead = async () => {
setRemoving(true);
for (const r of deadRelays) {
removeRelay(r.url);
}
- // Re-check remaining
await checkAll();
setRemoving(false);
};
@@ -175,6 +212,11 @@ export function RelaysView() {
setRepublishing(false);
};
+ const handleKeyDown = (e: React.KeyboardEvent) => {
+ if (e.key === "Enter") handleAddRelay();
+ if (e.key === "Escape") setInput("");
+ };
+
// Merge: show health results first, then any pool relays not yet checked
const checkedUrls = new Set(results.map((r) => r.url));
const uncheckedPoolRelays = poolRelays.filter((r) => !checkedUrls.has(r.url));
@@ -221,30 +263,48 @@ export function RelaysView() {
+ {/* Add relay input */}
+
+
+ { setInput(e.target.value); setAddError(null); }}
+ onKeyDown={handleKeyDown}
+ placeholder="wss://relay.example.com"
+ className="flex-1 bg-bg border border-border px-3 py-1.5 text-text text-[12px] font-mono focus:outline-none focus:border-accent/50 placeholder:text-text-dim"
+ />
+
+ {loggedIn && !!getNDK().signer && (
+
+ )}
+
+ {addError &&
{addError}
}
+
+
{/* Actions bar — show when there are dead relays */}
{deadRelays.length > 0 && (
{deadRelays.length} relay{deadRelays.length > 1 ? "s" : ""} offline
-
-
- {loggedIn && !!getNDK().signer && (
-
- )}
-
+
)}
@@ -259,11 +319,17 @@ export function RelaysView() {
key={result.url}
result={result}
poolConnected={poolConnectedUrls.has(result.url)}
+ onRemove={() => handleRemoveRelay(result.url)}
/>
))}
{uncheckedPoolRelays.map((relay) => (
-
+
handleRemoveRelay(relay.url)}
+ />
))}
diff --git a/src/components/shared/SettingsView.tsx b/src/components/shared/SettingsView.tsx
index ac44273..d921a09 100644
--- a/src/components/shared/SettingsView.tsx
+++ b/src/components/shared/SettingsView.tsx
@@ -4,7 +4,7 @@ import { writeTextFile } from "@tauri-apps/plugin-fs";
import { useUserStore } from "../../stores/user";
import { useMuteStore } from "../../stores/mute";
import { useBookmarkStore } from "../../stores/bookmark";
-import { getNDK, getStoredRelayUrls, addRelay, removeRelay, publishRelayList } from "../../lib/nostr";
+import { getStoredRelayUrls } from "../../lib/nostr";
import { useProfile } from "../../hooks/useProfile";
import { NWCWizard } from "./NWCWizard";
import { getNotificationSettings, saveNotificationSettings, ensurePermission } from "../../lib/notifications";
@@ -113,117 +113,6 @@ function MutedKeywordsSection() {
);
}
-function RelayRow({ url, onRemove }: { url: string; onRemove: () => void }) {
- const ndk = getNDK();
- const relay = ndk.pool?.relays.get(url);
- const connected = relay?.connected ?? false;
-
- return (
-
- );
-}
-
-function RelaySection() {
- const { loggedIn } = useUserStore();
- const [relays, setRelays] = useState