mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
Fix slow Nostr list sync by decoupling from profile and awaiting before feed render
The login-to-feed pipeline had several issues causing lists (blocks, subscriptions, hashtag preferences) to load too slowly or not reflect in the feed: 1. All list loading was chained after profileStatePromise, adding 2.5-6s of unnecessary delay. Lists only need relay URLs, not profile data. Now they chain off relaysReadyPromise instead, loading in parallel with profile fetching. 2. The video grid refreshed immediately without waiting for any lists, so the feed briefly displayed unfiltered content. Now the grid refresh waits (up to 8s) for critical feed-filtering lists to settle. 3. Hashtag preferences weren't cached into the snapshot before the feed rendered, so interests/disinterests were empty on first paint. 4. Subscription ensureLoaded had a 6s timeout that was too aggressive for relay fetch (12s) + decryption (3-12s), causing silent failures. Increased to 30s. 5. Subscription relay fetches for user and session actor ran sequentially. Now they run in parallel via Promise.all. https://claude.ai/code/session_01UKmTWb83AnQPRVQo3bKZqm
This commit is contained in:
3 files changed
+88
-38
No files matched your search
@@ -4135,6 +4135,14 @@ class Application {
|
||||
: Promise.resolve(detail?.postLogin ?? null);
|
||||
const postLoginResult = detail?.postLogin ?? null;
|
||||
|
||||
// relaysReadyPromise resolves as soon as the user's relay list is loaded,
|
||||
// before profile fetching completes. Lists can start loading immediately
|
||||
// after relays are available — they don't depend on profile data.
|
||||
const relaysReadyPromise =
|
||||
detail && typeof detail.relaysReadyPromise?.then === "function"
|
||||
? detail.relaysReadyPromise
|
||||
: postLoginPromise;
|
||||
|
||||
if (detail && typeof detail === "object") {
|
||||
try {
|
||||
detail.__handled = true;
|
||||
@@ -4270,8 +4278,10 @@ class Application {
|
||||
return null;
|
||||
});
|
||||
|
||||
// Chain list loading to profile state to ensure relays are loaded first.
|
||||
const listStatePromise = profileStatePromise.then(() => {
|
||||
// Start list loading as soon as relays are ready — lists don't depend on
|
||||
// profile data, only on relay URLs. This runs in parallel with profile
|
||||
// fetching instead of sequentially after it.
|
||||
const listStatePromise = relaysReadyPromise.then(() => {
|
||||
const tasks = [];
|
||||
if (activePubkey && typeof this.authService.loadBlocksForPubkey === "function") {
|
||||
tasks.push(
|
||||
@@ -4328,6 +4338,7 @@ class Application {
|
||||
});
|
||||
});
|
||||
|
||||
// DMs can wait for profile since they need encryption context.
|
||||
const dmStatePromise = profileStatePromise.then(() => {
|
||||
if (
|
||||
activePubkey &&
|
||||
@@ -4373,7 +4384,8 @@ class Application {
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
const hashtagPreferencesPromise = profileStatePromise.then(() => {
|
||||
// Hashtag preferences drive feed filtering — start as soon as relays are ready.
|
||||
const hashtagPreferencesPromise = relaysReadyPromise.then(() => {
|
||||
if (
|
||||
activePubkey &&
|
||||
this.hashtagPreferences &&
|
||||
@@ -4387,6 +4399,9 @@ class Application {
|
||||
this.capturePermissionPromptFromError(
|
||||
this.hashtagPreferences?.lastLoadError,
|
||||
);
|
||||
// Ensure the cached snapshot is updated before the feed renders
|
||||
// so the feed engine has access to interests/disinterests.
|
||||
this.updateCachedHashtagPreferences();
|
||||
})
|
||||
.catch((error) => {
|
||||
devLogger.warn(
|
||||
@@ -4399,13 +4414,9 @@ class Application {
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
void Promise.allSettled([
|
||||
profileStatePromise,
|
||||
listStatePromise,
|
||||
dmStatePromise,
|
||||
]);
|
||||
// Ensure DMs are tracked for cleanup.
|
||||
void dmStatePromise;
|
||||
void nwcPromise;
|
||||
void hashtagPreferencesPromise;
|
||||
|
||||
if (activePubkey) {
|
||||
const seedBlacklist = () => {
|
||||
@@ -4438,20 +4449,37 @@ class Application {
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Subscriptions are loaded in list tasks to align with per-category loading states.
|
||||
}
|
||||
|
||||
// Show loading state immediately while lists are syncing.
|
||||
try {
|
||||
this.reinitializeVideoListView({ reason: "login", postLoginResult });
|
||||
} catch (error) {
|
||||
devLogger.warn("Failed to reinitialize video list view after login:", error);
|
||||
}
|
||||
|
||||
this.lastIdentityRefreshPromise = this.refreshAllVideoGrids({
|
||||
reason: "auth-login",
|
||||
forceMainReload: true,
|
||||
});
|
||||
// Wait for the critical feed-filtering lists (blocks, subscriptions,
|
||||
// hashtag preferences) to settle before rendering the video grid. This
|
||||
// prevents the feed from briefly showing unfiltered content. We use a
|
||||
// time-boxed wait so the UI isn't blocked indefinitely if decryption stalls.
|
||||
const FEED_SYNC_TIMEOUT_MS = 8000;
|
||||
const feedSyncPromise = Promise.race([
|
||||
Promise.allSettled([
|
||||
listStatePromise,
|
||||
hashtagPreferencesPromise,
|
||||
profileStatePromise,
|
||||
]),
|
||||
new Promise((resolve) => setTimeout(resolve, FEED_SYNC_TIMEOUT_MS)),
|
||||
]).catch(() => null);
|
||||
|
||||
// Chain the grid refresh after the feed data is available.
|
||||
this.lastIdentityRefreshPromise = feedSyncPromise
|
||||
.then(() =>
|
||||
this.refreshAllVideoGrids({
|
||||
reason: "auth-login",
|
||||
forceMainReload: true,
|
||||
}),
|
||||
);
|
||||
this.lastIdentityRefreshPromise
|
||||
.catch((error) => {
|
||||
devLogger.error("Failed to refresh video grids after login:", error);
|
||||
|
||||
Reference in new issue
Block a user