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]>
Live logs proved ingest works (subscribed to 17 whitelisted authors,
received 61 events, injected 39) — but injecting foreign authors during
cold-start triggers the moderation/WoT stage to hydrate their social graph
(kind 30000), which competes with the initial feed load and stalls it
("Fetching recent videos…" hanging, needs a refresh).
- startWhenFeedReady(): defer the first ingest subscription until the
native feed's first videos:updated (with a 12s fallback for empty
feeds), keeping ingest off the cold-start critical path. Wired in
applicationBootstrap (kept thin so the size cap holds).
- emitRefresh now logs how many ingested videos survive the feed filter
("N ingested of M in filtered feed") so we can tell whether foreign
videos are filtered out vs. injected-but-not-rendered.
Tests: defer-until-feed-ready + fallback-timer; mutation-verified.
1236/1237 unit (known UploadModal flake).
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Two stabilization fixes after live testing showed ingest was invisible
and could amplify feed load:
1. Diagnostics were using devLogger.log, which is gated by IS_DEV_MODE
(false in all current builds) — so the [nip71Ingest] subscribe/flush
lines never printed and the feature was impossible to observe. Switched
the subscribe + per-flush lines to userLogger.info so we can confirm in
the console whether it subscribed (author count) and whether events are
arriving/injecting. (Temporary while stabilizing.)
2. Coalesce ingest-driven "videos:updated" signals behind an 8s throttle.
A burst of incoming events previously emitted a refresh per flush; each
refresh re-runs the feed and re-subscribes moderation/social context for
every video, which can contribute to a REQ storm. Injected videos are
already in the active store, so throttling only delays the visual
refresh, not the data.
Tests: throttle coalescing test (one immediate refresh per window, both
videos still stored); mutation-verified. 1234/1235 unit (known flake).
Co-Authored-By: Claude Opus 4.8 <[email protected]>
The admin whitelist hydrates asynchronously (remote fetch, then a second
hex-pubkey rebuild once NostrTools loads) and does not always emit a
change we can hook — e.g. loaded-from-cache-unchanged, or the async hex
rebuild that emits nothing. So at startup getWhitelistPubkeys() was empty,
resolveFilters() returned null, no subscription opened, and the
onWhitelistChange restart never fired: ingest stayed silent even when a
whitelisted author had foreign NIP-71 videos on the relays.
Fix: bounded retry (every 2s, ~60s) that re-attempts opening the
subscription until the author scope hydrates, in addition to the
onWhitelistChange listener. Author-scoping is kept deliberately — a broad
kinds:[21,22,...] subscription would be a REQ firehose that violates
bitvid's relay fan-out invariants.
Also adds dev-mode logging (subscribe author/kind count; per-flush events
in / injected) so ingest is observable in the console.
Tests: new race test (empty whitelist at start -> authors hydrate without
an emit -> retry subscribes); mutation-verified.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Pulls NIP-71 video events published by other Nostr apps (kinds
21/22/34235/34236), converts them via the ingest adapter, and injects
them into nostrClient.activeMap/allEvents so they flow through the
existing feed pipeline. Gated by FEATURE_NIP71_INGEST.
Mirrors bitvid's whitelist model: since the whitelist is "authors
allowed to publish/render here," ingest scopes its relay subscription to
whitelisted pubkeys while whitelist mode is on (bounded, no firehose) and
re-subscribes when the whitelist changes. When a deployer disables
whitelist mode, ingest opens to all authors (capped by a limit). Foreign
content is still gated by the same render-time filter
(shouldIncludeVideo -> accessControl.canAccess): whitelist, blacklist,
NSFW (content-warning -> isNsfw), private-skip, plus trust/mute/blur from
the feed engine. Native bitvid videos always win a shared root; among
ingested videos newest wins. Refreshes via the existing videos:updated
reactive path.
- accessControl.getWhitelistPubkeys(): hex whitelist for subscription scope.
- Wired in applicationBootstrap (start on init, stop on teardown).
- nostrService untouched (at its size cap); logic is a standalone service.
Mutation-verified: native-wins, whitelist scoping, feature gate.
Co-Authored-By: Claude Opus 4.8 <[email protected]>