From f760abde3d546c5d3e990d2de841f0cdbc07e80d Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Mon, 22 Jun 2026 21:00:30 -0400 Subject: [PATCH] nip71 ingest: cache into videosMap + faster refresh (consistent feed presence) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two symptoms: (1) on warm refresh, NIP-71 videos appear a few seconds after bitvid ones; (2) navigating profile -> home sometimes showed only bitvid. Root cause: ingest wrote only nostrClient.activeMap (the live feed-engine source), not nostrService.videosMap (the persisted/author-feed cache), and the refresh nudge was throttled at 8s — so NIP-71 entered the feed late and inconsistently across render paths. - Ingest now also nostrService.cacheVideos(injected) so the videos persist and appear in author-scoped feeds + optimistic/persisted render paths, not just the live activeMap engine run. - Refresh throttle 8s -> 2.5s so the post-inject feed re-render fires promptly. Tests + mutation-verified (injected videos forwarded to cacheVideos). 1268/1269 unit (known UploadModal flake). Co-Authored-By: Claude Opus 4.8 --- js/services/nip71IngestService.js | 18 +++++++++++++++--- tests/nip71-ingest-service.test.mjs | 13 +++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/js/services/nip71IngestService.js b/js/services/nip71IngestService.js index f28e3cf7..16cd4958 100644 --- a/js/services/nip71IngestService.js +++ b/js/services/nip71IngestService.js @@ -26,7 +26,7 @@ const FLUSH_DEBOUNCE_MS = 200; // Coalesce ingest-driven feed refreshes so a burst of incoming events can't // trigger a re-render/re-subscription storm. The injected videos are already in // the active store; this only controls how often we *signal* the feed. -const REFRESH_THROTTLE_MS = 8000; +const REFRESH_THROTTLE_MS = 2500; const DEFAULT_LIMIT = 200; const SUBSCRIPTION_KEY = "nip71-ingest"; // The admin whitelist hydrates asynchronously (remote fetch, then a second hex @@ -157,10 +157,22 @@ export function createNip71IngestService({ } } - let injected = 0; + const injectedVideos = []; for (const video of newestByRoot.values()) { if (injectVideo(video)) { - injected += 1; + injectedVideos.push(video); + } + } + const injected = injectedVideos.length; + + // Also cache into nostrService.videosMap so ingested videos persist and show + // up in the author-scoped feeds + the optimistic/persisted render paths — not + // just the live activeMap engine run. Keeps NIP-71 present across navigation. + if (injected > 0 && typeof nostrService?.cacheVideos === "function") { + try { + nostrService.cacheVideos(injectedVideos); + } catch (error) { + logger?.warn?.("[nip71Ingest] cacheVideos failed", error); } } diff --git a/tests/nip71-ingest-service.test.mjs b/tests/nip71-ingest-service.test.mjs index 41b7918b..5db3c472 100644 --- a/tests/nip71-ingest-service.test.mjs +++ b/tests/nip71-ingest-service.test.mjs @@ -44,6 +44,7 @@ function makeHarness({ whitelistOn = false, whitelist = [] } = {}) { getSubscriptionManager: () => manager, }; const feedListeners = new Set(); + const cached = []; const nostrService = { emit(name, detail) { emitted.push({ name, detail }); @@ -55,6 +56,7 @@ function makeHarness({ whitelistOn = false, whitelist = [] } = {}) { return () => feedListeners.delete(fn); }, getFilteredActiveVideos: () => Array.from(activeMap.values()), + cacheVideos: (videos) => cached.push(...videos), }; const accessControl = { whitelistMode: () => whitelistOn, @@ -71,6 +73,7 @@ function makeHarness({ whitelistOn = false, whitelist = [] } = {}) { activeMap, allEvents, emitted, + cached, get capturedFilters() { return capturedFilters; }, @@ -136,6 +139,16 @@ test("ingests a foreign NIP-71 video into the active store and signals a refresh assert.equal(refresh.detail.reason, "nip71-ingest"); }); +test("caches injected videos into nostrService (persisted/author-feed paths)", () => { + const h = makeHarness(); + const svc = makeService(h); + svc.start(); + h.fireEvent(foreignEvent({ id: "e1", title: "Hello" })); + svc.flush(); + assert.equal(h.cached.length, 1, "injected video forwarded to cacheVideos"); + assert.equal(h.cached[0].id, "e1"); +}); + test("never clobbers a native bitvid video sharing the same root", () => { const h = makeHarness(); const native = { id: "native", videoRootId: "shared", source: "nostr", created_at: 500, title: "Native" };