@@ -12,8 +12,7 @@ import {
1212 type FileStats ,
1313} from '../../utils/implementor-helpers'
1414import { useTheme } from '../../hooks/use-theme'
15- import { useTerminalLayout } from '../../hooks/use-terminal-layout'
16- import { computeSmartColumns } from '../../utils/layout-helpers'
15+ import { useGridLayout } from '../../hooks/use-grid-layout'
1716import { getRelativePath } from '../../utils/path-helpers'
1817import { PROPOSAL_BORDER_CHARS } from '../../utils/ui-constants'
1918import { Button } from '../button'
@@ -24,50 +23,17 @@ import type { AgentContentBlock, ContentBlock } from '../../types/chat'
2423interface ImplementorGroupProps {
2524 implementors : AgentContentBlock [ ]
2625 siblingBlocks : ContentBlock [ ]
27- onToggleCollapsed : ( id : string ) => void
2826 availableWidth : number
2927}
3028
31- /**
32- * Responsive card grid for comparing implementor proposals
33- */
3429export const ImplementorGroup = memo (
3530 ( {
3631 implementors,
3732 siblingBlocks,
3833 availableWidth,
3934 } : ImplementorGroupProps ) => {
4035 const theme = useTheme ( )
41- const { width } = useTerminalLayout ( )
42-
43- // Determine max columns based on terminal width
44- const maxColumns = useMemo ( ( ) => {
45- if ( width . is ( 'xs' ) ) return 1
46- if ( width . is ( 'sm' ) ) return 1
47- if ( width . is ( 'md' ) ) return 2
48- return 3 // lg
49- } , [ width ] )
50-
51- // Smart column selection based on item count
52- const columns = useMemo ( ( ) =>
53- computeSmartColumns ( implementors . length , maxColumns ) ,
54- [ implementors . length , maxColumns ] )
55-
56- // Calculate card width based on columns and available space
57- const cardWidth = useMemo ( ( ) => {
58- // No gap between columns - cards are flush
59- return Math . floor ( availableWidth / columns )
60- } , [ availableWidth , columns ] )
61-
62- // Masonry layout: distribute items to columns round-robin style
63- // (simpler than height-based, but still gives masonry effect)
64- const columnGroups = useMemo ( ( ) => {
65- const result : AgentContentBlock [ ] [ ] = Array . from ( { length : columns } , ( ) => [ ] )
66- implementors . forEach ( ( impl , idx ) => {
67- result [ idx % columns ] . push ( impl )
68- } )
69- return result
70- } , [ implementors , columns ] )
36+ const { columns, columnWidth : cardWidth , columnGroups } = useGridLayout ( implementors , availableWidth )
7137
7238 // Check if any implementors are still running
7339 const anyRunning = implementors . some ( impl => impl . status === 'running' )
@@ -84,52 +50,55 @@ export const ImplementorGroup = memo(
8450 marginTop : 1 ,
8551 } }
8652 >
87- < text
88- fg = { theme . muted }
89- attributes = { TextAttributes . DIM }
90- >
91- { headerText }
92- </ text >
93-
9453 { /* Masonry layout: columns side by side, cards stack vertically in each */ }
9554 < box
9655 style = { {
9756 flexDirection : 'row' ,
98- gap : 1 , // Small horizontal gap to balance visual weight with vertical double-borders
57+ gap : 1 ,
9958 width : '100%' ,
10059 alignItems : 'flex-start' ,
10160 } }
10261 >
103- { columnGroups . map ( ( columnItems , colIdx ) => (
104- < box
105- key = { `col-${ colIdx } ` }
62+ { columnGroups . map ( ( columnItems , colIdx ) => {
63+ // Use first agent's ID as stable column key
64+ const columnKey = columnItems [ 0 ] ?. agentId ?? `col-${ colIdx } `
65+ return (
66+ < box
67+ key = { columnKey }
10668 style = { {
10769 flexDirection : 'column' ,
10870 gap : 0 ,
10971 flexGrow : 1 ,
11072 flexShrink : 1 ,
11173 flexBasis : 0 ,
112- minWidth : 0 , // Allow shrinking below content size
74+ minWidth : 0 ,
11375 } }
11476 >
115- { columnItems . map ( ( agentBlock ) => {
116- const implementorIndex = getImplementorIndex (
117- agentBlock ,
118- siblingBlocks ,
119- )
120-
121- return (
122- < ImplementorCard
123- key = { agentBlock . agentId }
124- agentBlock = { agentBlock }
125- implementorIndex = { implementorIndex }
126- cardWidth = { cardWidth }
127- />
128- )
129- } ) }
130- </ box >
131- ) ) }
77+ { columnItems . map ( ( agentBlock ) => {
78+ const implementorIndex = getImplementorIndex (
79+ agentBlock ,
80+ siblingBlocks ,
81+ )
82+
83+ return (
84+ < ImplementorCard
85+ key = { agentBlock . agentId }
86+ agentBlock = { agentBlock }
87+ implementorIndex = { implementorIndex }
88+ cardWidth = { cardWidth }
89+ />
90+ )
91+ } ) }
92+ </ box >
93+ )
94+ } ) }
13295 </ box >
96+ < text
97+ fg = { theme . muted }
98+ attributes = { TextAttributes . DIM }
99+ >
100+ { headerText }
101+ </ text >
133102 </ box >
134103 )
135104 } ,
@@ -141,10 +110,6 @@ interface ImplementorCardProps {
141110 cardWidth : number
142111}
143112
144- /**
145- * Individual proposal card with dashed border
146- * Click file rows to view their diffs
147- */
148113const ImplementorCard = memo (
149114 ( {
150115 agentBlock,
@@ -274,10 +239,6 @@ const ImplementorCard = memo(
274239 } ,
275240)
276241
277- // ============================================================================
278- // COMPACT FILE STATS VIEW
279- // ============================================================================
280-
281242interface CompactFileStatsProps {
282243 fileStats : FileStats [ ]
283244 availableWidth : number
@@ -287,12 +248,6 @@ interface CompactFileStatsProps {
287248 fileDiffs : Map < string , string >
288249}
289250
290- /**
291- * Compact view showing file changes with full-width, center-aligned addition/deletion bars.
292- * The left side is a green bar (additions) and the right side is a red bar (deletions),
293- * both extending to the center with their +N / -N counts rendered in white inside the bars.
294- * Click a file name to view its diff inline below that row.
295- */
296251const CompactFileStats = memo ( ( {
297252 fileStats,
298253 availableWidth,
@@ -354,10 +309,6 @@ interface CompactFileRowProps {
354309 diff ?: string
355310}
356311
357- /**
358- * Single file row with full-width colored bars meeting at center.
359- * File name is underlined on hover, clickable to show diff inline below.
360- */
361312const CompactFileRow = memo ( ( {
362313 file,
363314 availableWidth,
0 commit comments