From 498d9cb5b2b761e652b297b06a70c9adb35d1c6f Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 5 Jan 2026 14:21:53 +0000 Subject: [PATCH] Revise watch-history tags to use month IDs Refactors the watch-history system to prioritize `YYYY-MM` month identifiers over legacy snapshot IDs in event tags, storage keys, and cross-module references. Updates event schemas, storage keying (v3), and client interfaces to align with the new month-based structure. Renames `snapshotId` parameters to `taskId` for republishing operations. --- js/nostr/client.js | 8 ++++---- js/nostr/watchHistory.js | 28 +++++++++++++--------------- js/nostrEventSchemas.js | 10 +++++++++- 3 files changed, 26 insertions(+), 20 deletions(-) diff --git a/js/nostr/client.js b/js/nostr/client.js index 8efcf450..1b340655 100644 --- a/js/nostr/client.js +++ b/js/nostr/client.js @@ -3377,14 +3377,14 @@ export class NostrClient { persistWatchHistoryEntryToManager(this.watchHistory, actorInput, entry); } - cancelWatchHistoryRepublish(snapshotId = null) { - cancelWatchHistoryRepublishForManager(this.watchHistory, snapshotId); + cancelWatchHistoryRepublish(taskId = null) { + cancelWatchHistoryRepublishForManager(this.watchHistory, taskId); } - scheduleWatchHistoryRepublish(snapshotId, operation, options = {}) { + scheduleWatchHistoryRepublish(taskId, operation, options = {}) { return scheduleWatchHistoryRepublishForManager( this.watchHistory, - snapshotId, + taskId, operation, options, ); diff --git a/js/nostr/watchHistory.js b/js/nostr/watchHistory.js index ef449661..efcbdfe0 100644 --- a/js/nostr/watchHistory.js +++ b/js/nostr/watchHistory.js @@ -28,7 +28,7 @@ import { devLogger, userLogger } from "../utils/logger.js"; * relay publishing, fetch/decrypt flows, and exposes a manager factory that is * dependency-injected with the nostr client hooks it needs. */ -const WATCH_HISTORY_STORAGE_KEY = "bitvid:watch-history:v2"; +const WATCH_HISTORY_STORAGE_KEY = "bitvid:watch-history:v3"; const WATCH_HISTORY_STORAGE_VERSION = 3; const WATCH_HISTORY_REPUBLISH_BASE_DELAY_MS = 2000; const WATCH_HISTORY_REPUBLISH_MAX_DELAY_MS = 5 * 60 * 1000; @@ -1227,8 +1227,8 @@ class WatchHistoryManager { } } - cancelRepublish(snapshotId = null) { - if (!snapshotId) { + cancelRepublish(taskId = null) { + if (!taskId) { for (const entry of this.republishTimers.values()) { if (entry && typeof entry.timer === "number") { clearTimeout(entry.timer); @@ -1241,7 +1241,7 @@ class WatchHistoryManager { this.republishTimers.clear(); return; } - const key = typeof snapshotId === "string" ? snapshotId.trim() : ""; + const key = typeof taskId === "string" ? taskId.trim() : ""; if (!key) { return; } @@ -1256,8 +1256,8 @@ class WatchHistoryManager { this.republishTimers.delete(key); } - scheduleRepublish(snapshotId, operation, options = {}) { - const key = typeof snapshotId === "string" ? snapshotId.trim() : ""; + scheduleRepublish(taskId, operation, options = {}) { + const key = typeof taskId === "string" ? taskId.trim() : ""; if (!key || typeof operation !== "function") { return; } @@ -1288,7 +1288,7 @@ class WatchHistoryManager { ); if (onSchedule) { try { - onSchedule({ snapshotId: key, attempt: attempt + 1, delay }); + onSchedule({ taskId: key, attempt: attempt + 1, delay }); } catch (error) { devLogger.warn( `[nostr] Failed to notify watch history republish schedule for ${key}:`, @@ -1476,7 +1476,7 @@ class WatchHistoryManager { ok: allOk, retryable: anyRetryable, results, - snapshotId: months.join(','), // Fake snapshot ID representing the batch + snapshotId: "", // No single snapshot ID // Return last event as pointerEvent? Or null? pointerEvent: results.length > 0 ? results[results.length-1].pointerEvent : null }; @@ -1567,7 +1567,6 @@ class WatchHistoryManager { // They should already be sorted by canonicalize, but ensure it. items.sort((a, b) => (b.watchedAt || 0) - (a.watchedAt || 0)); - const snapshotId = monthIdentifier; const { payload, included, skipped } = buildWatchHistoryPayload( monthIdentifier, items, @@ -1871,7 +1870,6 @@ class WatchHistoryManager { const event = buildWatchHistoryEvent({ pubkey: actorPubkey, created_at: createdAtCursor, - snapshotId, monthIdentifier, pointerTags, content: ciphertext, @@ -1922,7 +1920,7 @@ class WatchHistoryManager { ok: success, retryable: !success, actor: actorPubkey, - snapshotId, + monthIdentifier, items: items, // Return items for this month pointerEvent: signedEvent, publishResults: { @@ -2716,17 +2714,17 @@ export function persistWatchHistoryEntry(manager, actorInput, entry) { return manager.persistEntry(actorInput, entry); } -export function cancelWatchHistoryRepublish(manager, snapshotId = null) { - return manager.cancelRepublish(snapshotId); +export function cancelWatchHistoryRepublish(manager, taskId = null) { + return manager.cancelRepublish(taskId); } export function scheduleWatchHistoryRepublish( manager, - snapshotId, + taskId, operation, options = {}, ) { - return manager.scheduleRepublish(snapshotId, operation, options); + return manager.scheduleRepublish(taskId, operation, options); } export function getWatchHistoryFingerprint( diff --git a/js/nostrEventSchemas.js b/js/nostrEventSchemas.js index a5d64682..17c9896a 100644 --- a/js/nostrEventSchemas.js +++ b/js/nostrEventSchemas.js @@ -1358,22 +1358,29 @@ export function buildWatchHistoryIndexEvent({ const schema = getNostrEventSchema(NOTE_TYPES.WATCH_HISTORY_INDEX); const tags = []; const identifierName = schema?.identifierTag?.name || "d"; + + // Prioritize monthIdentifier, fallback to snapshotId (legacy) const identifierValue = (typeof monthIdentifier === "string" && monthIdentifier.trim()) || (typeof snapshotId === "string" && snapshotId.trim()) || schema?.identifierTag?.value || WATCH_HISTORY_LIST_IDENTIFIER; + if (identifierName && identifierValue) { tags.push([identifierName, identifierValue]); } + + // Ensure "month" tag is present if we have an identifier (assuming it's a month) const monthTagName = schema?.monthTagName || "month"; if (monthTagName && identifierValue) { tags.push([monthTagName, identifierValue]); } + const pointerTagName = schema?.monthPointerTagName || schema?.chunkPointerTagName || "a"; const resolvedMonthAddresses = Array.isArray(monthAddresses) && monthAddresses.length ? monthAddresses : chunkAddresses; + resolvedMonthAddresses?.forEach((address) => { if (typeof address === "string" && address) { tags.push([pointerTagName, address]); @@ -1428,7 +1435,8 @@ export function buildWatchHistoryEvent({ const schema = getNostrEventSchema(NOTE_TYPES.WATCH_HISTORY_CHUNK); const tags = []; const identifierName = schema?.identifierTag?.name || "d"; - // Prioritize monthIdentifier, fallback to snapshotId (which should now be the month), or schema default + + // Prioritize monthIdentifier, fallback to snapshotId const identifierValue = (typeof monthIdentifier === "string" && monthIdentifier.trim()) || (typeof snapshotId === "string" && snapshotId.trim()) ||