Skip to content

Commit 7aec03b

Browse files
committed
Compact mode!
1 parent 628204f commit 7aec03b

File tree

4 files changed

+123
-47
lines changed

4 files changed

+123
-47
lines changed

cli/src/chat.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import { useChatScrollbox } from './hooks/use-scroll-management'
3838
import { useSendMessage } from './hooks/use-send-message'
3939
import { useSuggestionEngine } from './hooks/use-suggestion-engine'
4040
import { useTerminalDimensions } from './hooks/use-terminal-dimensions'
41+
import { useTerminalLayout } from './hooks/use-terminal-layout'
4142
import { useTheme } from './hooks/use-theme'
4243
import { useTimeout } from './hooks/use-timeout'
4344
import { useUsageMonitor } from './hooks/use-usage-monitor'
@@ -112,6 +113,8 @@ export const Chat = ({
112113

113114
const { separatorWidth, terminalWidth, terminalHeight } =
114115
useTerminalDimensions()
116+
const { height: heightLayout } = useTerminalLayout()
117+
const isCompactHeight = heightLayout.is('xs')
115118
const messageAvailableWidth = separatorWidth
116119

117120
const theme = useTheme()
@@ -636,6 +639,7 @@ export const Chat = ({
636639
separatorWidth,
637640
initialPrompt,
638641
onSubmitPrompt,
642+
isCompactHeight,
639643
})
640644

641645
const {
@@ -1213,6 +1217,7 @@ export const Chat = ({
12131217
separatorWidth={separatorWidth}
12141218
shouldCenterInputVertically={shouldCenterInputVertically}
12151219
inputBoxTitle={inputBoxTitle}
1220+
isCompactHeight={isCompactHeight}
12161221
feedbackMode={feedbackMode}
12171222
handleExitFeedback={handleExitFeedback}
12181223
handleSubmit={handleSubmit}

cli/src/components/chat-input-bar.tsx

Lines changed: 104 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { BORDER_CHARS } from '../utils/ui-constants'
1414
import type { useTheme } from '../hooks/use-theme'
1515
import type { InputValue } from '../state/chat-store'
1616
import type { AgentMode } from '../utils/constants'
17+
import { useEvent } from '../hooks/use-event'
1718

1819
type Theme = ReturnType<typeof useTheme>
1920

@@ -51,6 +52,7 @@ interface ChatInputBarProps {
5152
separatorWidth: number
5253
shouldCenterInputVertically: boolean
5354
inputBoxTitle: string | undefined
55+
isCompactHeight: boolean
5456

5557
// Feedback mode
5658
feedbackMode: boolean
@@ -83,6 +85,7 @@ export const ChatInputBar = ({
8385
separatorWidth,
8486
shouldCenterInputVertically,
8587
inputBoxTitle,
88+
isCompactHeight,
8689
feedbackMode,
8790
handleExitFeedback,
8891
handleSubmit,
@@ -195,6 +198,45 @@ export const ChatInputBar = ({
195198
inputMode === 'default' ? inputPlaceholder : modeConfig.placeholder
196199
const borderColor = theme[modeConfig.color]
197200

201+
// Shared key intercept handler for suggestion menu navigation
202+
const handleKeyIntercept = useEvent(
203+
(key: {
204+
name?: string
205+
shift?: boolean
206+
ctrl?: boolean
207+
meta?: boolean
208+
option?: boolean
209+
}) => {
210+
// Intercept navigation keys when suggestion menu is active
211+
// The useChatKeyboard hook will handle menu selection/navigation
212+
const hasSuggestions = hasSlashSuggestions || hasMentionSuggestions
213+
if (!hasSuggestions) return false
214+
215+
const isPlainEnter =
216+
(key.name === 'return' || key.name === 'enter') &&
217+
!key.shift &&
218+
!key.ctrl &&
219+
!key.meta &&
220+
!key.option
221+
const isTab = key.name === 'tab' && !key.ctrl && !key.meta && !key.option
222+
const isUpDown =
223+
(key.name === 'up' || key.name === 'down') &&
224+
!key.ctrl &&
225+
!key.meta &&
226+
!key.option
227+
228+
// Don't intercept Up/Down when user is navigating history
229+
if (isUpDown && lastEditDueToNav) {
230+
return false
231+
}
232+
233+
if (isPlainEnter || isTab || isUpDown) {
234+
return true
235+
}
236+
return false
237+
},
238+
)
239+
198240
if (askUserState) {
199241
return (
200242
<box
@@ -233,6 +275,67 @@ export const ChatInputBar = ({
233275
)
234276
}
235277

278+
// Compact mode: no border, minimal chrome, supports menus and multiline
279+
if (isCompactHeight) {
280+
const compactMaxHeight = Math.floor(terminalHeight / 2)
281+
return (
282+
<>
283+
{hasSlashSuggestions ? (
284+
<SuggestionMenu
285+
items={slashSuggestionItems}
286+
selectedIndex={slashSelectedIndex}
287+
maxVisible={5}
288+
prefix="/"
289+
/>
290+
) : null}
291+
{hasMentionSuggestions ? (
292+
<SuggestionMenu
293+
items={[...agentSuggestionItems, ...fileSuggestionItems]}
294+
selectedIndex={agentSelectedIndex}
295+
maxVisible={5}
296+
prefix="@"
297+
/>
298+
) : null}
299+
<box
300+
style={{
301+
flexDirection: 'row',
302+
alignItems: 'flex-start',
303+
width: '100%',
304+
paddingLeft: 1,
305+
paddingRight: 1,
306+
backgroundColor: theme.surface,
307+
}}
308+
>
309+
{modeConfig.icon && (
310+
<box
311+
style={{
312+
flexShrink: 0,
313+
paddingRight: 1,
314+
}}
315+
>
316+
<text style={{ fg: theme[modeConfig.color] }}>
317+
{modeConfig.icon}
318+
</text>
319+
</box>
320+
)}
321+
<MultilineInput
322+
value={inputValue}
323+
onChange={handleInputChange}
324+
onSubmit={handleSubmit}
325+
onKeyIntercept={handleKeyIntercept}
326+
placeholder={effectivePlaceholder}
327+
focused={inputFocused && !feedbackMode}
328+
maxHeight={compactMaxHeight}
329+
width={adjustedInputWidth}
330+
ref={inputRef}
331+
cursorPosition={cursorPosition}
332+
/>
333+
</box>
334+
<InputModeBanner />
335+
</>
336+
)
337+
}
338+
236339
return (
237340
<>
238341
<box
@@ -301,43 +404,11 @@ export const ChatInputBar = ({
301404
value={inputValue}
302405
onChange={handleInputChange}
303406
onSubmit={handleSubmit}
304-
onKeyIntercept={(key) => {
305-
// Intercept navigation keys when suggestion menu is active
306-
// The useChatKeyboard hook will handle menu selection/navigation
307-
const hasSuggestions =
308-
hasSlashSuggestions || hasMentionSuggestions
309-
if (!hasSuggestions) return false
310-
311-
const isPlainEnter =
312-
(key.name === 'return' || key.name === 'enter') &&
313-
!key.shift &&
314-
!key.ctrl &&
315-
!key.meta &&
316-
!key.option
317-
const isTab =
318-
key.name === 'tab' && !key.ctrl && !key.meta && !key.option
319-
const isUpDown =
320-
(key.name === 'up' || key.name === 'down') &&
321-
!key.ctrl &&
322-
!key.meta &&
323-
!key.option
324-
325-
// Don't intercept Up/Down when user is navigating history
326-
// (lastEditDueToNav is true), let them continue paging through
327-
if (isUpDown && lastEditDueToNav) {
328-
return false
329-
}
330-
331-
if (isPlainEnter || isTab || isUpDown) {
332-
return true // Prevent default, let useChatKeyboard handle it
333-
}
334-
return false
335-
}}
407+
onKeyIntercept={handleKeyIntercept}
336408
placeholder={effectivePlaceholder}
337409
focused={inputFocused && !feedbackMode}
338410
maxHeight={Math.floor(terminalHeight / 2)}
339411
width={adjustedInputWidth}
340-
341412
ref={inputRef}
342413
cursorPosition={cursorPosition}
343414
/>

cli/src/components/thinking.tsx

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export const Thinking = memo(
3333
width,
3434
PREVIEW_LINE_COUNT,
3535
)
36-
// Pad to exactly PREVIEW_LINE_COUNT lines for consistent height while streaming
36+
// Pad to exactly PREVIEW_LINE_COUNT lines for consistent height while streaming.
3737
const previewLines = [...lines]
3838
while (previewLines.length < PREVIEW_LINE_COUNT) {
3939
previewLines.push('')
@@ -88,17 +88,15 @@ export const Thinking = memo(
8888
gap: 0,
8989
}}
9090
>
91-
{hasMore && (
92-
<text
93-
style={{
94-
wrapMode: 'none',
95-
fg: theme.muted,
96-
}}
97-
attributes={TextAttributes.ITALIC}
98-
>
99-
...
100-
</text>
101-
)}
91+
<text
92+
style={{
93+
wrapMode: 'none',
94+
fg: theme.muted,
95+
}}
96+
attributes={TextAttributes.ITALIC}
97+
>
98+
{hasMore ? '...' : ' '}
99+
</text>
102100
<text
103101
style={{
104102
wrapMode: 'word',

cli/src/hooks/use-chat-input.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ interface UseChatInputOptions {
1313
separatorWidth: number
1414
initialPrompt: string | null
1515
onSubmitPrompt: (content: string, mode: AgentMode) => void | Promise<unknown>
16+
isCompactHeight: boolean
1617
}
1718

1819
const BUILD_IT_TEXT = 'Build it!'
@@ -24,13 +25,14 @@ export const useChatInput = ({
2425
separatorWidth,
2526
initialPrompt,
2627
onSubmitPrompt,
28+
isCompactHeight,
2729
}: UseChatInputOptions) => {
2830
const hasAutoSubmittedRef = useRef(false)
2931
const inputMode = useChatStore((state) => state.inputMode)
3032

3133
// Estimate the collapsed toggle width as rendered by AgentModeToggle.
32-
// In bash mode, we don't show the toggle, so no width needed.
33-
const estimatedToggleWidth = inputMode !== 'default'
34+
// In bash mode or compact height, we don't show the toggle, so no width needed.
35+
const estimatedToggleWidth = inputMode !== 'default' || isCompactHeight
3436
? 0
3537
: stringWidth(`< ${agentMode}`) + 6 // 2 padding + 2 borders + 2 gap
3638

0 commit comments

Comments
 (0)