From 4625611ffc5541b65434aa419ed66de53ff7f739 Mon Sep 17 00:00:00 2001 From: Dotta Date: Tue, 16 Jun 2026 13:27:39 +0000 Subject: [PATCH] Fix live issue chat segment activity Co-Authored-By: Paperclip --- ui/src/components/IssueChatThread.tsx | 10 +++++++--- ui/src/components/IssueChatThreadClassic.tsx | 10 +++++++--- ui/src/lib/issue-chat-messages.test.ts | 19 +++++++++++++++++++ ui/src/lib/issue-chat-messages.ts | 11 +++++++++++ 4 files changed, 44 insertions(+), 6 deletions(-) diff --git a/ui/src/components/IssueChatThread.tsx b/ui/src/components/IssueChatThread.tsx index 0c96c409a91..d0603968f1c 100644 --- a/ui/src/components/IssueChatThread.tsx +++ b/ui/src/components/IssueChatThread.tsx @@ -48,6 +48,7 @@ import { usePaperclipIssueRuntime, type PaperclipIssueRuntimeReassignment } from import { buildIssueChatMessages, formatDurationWords, + isCoTSegmentActive, stabilizeThreadMessages, type IssueChatComment, type IssueChatLinkedRun, @@ -851,13 +852,16 @@ function IssueChatChainOfThought({ (p): p is ToolCallMessagePart => p.type === "tool-call", ); - const isActive = isMessageRunning; - const [expanded, setExpanded] = useState(isActive); - const rawSegments = Array.isArray(custom.chainOfThoughtSegments) ? (custom.chainOfThoughtSegments as SegmentTiming[]) : []; const segmentTiming = myIndex >= 0 ? rawSegments[myIndex] ?? null : null; + const isActive = isCoTSegmentActive({ + isMessageRunning, + segmentIndex: myIndex, + segmentCount: rawSegments.length, + }); + const [expanded, setExpanded] = useState(isActive); const liveElapsed = useLiveElapsed(segmentTiming?.startMs, isActive); useEffect(() => { diff --git a/ui/src/components/IssueChatThreadClassic.tsx b/ui/src/components/IssueChatThreadClassic.tsx index a4e9345b128..1763bef99c2 100644 --- a/ui/src/components/IssueChatThreadClassic.tsx +++ b/ui/src/components/IssueChatThreadClassic.tsx @@ -59,6 +59,7 @@ import { usePaperclipIssueRuntime, type PaperclipIssueRuntimeReassignment } from import { buildIssueChatMessages, formatDurationWords, + isCoTSegmentActive, stabilizeThreadMessages, type IssueChatComment, type IssueChatLinkedRun, @@ -876,13 +877,16 @@ function IssueChatChainOfThought({ (p): p is ToolCallMessagePart => p.type === "tool-call", ); - const isActive = isMessageRunning; - const [expanded, setExpanded] = useState(isActive); - const rawSegments = Array.isArray(custom.chainOfThoughtSegments) ? (custom.chainOfThoughtSegments as SegmentTiming[]) : []; const segmentTiming = myIndex >= 0 ? rawSegments[myIndex] ?? null : null; + const isActive = isCoTSegmentActive({ + isMessageRunning, + segmentIndex: myIndex, + segmentCount: rawSegments.length, + }); + const [expanded, setExpanded] = useState(isActive); const liveElapsed = useLiveElapsed(segmentTiming?.startMs, isActive); useEffect(() => { diff --git a/ui/src/lib/issue-chat-messages.test.ts b/ui/src/lib/issue-chat-messages.test.ts index 786e1b3ab20..8d1147edacd 100644 --- a/ui/src/lib/issue-chat-messages.test.ts +++ b/ui/src/lib/issue-chat-messages.test.ts @@ -3,6 +3,7 @@ import type { Agent } from "@paperclipai/shared"; import { buildAssistantPartsFromTranscript, buildIssueChatMessages, + isCoTSegmentActive, stabilizeThreadMessages, type IssueChatComment, type IssueChatLinkedRun, @@ -238,6 +239,24 @@ describe("buildAssistantPartsFromTranscript", () => { }]); }); + it("marks only the latest chain-of-thought segment active while a run is live", () => { + expect(isCoTSegmentActive({ + isMessageRunning: true, + segmentIndex: 0, + segmentCount: 2, + })).toBe(false); + expect(isCoTSegmentActive({ + isMessageRunning: true, + segmentIndex: 1, + segmentCount: 2, + })).toBe(true); + expect(isCoTSegmentActive({ + isMessageRunning: false, + segmentIndex: 1, + segmentCount: 2, + })).toBe(false); + }); + it("keeps run errors while suppressing init and system transcript noise", () => { const result = buildAssistantPartsFromTranscript([ { diff --git a/ui/src/lib/issue-chat-messages.ts b/ui/src/lib/issue-chat-messages.ts index fe2d0c37c04..4993cd709ed 100644 --- a/ui/src/lib/issue-chat-messages.ts +++ b/ui/src/lib/issue-chat-messages.ts @@ -538,6 +538,17 @@ export interface SegmentTiming { endMs: number; } +export function isCoTSegmentActive(args: { + isMessageRunning: boolean; + segmentIndex: number; + segmentCount: number; +}) { + const { isMessageRunning, segmentIndex, segmentCount } = args; + if (!isMessageRunning) return false; + if (segmentCount <= 0 || segmentIndex < 0) return true; + return segmentIndex === segmentCount - 1; +} + function computeSegmentTimings(entries: readonly IssueChatTranscriptEntry[]): SegmentTiming[] { const timings: SegmentTiming[] = []; let inSegment = false;