From 45708a06f112e3a777516ad80366306c51279c8f Mon Sep 17 00:00:00 2001 From: Dotta Date: Sat, 7 Mar 2026 08:31:59 -0600 Subject: [PATCH] ui: avoid duplicate and self comment toasts --- ui/src/context/LiveUpdatesProvider.tsx | 25 +++++++++++++++++++++---- ui/src/pages/IssueDetail.tsx | 10 +--------- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/ui/src/context/LiveUpdatesProvider.tsx b/ui/src/context/LiveUpdatesProvider.tsx index 6a1403af..3f3f222a 100644 --- a/ui/src/context/LiveUpdatesProvider.tsx +++ b/ui/src/context/LiveUpdatesProvider.tsx @@ -1,6 +1,7 @@ import { useEffect, useRef, type ReactNode } from "react"; -import { useQueryClient, type QueryClient } from "@tanstack/react-query"; +import { useQuery, useQueryClient, type QueryClient } from "@tanstack/react-query"; import type { Agent, Issue, LiveEvent } from "@paperclipai/shared"; +import { authApi } from "../api/auth"; import { useCompany } from "./CompanyContext"; import type { ToastInput } from "./ToastContext"; import { useToast } from "./ToastContext"; @@ -152,6 +153,7 @@ function buildActivityToast( queryClient: QueryClient, companyId: string, payload: Record, + currentActor: { userId: string | null; agentId: string | null }, ): ToastInput | null { const entityType = readString(payload.entityType); const entityId = readString(payload.entityId); @@ -200,6 +202,11 @@ function buildActivityToast( } const commentId = readString(details?.commentId); + const isSelfComment = + action === "issue.comment_added" && + ((actorType === "user" && !!currentActor.userId && actorId === currentActor.userId) || + (actorType === "agent" && !!currentActor.agentId && actorId === currentActor.agentId)); + if (isSelfComment) return null; const bodySnippet = readString(details?.bodySnippet); const reopened = details?.reopened === true; const reopenedFrom = readString(details?.reopenedFrom); @@ -448,6 +455,7 @@ function handleLiveEvent( event: LiveEvent, pushToast: (toast: ToastInput) => string | null, gate: ToastGate, + currentActor: { userId: string | null; agentId: string | null }, ) { if (event.companyId !== expectedCompanyId) return; @@ -485,7 +493,7 @@ function handleLiveEvent( invalidateActivityQueries(queryClient, expectedCompanyId, payload); const action = readString(payload.action); const toast = - buildActivityToast(queryClient, expectedCompanyId, payload) ?? + buildActivityToast(queryClient, expectedCompanyId, payload, currentActor) ?? buildJoinRequestToast(payload); if (toast) gatedPushToast(gate, pushToast, `activity:${action ?? "unknown"}`, toast); } @@ -496,6 +504,12 @@ export function LiveUpdatesProvider({ children }: { children: ReactNode }) { const queryClient = useQueryClient(); const { pushToast } = useToast(); const gateRef = useRef({ cooldownHits: new Map(), suppressUntil: 0 }); + const { data: session } = useQuery({ + queryKey: queryKeys.auth.session, + queryFn: () => authApi.getSession(), + retry: false, + }); + const currentUserId = session?.user?.id ?? session?.session?.userId ?? null; useEffect(() => { if (!selectedCompanyId) return; @@ -541,7 +555,10 @@ export function LiveUpdatesProvider({ children }: { children: ReactNode }) { try { const parsed = JSON.parse(raw) as LiveEvent; - handleLiveEvent(queryClient, selectedCompanyId, parsed, pushToast, gateRef.current); + handleLiveEvent(queryClient, selectedCompanyId, parsed, pushToast, gateRef.current, { + userId: currentUserId, + agentId: null, + }); } catch { // Ignore non-JSON payloads. } @@ -570,7 +587,7 @@ export function LiveUpdatesProvider({ children }: { children: ReactNode }) { socket.close(1000, "provider_unmount"); } }; - }, [queryClient, selectedCompanyId, pushToast]); + }, [queryClient, selectedCompanyId, pushToast, currentUserId]); return <>{children}; } diff --git a/ui/src/pages/IssueDetail.tsx b/ui/src/pages/IssueDetail.tsx index 84f07ed1..f787931e 100644 --- a/ui/src/pages/IssueDetail.tsx +++ b/ui/src/pages/IssueDetail.tsx @@ -419,17 +419,9 @@ export function IssueDetail() { const addComment = useMutation({ mutationFn: ({ body, reopen }: { body: string; reopen?: boolean }) => issuesApi.addComment(issueId!, body, reopen), - onSuccess: (comment) => { + onSuccess: () => { invalidateIssue(); queryClient.invalidateQueries({ queryKey: queryKeys.issues.comments(issueId!) }); - const issueRef = issue?.identifier ?? (issueId ? `Issue ${issueId.slice(0, 8)}` : "Issue"); - pushToast({ - dedupeKey: `activity:issue.comment_added:${issueId}:${comment.id}`, - title: `Comment posted on ${issueRef}`, - body: issue?.title ? truncate(issue.title, 96) : undefined, - tone: "success", - action: issueId ? { label: `View ${issueRef}`, href: `/issues/${issue?.identifier ?? issueId}` } : undefined, - }); }, });