4.8 KiB
Comment threads & discussion counts (Nostr → UI flow)
This note documents how bitvid turns Nostr comment events into UI comment threads and discussion counts. The primary implementations live in:
js/services/commentThreadService.js(comment thread hydration + caching)js/services/discussionCountService.js(NIP-45 count queries + DOM updates)
Comment thread flow (CommentThreadService)
Data sources
CommentThreadService is the single place that fetches and subscribes to comment events for a video. It relies on the injected nostrClient APIs:
fetchVideoComments(target, options)for the initial thread load.subscribeVideoComments(target, options)to keep the thread up to date in real time.
The service builds a target payload using the current video metadata:
videoEventId(event id)videoDefinitionAddressfrombuildVideoAddressPointer(video)videoKindvideoAuthorPubkeyrootIdentifier/rootIdentifierRelay(derived fromvideo.videoRootId, pointer identifiers, or tag data)- optional
parentCommentIdcontext when fetching a reply thread
Internally, it also infers parentage from the incoming comment events by looking at e tags; the most recent e tag that is not the video event id becomes the parent comment id for threading. It also watches for the i tag to update root identifier and relay details.
Caching
When FEATURE_IMPROVED_COMMENT_FETCHING is enabled, comment events are cached in localStorage:
- Key format:
bitvid:comments:<videoEventId> - TTL: 5 minutes
- Versioned schema:
COMMENT_CACHE_VERSION = 2
Cache usage is simple: load from cache during loadThread, and write the latest event list after thread updates. Cache diagnostics (commentCacheDiagnostics.storageUnavailable) are surfaced to consumers to indicate storage issues.
Polling & refresh
There is no polling loop for comments. Refresh happens through:
- A one-time fetch via
fetchVideoCommentsduringloadThread. - A live subscription via
subscribeVideoComments, which streams new comments into the thread.
Profile hydration for comment authors is batched and debounced (default 25ms), with up to three retry attempts and a backoff delay between attempts.
UI surfacing
VideoModalCommentController owns the comment UI and subscribes to CommentThreadService callbacks:
onThreadReady→ full thread renderonCommentsAppended→ incremental updatesonError→ status messaging
The controller translates the thread snapshot into UI state for the modal and uses the comment thread’s profile cache to annotate authors.
Telemetry/logging
All comment-thread telemetry is routed through logger.dev/logger.user (no direct console logging). The service emits warnings for:
- Nostr client failures (fetch/subscribe)
- Comment cache read/write errors
- Profile hydration retries and failures
Discussion count flow (DiscussionCountService)
Data sources
DiscussionCountService uses NIP-45 COUNT queries to count comment events across relays. It requires a Nostr client that exposes:
nostrClient.pool(relay pool)nostrClient.countEventsAcrossRelays(filters)
Filters & event kinds
The service builds COUNT filters based on the current video metadata. The base event kind is COMMENT_EVENT_KIND (video comment kind). Filters include:
#E(root event id)#A(address pointer frombuildVideoAddressPointer(video))#I(root identifier / videoRootId)#K(root kind)#P(root author)
It always tries an #E filter first, then constructs an uppercase-tag filter that uses the best available pointer information, and finally a lowercase #A filter when available.
Caching
Counts are cached in-memory per video id during a session:
videoDiscussionCountCachestores resolved counts.inFlightDiscussionCountsdeduplicates concurrent requests.
There is no persistence beyond the runtime session.
Polling & refresh
Discussion counts are not polled on a timer. They refresh when the UI asks for them (typically after feed renders). The application triggers refreshes via app.refreshVideoDiscussionCounts, which is wired into video list rendering flows.
UI surfacing
The service updates DOM nodes directly using data attributes:
- Root node:
[data-discussion-count="<video id>"] - Count value:
[data-discussion-count-value]
The element’s data-count-state is set to:
pendingwhile a COUNT request is in flightreadyafter a successful responseunsupportedorerrorwhen COUNT is unavailable or fails
If relay support is missing, the service sets a tooltip (title) explaining that COUNT is unsupported.
Telemetry/logging
Failures to fetch counts are logged through the injected logger (default devLogger.warn), and the UI falls back to the error/unsupported state rather than throwing.