From 08c5c71763d689cabf8ac2474728026be606b176 Mon Sep 17 00:00:00 2001 From: Silberengel Date: Thu, 14 May 2026 21:40:27 +0200 Subject: [PATCH] more efficiency fixes --- .../Sidebar/SidebarCalendarWeekWidget.tsx | 4 +- src/providers/FeedProvider.tsx | 65 +++++++++++++------ src/services/client.service.ts | 10 ++- src/services/indexed-db.service.ts | 52 ++++++++++----- 4 files changed, 90 insertions(+), 41 deletions(-) diff --git a/src/components/Sidebar/SidebarCalendarWeekWidget.tsx b/src/components/Sidebar/SidebarCalendarWeekWidget.tsx index 6a052ce4..ab22f18c 100644 --- a/src/components/Sidebar/SidebarCalendarWeekWidget.tsx +++ b/src/components/Sidebar/SidebarCalendarWeekWidget.tsx @@ -20,7 +20,6 @@ import client from '@/services/client.service' import { registerSessionInteractivePrewarmListener } from '@/services/session-interactive-prewarm-bridge' import indexedDb from '@/services/indexed-db.service' import { CALENDAR_EVENT_KINDS, ExtendedKind } from '@/constants' -import { appendCuratedReadOnlyRelays } from '@/pages/primary/SpellsPage/fauxSpellFeeds' import { CalendarDays, ChevronLeft, ChevronRight } from 'lucide-react' import { type Event } from 'nostr-tools' import { useCallback, useEffect, useMemo, useReducer, useState } from 'react' @@ -69,7 +68,8 @@ export default function SidebarCalendarWeekWidget() { applySocialKindBlockedFilter: false } ) - return appendCuratedReadOnlyRelays(base, blockedRelays).slice(0, SIDEBAR_CALENDAR_MAX_RELAYS) + /** Sidebar only: avoid prepending {@link READ_ONLY_RELAY_URLS} so idle shell does not open aggregator sockets. */ + return base.slice(0, SIDEBAR_CALENDAR_MAX_RELAYS) }, [favoriteRelays, blockedRelays, relayList]) const relayKey = useMemo(() => [...relayUrls].sort().join('|'), [relayUrls]) diff --git a/src/providers/FeedProvider.tsx b/src/providers/FeedProvider.tsx index 6cf25298..5421ce34 100644 --- a/src/providers/FeedProvider.tsx +++ b/src/providers/FeedProvider.tsx @@ -67,7 +67,10 @@ export function FeedProvider({ children }: { children: ReactNode }) { [favoriteRelays, relaySets] ) - /** Home Notes/Gallery stay focused: favorites/defaults plus the mixed trending relay. */ + /** + * Mixed trending slice (nostrarchives / Wisp-style feed) so the home timeline isn’t only the user’s + * graph — keeps a finger on what the wider network is surfacing, alongside favorites / NIP-65. + */ const primaryExtraRelayUrls = useMemo(() => [buildWispTrendingNotesRelayUrl()], []) /** Home Replies widen to relays that can surface inbox/reply context. */ @@ -182,6 +185,8 @@ export function FeedProvider({ children }: { children: ReactNode }) { ) const lastRelayInitDebugKey = useRef('') const lastHadFavoriteRelaysRef = useRef(null) + const relayUrlDebounceTimerRef = useRef | null>(null) + useEffect(() => { const initKey = [ isInitialized ? '1' : '0', @@ -194,29 +199,47 @@ export function FeedProvider({ children }: { children: ReactNode }) { replyExtraRelayLayers.httpRelayUrls.length, blockedRelays.length ].join('\x1e') - if (initKey !== lastRelayInitDebugKey.current) { - lastRelayInitDebugKey.current = initKey - logger.debug('FeedProvider relay init:', { - isInitialized, - favoriteRelays: favoriteRelays.length, - relaySets: relaySets.length, - relaySetRelays: favoriteFeedRelayUrls.length - favoriteRelays.length, - inboxRelays: replyExtraRelayLayers.inboxRelayUrls.length, - outboxRelays: replyExtraRelayLayers.outboxRelayUrls.length, - cacheRelays: replyExtraRelayLayers.cacheRelayUrls.length, - httpRelays: replyExtraRelayLayers.httpRelayUrls.length, - blockedRelays: blockedRelays.length - }) - } - const hasFavoriteRelays = favoriteFeedRelayUrls.length > 0 - const prevHad = lastHadFavoriteRelaysRef.current - lastHadFavoriteRelaysRef.current = hasFavoriteRelays - if (!hasFavoriteRelays && prevHad !== false) { - logger.debug('FeedProvider: no favorite or relay-set relays, using defaults') + const flush = () => { + if (initKey !== lastRelayInitDebugKey.current) { + lastRelayInitDebugKey.current = initKey + logger.debug('FeedProvider relay init:', { + isInitialized, + favoriteRelays: favoriteRelays.length, + relaySets: relaySets.length, + relaySetRelays: favoriteFeedRelayUrls.length - favoriteRelays.length, + inboxRelays: replyExtraRelayLayers.inboxRelayUrls.length, + outboxRelays: replyExtraRelayLayers.outboxRelayUrls.length, + cacheRelays: replyExtraRelayLayers.cacheRelayUrls.length, + httpRelays: replyExtraRelayLayers.httpRelayUrls.length, + blockedRelays: blockedRelays.length + }) + } + + const hasFavoriteRelays = favoriteFeedRelayUrls.length > 0 + const prevHad = lastHadFavoriteRelaysRef.current + lastHadFavoriteRelaysRef.current = hasFavoriteRelays + if (!hasFavoriteRelays && prevHad !== false) { + logger.debug('FeedProvider: no favorite or relay-set relays, using defaults') + } + + updateFeedRelayUrls() } - updateFeedRelayUrls() + if (relayUrlDebounceTimerRef.current) { + clearTimeout(relayUrlDebounceTimerRef.current) + } + relayUrlDebounceTimerRef.current = setTimeout(() => { + relayUrlDebounceTimerRef.current = null + flush() + }, 80) + + return () => { + if (relayUrlDebounceTimerRef.current) { + clearTimeout(relayUrlDebounceTimerRef.current) + relayUrlDebounceTimerRef.current = null + } + } }, [isInitialized, favoriteRelaysIdentity, blockedRelaysIdentity, replyExtraRelaysIdentity, updateFeedRelayUrls]) return ( diff --git a/src/services/client.service.ts b/src/services/client.service.ts index d5b31fb8..65651b5f 100644 --- a/src/services/client.service.ts +++ b/src/services/client.service.ts @@ -483,7 +483,15 @@ class ClientService extends EventTarget { if (!this.sessionPrewarmBaseCompleted) { this.sessionPrewarmBaseCompleted = true - fastTasks.push(this.prewarmProfileSearchIndexFromIdb(), this.fetchNip66RelayDiscovery()) + fastTasks.push(this.prewarmProfileSearchIndexFromIdb()) + /** NIP-66 discovery hits extra relays; defer so first feed/session work is not competing for sockets. */ + if (typeof window !== 'undefined') { + window.setTimeout(() => { + void this.fetchNip66RelayDiscovery() + }, 12_000) + } else { + void this.fetchNip66RelayDiscovery() + } } if (fastTasks.length === 0 && !options.pubkey) { diff --git a/src/services/indexed-db.service.ts b/src/services/indexed-db.service.ts index 97b7ca7c..8a8770e3 100644 --- a/src/services/indexed-db.service.ts +++ b/src/services/indexed-db.service.ts @@ -811,35 +811,53 @@ class IndexedDbService { }) } - async iterateProfileEvents(callback: (event: Event) => Promise): Promise { + /** + * Loads all cached kind-0 rows in one synchronous cursor pass (no `await` inside `onsuccess`, which + * would risk inactive transactions), then invokes `callback` in chunks with `requestAnimationFrame` + * yields so FlexSearch indexing does not monopolize the main thread. + */ + async iterateProfileEvents(callback: (event: Event) => void | Promise): Promise { await this.initPromise if (!this.db) { return } - return new Promise((resolve, reject) => { - const transaction = this.db!.transaction(StoreNames.PROFILE_EVENTS, 'readwrite') + const events = await new Promise((resolve, reject) => { + const out: Event[] = [] + const transaction = this.db!.transaction(StoreNames.PROFILE_EVENTS, 'readonly') const store = transaction.objectStore(StoreNames.PROFILE_EVENTS) const request = store.openCursor() request.onsuccess = (event) => { - const cursor = (event.target as IDBRequest).result - if (cursor) { - const value = (cursor.value as TValue).value - if (value) { - callback(value) - } - cursor.continue() - } else { - transaction.commit() - resolve() + const cursor = (event.target as IDBRequest).result as IDBCursorWithValue | null + if (!cursor) { + resolve(out) + return } + const value = (cursor.value as TValue).value + if (value) out.push(value) + cursor.continue() } - - request.onerror = (event) => { - transaction.commit() - reject(event) + request.onerror = () => { + reject(request.error ?? new Error('iterateProfileEvents: cursor failed')) } }) + + const yieldToMain = () => + new Promise((resolve) => { + if (typeof requestAnimationFrame === 'function') { + requestAnimationFrame(() => resolve()) + } else { + setTimeout(resolve, 0) + } + }) + + const chunkYieldEvery = 150 + for (let i = 0; i < events.length; i++) { + await Promise.resolve(callback(events[i]!)) + if (i > 0 && (i + 1) % chunkYieldEvery === 0) { + await yieldToMain() + } + } } async putFollowingFavoriteRelays(pubkey: string, relays: [string, string[]][]): Promise {