@@ -221,8 +221,6 @@ export const ToolGroupMessage: React.FC<ToolGroupMessageProps> = ({
221221
222222 const staticHeight = useMemo ( ( ) => {
223223 let height = 0 ;
224- // To match rendering, we track if all tools so far have been topics
225- let allPreviousWereTopics = true ;
226224
227225 for ( let i = 0 ; i < groupedTools . length ; i ++ ) {
228226 const group = groupedTools [ i ] ;
@@ -251,47 +249,65 @@ export const ToolGroupMessage: React.FC<ToolGroupMessageProps> = ({
251249 // Align isFirst logic with rendering
252250 let isFirst = i === 0 ;
253251 if ( ! isFirst ) {
254- isFirst = allPreviousWereTopics ;
255- }
256-
257- // Update state for next tool
258- if ( isAgentGroup || ! isTopicToolCall ) {
259- allPreviousWereTopics = false ;
252+ // Check if all previous tools were topics (matches rendering logic exactly)
253+ let allPreviousTopics = true ;
254+ for ( let j = 0 ; j < i ; j ++ ) {
255+ const prevGroupItem = groupedTools [ j ] ;
256+ if (
257+ Array . isArray ( prevGroupItem ) ||
258+ ! isTopicTool ( prevGroupItem . name )
259+ ) {
260+ allPreviousTopics = false ;
261+ break ;
262+ }
263+ }
264+ isFirst = allPreviousTopics ;
260265 }
261266
262267 const isFirstProp = ! ! ( isFirst
263268 ? ( borderTopOverride ?? true )
264269 : prevIsCompact ) ;
265270
266- // Align closing border logic
267271 const showClosingBorder =
268272 ! isCompact &&
269273 ! isTopicToolCall &&
270274 ( nextIsCompact || nextIsTopicToolCall || isLast ) ;
271275
272276 if ( isAgentGroup ) {
273- height += 1 ; // Header
274- height += group . length ; // 1 line per agent
275- if ( isFirstProp ) height += 1 ; // Top border
276- if ( showClosingBorder ) height += 1 ; // Bottom border
277+ // Agent Group Spacing Breakdown:
278+ // 1. Top Boundary (0 or 1): Only present via borderTop if isFirstProp is true.
279+ // 2. Header Content (1): The "≡ Running Agent..." status text.
280+ // 3. Agent List (group.length lines): One line per agent in the group.
281+ // 4. Closing Border (1): Added if transition logic (showClosingBorder) requires it.
282+ height +=
283+ ( isFirstProp ? 1 : 0 ) +
284+ 1 +
285+ group . length +
286+ ( showClosingBorder ? 1 : 0 ) ;
277287 } else if ( isTopicToolCall ) {
288+ // Topic Message Spacing Breakdown:
289+ // 1. Top Margin (1): Present unless it's the very first item following a boundary.
290+ // 2. Topic Content (1).
291+ // 3. Bottom Margin (1): Always present around TopicMessage for breathing room.
278292 const hasTopMargin = ! ( isFirst && isToolGroupBoundary ) ;
279- height += 1 + ( hasTopMargin ? 1 : 0 ) + 1 ; // TopicMessage + top/bottom padding
293+ height += ( hasTopMargin ? 1 : 0 ) + 1 + 1 ;
280294 } else if ( isCompact ) {
281- height += 1 ; // Base height for compact tool header
295+ // Compact Tool: Always renders as a single dense line.
296+ height += 1 ;
282297 } else {
283- // Standard tool (Shell or ToolMessage)
284- if ( isFirstProp ) height += 1 ; // StickyHeader borderTop
285-
298+ // Standard Tool (ToolMessage / ShellToolMessage) Spacing Breakdown:
299+ // 1. TOOL_RESULT_STANDARD_RESERVED_LINE_COUNT (4) accounts for the top boundary,
300+ // internal separator, header padding, and the group closing border.
301+ // (Subtract 1 to isolate the group-level closing border.)
302+ // 2. Header Content (1): TOOL_RESULT_STATIC_HEIGHT (the tool name/status).
303+ // 3. Output File Message (1): (conditional) if outputFile is present.
304+ // 4. Group Closing Border (1): (conditional) if transition logic (showClosingBorder) requires it.
286305 height +=
287- TOOL_RESULT_STATIC_HEIGHT + TOOL_RESULT_STANDARD_RESERVED_LINE_COUNT ;
288-
289- // Account for "Output saved to..." message
290- if ( group . outputFile ) {
291- height += 1 ;
292- }
293-
294- if ( showClosingBorder ) height += 1 ; // Bottom border
306+ TOOL_RESULT_STANDARD_RESERVED_LINE_COUNT -
307+ 1 +
308+ TOOL_RESULT_STATIC_HEIGHT +
309+ ( group . outputFile ? 1 : 0 ) +
310+ ( showClosingBorder ? 1 : 0 ) ;
295311 }
296312 }
297313 return height ;
0 commit comments