nip71: fix feed hang — surface published_at so ingest skips history fetch

Deep dive into the "nothing loads / Fetching recent videos… hangs" symptom
found the real blocker: it was NOT the moderation/WoT stage (that uses
cached reads and passes unknown authors through clean). It was the feed's
resolve-posted-at stage.

resolveVideoPostedAt() short-circuits when a video has nip71.publishedAt,
otherwise it does a per-video `nostrClient.hydrateVideoHistory()` network
fetch (looking up kind-30078 history). Foreign NIP-71 videos have no such
history, and the ingest adapter wasn't setting nip71.publishedAt — so a
burst of ~32 ingested videos triggered ~32 blocking history fetches that
stalled the render and flooded relays.

Fix: the ingest adapter now surfaces nip71.publishedAt from the NIP-71
published_at tag (falling back to created_at). The feed short-circuits and
never fetches history for ingested videos. Re-enables FEATURE_NIP71_INGEST
by default now that the hang is addressed.

Tests: adapter published_at extraction + created_at fallback;
mutation-verified. 1237/1238 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 16:54:41 -04:00
1 parent 639d045e28
commit 96a985ce89
3 files changed
+36 -8

No files matched your search

+1 -7
View File
@@ -246,14 +246,8 @@ export const ALLOW_NSFW_CONTENT = false;
*
* Set to `false` to disable all inbound NIP-71 ingest with a single switch if it
* ever causes problems (spam, low-quality content, performance).
*
* NOTE: currently OFF by default. Ingest's data pipeline works, but surfacing a
* burst of ingested authors pushes bitvid's moderation/WoT stage to hydrate
* trust + report data for many cold authors at once, which storms relays and
* stalls the feed render. Re-enable once that hydration is bounded/non-blocking
* (or whitelisted authors are treated as pre-trusted). See the NIP-71 plan doc.
*/
export const FEATURE_NIP71_INGEST = false;
export const FEATURE_NIP71_INGEST = true;
/**
* Emergency fallback accounts for seeding the moderation graph.
+16 -1
View File
@@ -153,6 +153,18 @@ export function buildVideoFromNip71Event(event = {}) {
const dTag = dTagFromEvent(event);
const videoRootId = dTag || trimString(event.id);
// The feed's resolve-posted-at stage fetches per-video kind-30078 history when
// a timestamp isn't already known. Foreign NIP-71 videos have no such history,
// so without a posted-at the feed would fire a blocking history fetch per
// ingested video (a relay storm that stalls the render). Surface the NIP-71
// published_at (falling back to created_at) so the feed short-circuits.
const createdAt = Number.isFinite(event.created_at) ? Math.floor(event.created_at) : 0;
const publishedAtNum = Number(metadata.publishedAt);
const publishedAt =
Number.isFinite(publishedAtNum) && publishedAtNum > 0
? Math.floor(publishedAtNum)
: createdAt;
return {
id: event.id,
videoRootId,
@@ -181,7 +193,10 @@ export function buildVideoFromNip71Event(event = {}) {
infoJsonUrl: "",
enableComments: true,
pubkey: trimString(event.pubkey),
created_at: Number.isFinite(event.created_at) ? event.created_at : 0,
created_at: createdAt,
// Lets the feed's resolve-posted-at stage short-circuit instead of fetching
// non-existent kind-30078 history for foreign videos.
nip71: { publishedAt },
tags: Array.isArray(event.tags) ? event.tags : [],
// Provenance markers so the rest of the app can distinguish ingested videos
// from native bitvid uploads (e.g. to hide WebTorrent-only affordances).
+19
View File
@@ -56,6 +56,25 @@ test("maps a foreign NIP-71 event to a playable bitvid video", () => {
assert.equal(v.isPrivate, false);
});
test("surfaces nip71.publishedAt so the feed skips per-video history fetches", () => {
// The published_at tag becomes nip71.publishedAt (feed resolve-posted-at uses
// it to avoid a blocking kind-30078 history fetch per ingested video).
const withPublished = buildVideoFromNip71Event(
foreignEvent({
tags: [
["title", "Has published_at"],
["published_at", "1700000123"],
["imeta", "url https://example.com/v.mp4", "m video/mp4"],
],
}),
);
assert.equal(withPublished.nip71.publishedAt, 1700000123);
// Falls back to created_at when there's no published_at tag.
const noPublished = buildVideoFromNip71Event(foreignEvent({ created_at: 1699999999 }));
assert.equal(noPublished.nip71.publishedAt, 1699999999);
});
test("content-warning maps to isNsfw so the NSFW gate applies", () => {
const clean = buildVideoFromNip71Event(foreignEvent());
assert.equal(clean.isNsfw, false);