mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
fix(popularity): headline reads the authoritative view counter, not a re-count
The chart showed a different (higher, fluctuating) total than the card's view count because it re-derived its own total from a one-off, unwindowed relay listVideoViewEvents — which varies by relay response and counts a wider set than the counter. Now the modal headline subscribes to the same viewCounter the card uses (the deduped, cached, authoritative total), so it always matches the number shown everywhere and is stable. The chart below is purely the time DISTRIBUTION of recorded views, listed within the counter's backfill window (VIEW_COUNT_BACKFILL_MAX_DAYS) so it lines up with the headline's window. The count subscription is cleaned up on close. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
6911df075a
commit
1bc2b06395
1 file changed
+52
-10
+52
-10
@@ -10,7 +10,12 @@ import {
|
||||
subscribeVideoViewEventsWithDefaultClient,
|
||||
} from "./nostrViewEventsFacade.js";
|
||||
import { resolveVideoPointer } from "./utils/videoPointer.js";
|
||||
import { formatViewCount } from "./viewCounter.js";
|
||||
import {
|
||||
formatViewCount,
|
||||
subscribeToVideoViewCount,
|
||||
unsubscribeFromVideoViewCount,
|
||||
} from "./viewCounter.js";
|
||||
import { VIEW_COUNT_BACKFILL_MAX_DAYS } from "./config.js";
|
||||
import { devLogger } from "./utils/logger.js";
|
||||
|
||||
export const VIEW_CHART_WINDOW_SECONDS = 86400; // 1 day, matches the counter dedupe window
|
||||
@@ -226,6 +231,8 @@ export function openPopularityModal({
|
||||
const eventsById = new Map();
|
||||
let closed = false;
|
||||
let unsubscribe = null;
|
||||
let countToken = null;
|
||||
let counterTotal = null;
|
||||
let rerenderTimer = null;
|
||||
|
||||
const addEvent = (event) => {
|
||||
@@ -235,22 +242,37 @@ export function openPopularityModal({
|
||||
return true;
|
||||
};
|
||||
|
||||
const render = () => {
|
||||
// The headline number must match what the card / counter shows everywhere
|
||||
// (the authoritative, deduped, cached viewCounter total) — NOT a separate count
|
||||
// re-derived from a one-off relay list (which varies by relay response and
|
||||
// window). The chart below shows only the time DISTRIBUTION of recorded views.
|
||||
const renderHeadline = () => {
|
||||
if (closed) return;
|
||||
const { series, total } = buildViewCountTimeSeries([...eventsById.values()]);
|
||||
const total = Number.isFinite(counterTotal)
|
||||
? counterTotal
|
||||
: buildViewCountTimeSeries([...eventsById.values()]).total;
|
||||
if (!Number.isFinite(total)) {
|
||||
return;
|
||||
}
|
||||
totalLine.textContent =
|
||||
total > 0
|
||||
? `${formatViewCount(total)} ${total === 1 ? "view" : "views"}`
|
||||
: "No views recorded yet.";
|
||||
};
|
||||
|
||||
const renderChart = () => {
|
||||
if (closed) return;
|
||||
const { series } = buildViewCountTimeSeries([...eventsById.values()]);
|
||||
chartHost.textContent = "";
|
||||
chartHost.appendChild(buildViewCountChartSvg(doc, series));
|
||||
renderHeadline();
|
||||
};
|
||||
|
||||
const scheduleRender = () => {
|
||||
if (rerenderTimer) return;
|
||||
rerenderTimer = setTimeout(() => {
|
||||
rerenderTimer = null;
|
||||
render();
|
||||
renderChart();
|
||||
}, 250);
|
||||
};
|
||||
|
||||
@@ -268,6 +290,13 @@ export function openPopularityModal({
|
||||
// best effort
|
||||
}
|
||||
}
|
||||
if (countToken != null) {
|
||||
try {
|
||||
unsubscribeFromVideoViewCount(pointer, countToken);
|
||||
} catch (error) {
|
||||
// best effort
|
||||
}
|
||||
}
|
||||
doc.removeEventListener("keydown", onKeydown, true);
|
||||
backdrop.remove();
|
||||
if (activePopularityModal === handle) {
|
||||
@@ -288,19 +317,32 @@ export function openPopularityModal({
|
||||
});
|
||||
doc.addEventListener("keydown", onKeydown, true);
|
||||
|
||||
// Initial fetch + live subscription.
|
||||
// Headline total: subscribe to the same counter the card uses (stable + cached).
|
||||
try {
|
||||
countToken = subscribeToVideoViewCount(pointer, ({ total } = {}) => {
|
||||
if (closed) return;
|
||||
if (Number.isFinite(total)) {
|
||||
counterTotal = Number(total);
|
||||
renderHeadline();
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
devLogger.warn("[popularity] Failed to subscribe to view count:", error);
|
||||
}
|
||||
|
||||
// Chart shape: list the recorded view events within the counter's backfill
|
||||
// window so the distribution lines up with the headline's window.
|
||||
const since =
|
||||
Math.floor(Date.now() / 1000) - VIEW_COUNT_BACKFILL_MAX_DAYS * 86400;
|
||||
Promise.resolve()
|
||||
.then(() => listViewEvents(pointer, {}))
|
||||
.then(() => listViewEvents(pointer, { since }))
|
||||
.then((events) => {
|
||||
if (closed) return;
|
||||
(Array.isArray(events) ? events : []).forEach(addEvent);
|
||||
render();
|
||||
renderChart();
|
||||
})
|
||||
.catch((error) => {
|
||||
devLogger.warn("[popularity] Failed to load view events:", error);
|
||||
if (!closed) {
|
||||
totalLine.textContent = "Couldn’t load view data.";
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
|
||||
Reference in new issue
Block a user