fix: improve activity rendering, spacing, and path truncation (#654)

* fix: polish activity tool rows and message spacing

Show each tool call as its own row in live and activity views
Keep tool descriptions inline with truncation and cleaner read-file paths
Tune spacing, todo counts, and bash timing display for clearer chat flow

* fix: refine activity tool rows and truncation behavior

Make activity collapse behavior consistent across modes and rename default mode label to Expanded
Show tool descriptions inline with improved spacing and one-row-per-tool rendering
Fix read/edit path truncation to clip from the start while keeping edit filenames highlighted

* fix: resolve chat activity lint regressions

Remove unused activity render mode state in message body
Mark path helper animation arg as intentionally unused to satisfy lint
This commit is contained in:
Bohdan Triapitsyn
2026-03-13 14:57:30 +02:00
committed by GitHub
parent 9605fa5106
commit b7196e65c9
5 changed files with 151 additions and 304 deletions
@@ -33,7 +33,7 @@ import { toast } from '@/components/ui';
import { formatTimestampForDisplay } from './timeFormat';
import { ToolRevealOnMount } from './parts/ToolRevealOnMount';
import { StaticToolRow } from './parts/ProgressiveGroup';
import { getStaticGroupToolName, isExpandableTool, isStandaloneTool } from './parts/toolRenderUtils';
import { isExpandableTool, isStandaloneTool } from './parts/toolRenderUtils';
import TurnActivity from '../components/TurnActivity';
type SubtaskPartLike = Part & {
@@ -699,9 +699,8 @@ const AssistantMessageBody: React.FC<Omit<MessageBodyProps, 'isUser'>> = ({
const createSessionFromAssistantMessage = useSessionStore((state) => state.createSessionFromAssistantMessage);
const openMultiRunLauncherWithPrompt = useUIStore((state) => state.openMultiRunLauncherWithPrompt);
const chatRenderMode = useUIStore((state) => state.chatRenderMode);
const activityRenderMode = useUIStore((state) => state.activityRenderMode);
const isSortedRenderMode = chatRenderMode === 'sorted';
const collapsedPreviewCount = activityRenderMode === 'collapsed' ? 7 : 0;
const collapsedPreviewCount = 7;
const isLastAssistantInTurn = turnGroupingContext?.isLastAssistantInTurn ?? false;
const hasStopFinish = messageFinish === 'stop';
@@ -1111,24 +1110,25 @@ const AssistantMessageBody: React.FC<Omit<MessageBodyProps, 'isUser'>> = ({
return;
}
rendered.push(
<TurnActivity
key={`progressive-group-${segment.id}`}
parts={visibleSegmentParts}
isExpanded={turnGroupingContext.isGroupExpanded === true}
collapsedPreviewCount={collapsedPreviewCount}
onToggle={toggleActivityGroup}
syntaxTheme={syntaxTheme}
isMobile={isMobile}
expandedTools={expandedTools}
onToggleTool={onToggleTool}
onShowPopup={onShowPopup}
onContentChange={onContentChange}
streamPhase={streamPhase}
showHeader={true}
animateRows={animateActivityRows}
animatedToolIds={animatedToolIdsLookup}
diffStats={turnGroupingContext.diffStats}
/>
<div key={`progressive-group-${segment.id}`} className="mb-3">
<TurnActivity
parts={visibleSegmentParts}
isExpanded={turnGroupingContext.isGroupExpanded === true}
collapsedPreviewCount={collapsedPreviewCount}
onToggle={toggleActivityGroup}
syntaxTheme={syntaxTheme}
isMobile={isMobile}
expandedTools={expandedTools}
onToggleTool={onToggleTool}
onShowPopup={onShowPopup}
onContentChange={onContentChange}
streamPhase={streamPhase}
showHeader={true}
animateRows={animateActivityRows}
animatedToolIds={animatedToolIdsLookup}
diffStats={turnGroupingContext.diffStats}
/>
</div>
);
});
}
@@ -1190,7 +1190,7 @@ const AssistantMessageBody: React.FC<Omit<MessageBodyProps, 'isUser'>> = ({
if (partText && partText.trim().length > 0) {
rendered.push(
<FadeInOnReveal key={`reasoning-${messageId}-${i}`}>
<div className="text-sm text-muted-foreground/60 italic leading-relaxed whitespace-pre-wrap">
<div className="my-0.5 text-sm text-muted-foreground/60 italic leading-relaxed whitespace-pre-wrap">
{partText}
</div>
</FadeInOnReveal>
@@ -1239,40 +1239,28 @@ const AssistantMessageBody: React.FC<Omit<MessageBodyProps, 'isUser'>> = ({
continue;
}
// Static tools: group consecutive tools of the same kind into compact rows
const groupToolName = getStaticGroupToolName(toolName);
const group: ToolPartType[] = [toolPart];
let j = i + 1;
while (j < visibleParts.length) {
const next = visibleParts[j];
if (next.type !== 'tool') break;
const nextTool = next as ToolPartType;
if (!shouldShowTool(nextTool)) { j++; continue; }
const nextName = nextTool.tool?.toLowerCase() ?? '';
if (getStaticGroupToolName(nextName) !== groupToolName) break;
group.push(nextTool);
j++;
}
// Static tools: one row per tool call (no grouping)
rendered.push(
<FadeInOnReveal key={`static-tools-${toolPart.id}`}>
<ToolRevealOnMount animate={group.some((candidate) => animatedToolIdsLookup.has(candidate.id))} wipe>
<ToolRevealOnMount animate={animatedToolIdsLookup.has(toolPart.id)} wipe>
<StaticToolRow
toolName={groupToolName}
activities={group.map(tp => ({
id: tp.id,
turnId: '',
messageId: messageId,
partIndex: 0,
part: tp,
kind: 'tool' as const,
}))}
animateTailText={group.some((candidate) => animatedToolIdsLookup.has(candidate.id))}
toolName={toolName}
activities={[
{
id: toolPart.id,
turnId: '',
messageId,
partIndex: 0,
part: toolPart,
kind: 'tool' as const,
},
]}
animateTailText={animatedToolIdsLookup.has(toolPart.id)}
/>
</ToolRevealOnMount>
</FadeInOnReveal>
);
i = j;
i++;
continue;
}