zaps: optimistic zap-total bump so the badge updates instantly on send

Mirrors ingestLocalViewEvent for view counts: sending a zap through the
video modal now bumps that video's zap total immediately on the card + modal
badge, instead of waiting for the relay receipt (delayed by the 2-min cache
TTL, and dependent on the recipient's wallet actually publishing a 9735).

- zapTotals store tracks optimisticSats separately from real receipt sats;
  getSnapshot/request return the sum. When the real receipt for the pointer
  later arrives, runBatch clears the optimistic portion so it's never
  double-counted. If the receipt never lands (custodial wallets often don't
  publish), the bump persists in-memory until reload — keeping the zapper's
  own view honest.
- ZapController fires a new onZapSuccess({video, sats}) at the fully-success
  terminal point (sats = context.shares.total; both creator + platform shares
  tag the video, so the eventual relay total matches).
- ModalManager derives the pointer via app.deriveVideoPointerInfo (the SAME
  key the cards/modal subscribe with) and calls ingestLocalVideoZap.

Live-verified: a rendered card's badge went 0 sats -> 5K sats instantly on
ingest, no relay round-trip. Test covers the bump + replace-on-real-receipt.

Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
thePR0M3TH3ANandClaude Fable 5 committed 2026-07-04 14:55:14 -04:00
1 parent 8d45f25962
commit af09505fb3
4 files changed
+115 -4

No files matched your search

+16
View File
@@ -13,6 +13,7 @@ import initEditModal from "./initEditModal.js";
import initDeleteModal from "./initDeleteModal.js";
import { EventDetailsModal } from "./components/EventDetailsModal.js";
import ZapController from "./zapController.js";
import { ingestLocalVideoZap } from "../zapTotals.js";
import { nostrClient } from "../nostrClientFacade.js";
export default class ModalManager {
@@ -271,6 +272,21 @@ export default class ModalManager {
callbacks: {
onSuccess: (message) => app.showSuccess(message),
onError: (message) => app.showError(message),
// Optimistically bump the zapped video's total using the SAME pointer
// the cards/modal badge subscribe with, so the badge updates instantly.
onZapSuccess: ({ video, sats } = {}) => {
try {
const info =
video && typeof app.deriveVideoPointerInfo === "function"
? app.deriveVideoPointerInfo(video)
: null;
if (info?.pointer && sats > 0) {
ingestLocalVideoZap(info.pointer, sats);
}
} catch (error) {
/* best-effort optimistic update */
}
},
},
requestWalletPane: () => app.openWalletPane(),
});