diff --git a/desktop/src/features/collections/derivedLinks.test.mjs b/desktop/src/features/collections/derivedLinks.test.mjs
index e7da4ff931..ba1d6116ee 100644
--- a/desktop/src/features/collections/derivedLinks.test.mjs
+++ b/desktop/src/features/collections/derivedLinks.test.mjs
@@ -229,6 +229,52 @@ test("suppresses matching PR mention rows across sources but keeps unrelated cha
);
});
+test("suppresses a PR conversation without hiding another thread from the same channel source", () => {
+ const message = (eventId, threadRootId, label) => ({
+ activityType: "channel-message",
+ actorIdentity: "buzz:human",
+ authorPubkey: "human",
+ channelId: "channel-1",
+ createdAt: 1,
+ eventId,
+ kind: "thread",
+ label,
+ sourceMemberId: "channel-source",
+ threadRootId,
+ url: "",
+ });
+ const prConversation = message("pr-reply", "pr-thread", "PR chatter");
+ const unrelatedConversation = message(
+ "design-reply",
+ "design-thread",
+ "Unrelated design decision",
+ );
+ const pullRequest = {
+ ...prConversation,
+ activityType: "github-pr",
+ kind: "pull request",
+ label: "PR #7",
+ sourceEventIds: ["pr-reply"],
+ sourceMemberIds: ["channel-source"],
+ url: "https://github.com/block/buzz/pull/7",
+ };
+ const stateChange = {
+ ...pullRequest,
+ activityType: "github-pr-activity",
+ createdAt: 2,
+ kind: "PR review",
+ url: `${pullRequest.url}#pullrequestreview-1`,
+ };
+
+ assert.deepEqual(
+ projectCollectionActivity(
+ [prConversation, unrelatedConversation, pullRequest, stateChange],
+ { human: false },
+ ),
+ [unrelatedConversation, stateChange],
+ );
+});
+
test("names channel-derived live thread provenance", () => {
assert.equal(
collectionChannelThreadProvenance("#design"),
diff --git a/desktop/src/features/collections/derivedLinks.ts b/desktop/src/features/collections/derivedLinks.ts
index 0f08fde0f7..5cbae89518 100644
--- a/desktop/src/features/collections/derivedLinks.ts
+++ b/desktop/src/features/collections/derivedLinks.ts
@@ -362,15 +362,22 @@ export function projectCollectionActivity(
activity: readonly DerivedCollectionActivity[],
actorIsAgent: Readonly>,
): DerivedCollectionActivity[] {
+ const conversationIdentity = (item: DerivedCollectionActivity) =>
+ item.channelId
+ ? `${item.channelId}:${item.threadRootId ?? item.eventId ?? item.sourceMemberId}`
+ : null;
const pullRequestUrls = new Set(
activity
.filter((item) => item.activityType === "github-pr")
.map((item) => item.url.toLocaleLowerCase()),
);
- const pullRequestSources = new Set(
+ const pullRequestConversations = new Set(
activity
.filter((item) => item.activityType === "github-pr")
- .flatMap((item) => item.sourceMemberIds ?? [item.sourceMemberId]),
+ .flatMap((item) => {
+ const identity = conversationIdentity(item);
+ return identity ? [identity] : [];
+ }),
);
const pullRequestEvents = new Set(
activity
@@ -384,7 +391,7 @@ export function projectCollectionActivity(
]).some((mention) => pullRequestUrls.has(mention.url.toLocaleLowerCase()));
return (
!mentionsResolvedPullRequest &&
- !pullRequestSources.has(item.sourceMemberId) &&
+ !pullRequestConversations.has(conversationIdentity(item) ?? "") &&
(!item.eventId || !pullRequestEvents.has(item.eventId))
);
});
diff --git a/desktop/src/features/collections/hooks.ts b/desktop/src/features/collections/hooks.ts
index af1aa63443..6aec009d88 100644
--- a/desktop/src/features/collections/hooks.ts
+++ b/desktop/src/features/collections/hooks.ts
@@ -67,7 +67,6 @@ function sourceLabel(member: CollectionMember): string {
function messageActivity(
member: CollectionMember,
events: Awaited>,
- includePullRequests: boolean,
): DerivedCollectionActivity[] {
const reference = member.reference;
if (
@@ -102,25 +101,21 @@ function messageActivity(
reference.type === "thread" ? reference.root_event_id : thread.rootId,
url: "",
};
- const pullRequests = includePullRequests
- ? extractGitHubPullRequestLinks([event]).map(
- (link): DerivedCollectionActivity => ({
- ...link,
- activityType: "github-pr",
- actorIdentity: `buzz:${event.pubkey.toLocaleLowerCase()}`,
- authorPubkey: event.pubkey,
- channelId,
- createdAt: event.created_at,
- eventId: event.id,
- provenanceLabel: `${sourceLabel(member)} → mentioned PR`,
- sourceMemberId: member.id,
- threadRootId:
- reference.type === "thread"
- ? reference.root_event_id
- : thread.rootId,
- }),
- )
- : [];
+ const pullRequests = extractGitHubPullRequestLinks([event]).map(
+ (link): DerivedCollectionActivity => ({
+ ...link,
+ activityType: "github-pr",
+ actorIdentity: `buzz:${event.pubkey.toLocaleLowerCase()}`,
+ authorPubkey: event.pubkey,
+ channelId,
+ createdAt: event.created_at,
+ eventId: event.id,
+ provenanceLabel: `${sourceLabel(member)} → mentioned PR`,
+ sourceMemberId: member.id,
+ threadRootId:
+ reference.type === "thread" ? reference.root_event_id : thread.rootId,
+ }),
+ );
return [message, ...pullRequests];
});
}
@@ -332,7 +327,7 @@ async function discoverCollectionMemberActivity(
},
);
return {
- items: [...threadActivity, ...messageActivity(member, messages, false)],
+ items: [...threadActivity, ...messageActivity(member, messages)],
warnings: [],
};
}
@@ -344,14 +339,14 @@ async function discoverCollectionMemberActivity(
{ limit: COLLECTION_CHANNEL_DISCOVERY_LIMIT },
);
return {
- items: messageActivity(member, [root, ...replies.events], true),
+ items: messageActivity(member, [root, ...replies.events]),
warnings: [],
};
}
if (member.reference.type === "message") {
const event = await getEventById(member.reference.event_id);
return {
- items: messageActivity(member, [event], true),
+ items: messageActivity(member, [event]),
warnings: [],
};
}
diff --git a/desktop/src/features/collections/ui/CollectionDetailScreen.tsx b/desktop/src/features/collections/ui/CollectionDetailScreen.tsx
index c5af1f4229..60f356be47 100644
--- a/desktop/src/features/collections/ui/CollectionDetailScreen.tsx
+++ b/desktop/src/features/collections/ui/CollectionDetailScreen.tsx
@@ -52,6 +52,8 @@ import {
CollectionRowActions,
DerivedCollectionActivityRow,
} from "./CollectionDetailRows";
+
+const PEOPLE_PREVIEW_LIMIT = 8;
import { CollectionGlyph } from "./CollectionGlyph";
import { CollectionIconDialog } from "./CollectionIconDialog";
@@ -98,6 +100,12 @@ export function CollectionDetailScreen({
!identity.startsWith("buzz:") ||
!profiles[identity.slice("buzz:".length)]?.isAgent,
);
+ const visiblePeopleIdentities = peopleIdentities.slice(
+ 0,
+ PEOPLE_PREVIEW_LIMIT,
+ );
+ const hiddenPeopleCount =
+ peopleIdentities.length - visiblePeopleIdentities.length;
const members = (data?.members ?? []).filter((member) =>
collectionMemberMatches(member, search, type),
);
@@ -454,7 +462,7 @@ export function CollectionDetailScreen({
- {peopleIdentities.map((identity) => {
+ {visiblePeopleIdentities.map((identity) => {
const pubkey = identity.startsWith("buzz:")
? identity.slice("buzz:".length)
: null;
@@ -494,6 +502,11 @@ export function CollectionDetailScreen({
);
})}
+ {hiddenPeopleCount > 0 ? (
+
+ +{hiddenPeopleCount} more
+
+ ) : null}
{selectedPerson ? (