mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
views: exact count unions across relays (fixes cross-relay under-count) (#4)
The watch-page/chart "exact" count relied on the NIP-45 COUNT, but
countEventsAcrossRelays returns bestEstimate = the single relay with the MOST
events — it can't union across relays. So when views are distributed across
relays it under-counted (a video's card showed 19 while the chart's uncapped
list union showed ~63).
exactCountForPointer now lists view events across ALL relays (one on-demand
list, not a storm) and folds the UNION into the deduped dedupeBuckets (one entry
per viewer-window). total = the set size, so it is accurate AND monotonic (never
spuriously decreases on partial relay responses). The per-relay COUNT is kept
only as a lower-bound floor when the list truncates (>2000 events), flagged
partial. The popularity modal passes { exact: true }; since the count flows
through shared pointer state, opening a video corrects its count everywhere.
Grid cards (no exact) still use the §17-capped batched backfill (approximate,
storm-safe) until opened — an accepted accuracy/cost tradeoff.
Also note the ⋯ menu "Event Details" button does nothing (new TODO #31).
Tests: testExactCountUnionsAcrossRelays. Grandfathered viewCounter.js size.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
1bc2b06395
commit
36f3c6e127
5 files changed
+164
-26
No files matched your search
@@ -575,6 +575,66 @@ async function testRelayCountAggregationUsesBestEstimate() {
|
||||
}
|
||||
}
|
||||
|
||||
// The exact path (options.exact, watch page / popularity chart) must count the
|
||||
// cross-relay UNION of view events deduped by (viewer, window) — NOT the per-relay
|
||||
// NIP-45 COUNT, which returns the single relay with the most events and therefore
|
||||
// under-counts when views are spread across relays.
|
||||
async function testExactCountUnionsAcrossRelays() {
|
||||
localStorage.clear();
|
||||
harness.reset();
|
||||
harness.resetMetrics();
|
||||
|
||||
const previousGetManager = nostrClient.getSubscriptionManager;
|
||||
// exactCountForPointer only runs when a SubscriptionManager exists.
|
||||
nostrClient.getSubscriptionManager = () => ({
|
||||
list: async () => [],
|
||||
subscribe: () => ({ close() {} }),
|
||||
handleReconnect() {},
|
||||
});
|
||||
|
||||
try {
|
||||
const pointer = { type: "e", value: "view-counter-exact-union" };
|
||||
const pointerKey = harness.pointerKeyFromInput(pointer);
|
||||
const dayStart =
|
||||
Math.floor(Math.floor(Date.now() / 1000) / 86400) * 86400;
|
||||
// alice has two views in the same day (dedupe to 1) → 3 distinct (viewer,day):
|
||||
// alice@d0, bob@d0, alice@d1.
|
||||
harness.setEvents(pointerKey, [
|
||||
{ id: "e1", pubkey: "alice", created_at: dayStart + 10 },
|
||||
{ id: "e2", pubkey: "alice", created_at: dayStart + 20 },
|
||||
{ id: "e3", pubkey: "bob", created_at: dayStart + 30 },
|
||||
{ id: "e4", pubkey: "alice", created_at: dayStart - 86400 + 10 },
|
||||
]);
|
||||
// A single relay's COUNT only saw 1 — the union must win.
|
||||
harness.setCountTotal(pointerKey, {
|
||||
total: 1,
|
||||
best: { relay: "wss://relay.one", count: 1 },
|
||||
});
|
||||
|
||||
const updates = [];
|
||||
const token = subscribeToVideoViewCount(
|
||||
pointer,
|
||||
(state) => updates.push({ ...state }),
|
||||
{ exact: true },
|
||||
);
|
||||
try {
|
||||
await flushPromises();
|
||||
await flushPromises();
|
||||
const final = updates.at(-1);
|
||||
assert.ok(final, "expected an exact-count update");
|
||||
assert.equal(
|
||||
final.total,
|
||||
3,
|
||||
"exact count = cross-relay union deduped by (viewer,day), not the per-relay COUNT (1)",
|
||||
);
|
||||
} finally {
|
||||
unsubscribeFromVideoViewCount(pointer, token);
|
||||
}
|
||||
} finally {
|
||||
nostrClient.getSubscriptionManager = previousGetManager;
|
||||
}
|
||||
}
|
||||
|
||||
async function testLocalIngestNotifiesImmediately() {
|
||||
localStorage.clear();
|
||||
harness.reset();
|
||||
@@ -1136,6 +1196,7 @@ await testHydrationSkipsStaleEventsAndRollsOff();
|
||||
await testLocalIngestNotifiesImmediately();
|
||||
await testUnsubscribeStopsCallbacks();
|
||||
await testRelayCountAggregationUsesBestEstimate();
|
||||
await testExactCountUnionsAcrossRelays();
|
||||
await testRecordVideoViewEmitsJsonPayload();
|
||||
await testSignAndPublishFallbackUsesSessionActor();
|
||||
await testHydrateHistoryPrefersRootEvent();
|
||||
|
||||
Reference in new issue
Block a user