From 1722618c6dae750a2312dfa974d054f2ad7ddc44 Mon Sep 17 00:00:00 2001 From: BoulderBadgeDad Date: Thu, 25 Jun 2026 23:21:59 -0700 Subject: [PATCH] video wishlist badge: stop the second writer clobbering it with the TV-only count MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit the other half of the bug: the endpoint total was fixed, but setCounts() (movie/ episode load) AND setYtCounts() (youtube load) ALSO wrote the nav badge from their OWN partial state. these load separately, and on the dashboard only one runs — so whichever fired last overwrote the badge with a partial count (correct number, then 'switches' to TV-only). fix: the /wishlist/counts endpoint is the single source of truth for the grand total; both setters now just call refreshBadge() to (re)sync from it instead of computing a partial sum. regression test pins it. --- tests/test_video_wishlist_clear.py | 13 +++++++++++++ webui/static/video/video-wishlist.js | 9 ++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/tests/test_video_wishlist_clear.py b/tests/test_video_wishlist_clear.py index 9e03b0f4..6e154a75 100644 --- a/tests/test_video_wishlist_clear.py +++ b/tests/test_video_wishlist_clear.py @@ -130,3 +130,16 @@ def test_clear_button_wired_for_all_tabs(): assert "kind: kind" in _JS # clears the active tab # shown only when the active tab has items assert "function updateClearBtn(" in _JS + + +def test_nav_badge_uses_the_endpoint_total_not_a_partial_sum(): + # regression: movie/episode and YouTube counts load separately, so neither setter may + # compute the nav badge from its partial state (it 'switches' to the TV-only number). + # Both must defer to the authoritative /wishlist/counts endpoint via refreshBadge(). + import re + sc = _JS[_JS.index("function setCounts("):_JS.index("function setYtCounts(")] + syc = _JS[_JS.index("function setYtCounts("):] + syc = syc[:syc.index("function ", 1)] + assert "refreshBadge();" in sc and "refreshBadge();" in syc + assert "state.counts.movie + state.counts.episode" not in _JS # old partial-total math gone + assert "/api/video/wishlist/counts" in _JS # the endpoint is the source diff --git a/webui/static/video/video-wishlist.js b/webui/static/video/video-wishlist.js index 8294bc2f..953295da 100644 --- a/webui/static/video/video-wishlist.js +++ b/webui/static/video/video-wishlist.js @@ -299,12 +299,18 @@ } // ── counts / badges / pager ─────────────────────────────────────────────── + // The nav badge is the WHOLE wishlist (movies + episodes + YouTube videos). movie/episode + // and YouTube counts arrive from SEPARATE loads, and on the dashboard only one of them + // runs — so neither setter can compute the whole total from its partial state without + // clobbering the other (the bug: badge shows the right number, then 'switches' to the + // TV-only count). The endpoint /wishlist/counts is the single source of truth for the + // grand total, so the setters just (re)sync the nav badge from it. function setCounts(counts) { state.counts = { movie: (counts && counts.movie) || 0, show: (counts && counts.show) || 0, episode: (counts && counts.episode) || 0 }; var cm = $('[data-vwsh-count-movie]'); if (cm) cm.textContent = state.counts.movie; var cs = $('[data-vwsh-count-show]'); if (cs) cs.textContent = state.counts.show; - updateBadges(counts && counts.total != null ? counts.total : (state.counts.movie + state.counts.episode)); + refreshBadge(); // authoritative grand total updateSub(); updateClearBtn(); } @@ -312,6 +318,7 @@ state.ytChannel = (counts && counts.channel) || 0; state.ytVideo = (counts && counts.video) || 0; var cy = $('[data-vwsh-count-youtube]'); if (cy) cy.textContent = state.ytVideo; + refreshBadge(); // authoritative grand total updateSub(); updateClearBtn(); }