nip71 ingest: cache into videosMap + faster refresh (consistent feed presence)

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 <[email protected]>
This commit is contained in:
thePR0M3TH3ANandClaude Opus 4.8 committed 2026-06-22 21:00:30 -04:00
1 parent 80f058f67c
commit f760abde3d
2 files changed
+28 -3

No files matched your search

+15 -3
View File
@@ -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);
}
}
+13
View File
@@ -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" };