mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
Second root cause of "watch history delete doesn't work / list isn't getting overwritten": reads UNION items across every event the relays return, and a publish only reports success when ALL relays accept. With a flaky relay list the reduced (post-removal) event reaches some relays while others keep serving the old, larger copy — and the reader deduped only by event id, so both the stale and fresh versions of the same month (d-tag) were unioned, resurrecting removed items. Add dedupeNewestPerReplaceableAddress and apply it at both read union sites (the chunk-events path and the pointer-events path): keep only the newest event per d-tag (month) before unioning, so a lagging relay can't reintroduce removed items. Distinct months are all preserved; events without a d-tag are kept by id. Tests + mutation-verified: newest-per-month wins (incl. the fewer-items removal case), distinct months preserved, newest-first ordering, no-d-tag kept by id. Reverting to id-dedup fails the resurrection scenarios. Build + full lint green (watchHistory.js back under file-size budget). Co-Authored-By: Claude Opus 4.8 <[email protected]>
40 lines
1.6 KiB
JavaScript
40 lines
1.6 KiB
JavaScript
// Watch-history events are PARAMETERIZED REPLACEABLE (kind + pubkey + d-tag,
|
|
// where the d-tag is the month). When reading across multiple relays we can
|
|
// receive several versions of the same month — e.g. a relay that missed the
|
|
// latest removal/clear publish still serves the old, larger copy. The reader
|
|
// unions items across events, so unless we keep only the NEWEST version per
|
|
// address, a lagging relay resurrects items the user just removed.
|
|
//
|
|
// This collapses a set of events to the newest per replaceable address (by
|
|
// created_at), so the union downstream reflects the user's latest intent.
|
|
|
|
/**
|
|
* @param {Array<object>} events raw nostr events
|
|
* @returns {Array<object>} newest event per d-tag (events without a d-tag are
|
|
* kept individually by id), sorted newest-first
|
|
*/
|
|
export function dedupeNewestPerReplaceableAddress(events) {
|
|
const sorted = (Array.isArray(events) ? events : [])
|
|
.filter((event) => event && typeof event === "object")
|
|
.slice()
|
|
.sort((a, b) => (Number(b?.created_at) || 0) - (Number(a?.created_at) || 0));
|
|
|
|
const seen = new Set();
|
|
const out = [];
|
|
for (const event of sorted) {
|
|
const dTag = Array.isArray(event.tags)
|
|
? event.tags.find((tag) => Array.isArray(tag) && tag[0] === "d")?.[1] || ""
|
|
: "";
|
|
// Distinct months have distinct d-tags, so deduping by d-tag keeps one
|
|
// (newest) per month while preserving every month. Events with no d-tag
|
|
// can't be addressed, so they're kept individually by id.
|
|
const key = dTag ? `d:${dTag}` : `id:${event.id}`;
|
|
if (seen.has(key)) {
|
|
continue;
|
|
}
|
|
seen.add(key);
|
|
out.push(event);
|
|
}
|
|
return out;
|
|
}
|