Files
bitvid/js/mostZappedView.js
thePR0M3TH3ANandClaude Fable 5 8dfd2034b0 feeds: "Most Zapped" sidebar tab ranked by zap totals (#47)
Trending-pattern clone ranked by sats from kind-9735 receipts:

- js/zapTotals.js: per-pointer receipt aggregation. The feed's metric getter
  returns the cached total and schedules a batched fetch (#a/#e chunks via
  the L1 SubscriptionManager) for unknown/stale pointers; amounts parse via
  nip57 bolt11 with the zap request's amount tag as fallback; receipts are
  deduped by id so re-fetches never double-count; zero-receipt answers are
  cached (2-min TTL).
- sorters.js: shared count-ranked sorter (metric desc, recency tie-break,
  author spread, trusted-muted sink); Trending delegates unchanged, Most
  Zapped ranks on runtime.getZapTotal.
- Full wiring: feed definition, view + info popover, change-signal re-rank
  (order settles as receipts stream in), sidebar link, viewManager registry,
  feedCoordinator case/loader, FEED_TYPES.MOST_ZAPPED, and a
  FEATURE_MOST_ZAPPED_FEED flag (default on; off hides the tab).

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-03 21:38:53 -04:00

66 lines
2.0 KiB
JavaScript

// js/mostZappedView.js
//
// The "Most Zapped" tab presentation hook (#47). The ranking lives in the feed
// engine (createMostZappedSorter); zap totals arrive asynchronously as the
// feed's metric getter schedules batched receipt fetches. While this view is
// active we listen for zapTotals' change signal and re-run the feed (a cheap
// re-rank over the in-memory active set, no relay re-fetch) so the order
// visibly settles as totals stream in — same pattern as trendingView.
import { attachFeedInfoPopover } from "./ui/components/FeedInfoPopover.js";
import { onZapTotalsChanged } from "./zapTotals.js";
const MOST_ZAPPED_RERANK_DEBOUNCE_MS = 700;
let unsubscribeTotals = null;
let rerankTimer = null;
export function teardownMostZappedView() {
if (typeof unsubscribeTotals === "function") {
try {
unsubscribeTotals();
} catch (error) {
// best effort
}
unsubscribeTotals = null;
}
if (rerankTimer) {
clearTimeout(rerankTimer);
rerankTimer = null;
}
}
export function initMostZappedView({ getApp, isActive } = {}) {
const infoTrigger = document.getElementById("mostZappedInfoTrigger");
if (infoTrigger) {
attachFeedInfoPopover(
infoTrigger,
"Most Zapped ranks recently-added videos by their zap total (sats). The order settles as zap receipts load in."
);
}
// Reset any prior subscription (re-entering the tab).
teardownMostZappedView();
const stillActive = typeof isActive === "function" ? isActive : () => true;
unsubscribeTotals = onZapTotalsChanged(() => {
if (rerankTimer) {
return;
}
rerankTimer = setTimeout(() => {
rerankTimer = null;
if (!stillActive()) {
teardownMostZappedView();
return;
}
const app = typeof getApp === "function" ? getApp() : null;
if (app && typeof app.refreshMostZappedFeed === "function") {
Promise.resolve(
app.refreshMostZappedFeed({ reason: "zap-total-update" })
).catch(() => {});
}
}, MOST_ZAPPED_RERANK_DEBOUNCE_MS);
});
}