+ {/* Header */}
+
+ Debug
+
+
+
+ {/* Uptime + Live Sub */}
+
+
+ NDK uptime: {state.uptimeMs !== null ? formatUptime(state.uptimeMs) : "—"}
+
+
+
+ live sub {state.liveSubActive ? "on" : "off"}
+
+
+
+ {/* Relays */}
+
+
Relays ({connectedCount}/{state.relays.length})
+
+ {state.relays.map((r) => (
+
+
+ {shortenUrl(r.url)}
+
+ ))}
+
+
+
+ {/* Feed Timestamps */}
+
+
Last updated
+
+ {(["global", "following", "trending"] as const).map((tab) => (
+
+ {tab}:
+ {state.lastUpdated[tab] ? timeAgo(state.lastUpdated[tab]) : "—"}
+
+ ))}
+
+
+
+ {/* Recent Diagnostics */}
+
+
Recent log
+ {state.recentDiag.length === 0 ? (
+
No entries
+ ) : (
+
+ {state.recentDiag.map((d, i) => (
+
+
+ {new Date(d.ts).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit" })}
+
+ {d.action}
+ {d.durationMs !== undefined && (
+ {d.durationMs}ms
+ )}
+ {d.eventsReturned !== undefined && (
+ {d.eventsReturned}ev
+ )}
+
+ ))}
+
+ )}
+
+
+ );
+}
diff --git a/src/hooks/useKeyboardShortcuts.ts b/src/hooks/useKeyboardShortcuts.ts
index e869c4d..82d40fa 100644
--- a/src/hooks/useKeyboardShortcuts.ts
+++ b/src/hooks/useKeyboardShortcuts.ts
@@ -3,11 +3,18 @@ import { useUIStore } from "../stores/ui";
import { useFeedStore } from "../stores/feed";
export function useKeyboardShortcuts() {
- const { currentView, setView, goBack, toggleHelp } = useUIStore();
+ const { currentView, setView, goBack, toggleHelp, showDebugPanel, toggleDebugPanel } = useUIStore();
const { focusedNoteIndex, setFocusedNoteIndex, notes } = useFeedStore();
useEffect(() => {
const handler = (e: KeyboardEvent) => {
+ // Ctrl+Shift+D works everywhere, even in text fields
+ if (e.ctrlKey && e.shiftKey && e.key === "D") {
+ e.preventDefault();
+ toggleDebugPanel();
+ return;
+ }
+
const tag = (e.target as HTMLElement).tagName;
if (tag === "INPUT" || tag === "TEXTAREA" || (e.target as HTMLElement).isContentEditable) return;
switch (e.key) {
@@ -21,6 +28,7 @@ export function useKeyboardShortcuts() {
setTimeout(() => (document.querySelector("[data-search-input]") as HTMLInputElement)?.focus(), 50);
break;
case "Escape":
+ if (showDebugPanel) { toggleDebugPanel(); break; }
goBack();
break;
case "?":
@@ -38,5 +46,5 @@ export function useKeyboardShortcuts() {
};
window.addEventListener("keydown", handler);
return () => window.removeEventListener("keydown", handler);
- }, [currentView, focusedNoteIndex, notes.length]);
+ }, [currentView, focusedNoteIndex, notes.length, showDebugPanel]);
}
diff --git a/src/lib/feedDiagnostics.ts b/src/lib/feedDiagnostics.ts
index 523b608..5aaba18 100644
--- a/src/lib/feedDiagnostics.ts
+++ b/src/lib/feedDiagnostics.ts
@@ -32,6 +32,10 @@ function getLog(): DiagEntry[] {
}
}
+export function getRecentDiagEntries(count = 5): DiagEntry[] {
+ return getLog().slice(-count).reverse();
+}
+
function saveLog(entries: DiagEntry[]) {
localStorage.setItem(DIAG_KEY, JSON.stringify(entries.slice(-MAX_ENTRIES)));
}
diff --git a/src/lib/nostr/core.ts b/src/lib/nostr/core.ts
index bbc6972..0e516fd 100644
--- a/src/lib/nostr/core.ts
+++ b/src/lib/nostr/core.ts
@@ -57,16 +57,22 @@ export function saveRelayUrls(urls: string[]) {
}
let ndk: NDK | null = null;
+let ndkCreatedAt: number | null = null;
export function getNDK(): NDK {
if (!ndk) {
ndk = new NDK({
explicitRelayUrls: getStoredRelayUrls(),
});
+ ndkCreatedAt = Date.now();
}
return ndk;
}
+export function getNDKUptimeMs(): number | null {
+ return ndkCreatedAt ? Date.now() - ndkCreatedAt : null;
+}
+
/**
* Destroy the current NDK instance and create a fresh one.
* Preserves the signer (login state) but resets all relay connections.
@@ -87,6 +93,7 @@ export async function resetNDK(): Promise