fix: ensure active feed refreshes when hashtag preferences change

* Update `refreshAllVideoGrids` in `js/app.js` to detect the currently active feed (For You, Kids, Explore, or Recent) and invoke the corresponding loader, preventing staleness when interacting with the UI.
* Refactor `HashtagPreferencesService` mutation methods (`addInterest`, etc.) to be `async` and await the `publish()` call, ensuring state is persisted before returning.
* Remove redundant `persistPreferencesFromMenu` call in `TagPreferenceMenuController`, relying on the service's internal persistence logic.
* Update unit tests for `HashtagPreferencesService` and `TagPreferenceMenuController` to align with the async behavior.

Co-authored-by: PR0M3TH3AN <[email protected]>
This commit is contained in:
google-labs-jules[bot]andPR0M3TH3AN committed 2026-02-03 01:55:21 +00:00
1 parent 71e0ecd605
commit f77c89baf6
5 files changed
+44 -38

No files matched your search

+10 -2
View File
@@ -2365,13 +2365,21 @@ class Application {
}
try {
await this.loadVideos(forceMainReload);
if (this.isForYouFeedActive()) {
await this.loadForYouVideos(forceMainReload);
} else if (this.isFeedActive("kids")) {
await this.loadKidsVideos(forceMainReload);
} else if (this.isFeedActive("explore")) {
await this.loadExploreVideos(forceMainReload);
} else {
await this.loadVideos(forceMainReload);
}
} catch (error) {
const contextMessage = normalizedReason
? ` after ${normalizedReason}`
: "";
devLogger.error(
`Failed to refresh recent videos${contextMessage}:`,
`Failed to refresh videos${contextMessage}:`,
error,
);
}
+24 -16
View File
@@ -397,7 +397,7 @@ class HashtagPreferencesService {
return Array.from(this.disinterests).sort((a, b) => a.localeCompare(b));
}
addInterest(tag) {
async addInterest(tag) {
const normalized = normalizeHashtag(tag);
if (!normalized) {
return false;
@@ -409,16 +409,18 @@ class HashtagPreferencesService {
if (!hadInterest || removedFromDisinterests) {
this.emitChange("interest-added", { tag: normalized });
this.publish().catch((err) =>
userLogger.warn(`${LOG_PREFIX} Auto-save failed`, err),
);
try {
await this.publish();
} catch (err) {
userLogger.warn(`${LOG_PREFIX} Auto-save failed`, err);
}
return true;
}
return false;
}
removeInterest(tag) {
async removeInterest(tag) {
const normalized = normalizeHashtag(tag);
if (!normalized || !this.interests.has(normalized)) {
return false;
@@ -426,13 +428,15 @@ class HashtagPreferencesService {
this.interests.delete(normalized);
this.emitChange("interest-removed", { tag: normalized });
this.publish().catch((err) =>
userLogger.warn(`${LOG_PREFIX} Auto-save failed`, err),
);
try {
await this.publish();
} catch (err) {
userLogger.warn(`${LOG_PREFIX} Auto-save failed`, err);
}
return true;
}
addDisinterest(tag) {
async addDisinterest(tag) {
const normalized = normalizeHashtag(tag);
if (!normalized) {
return false;
@@ -444,16 +448,18 @@ class HashtagPreferencesService {
if (!hadDisinterest || removedFromInterests) {
this.emitChange("disinterest-added", { tag: normalized });
this.publish().catch((err) =>
userLogger.warn(`${LOG_PREFIX} Auto-save failed`, err),
);
try {
await this.publish();
} catch (err) {
userLogger.warn(`${LOG_PREFIX} Auto-save failed`, err);
}
return true;
}
return false;
}
removeDisinterest(tag) {
async removeDisinterest(tag) {
const normalized = normalizeHashtag(tag);
if (!normalized || !this.disinterests.has(normalized)) {
return false;
@@ -461,9 +467,11 @@ class HashtagPreferencesService {
this.disinterests.delete(normalized);
this.emitChange("disinterest-removed", { tag: normalized });
this.publish().catch((err) =>
userLogger.warn(`${LOG_PREFIX} Auto-save failed`, err),
);
try {
await this.publish();
} catch (err) {
userLogger.warn(`${LOG_PREFIX} Auto-save failed`, err);
}
return true;
}
-10
View File
@@ -276,16 +276,6 @@ export default class TagPreferenceMenuController {
this.callbacks.onPreferenceUpdate();
}
try {
await this.persistPreferencesFromMenu();
} catch (error) {
return;
}
if (this.callbacks.onPreferenceUpdate) {
this.callbacks.onPreferenceUpdate();
}
if (typeof detail?.closePopover === "function") {
detail.closePopover({ restoreFocus: false });
}
+5 -5
View File
@@ -325,17 +325,17 @@ test(
test(
"interest and disinterest lists remain exclusive",
{ concurrency: false },
() => {
async () => {
hashtagPreferences.reset();
assert.equal(hashtagPreferences.addInterest("Focus"), true);
assert.equal(hashtagPreferences.addDisinterest("focus"), true);
assert.equal(await hashtagPreferences.addInterest("Focus"), true);
assert.equal(await hashtagPreferences.addDisinterest("focus"), true);
assert.deepEqual(hashtagPreferences.getInterests(), []);
assert.deepEqual(hashtagPreferences.getDisinterests(), ["focus"]);
assert.equal(hashtagPreferences.removeDisinterest("focus"), true);
assert.equal(await hashtagPreferences.removeDisinterest("focus"), true);
assert.equal(hashtagPreferences.getDisinterests().length, 0);
assert.equal(hashtagPreferences.removeInterest("focus"), false);
assert.equal(await hashtagPreferences.removeInterest("focus"), false);
},
);
@@ -121,7 +121,7 @@ describe("TagPreferenceMenuController", () => {
});
describe("handleMenuAction", () => {
it("should call service method, notify update, publish, and notify update again", async () => {
it("should call service method and notify update", async () => {
const tag = "bitcoin";
const action = TAG_PREFERENCE_ACTIONS.ADD_INTEREST;
@@ -130,11 +130,11 @@ describe("TagPreferenceMenuController", () => {
assert.strictEqual(mockHashtagPreferences.addInterest.mock.calls.length, 1);
assert.strictEqual(mockHashtagPreferences.addInterest.mock.calls[0].arguments[0], tag);
// onPreferenceUpdate should be called twice (optimistic + confirmed)
assert.strictEqual(mockCallbacks.onPreferenceUpdate.mock.calls.length, 2);
// onPreferenceUpdate should be called once
assert.strictEqual(mockCallbacks.onPreferenceUpdate.mock.calls.length, 1);
// publish should be called
assert.strictEqual(mockHashtagPreferences.publish.mock.calls.length, 1);
// publish should not be called by controller directly
assert.strictEqual(mockHashtagPreferences.publish.mock.calls.length, 0);
});
it("should handle error and call showError", async () => {