diff --git a/apps/web/features/issues/components/board-card.tsx b/apps/web/features/issues/components/board-card.tsx index d418b103..a6eed5e0 100644 --- a/apps/web/features/issues/components/board-card.tsx +++ b/apps/web/features/issues/components/board-card.tsx @@ -91,7 +91,7 @@ export function BoardCardContent({ {/* Title */}

{issue.title}

diff --git a/apps/web/features/issues/components/comment-card.tsx b/apps/web/features/issues/components/comment-card.tsx index a8ea065a..5db099fb 100644 --- a/apps/web/features/issues/components/comment-card.tsx +++ b/apps/web/features/issues/components/comment-card.tsx @@ -168,7 +168,7 @@ function CommentCard({ collectReplies(entry.id); return ( - + {/* Parent comment */}
{ - const content = editorRef.current?.getMarkdown()?.trim(); + const content = editorRef.current?.getMarkdown()?.replace(/(\n\s*)+$/, "").trim(); if (!content || submitting) return; setSubmitting(true); try { @@ -28,8 +28,8 @@ function CommentInput({ onSubmit }: CommentInputProps) { }; return ( -
-
+
+
-
+
+ )}
); } @@ -856,9 +869,30 @@ export function IssueDetail({ issueId, onDelete, defaultSidebarOpen = true, layo } } + // Coalesce: same actor + same action within 2 min → keep last only + const COALESCE_MS = 2 * 60 * 1000; + const coalesced: TimelineEntry[] = []; + for (const entry of topLevel) { + if (entry.type === "activity") { + const prev = coalesced[coalesced.length - 1]; + if ( + prev?.type === "activity" && + prev.action === entry.action && + prev.actor_type === entry.actor_type && + prev.actor_id === entry.actor_id && + Math.abs(new Date(entry.created_at).getTime() - new Date(prev.created_at).getTime()) <= COALESCE_MS + ) { + // Replace previous with this one (keep the later result) + coalesced[coalesced.length - 1] = entry; + continue; + } + } + coalesced.push(entry); + } + // Group consecutive activities together so the connector line works const groups: { type: "activities" | "comment"; entries: TimelineEntry[] }[] = []; - for (const entry of topLevel) { + for (const entry of coalesced) { if (entry.type === "activity") { const last = groups[groups.length - 1]; if (last?.type === "activities") { diff --git a/apps/web/features/issues/components/reply-input.tsx b/apps/web/features/issues/components/reply-input.tsx index dc355509..b95662c4 100644 --- a/apps/web/features/issues/components/reply-input.tsx +++ b/apps/web/features/issues/components/reply-input.tsx @@ -34,7 +34,7 @@ function ReplyInput({ const [submitting, setSubmitting] = useState(false); const handleSubmit = async () => { - const content = editorRef.current?.getMarkdown()?.trim(); + const content = editorRef.current?.getMarkdown()?.replace(/(\n\s*)+$/, "").trim(); if (!content || submitting) return; setSubmitting(true); try {