Files
bitvid/docs/comment-threading.md
T

117 lines
4.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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)
- `videoDefinitionAddress` from `buildVideoAddressPointer(video)`
- `videoKind`
- `videoAuthorPubkey`
- `rootIdentifier` / `rootIdentifierRelay` (derived from `video.videoRootId`, pointer identifiers, or tag data)
- optional `parentCommentId` context 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:
1. A one-time fetch via `fetchVideoComments` during `loadThread`.
2. 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 render
- `onCommentsAppended` → incremental updates
- `onError` → 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 from `buildVideoAddressPointer(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:
- `videoDiscussionCountCache` stores resolved counts.
- `inFlightDiscussionCounts` deduplicates 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:
- `pending` while a COUNT request is in flight
- `ready` after a successful response
- `unsupported` or `error` when 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.