diff --git a/apps/web/src/components/chat/MessagesTimeline.tsx b/apps/web/src/components/chat/MessagesTimeline.tsx index 483859ba03005fb62c090ff93b2ac307ed1fa5be..03775a22f1090754f506dff04839d1132327525e 100644 --- a/apps/web/src/components/chat/MessagesTimeline.tsx +++ b/apps/web/src/components/chat/MessagesTimeline.tsx @@ -2641,6 +2641,7 @@ function ActivityGroupTimelineRow({ details.push( 0} @@ -2667,7 +2668,7 @@ function ActivityGroupTimelineRow({ shimmer={thinking} /> - {row.expanded ?
{details}
: null} + {row.expanded ?
{details}
: null} ); } @@ -2685,15 +2686,16 @@ function ThinkingTimelineRow() { } /** - * Thinking inside an expanded activity group: the trace is already one click - * deep, so the text renders under its "Thought" header without another toggle. + * Thinking inside a tool group has its own disclosure, preserved across recycling. * A group whose row already reads "Thought" (no visible tool) skips the header. */ function ReasoningTraceBlock({ + anchorKey, messages, live, showHeader, }: { + anchorKey: string; messages: ReadonlyArray; live: boolean; showHeader: boolean; @@ -2701,6 +2703,7 @@ function ReasoningTraceBlock({ const ctx = use(TimelineRowCtx); const { isWorking, unsettledTurnId } = use(TimelineRowActivityCtx); const first = messages[0]!; + const expanded = !showHeader || ctx.expandedReasoningMessageIds.has(first.id); const streaming = live && messages.some((reasoningMessage) => reasoningMessage.streaming) && @@ -2714,10 +2717,17 @@ function ReasoningTraceBlock({ return null; } const label = streaming ? "Thinking" : "Thought"; + const collapsedPreview = messages.find((message) => message.text.trim().length > 0)?.text.trim(); + const headerText = expanded ? label : (collapsedPreview ?? label); return (
{showHeader ? ( -
+ + ) : null} + {expanded ? ( +
+ {messages.map((reasoningMessage) => ( + + ))}
) : null} -
- {messages.map((reasoningMessage) => ( - - ))} -
); }