mirror of
https://github.com/hoornet/vega.git
synced 2026-05-06 12:19:11 -07:00
Instant thread display, faster fetches, trending always shows notes
Threads now render the focused note immediately instead of showing a loading skeleton. Root + ancestors fetch in parallel, timeouts cut from 10s to 5s per round-trip, ancestor lookups from 5s to 2s. Trending feed adds a base recency score so notes always appear even when engagement data times out from relays.
This commit is contained in:
@@ -40,36 +40,42 @@ export function ThreadView() {
|
||||
let cancelled = false;
|
||||
|
||||
async function loadThread() {
|
||||
setLoading(true);
|
||||
setLoadError(null);
|
||||
setTree(null);
|
||||
setAncestors([]);
|
||||
setRootEvent(null);
|
||||
setShowRootReply(false);
|
||||
|
||||
// Show focused note immediately as a minimal tree (no waiting)
|
||||
const minimalTree = buildThreadTree(focusedEvent.id, [focusedEvent]);
|
||||
setRootEvent(focusedEvent);
|
||||
setTree(minimalTree);
|
||||
setLoading(false);
|
||||
|
||||
try {
|
||||
// Ensure we have relay connectivity before fetching
|
||||
const connected = await ensureConnected();
|
||||
if (!connected && !cancelled) {
|
||||
setLoadError("No relay connections available. Check your network.");
|
||||
return;
|
||||
}
|
||||
|
||||
const rootId = getRootEventId(focusedEvent);
|
||||
let root: NDKEvent;
|
||||
let root: NDKEvent = focusedEvent;
|
||||
let fetchedAncestors: NDKEvent[] = [];
|
||||
|
||||
if (!rootId || rootId === focusedEvent.id) {
|
||||
root = focusedEvent;
|
||||
} else {
|
||||
const fetched = await fetchNoteById(rootId);
|
||||
if (rootId && rootId !== focusedEvent.id) {
|
||||
// Fetch root and ancestors in parallel with thread replies
|
||||
const [fetched, ancestorResult] = await Promise.all([
|
||||
fetchNoteById(rootId),
|
||||
fetchAncestors(focusedEvent),
|
||||
]);
|
||||
if (fetched) {
|
||||
root = fetched;
|
||||
fetchedAncestors = await fetchAncestors(focusedEvent);
|
||||
fetchedAncestors = fetchedAncestors.filter((a) => a.id !== root.id);
|
||||
fetchedAncestors = ancestorResult.filter((a) => a.id !== root.id);
|
||||
if (!cancelled) setAncestors(fetchedAncestors);
|
||||
} else {
|
||||
root = focusedEvent;
|
||||
if (!cancelled) setLoadError("Could not fetch the root note — relay may be slow.");
|
||||
} else if (!cancelled) {
|
||||
setLoadError("Could not fetch the root note — relay may be slow.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +86,6 @@ export function ThreadView() {
|
||||
if (cancelled) return;
|
||||
|
||||
// Build event list: root + thread replies + focused event + ancestors
|
||||
// Always include focused event — relay may not return it in thread fetch
|
||||
const allEvents = [root, ...events.filter((e) => e.id !== root.id)];
|
||||
if (focusedEvent.id !== root.id && !allEvents.some((e) => e.id === focusedEvent.id)) {
|
||||
allEvents.push(focusedEvent);
|
||||
@@ -93,16 +98,9 @@ export function ThreadView() {
|
||||
|
||||
const built = buildThreadTree(root.id, allEvents);
|
||||
setTree(built);
|
||||
|
||||
// Warn if we got zero replies (possible timeout)
|
||||
if (events.length === 0 && !loadError) {
|
||||
console.warn("[Wrystr] Thread fetch returned 0 replies — possible timeout or empty thread");
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to load thread:", err);
|
||||
if (!cancelled) setLoadError(`Failed to load: ${err}`);
|
||||
} finally {
|
||||
if (!cancelled) setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ export function withTimeout<T>(promise: Promise<T>, ms: number, fallback: T): Pr
|
||||
}
|
||||
|
||||
export const FEED_TIMEOUT = 8000; // 8s for feed fetches
|
||||
export const THREAD_TIMEOUT = 10000; // 10s per thread round-trip
|
||||
export const THREAD_TIMEOUT = 5000; // 5s per thread round-trip
|
||||
export const SINGLE_TIMEOUT = 5000; // 5s for single event lookups
|
||||
|
||||
const EMPTY_SET = new Set<NDKEvent>();
|
||||
|
||||
@@ -148,6 +148,8 @@ export async function fetchThreadEvents(rootId: string): Promise<NDKEvent[]> {
|
||||
return Array.from(allEvents.values());
|
||||
}
|
||||
|
||||
const ANCESTOR_TIMEOUT = 2000; // 2s per parent — fail fast
|
||||
|
||||
export async function fetchAncestors(event: NDKEvent, maxDepth = 5): Promise<NDKEvent[]> {
|
||||
const ancestors: NDKEvent[] = [];
|
||||
let current = event;
|
||||
@@ -162,7 +164,10 @@ export async function fetchAncestors(event: NDKEvent, maxDepth = 5): Promise<NDK
|
||||
eTags[eTags.length - 1][1];
|
||||
|
||||
if (!parentId) break;
|
||||
const parent = await fetchNoteById(parentId);
|
||||
const instance = getNDK();
|
||||
const filter: NDKFilter = { ids: [parentId], limit: 1 };
|
||||
const events = await fetchWithTimeout(instance, filter, ANCESTOR_TIMEOUT);
|
||||
const parent = Array.from(events)[0] ?? null;
|
||||
if (!parent) break;
|
||||
ancestors.unshift(parent);
|
||||
current = parent;
|
||||
|
||||
@@ -246,10 +246,11 @@ export const useFeedStore = create<FeedState>((set, get) => ({
|
||||
const eng = engagement.get(note.id) ?? { reactions: 0, replies: 0, zapSats: 0 };
|
||||
const ageHours = (now - (note.created_at ?? now)) / 3600;
|
||||
const decay = 1 / (1 + ageHours * 0.15);
|
||||
const score = (eng.reactions * 1 + eng.replies * 3 + eng.zapSats * 0.01) * decay;
|
||||
const engScore = eng.reactions * 1 + eng.replies * 3 + eng.zapSats * 0.01;
|
||||
// Base recency score ensures notes appear even when engagement data times out
|
||||
const score = (engScore + 0.1) * decay;
|
||||
return { note, score };
|
||||
})
|
||||
.filter((s) => s.score > 0)
|
||||
.sort((a, b) => b.score - a.score)
|
||||
.slice(0, 50)
|
||||
.map((s) => s.note);
|
||||
|
||||
Reference in New Issue
Block a user