From 6d9b0cc8fff65447849d0d55db177dcdff315c48 Mon Sep 17 00:00:00 2001 From: DanConwayDev Date: Wed, 4 Mar 2026 13:54:32 +0000 Subject: feat: fetch and display NIP-22 comment counts on issues and proposals Download kind-1111 NIP-22 comments from relays and show a comment count in the detail view of `ngit list ` and `ngit issue list `. - git_events: add KIND_COMMENT constant (kind 1111) - client: import KIND_COMMENT; add `comments` field to FetchReport; route kind-1111 events into report.comments in process_fetched_events; consolidate comments across relay reports; display "N comment(s)" in FetchReport; add a #E-tagged kind-1111 filter in get_fetch_filters covering all known issue and proposal root IDs - issue_list: add get_comment_counts() to query the local cache for kind-1111 events by #E tag and count per issue; extend the filtered tuple with comment_count; show a CMTS column in the table, a "Comments: N" line in the detail view, and a "comments" field in JSON - list: add KIND_COMMENT import; add resolve_event_id() helper and get_comment_count_for_proposal() to look up the count for a single proposal from the local cache; pass comment_count into show_proposal_details(); display "Comments: N" in plain text and "comments" in JSON; align detail-view labels --- src/lib/client.rs | 67 +++++++++++++++++++++++++++++++++++++++------------ src/lib/git_events.rs | 2 ++ 2 files changed, 53 insertions(+), 16 deletions(-) (limited to 'src/lib') diff --git a/src/lib/client.rs b/src/lib/client.rs index 1f46e3c..8501a1f 100644 --- a/src/lib/client.rs +++ b/src/lib/client.rs @@ -56,9 +56,9 @@ use crate::{ get_dirs, git::{Repo, RepoActions, get_git_config_item}, git_events::{ - KIND_PULL_REQUEST, KIND_PULL_REQUEST_UPDATE, KIND_USER_GRASP_LIST, event_is_cover_letter, - event_is_patch_set_root, event_is_revision_root, event_is_valid_pr_or_pr_update, - status_kinds, + KIND_COMMENT, KIND_PULL_REQUEST, KIND_PULL_REQUEST_UPDATE, KIND_USER_GRASP_LIST, + event_is_cover_letter, event_is_patch_set_root, event_is_revision_root, + event_is_valid_pr_or_pr_update, status_kinds, }, login::{get_likely_logged_in_user, user::get_user_ref_from_cache}, repo_ref::{RepoRef, normalize_grasp_server_url}, @@ -1877,7 +1877,7 @@ async fn create_relays_request( }) } -#[allow(clippy::too_many_lines)] +#[allow(clippy::too_many_lines, clippy::too_many_arguments)] async fn process_fetched_events( events: Vec, request: &FetchRequest, @@ -1996,6 +1996,8 @@ async fn process_fetched_events( { fresh_profiles.insert(event.pubkey); } + } else if event.kind.eq(&KIND_COMMENT) { + report.comments.insert(event.id); } else if [Kind::RelayList, Kind::Metadata, KIND_USER_GRASP_LIST].contains(&event.kind) { if request.missing_contributor_profiles.contains(&event.pubkey) { @@ -2023,9 +2025,7 @@ async fn process_fetched_events( for event in &events { if !request.existing_events.contains(&event.id) { let tagged_root_id = event.tags.iter().find_map(|t| { - if t.as_slice().len() > 1 - && (t.as_slice()[0].eq("E") || t.as_slice()[0].eq("e")) - { + if t.as_slice().len() > 1 && (t.as_slice()[0].eq("E") || t.as_slice()[0].eq("e")) { EventId::parse(&t.as_slice()[1]).ok() } else { None @@ -2038,9 +2038,11 @@ async fn process_fetched_events( // as their parent (new issues/proposals already inflate the count). if let Some(root_id) = &tagged_root_id { if report.issues.contains(root_id) { - // status for a new issue in this batch — skip (counted via issues) + // status for a new issue in this batch — skip (counted + // via issues) } else if report.proposals.contains(root_id) { - // status for a new proposal in this batch — skip (counted via proposals) + // status for a new proposal in this batch — skip + // (counted via proposals) } else if request.issue_ids.contains(root_id) { report.issue_statuses.insert(event.id); } else { @@ -2052,12 +2054,11 @@ async fn process_fetched_events( let not_tagged_with_new_proposal = tagged_root_id .as_ref() .is_none_or(|id| !report.proposals.contains(id)); - if not_tagged_with_new_proposal { - if (event.kind.eq(&Kind::GitPatch) && !event_is_patch_set_root(event)) - || event.kind.eq(&KIND_PULL_REQUEST_UPDATE) - { - report.commits.insert(event.id); - } + if not_tagged_with_new_proposal + && ((event.kind.eq(&Kind::GitPatch) && !event_is_patch_set_root(event)) + || event.kind.eq(&KIND_PULL_REQUEST_UPDATE)) + { + report.commits.insert(event.id); } } } @@ -2117,6 +2118,9 @@ pub fn consolidate_fetch_reports(reports: Vec>) -> FetchRepo for c in relay_report.issue_statuses { report.issue_statuses.insert(c); } + for c in relay_report.comments { + report.comments.insert(c); + } report.deletions += relay_report.deletions; for c in relay_report.contributor_profiles { report.contributor_profiles.insert(c); @@ -2223,6 +2227,24 @@ pub fn get_fetch_filters( .kinds(status_kinds()), ] }, + // Fetch NIP-22 kind-1111 comments for issues and proposals (patches/PRs). + // Comments use an uppercase `E` tag pointing to the root event ID. + { + let all_root_ids: HashSet = issue_ids + .iter() + .chain(proposal_ids.iter()) + .copied() + .collect(); + if all_root_ids.is_empty() { + vec![] + } else { + vec![ + nostr::Filter::default() + .custom_tags(SingleLetterTag::uppercase(Alphabet::E), all_root_ids) + .kind(KIND_COMMENT), + ] + } + }, // Request kind-5 deletions for state events and repo announcements by // their event ID (#e tag), as per NIP-09. The #a-tagged filter above // covers addressable-event deletions; this covers the specific event IDs @@ -2309,6 +2331,8 @@ pub struct FetchReport { statuses: HashSet, issues: HashSet, issue_statuses: HashSet, + /// NIP-22 kind-1111 comments against issues, patches, and PRs. + comments: HashSet, /// Count of kind-5 deletion events received (for display purposes). deletions: u32, contributor_profiles: HashSet, @@ -2383,7 +2407,18 @@ impl Display for FetchReport { display_items.push(format!( "{} issue status{}", self.issue_statuses.len(), - if self.issue_statuses.len() > 1 { "es" } else { "" }, + if self.issue_statuses.len() > 1 { + "es" + } else { + "" + }, + )); + } + if !self.comments.is_empty() { + display_items.push(format!( + "{} comment{}", + self.comments.len(), + if self.comments.len() > 1 { "s" } else { "" }, )); } if self.deletions > 0 { diff --git a/src/lib/git_events.rs b/src/lib/git_events.rs index 32c23ac..dde0e1a 100644 --- a/src/lib/git_events.rs +++ b/src/lib/git_events.rs @@ -88,6 +88,8 @@ pub fn status_kinds() -> Vec { pub const KIND_PULL_REQUEST: Kind = Kind::Custom(1618); pub const KIND_PULL_REQUEST_UPDATE: Kind = Kind::Custom(1619); pub const KIND_USER_GRASP_LIST: Kind = Kind::Custom(10317); +/// NIP-22 comment (kind 1111) — threaded comments on any event. +pub const KIND_COMMENT: Kind = Kind::Custom(1111); pub fn event_is_patch_set_root(event: &Event) -> bool { event.kind.eq(&Kind::GitPatch) -- cgit v1.2.3