mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
fix(watch-history): publish empty month events so clears propagate
Third and final watch-history-delete gap: removing the LAST item of a month (or
clearing all) left the file's items live on relays. updateWatchHistoryList
already sets records[month]=[] to clear an emptied month, but publishMonthRecord
early-returned on an empty month (`if (!items.length) return {ok:true}`), so the
clearing event was never published and relays kept serving the old, full copy.
Now an empty month still publishes a newest, EMPTY replaceable event for that
d-tag, overwriting the old copy so the clear actually takes effect (and reads —
which take the newest per d-tag after fff2bef2 — see it as empty).
Tests + mutation-verified: an emptied month publishes a clearing event with the
right d-tag and no pointer tags, while a month that still has items publishes its
remaining pointers. Restoring the early-return fails the clearing scenario.
Build + full lint green (watchHistory.js under file-size budget).
Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
421e50a90f
commit
a3799f3fba
2 files changed
+96
-1
No files matched your search
@@ -1302,7 +1302,8 @@ class WatchHistoryManager {
|
||||
}
|
||||
|
||||
async publishMonthRecord(monthIdentifier, items, options = {}) {
|
||||
if (!items || !items.length) return { ok: true };
|
||||
// Empty month = "clear it": still publish a newest EMPTY event for the d-tag (skipping it left removed items live on relays).
|
||||
items = Array.isArray(items) ? items : [];
|
||||
|
||||
const pool = typeof this.deps.getPool === "function" ? this.deps.getPool() : null;
|
||||
if (!pool) {
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
// Clearing a month (removing its last item) must publish an EMPTY replaceable
|
||||
// event for that month's d-tag, so the newest copy on relays has no items and the
|
||||
// removed items don't linger. publishMonthRecord used to early-return on an empty
|
||||
// month, so the clear silently did nothing and the old items stayed live.
|
||||
|
||||
import "./test-helpers/setup-localstorage.mjs";
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
import { createWatchHistoryManager } from "../js/nostr/watchHistory.js";
|
||||
|
||||
function makeManager(publishCalls) {
|
||||
const actorPubkey = "d".repeat(64);
|
||||
const sessionPrivateKey = "e".repeat(64);
|
||||
const toolkit = {
|
||||
nip04: { encrypt: async (_priv, _pub, plaintext) => `enc:${plaintext}` },
|
||||
};
|
||||
const manager = createWatchHistoryManager({
|
||||
getActivePubkey: () => actorPubkey,
|
||||
getSessionActor: () => ({ pubkey: actorPubkey, privateKey: sessionPrivateKey }),
|
||||
ensureSessionActor: async () => actorPubkey,
|
||||
resolveActiveSigner: () => null,
|
||||
shouldRequestExtensionPermissions: () => false,
|
||||
ensureExtensionPermissions: async () => ({ ok: true }),
|
||||
ensureNostrTools: async () => toolkit,
|
||||
getCachedNostrTools: () => toolkit,
|
||||
signEventWithPrivateKey: (event) => ({
|
||||
...event,
|
||||
id: `id-${event.created_at}`,
|
||||
sig: "sig",
|
||||
}),
|
||||
getWriteRelays: () => ["wss://relay.test"],
|
||||
getRelayFallback: () => ["wss://relay.test"],
|
||||
getPool: () => ({
|
||||
publish(urls, event) {
|
||||
publishCalls.push({ urls, event });
|
||||
return {
|
||||
on(name, handler) {
|
||||
if (name === "ok") {
|
||||
setTimeout(handler, 0);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
};
|
||||
},
|
||||
async list() {
|
||||
return [];
|
||||
},
|
||||
}),
|
||||
});
|
||||
return { manager, actorPubkey };
|
||||
}
|
||||
|
||||
test("an emptied month still publishes a clearing event with the right d-tag and no pointers", async () => {
|
||||
const publishCalls = [];
|
||||
const { manager, actorPubkey } = makeManager(publishCalls);
|
||||
try {
|
||||
const result = await manager.publishRecords(
|
||||
{ "2026-06": [] },
|
||||
{ actorPubkey },
|
||||
);
|
||||
assert.equal(result.ok, true, "the clearing publish should report ok");
|
||||
assert.equal(
|
||||
publishCalls.length,
|
||||
1,
|
||||
"an empty month MUST still publish a (clearing) event — not be skipped",
|
||||
);
|
||||
const ev = publishCalls[0].event;
|
||||
const dTag = ev.tags.find((t) => t[0] === "d");
|
||||
assert.equal(dTag?.[1], "2026-06", "clears the correct month address");
|
||||
const pointerTags = ev.tags.filter((t) => t[0] === "e" || t[0] === "a");
|
||||
assert.equal(pointerTags.length, 0, "the clearing event carries no pointer tags");
|
||||
} finally {
|
||||
manager.clear();
|
||||
}
|
||||
});
|
||||
|
||||
test("a month that still has items publishes its remaining pointers", async () => {
|
||||
const publishCalls = [];
|
||||
const { manager, actorPubkey } = makeManager(publishCalls);
|
||||
try {
|
||||
await manager.publishRecords(
|
||||
{ "2026-06": [{ type: "e", value: "keep-1", watchedAt: 5 }] },
|
||||
{ actorPubkey },
|
||||
);
|
||||
assert.equal(publishCalls.length, 1);
|
||||
const pointerTags = publishCalls[0].event.tags.filter(
|
||||
(t) => t[0] === "e" || t[0] === "a",
|
||||
);
|
||||
assert.equal(pointerTags.length, 1, "the surviving pointer is published");
|
||||
assert.equal(pointerTags[0][1], "keep-1");
|
||||
} finally {
|
||||
manager.clear();
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user