Skip to content

Commit 1ce6109

Browse files
committed
feat(cli): Add card-based grid layout for implementor proposals with file stats, inline diffs, and console logging
1 parent f9b3fff commit 1ce6109

File tree

12 files changed

+1560
-170
lines changed

12 files changed

+1560
-170
lines changed

cli/knowledge.md

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,30 @@ Dynamic imports make code harder to analyze, break tree-shaking, and can hide ci
4040

4141
Use tmux to test CLI behavior in a controlled, scriptable way. This is especially useful for testing UI updates, authentication flows, and time-dependent behavior.
4242

43-
### Basic Pattern
43+
### Recommended: Use Helper Scripts
44+
45+
**Use the helper scripts in `scripts/tmux/`** for reliable CLI testing:
46+
47+
```bash
48+
# Start a test session
49+
SESSION=$(./scripts/tmux/tmux-cli.sh start)
50+
51+
# Send commands and capture output
52+
./scripts/tmux/tmux-cli.sh send "$SESSION" "/help"
53+
./scripts/tmux/tmux-cli.sh capture "$SESSION" --wait 2 --label "after-help"
54+
55+
# View session data
56+
bun .agents/tmux-viewer/index.tsx "$SESSION" --json
57+
58+
# Clean up
59+
./scripts/tmux/tmux-cli.sh stop "$SESSION"
60+
```
61+
62+
Session logs are saved to `debug/tmux-sessions/{session}/` in YAML format for easy debugging.
63+
64+
See `scripts/tmux/README.md` for full documentation or `cli/tmux.knowledge.md` for low-level details.
65+
66+
### Manual Pattern (Legacy)
4467

4568
```bash
4669
tmux new-session -d -s test-session 'cd /path/to/codebuff && bun --cwd=cli run dev 2>&1' && \
@@ -86,10 +109,6 @@ tmux new-session -d -s test-session 'cd /path/to/codebuff && bun --cwd=cli run d
86109
# ✅ Works: tmux send-keys -t session $'\e[200~hello\e[201~'
87110
```
88111

89-
**Why standard send-keys fails:** When `tmux send-keys` sends multiple characters without bracketed paste mode, the CLI's input handling only captures some characters due to timing issues with OpenTUI's async event processing. This manifests as partial input (e.g., only the last character appearing in the input field like `a▍` instead of the full message).
90-
91-
**Bracketed paste mode** wraps the input in escape sequences (`\e[200~` start, `\e[201~` end) that signal to the terminal "this is pasted content" - the CLI handles this correctly and receives the full input.
92-
93112
## Migration from Custom OpenTUI Fork
94113

95114
**October 2024**: Migrated from custom `CodebuffAI/opentui#codebuff/custom` fork to official `@opentui/react@^0.1.27` and `@opentui/core@^0.1.27` packages. Updated to `^0.1.28` in February 2025.
@@ -103,6 +122,38 @@ tmux new-session -d -s test-session 'cd /path/to/codebuff && bun --cwd=cli run d
103122
- Paste functionality still works through the terminal's native paste mechanism, but we can no longer intercept paste events separately from typing.
104123
- If custom paste handling is needed in the future, it must be reimplemented using `useKeyboard` hook or by checking the official OpenTUI for updates.
105124

125+
## OpenTUI Flex Layouts
126+
127+
### Multi-Column / Masonry Layouts
128+
129+
For columns that share space equally within a container, use the **flex trio pattern**:
130+
131+
```tsx
132+
<box style={{ flexDirection: 'row', width: '100%' }}>
133+
{columns.map((col, idx) => (
134+
<box
135+
key={idx}
136+
style={{
137+
flexDirection: 'column',
138+
flexGrow: 1, // Take equal share of space
139+
flexShrink: 1, // Allow shrinking
140+
flexBasis: 0, // Start from 0 and grow (not from content size)
141+
minWidth: 0, // Critical! Allows shrinking below content width
142+
}}
143+
>
144+
{/* Column content */}
145+
</box>
146+
))}
147+
</box>
148+
```
149+
150+
**Why not explicit width?** Using `width: someNumber` for columns causes OpenTUI to overflow beyond container boundaries. The flex trio pattern respects the parent container's width constraints.
151+
152+
**Key points:**
153+
- `minWidth: 0` is essential - without it, content won't shrink below its natural width
154+
- Use `width: '100%'` (string) for parent containers, not numeric values
155+
- `alignItems: 'flex-start'` prevents children from stretching to fill row height
156+
106157
## OpenTUI Text Rendering Constraints
107158

108159
**CRITICAL**: OpenTUI has strict requirements for text rendering that must be followed:

cli/src/components/agent-branch-item.tsx

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { TextAttributes } from '@opentui/core'
22
import React, { memo, type ReactNode } from 'react'
33

44
import { Button } from './button'
5+
import { CollapseButton } from './collapse-button'
56
import { useTheme } from '../hooks/use-theme'
67
import { useWhyDidYouUpdateById } from '../hooks/use-why-did-you-update'
78
import { getCliEnv } from '../utils/env'
@@ -282,19 +283,7 @@ export const AgentBranchItem = memo((props: AgentBranchItemProps) => {
282283
</box>
283284
)}
284285
{renderExpandedContent(children)}
285-
{onToggle && (
286-
<Button
287-
style={{
288-
alignSelf: 'flex-end',
289-
marginTop: 0,
290-
}}
291-
onClick={onToggle}
292-
>
293-
<text fg={theme.secondary} style={{ wrapMode: 'none' }}>
294-
▴ collapse
295-
</text>
296-
</Button>
297-
)}
286+
{onToggle && <CollapseButton onClick={onToggle} />}
298287
</box>
299288
)}
300289
</box>
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { memo } from 'react'
2+
3+
import { Button } from './button'
4+
import { useTheme } from '../hooks/use-theme'
5+
6+
import type { CSSProperties } from 'react'
7+
8+
interface CollapseButtonProps {
9+
onClick: () => void
10+
style?: CSSProperties
11+
}
12+
13+
/**
14+
* Reusable collapse button with '▴ collapse' text
15+
* Styled with theme.secondary color, aligned to the right by default
16+
*/
17+
export const CollapseButton = memo(({ onClick, style }: CollapseButtonProps) => {
18+
const theme = useTheme()
19+
20+
return (
21+
<Button
22+
style={{
23+
alignSelf: 'flex-end',
24+
marginTop: 0,
25+
...style,
26+
}}
27+
onClick={onClick}
28+
>
29+
<text fg={theme.secondary} style={{ wrapMode: 'none' }}>
30+
▴ collapse
31+
</text>
32+
</Button>
33+
)
34+
})

0 commit comments

Comments
 (0)