fix(subscriptions): improve reliability of list loading and button responsiveness

- Implemented optimistic UI updates for the channel subscribe button to improve responsiveness.
- Fixed a race condition in `js/subscriptions.js` and `js/services/hashtagPreferencesService.js` where lists were wiped on refresh due to `profileCache.activePubkey` not being initialized in time. Now using `profileCache.getProfileData(pubkey, ...)` to ensure the correct cache entry is loaded.
- Validated with targeted unit tests.

Co-authored-by: PR0M3TH3AN <[email protected]>
This commit is contained in:
google-labs-jules[bot]andPR0M3TH3AN committed 2026-01-26 18:56:34 +00:00
1 parent 560dcf96b0
commit 73541168f7
4 files changed
+81 -3

No files matched your search

+18 -1
View File
@@ -4163,11 +4163,28 @@ function renderSubscribeButton(channelHex) {
}
return;
}
// Optimistic UI update
const wasSubscribed = toggleBtn.dataset.state === "subscribed";
const nextState = wasSubscribed ? "unsubscribed" : "subscribed";
const label = toggleBtn.querySelector("span");
toggleBtn.dataset.state = nextState;
if (label) {
label.textContent = nextState === "subscribed" ? "Unsubscribe" : "Subscribe";
}
try {
await subscriptions.toggleChannel(channelHex, currentApp.pubkey);
// Re-render the button so it toggles state
// Re-render to ensure state is perfectly synced with the service
renderSubscribeButton(channelHex);
} catch (err) {
// Revert UI on failure
toggleBtn.dataset.state = wasSubscribed ? "subscribed" : "unsubscribed";
if (label) {
label.textContent = wasSubscribed ? "Unsubscribe" : "Subscribe";
}
userLogger.error("Failed to update subscription:", err);
const permissionErrorCodes = new Set([
"extension-permission-denied",
+1 -1
View File
@@ -285,7 +285,7 @@ class HashtagPreferencesService {
loadFromCache(pubkey) {
// profileCache internally uses activePubkey, but we should verify if it matches
try {
let cached = profileCache.get("interests");
let cached = profileCache.getProfileData(pubkey, "interests");
// Legacy fallback migration
if (!cached && typeof localStorage !== "undefined") {
+1 -1
View File
@@ -310,7 +310,7 @@ class SubscriptionsManager {
const normalizedUserPubkey = normalizeHexPubkey(userPubkey) || userPubkey;
// 1. Attempt to load from cache first
const cached = profileCache.get("subscriptions");
const cached = profileCache.getProfileData(normalizedUserPubkey, "subscriptions");
if (Array.isArray(cached)) {
devLogger.log("[SubscriptionsManager] Loaded subscriptions from cache.");
this.subscribedPubkeys = new Set(cached);
+61
View File
@@ -0,0 +1,61 @@
import { subscriptions } from './js/subscriptions.js';
import { hashtagPreferences } from './js/services/hashtagPreferencesService.js';
import { profileCache } from './js/state/profileCache.js';
// Mock profileCache.getProfileData
const originalGetProfileData = profileCache.getProfileData;
const originalGet = profileCache.get;
let calls = [];
profileCache.getProfileData = (pubkey, section) => {
calls.push({ method: 'getProfileData', pubkey, section });
return null;
};
profileCache.get = (section) => {
calls.push({ method: 'get', section });
return null;
};
async function runVerify() {
console.log("Verifying fix...");
// Test SubscriptionsManager
const subPubkey = "0000000000000000000000000000000000000000000000000000000000000001";
// We need to mock updateFromRelays to avoid network calls, or just catch error
subscriptions.updateFromRelays = async () => {};
await subscriptions.loadSubscriptions(subPubkey);
const subCall = calls.find(c => c.section === "subscriptions");
if (subCall && subCall.method === "getProfileData" && subCall.pubkey === subPubkey) {
console.log("PASS: subscriptions.loadSubscriptions used getProfileData with correct pubkey.");
} else {
console.error("FAIL: subscriptions.loadSubscriptions did not use getProfileData correctly.", calls);
process.exit(1);
}
calls = []; // Reset
// Test HashtagPreferencesService
const tagPubkey = "0000000000000000000000000000000000000000000000000000000000000002";
// Mock load logic to avoid network
hashtagPreferences.loadFromCache(tagPubkey);
const tagCall = calls.find(c => c.section === "interests");
if (tagCall && tagCall.method === "getProfileData" && tagCall.pubkey === tagPubkey) {
console.log("PASS: hashtagPreferences.loadFromCache used getProfileData with correct pubkey.");
} else {
console.error("FAIL: hashtagPreferences.loadFromCache did not use getProfileData correctly.", calls);
process.exit(1);
}
console.log("All verifications passed.");
}
runVerify().catch(err => {
console.error(err);
process.exit(1);
});