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:
thePR0M3TH3ANandClaude Opus 4.8 committed 2026-06-24 17:29:53 -04:00
1 parent 1bc2b06395
commit 36f3c6e127
5 files changed
+164 -26

No files matched your search

+61
View File
@@ -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();