mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
fix(watch-history): logged-out (local) delete now rewrites the local queue
The other half of the synced-vs-local design: for logged-out / session (local-only) actors, watch history lives in a localStorage queue (loadLatest -> collectQueueItems), but snapshot no-op'd for local-only actors — so a removal never touched the queue and the item reappeared on reload (same symptom as the logged-in bug, different layer). Now a REPLACE snapshot for a local-only actor rewrites the local queue to exactly the provided items (new replaceLocalQueue helper): removals prune it, an empty replace clears it. Normal watch-tracking adds still flow through the queue via recordVideoView, unchanged. Tests + mutation-verified: local replace seeds then prunes the queue; empty replace clears it. Reverting to the no-op fails both. Build + full lint green (watchHistoryService under file-size budget). Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
4c6415c2c4
commit
2f95d4486e
2 files changed
+105
No files matched your search
@@ -426,6 +426,30 @@ function clearQueue(actorKey) {
|
||||
notifyQueueChange(actorKey);
|
||||
}
|
||||
|
||||
// Replace the local watch-history queue with exactly `items` (used by a
|
||||
// local-only REPLACE snapshot, i.e. a logged-out removal/clear). Source of truth
|
||||
// for local history is loadLatest -> collectQueueItems(queue), so this is what
|
||||
// makes a local delete persist.
|
||||
function replaceLocalQueue(actorKey, items) {
|
||||
const queue = ensureQueue(actorKey);
|
||||
queue.items.clear();
|
||||
queue.throttle.clear();
|
||||
const now = Date.now();
|
||||
for (const item of Array.isArray(items) ? items : []) {
|
||||
const pointer = normalizePointerInput(item);
|
||||
if (!pointer) {
|
||||
continue;
|
||||
}
|
||||
const key = pointerKey(pointer);
|
||||
if (!key) {
|
||||
continue;
|
||||
}
|
||||
queue.items.set(key, { pointer, addedAt: now, updatedAt: now });
|
||||
}
|
||||
persistQueueState();
|
||||
notifyQueueChange(actorKey);
|
||||
}
|
||||
|
||||
function pruneQueueAfterSnapshot(actorKey, queue, keysToClear, snapshotStart) {
|
||||
if (!actorKey || !queue) {
|
||||
return;
|
||||
@@ -794,6 +818,15 @@ async function snapshot(items, options = {}) {
|
||||
}
|
||||
|
||||
if (!isFeatureEnabled(actorKey)) {
|
||||
// Local-only actors (logged out / session) keep watch history in the local
|
||||
// queue, not on relays. A REPLACE snapshot (i.e. a removal/clear) must
|
||||
// rewrite that queue so the deletion actually sticks — otherwise the removed
|
||||
// item lingers locally and reappears on reload. Normal watch-tracking adds
|
||||
// still flow through the queue via recordVideoView.
|
||||
if (replace && Array.isArray(items)) {
|
||||
replaceLocalQueue(actorKey, items);
|
||||
return { ok: true, local: true, reason: "local-replaced", itemCount: items.length };
|
||||
}
|
||||
devLogger.debug(
|
||||
"[watchHistoryService] Snapshot skipped because this actor is limited to local watch history.",
|
||||
{
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
// Logged-out / local-only watch history lives in a localStorage queue, not on
|
||||
// relays. A removal there is a REPLACE snapshot — it must rewrite the local queue
|
||||
// so the deletion sticks. Previously snapshot no-op'd for local-only actors, so
|
||||
// removing an item locally did nothing and it reappeared on reload.
|
||||
|
||||
import "./test-helpers/setup-localstorage.mjs";
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
import { watchHistoryService } from "../js/watchHistoryService.js";
|
||||
import { nostrClient } from "../js/nostrClientFacade.js";
|
||||
|
||||
// Make `actor` local-only by making it the session actor (feature disabled for
|
||||
// the session actor → local queue path).
|
||||
function makeLocalActor(actor) {
|
||||
nostrClient.pubkey = "";
|
||||
nostrClient.sessionActor = { pubkey: actor };
|
||||
}
|
||||
|
||||
test("local-only replace snapshot seeds and then prunes the local queue", async () => {
|
||||
const actor = "f".repeat(64);
|
||||
makeLocalActor(actor);
|
||||
|
||||
// Seed three items via a replace snapshot (the local queue path).
|
||||
const seed = await watchHistoryService.snapshot(
|
||||
[
|
||||
{ type: "e", value: "local-a" },
|
||||
{ type: "e", value: "local-b" },
|
||||
{ type: "e", value: "local-c" },
|
||||
],
|
||||
{ actor, replace: true },
|
||||
);
|
||||
assert.equal(seed.skipped, undefined, "local replace must NOT be skipped");
|
||||
|
||||
let items = await watchHistoryService.loadLatest(actor, { allowStale: false });
|
||||
assert.deepEqual(
|
||||
items.map((i) => i.value).sort(),
|
||||
["local-a", "local-b", "local-c"],
|
||||
"seeded local queue should have all three",
|
||||
);
|
||||
|
||||
// Remove "local-b": replace with the remaining two.
|
||||
await watchHistoryService.snapshot(
|
||||
[
|
||||
{ type: "e", value: "local-a" },
|
||||
{ type: "e", value: "local-c" },
|
||||
],
|
||||
{ actor, replace: true },
|
||||
);
|
||||
|
||||
items = await watchHistoryService.loadLatest(actor, { allowStale: false });
|
||||
assert.deepEqual(
|
||||
items.map((i) => i.value).sort(),
|
||||
["local-a", "local-c"],
|
||||
"removed item must be gone from the local queue (delete sticks)",
|
||||
);
|
||||
});
|
||||
|
||||
test("local-only clear (empty replace) empties the local queue", async () => {
|
||||
const actor = "e".repeat(64);
|
||||
makeLocalActor(actor);
|
||||
|
||||
await watchHistoryService.snapshot([{ type: "e", value: "only" }], {
|
||||
actor,
|
||||
replace: true,
|
||||
});
|
||||
let items = await watchHistoryService.loadLatest(actor, { allowStale: false });
|
||||
assert.equal(items.length, 1);
|
||||
|
||||
await watchHistoryService.snapshot([], { actor, replace: true });
|
||||
items = await watchHistoryService.loadLatest(actor, { allowStale: false });
|
||||
assert.equal(items.length, 0, "clearing the last local item must empty the queue");
|
||||
});
|
||||
Reference in new issue
Block a user