Merge pull request #1941 from PR0M3TH3AN/codex/update-non-blocking-auth-login-flow

Defer decrypt permission prompts; surface “loading in background” for hashtag prefs & subscriptions
This commit is contained in:
thePR0M3TH3AN authored and GitHub committed 2026-01-29 13:08:48 -05:00
commit 8e26d7bed8
5 files changed
+346 -34

No files matched your search

+6
View File
@@ -1049,6 +1049,12 @@
<p class="text-sm text-muted">
These are the creators you’ve subscribed to directly in bitvid.
</p>
<p
id="subscriptionsStatus"
class="text-sm text-muted"
role="status"
aria-live="polite"
></p>
<div class="flex items-center justify-between gap-3">
<h3 class="text-base font-semibold text-text">Subscriptions</h3>
<button
+8 -4
View File
@@ -2389,7 +2389,7 @@ class Application {
this.tagPreferenceMenuController.refreshActiveMenus();
}
async loadHashtagPreferencesForPubkey(pubkey) {
async loadHashtagPreferencesForPubkey(pubkey, options = {}) {
if (
!this.hashtagPreferences ||
typeof this.hashtagPreferences.load !== "function"
@@ -2398,7 +2398,7 @@ class Application {
}
try {
await this.hashtagPreferences.load(pubkey);
await this.hashtagPreferences.load(pubkey, options);
return true;
} catch (error) {
devLogger.error(
@@ -4189,7 +4189,9 @@ class Application {
}
const hashtagPreferencesPromise = Promise.resolve(
this.loadHashtagPreferencesForPubkey(loginContext.pubkey),
this.loadHashtagPreferencesForPubkey(loginContext.pubkey, {
allowPermissionPrompt: false,
}),
).catch((error) => {
devLogger.warn(
"[Application] Background hashtag preferences load failed:",
@@ -4343,7 +4345,9 @@ class Application {
});
// Load subscriptions in the background to avoid blocking the main feed.
subscriptions.ensureLoaded(activePubkey).catch((error) => {
subscriptions
.ensureLoaded(activePubkey, { allowPermissionPrompt: false })
.catch((error) => {
devLogger.warn(
"[Application] Failed to load subscriptions during login:",
error
+98 -10
View File
@@ -238,6 +238,7 @@ class HashtagPreferencesService {
this.eventId = null;
this.eventCreatedAt = null;
this.loaded = false;
this.backgroundLoading = false;
this.preferencesVersion = DEFAULT_VERSION;
this.decryptRetryTimeoutId = null;
@@ -245,12 +246,12 @@ class HashtagPreferencesService {
if (event === "profileChanged") {
this.reset();
if (detail.pubkey) {
this.load(detail.pubkey);
this.load(detail.pubkey, { allowPermissionPrompt: false });
}
} else if (event === "runtimeCleared" && detail.pubkey === this.activePubkey) {
this.reset();
if (this.activePubkey) {
this.load(this.activePubkey);
this.load(this.activePubkey, { allowPermissionPrompt: false });
}
}
});
@@ -266,6 +267,7 @@ class HashtagPreferencesService {
this.eventId = null;
this.eventCreatedAt = null;
this.loaded = false;
this.backgroundLoading = false;
this.preferencesVersion = DEFAULT_VERSION;
if (this.decryptRetryTimeoutId) {
clearTimeout(this.decryptRetryTimeoutId);
@@ -425,7 +427,7 @@ class HashtagPreferencesService {
}
}
scheduleDecryptRetry(pubkey, error) {
scheduleDecryptRetry(pubkey, error, options = {}) {
const normalized = normalizeHexPubkey(pubkey);
if (!normalized) {
return;
@@ -438,7 +440,7 @@ class HashtagPreferencesService {
if (this.activePubkey && this.activePubkey !== normalized) {
return;
}
this.load(normalized).catch((retryError) => {
this.load(normalized, options).catch((retryError) => {
userLogger.warn(`${LOG_PREFIX} Decryption retry failed`, retryError);
});
}, DECRYPT_RETRY_DELAY_MS);
@@ -448,14 +450,18 @@ class HashtagPreferencesService {
);
}
async load(pubkey) {
async load(pubkey, options = {}) {
const normalized = normalizeHexPubkey(pubkey);
const allowPermissionPrompt = options?.allowPermissionPrompt !== false;
let wasLoadedForUser =
this.activePubkey &&
this.activePubkey === normalized &&
(this.interests.size > 0 || this.disinterests.size > 0 || this.loaded);
this.activePubkey = normalized;
if (allowPermissionPrompt) {
this.backgroundLoading = false;
}
if (!normalized) {
this.reset();
@@ -466,7 +472,12 @@ class HashtagPreferencesService {
// Try cache first
if (this.loadFromCache(normalized)) {
wasLoadedForUser = true;
if (!allowPermissionPrompt && !this.backgroundLoading) {
this.backgroundLoading = true;
this.emitChange("background-loading", { background: true });
}
}
const wasBackgroundLoading = this.backgroundLoading;
if (
!nostrClient ||
@@ -481,10 +492,18 @@ class HashtagPreferencesService {
userLogger.warn(
`${LOG_PREFIX} Keeping existing preferences despite client unavailability.`,
);
if (wasBackgroundLoading) {
this.backgroundLoading = false;
this.emitChange("background-loaded", { background: false });
}
return;
}
this.reset();
this.loaded = true;
if (wasBackgroundLoading) {
this.backgroundLoading = false;
this.emitChange("background-loaded", { background: false });
}
return;
}
@@ -495,10 +514,18 @@ class HashtagPreferencesService {
userLogger.warn(
`${LOG_PREFIX} Keeping existing preferences; no relays available for refresh.`,
);
if (wasBackgroundLoading) {
this.backgroundLoading = false;
this.emitChange("background-loaded", { background: false });
}
return;
}
this.reset();
this.loaded = true;
if (wasBackgroundLoading) {
this.backgroundLoading = false;
this.emitChange("background-loaded", { background: false });
}
return;
}
@@ -553,6 +580,10 @@ class HashtagPreferencesService {
`${LOG_PREFIX} Failed to load hashtag preferences (keeping local state if any).`,
fetchError,
);
if (wasBackgroundLoading) {
this.backgroundLoading = false;
this.emitChange("background-loaded", { background: false });
}
return;
}
@@ -563,11 +594,19 @@ class HashtagPreferencesService {
userLogger.warn(
`${LOG_PREFIX} Keeping existing preferences despite empty relay response.`,
);
if (wasBackgroundLoading) {
this.backgroundLoading = false;
this.emitChange("background-loaded", { background: false });
}
return;
}
this.reset();
this.loaded = true;
if (wasBackgroundLoading) {
this.backgroundLoading = false;
this.emitChange("background-loaded", { background: false });
}
return;
}
@@ -600,10 +639,18 @@ class HashtagPreferencesService {
if (!latest) {
if (wasLoadedForUser) {
if (wasBackgroundLoading) {
this.backgroundLoading = false;
this.emitChange("background-loaded", { background: false });
}
return;
}
this.reset();
this.loaded = true;
if (wasBackgroundLoading) {
this.backgroundLoading = false;
this.emitChange("background-loaded", { background: false });
}
return;
}
@@ -616,12 +663,18 @@ class HashtagPreferencesService {
devLogger.log(
`${LOG_PREFIX} Ignoring stale/concurrent preferences event (remote: ${latestCreatedAt}, local: ${currentCreatedAt}).`,
);
if (wasBackgroundLoading) {
this.backgroundLoading = false;
this.emitChange("background-loaded", { background: false });
}
return;
}
let decryptResult;
try {
const decryptPromise = this.decryptEvent(latest, normalized);
const decryptPromise = this.decryptEvent(latest, normalized, {
allowPermissionPrompt,
});
const timeoutPromise = new Promise((_, reject) =>
setTimeout(
() => {
@@ -649,7 +702,15 @@ class HashtagPreferencesService {
if (!wasLoadedForUser && !this.loaded) {
this.loaded = true;
}
this.scheduleDecryptRetry(normalized, decryptResult.error);
this.scheduleDecryptRetry(normalized, decryptResult.error, {
allowPermissionPrompt,
});
return;
}
if (
!allowPermissionPrompt &&
decryptResult.error?.code === "hashtag-preferences-permission-required"
) {
return;
}
@@ -659,11 +720,19 @@ class HashtagPreferencesService {
userLogger.warn(
`${LOG_PREFIX} Preserving cached preferences despite decryption failure.`,
);
if (wasBackgroundLoading) {
this.backgroundLoading = false;
this.emitChange("background-loaded", { background: false });
}
return;
}
this.reset();
this.loaded = true;
if (wasBackgroundLoading) {
this.backgroundLoading = false;
this.emitChange("background-loaded", { background: false });
}
return;
}
@@ -681,6 +750,9 @@ class HashtagPreferencesService {
? latest.created_at
: null;
this.loaded = true;
if (this.backgroundLoading) {
this.backgroundLoading = false;
}
this.saveToCache();
this.emitChange("sync", { scheme: decryptResult.scheme });
} catch (error) {
@@ -693,7 +765,7 @@ class HashtagPreferencesService {
}
}
async decryptEvent(event, userPubkey) {
async decryptEvent(event, userPubkey, options = {}) {
const ciphertext = typeof event?.content === "string" ? event.content : "";
if (!ciphertext) {
const error = new Error("Preference event missing ciphertext content.");
@@ -701,8 +773,13 @@ class HashtagPreferencesService {
return { ok: false, error };
}
const allowPermissionPrompt = options?.allowPermissionPrompt !== false;
let signer = getActiveSigner();
if (!signer && typeof nostrClient?.ensureActiveSignerForPubkey === "function") {
if (
!signer &&
typeof nostrClient?.ensureActiveSignerForPubkey === "function" &&
allowPermissionPrompt
) {
signer = await nostrClient.ensureActiveSignerForPubkey(userPubkey);
}
const signerHasNip04 = typeof signer?.nip04Decrypt === "function";
@@ -716,6 +793,13 @@ class HashtagPreferencesService {
(!signerHasNip44 && requiresNip44) ||
(!signerHasNip04 && requiresNip04)
) {
if (!allowPermissionPrompt) {
const error = new Error(
"Decrypt permissions are required to read hashtag preferences.",
);
error.code = "hashtag-preferences-permission-required";
return { ok: false, error };
}
try {
await requestDefaultExtensionPermissions();
} catch (error) {
@@ -766,7 +850,11 @@ class HashtagPreferencesService {
);
}
const nostrApi = typeof window !== "undefined" ? window?.nostr : null;
const nostrApi = allowPermissionPrompt
? typeof window !== "undefined"
? window?.nostr
: null
: null;
if (nostrApi) {
if (typeof nostrApi.nip04?.decrypt === "function") {
registerDecryptor(
+84 -14
View File
@@ -329,6 +329,7 @@ class SubscriptionsManager {
this.currentUserPubkey = null;
this.loaded = false;
this.loadingPromise = null;
this.backgroundLoading = false;
this.subscriptionListView = null;
this.lastRunOptions = null;
this.lastResult = null;
@@ -350,7 +351,9 @@ class SubscriptionsManager {
} else if (event === "runtimeCleared" && detail.pubkey === this.currentUserPubkey) {
this.reset();
if (this.currentUserPubkey) {
this.loadSubscriptions(this.currentUserPubkey);
this.loadSubscriptions(this.currentUserPubkey, {
allowPermissionPrompt: false,
});
}
}
});
@@ -359,12 +362,13 @@ class SubscriptionsManager {
/**
* Decrypt the subscription list from kind=30000 (d="subscriptions").
*/
async loadSubscriptions(userPubkey) {
async loadSubscriptions(userPubkey, options = {}) {
if (!userPubkey) {
userLogger.warn("[SubscriptionsManager] No pubkey => cannot load subs.");
return;
}
const allowPermissionPrompt = options?.allowPermissionPrompt !== false;
const normalizedUserPubkey = normalizeHexPubkey(userPubkey) || userPubkey;
// 1. Attempt to load from cache first
@@ -379,14 +383,25 @@ class SubscriptionsManager {
this.loaded = true;
// Trigger background update
this.updateFromRelays(userPubkey).catch((err) => {
if (!allowPermissionPrompt && !this.backgroundLoading) {
this.backgroundLoading = true;
this.emitter.emit("change", {
action: "background-loading",
subscribedPubkeys: Array.from(this.subscribedPubkeys),
background: true,
});
} else if (allowPermissionPrompt) {
this.backgroundLoading = false;
}
this.updateFromRelays(userPubkey, { allowPermissionPrompt }).catch((err) => {
devLogger.warn("[SubscriptionsManager] Background update failed:", err);
});
return;
}
// 2. If no cache, must wait for relays
await this.updateFromRelays(userPubkey);
await this.updateFromRelays(userPubkey, { allowPermissionPrompt });
}
saveToCache(userPubkey) {
@@ -402,7 +417,7 @@ class SubscriptionsManager {
return this.emitter.on(eventName, handler);
}
scheduleDecryptRetry(userPubkey, error) {
scheduleDecryptRetry(userPubkey, error, options = {}) {
const normalized = normalizeHexPubkey(userPubkey) || userPubkey;
if (!normalized) {
return;
@@ -415,7 +430,7 @@ class SubscriptionsManager {
if (this.currentUserPubkey && this.currentUserPubkey !== normalized) {
return;
}
this.updateFromRelays(normalized).catch((retryError) => {
this.updateFromRelays(normalized, options).catch((retryError) => {
userLogger.warn("[SubscriptionsManager] Decryption retry failed:", retryError);
});
}, DECRYPT_RETRY_DELAY_MS);
@@ -425,10 +440,12 @@ class SubscriptionsManager {
);
}
async updateFromRelays(userPubkey) {
async updateFromRelays(userPubkey, options = {}) {
if (!userPubkey) return;
try {
const allowPermissionPrompt = options?.allowPermissionPrompt !== false;
const wasBackgroundLoading = this.backgroundLoading;
const normalizedUserPubkey = normalizeHexPubkey(userPubkey) || userPubkey;
const readRelays = relayManager.getReadRelayUrls();
@@ -437,6 +454,13 @@ class SubscriptionsManager {
devLogger.warn(
"[SubscriptionsManager] No relay URLs available while loading subscriptions.",
);
if (wasBackgroundLoading) {
this.backgroundLoading = false;
this.emitter.emit("change", {
action: "background-loaded",
subscribedPubkeys: Array.from(this.subscribedPubkeys),
});
}
return;
}
@@ -516,6 +540,13 @@ class SubscriptionsManager {
// This means no updates. We keep what we have.
devLogger.log("[SubscriptionsManager] No updates from relays.");
}
if (wasBackgroundLoading) {
this.backgroundLoading = false;
this.emitter.emit("change", {
action: "background-loaded",
subscribedPubkeys: Array.from(this.subscribedPubkeys),
});
}
return;
}
@@ -534,7 +565,9 @@ class SubscriptionsManager {
let decryptResult;
try {
const decryptPromise = this.decryptSubscriptionEvent(newest, userPubkey);
const decryptPromise = this.decryptSubscriptionEvent(newest, userPubkey, {
allowPermissionPrompt,
});
const timeoutPromise = new Promise((_, reject) =>
setTimeout(
() => {
@@ -557,13 +590,28 @@ class SubscriptionsManager {
if (!this.loaded && !cachedSnapshot.hasSnapshot) {
this.loaded = true;
}
this.scheduleDecryptRetry(normalizedUserPubkey, decryptResult.error);
this.scheduleDecryptRetry(normalizedUserPubkey, decryptResult.error, {
allowPermissionPrompt,
});
return;
}
if (
!allowPermissionPrompt &&
decryptResult.error?.code === "subscriptions-permission-required"
) {
return;
}
userLogger.error(
"[SubscriptionsManager] Failed to decrypt subscription list:",
decryptResult.error,
);
if (wasBackgroundLoading) {
this.backgroundLoading = false;
this.emitter.emit("change", {
action: "background-loaded",
subscribedPubkeys: Array.from(this.subscribedPubkeys),
});
}
if (!this.loaded) {
this.subscribedPubkeys.clear();
this.subsEventId = null;
@@ -583,6 +631,9 @@ class SubscriptionsManager {
this.subscribedPubkeys = newSet;
this.currentUserPubkey = normalizedUserPubkey;
this.loaded = true;
if (wasBackgroundLoading) {
this.backgroundLoading = false;
}
// Update persistent cache
this.saveToCache(normalizedUserPubkey);
@@ -610,6 +661,11 @@ class SubscriptionsManager {
error
);
});
} else if (wasBackgroundLoading) {
this.emitter.emit("change", {
action: "background-loaded",
subscribedPubkeys: Array.from(this.subscribedPubkeys),
});
}
} catch (err) {
@@ -623,6 +679,7 @@ class SubscriptionsManager {
this.subsEventCreatedAt = null;
this.currentUserPubkey = null;
this.loaded = false;
this.backgroundLoading = false;
this.lastRunOptions = null;
this.lastResult = null;
this.hasRenderedOnce = false;
@@ -633,7 +690,7 @@ class SubscriptionsManager {
this.emitter.emit("change", { action: "reset", subscribedPubkeys: [] });
}
async ensureLoaded(actorHex) {
async ensureLoaded(actorHex, options = {}) {
devLogger.log("[SubscriptionsManager] ensureLoaded start", actorHex);
const normalizedActor = normalizeHexPubkey(actorHex) || actorHex;
if (!normalizedActor) {
@@ -655,7 +712,7 @@ class SubscriptionsManager {
return;
}
const loader = this.loadSubscriptions(normalizedActor);
const loader = this.loadSubscriptions(normalizedActor, options);
this.loadingPromise = loader;
try {
@@ -709,7 +766,7 @@ class SubscriptionsManager {
return Array.from(this.subscribedPubkeys);
}
async decryptSubscriptionEvent(event, userPubkey) {
async decryptSubscriptionEvent(event, userPubkey, options = {}) {
const ciphertext = typeof event?.content === "string" ? event.content : "";
if (!ciphertext) {
const error = new Error("Subscription event is missing ciphertext content.");
@@ -717,6 +774,7 @@ class SubscriptionsManager {
return { ok: false, error };
}
const allowPermissionPrompt = options?.allowPermissionPrompt !== false;
const hints = extractEncryptionHints(event);
const requiresNip44 = hints.includes("nip44") || hints.includes("nip44_v2");
const requiresNip04 = !hints.length || hints.includes("nip04") || !requiresNip44;
@@ -731,7 +789,8 @@ class SubscriptionsManager {
if (
!signer ||
(!signerHasRequiredDecryptors(signer) &&
typeof nostrClient?.ensureActiveSignerForPubkey === "function")
typeof nostrClient?.ensureActiveSignerForPubkey === "function" &&
allowPermissionPrompt)
) {
signer = await nostrClient.ensureActiveSignerForPubkey(userPubkey);
}
@@ -739,7 +798,11 @@ class SubscriptionsManager {
const signerHasNip04 = typeof signer?.nip04Decrypt === "function";
const signerHasNip44 = typeof signer?.nip44Decrypt === "function";
const nostrApi = typeof window !== "undefined" ? window?.nostr : null;
const nostrApi = allowPermissionPrompt
? typeof window !== "undefined"
? window?.nostr
: null
: null;
const windowHasNip04 = typeof nostrApi?.nip04?.decrypt === "function";
const windowHasNip44 =
(nostrApi?.nip44 && typeof nostrApi.nip44.decrypt === "function") ||
@@ -749,6 +812,13 @@ class SubscriptionsManager {
(!signerHasNip44 && !windowHasNip44 && requiresNip44) ||
(!signerHasNip04 && !windowHasNip04 && requiresNip04)
) {
if (!allowPermissionPrompt) {
const error = new Error(
"Decrypt permissions are required to read subscriptions."
);
error.code = "subscriptions-permission-required";
return { ok: false, error };
}
try {
const permissionResult = await requestDefaultExtensionPermissions();
if (!permissionResult?.ok) {
+150 -6
View File
@@ -1314,6 +1314,7 @@ export class ProfileModalController {
this.walletStatusText = null;
this.profileWalletStatusText = null;
this.hashtagStatusText = null;
this.hashtagBackgroundLoading = false;
this.hashtagInterestList = null;
this.hashtagInterestEmpty = null;
this.hashtagInterestInput = null;
@@ -1324,6 +1325,8 @@ export class ProfileModalController {
this.hashtagDisinterestInput = null;
this.addHashtagDisinterestButton = null;
this.profileHashtagDisinterestRefreshBtn = null;
this.subscriptionsStatusText = null;
this.subscriptionsBackgroundLoading = false;
this.profileSubscriptionsRefreshBtn = null;
this.profileFriendsRefreshBtn = null;
this.profileBlockedRefreshBtn = null;
@@ -1421,8 +1424,8 @@ export class ProfileModalController {
this.subscriptionsService &&
typeof this.subscriptionsService.on === "function"
) {
this.subscriptionsService.on("change", () => {
void this.populateSubscriptionsList();
this.subscriptionsService.on("change", (detail) => {
this.handleSubscriptionsChange(detail);
});
}
@@ -1718,6 +1721,8 @@ export class ProfileModalController {
document.getElementById("profileAddHashtagDisinterestBtn") || null;
this.profileHashtagDisinterestRefreshBtn =
document.getElementById("profileHashtagDisinterestRefreshBtn") || null;
this.subscriptionsStatusText =
document.getElementById("subscriptionsStatus") || null;
this.profileRelayList = this.relayList;
this.profileRelayInput = this.relayInput;
@@ -5343,7 +5348,7 @@ export class ProfileModalController {
return;
}
void service
.loadSubscriptions(activeHex)
.loadSubscriptions(activeHex, { allowPermissionPrompt: true })
.then(() => {
void this.populateSubscriptionsList();
})
@@ -5397,7 +5402,9 @@ export class ProfileModalController {
if (!service || typeof service.load !== "function") {
return;
}
void service.load(activeHex).catch((error) => {
void service
.load(activeHex, { allowPermissionPrompt: true })
.catch((error) => {
devLogger.warn(
"[profileModal] Failed to refresh hashtag preferences:",
error,
@@ -7072,11 +7079,20 @@ export class ProfileModalController {
this.populateStoragePane();
} else if (target === "hashtags") {
this.populateHashtagPreferences();
if (activeHex && this.hashtagPreferencesService) {
this.hashtagPreferencesService
.load(activeHex, { allowPermissionPrompt: true })
.catch(noop);
}
this.refreshHashtagBackgroundStatus();
} else if (target === "subscriptions") {
if (activeHex && this.subscriptionsService) {
this.subscriptionsService.loadSubscriptions(activeHex).catch(noop);
this.subscriptionsService
.loadSubscriptions(activeHex, { allowPermissionPrompt: true })
.catch(noop);
}
void this.populateSubscriptionsList();
this.refreshSubscriptionsBackgroundStatus();
} else if (target === "blocked") {
if (activeHex && this.services.userBlocks) {
this.services.userBlocks.loadBlocks(activeHex).catch(noop);
@@ -7762,6 +7778,89 @@ export class ProfileModalController {
}
}
setSubscriptionsStatus(message = "", tone = "muted") {
if (!(this.subscriptionsStatusText instanceof HTMLElement)) {
return;
}
const classList = this.subscriptionsStatusText.classList;
classList.remove(
"text-status-success",
"text-status-warning",
"text-status-danger",
"text-status-info",
"text-muted",
);
const normalized =
typeof message === "string" && message.trim() ? message.trim() : "";
if (!normalized) {
this.subscriptionsStatusText.textContent = "";
this.subscriptionsStatusText.classList.add("text-muted", "hidden");
return;
}
this.subscriptionsStatusText.textContent = normalized;
this.subscriptionsStatusText.classList.remove("hidden");
switch (tone) {
case "success":
classList.add("text-status-success");
break;
case "warning":
case "error":
classList.add("text-status-warning");
break;
case "info":
classList.add("text-status-info");
break;
default:
classList.add("text-muted");
break;
}
}
refreshHashtagBackgroundStatus() {
const isBackground = this.hashtagPreferencesService?.backgroundLoading === true;
const statusText = this.hashtagStatusText?.textContent?.trim?.() || "";
if (isBackground && !this.hashtagBackgroundLoading) {
this.hashtagBackgroundLoading = true;
if (!statusText) {
this.setHashtagStatus("Loading in background…", "info");
}
return;
}
if (!isBackground && this.hashtagBackgroundLoading) {
if (statusText === "Loading in background…") {
this.setHashtagStatus("", "muted");
}
this.hashtagBackgroundLoading = false;
}
}
refreshSubscriptionsBackgroundStatus() {
const isBackground = this.subscriptionsService?.backgroundLoading === true;
const statusText = this.subscriptionsStatusText?.textContent?.trim?.() || "";
if (isBackground && !this.subscriptionsBackgroundLoading) {
this.subscriptionsBackgroundLoading = true;
if (!statusText) {
this.setSubscriptionsStatus("Loading in background…", "info");
}
return;
}
if (!isBackground && this.subscriptionsBackgroundLoading) {
if (statusText === "Loading in background…") {
this.setSubscriptionsStatus("", "muted");
}
this.subscriptionsBackgroundLoading = false;
}
}
clearHashtagInputs() {
if (this.hashtagInterestInput instanceof HTMLInputElement) {
this.hashtagInterestInput.value = "";
@@ -7780,6 +7879,7 @@ export class ProfileModalController {
if (!snapshot.interests.length && !snapshot.disinterests.length) {
this.setHashtagStatus("", "muted");
}
this.refreshHashtagBackgroundStatus();
}
renderHashtagList(type, tags) {
@@ -8097,7 +8197,45 @@ export class ProfileModalController {
detail && typeof detail.preferences === "object"
? detail.preferences
: detail;
const action = typeof detail?.action === "string" ? detail.action : "";
if (action === "background-loading") {
this.hashtagBackgroundLoading = true;
this.setHashtagStatus("Loading in background…", "info");
} else if (
(action === "sync" || action === "background-loaded" || action === "reset") &&
this.hashtagBackgroundLoading
) {
const statusText = this.hashtagStatusText?.textContent?.trim?.() || "";
if (statusText === "Loading in background…") {
this.setHashtagStatus("", "muted");
}
this.hashtagBackgroundLoading = false;
}
this.populateHashtagPreferences(preferences);
this.refreshHashtagBackgroundStatus();
}
handleSubscriptionsChange(detail = {}) {
const action = typeof detail?.action === "string" ? detail.action : "";
if (action === "background-loading") {
this.subscriptionsBackgroundLoading = true;
this.setSubscriptionsStatus("Loading in background…", "info");
} else if (
(action === "sync" || action === "background-loaded" || action === "reset") &&
this.subscriptionsBackgroundLoading
) {
const statusText = this.subscriptionsStatusText?.textContent?.trim?.() || "";
if (statusText === "Loading in background…") {
this.setSubscriptionsStatus("", "muted");
}
this.subscriptionsBackgroundLoading = false;
}
void this.populateSubscriptionsList();
this.refreshSubscriptionsBackgroundStatus();
}
populateBlockedList(blocked = null) {
@@ -8291,12 +8429,14 @@ export class ProfileModalController {
const service = this.subscriptionsService;
if (!service) {
this.clearSubscriptionsList();
this.refreshSubscriptionsBackgroundStatus();
return;
}
const activeHex = this.normalizeHexPubkey(this.getActivePubkey());
if (!activeHex) {
this.clearSubscriptionsList();
this.refreshSubscriptionsBackgroundStatus();
return;
}
@@ -8395,6 +8535,7 @@ export class ProfileModalController {
if (!deduped.length) {
this.subscriptionListEmpty.classList.remove("hidden");
this.subscriptionList.classList.add("hidden");
this.refreshSubscriptionsBackgroundStatus();
return;
}
@@ -8491,6 +8632,7 @@ export class ProfileModalController {
);
}
}
this.refreshSubscriptionsBackgroundStatus();
} catch (error) {
devLogger.warn("[profileModal] Failed to populate subscriptions list:", error);
}
@@ -13005,7 +13147,9 @@ export class ProfileModalController {
this.services.userBlocks.loadBlocks(activePubkey).catch(noop);
}
if (this.subscriptionsService) {
this.subscriptionsService.loadSubscriptions(activePubkey).catch(noop);
this.subscriptionsService
.loadSubscriptions(activePubkey, { allowPermissionPrompt: false })
.catch(noop);
}
}