Skip to content

Commit 1584c88

Browse files
committed
Fix status row gating
1 parent 6dfc0a0 commit 1584c88

File tree

3 files changed

+87
-136
lines changed

3 files changed

+87
-136
lines changed

cli/src/chat.tsx

Lines changed: 22 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { TextAttributes } from '@opentui/core'
12
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
23
import { useShallow } from 'zustand/react/shallow'
34

@@ -20,6 +21,7 @@ import { useAgentValidation } from './hooks/use-agent-validation'
2021
import { useAuthState } from './hooks/use-auth-state'
2122
import { useChatInput } from './hooks/use-chat-input'
2223
import { useClipboard } from './hooks/use-clipboard'
24+
import { useConnectionStatus } from './hooks/use-connection-status'
2325
import { useElapsedTime } from './hooks/use-elapsed-time'
2426
import { useExitHandler } from './hooks/use-exit-handler'
2527
import { useInputHistory } from './hooks/use-input-history'
@@ -33,24 +35,19 @@ import { useSuggestionMenuHandlers } from './hooks/use-suggestion-menu-handlers'
3335
import { useTerminalDimensions } from './hooks/use-terminal-dimensions'
3436
import { useTheme } from './hooks/use-theme'
3537
import { useValidationBanner } from './hooks/use-validation-banner'
36-
import { useConnectionStatus } from './hooks/use-connection-status'
3738
import { useChatStore } from './state/chat-store'
3839
import { createChatScrollAcceleration } from './utils/chat-scroll-accel'
3940
import { formatQueuedPreview } from './utils/helpers'
4041
import { loadLocalAgents } from './utils/local-agent-registry'
4142
import { buildMessageTree } from './utils/message-tree-utils'
43+
import { computeInputLayoutMetrics } from './utils/text-layout'
4244
import { createMarkdownPalette } from './utils/theme-system'
4345
import { BORDER_CHARS } from './utils/ui-constants'
44-
import { computeInputLayoutMetrics } from './utils/text-layout'
4546

4647
import type { SendMessageTimerEvent } from './hooks/use-send-message'
4748
import type { ContentBlock } from './types/chat'
4849
import type { SendMessageFn } from './types/contracts/send-message'
49-
import type { KeyEvent, ScrollBoxRenderable } from '@opentui/core'
50-
import { TextAttributes } from '@opentui/core'
51-
52-
const MAX_VIRTUALIZED_TOP_LEVEL = 60
53-
const VIRTUAL_OVERSCAN = 12
50+
import type { ScrollBoxRenderable } from '@opentui/core'
5451

5552
const DEFAULT_AGENT_IDS = {
5653
DEFAULT: 'base2',
@@ -81,7 +78,7 @@ export const Chat = ({
8178
const scrollRef = useRef<ScrollBoxRenderable | null>(null)
8279
const inputRef = useRef<MultilineInputHandle | null>(null)
8380

84-
const { terminalWidth, separatorWidth } = useTerminalDimensions()
81+
const { separatorWidth } = useTerminalDimensions()
8582

8683
const theme = useTheme()
8784
const markdownPalette = useMemo(() => createMarkdownPalette(theme), [theme])
@@ -119,7 +116,6 @@ export const Chat = ({
119116
agentMode,
120117
setAgentMode,
121118
toggleAgentMode,
122-
hasReceivedPlanResponse,
123119
setHasReceivedPlanResponse,
124120
lastMessageMode,
125121
setLastMessageMode,
@@ -185,7 +181,6 @@ export const Chat = ({
185181
const {
186182
isAuthenticated,
187183
setIsAuthenticated,
188-
user,
189184
setUser,
190185
handleLoginSuccess,
191186
logoutMutation,
@@ -378,26 +373,7 @@ export const Chat = ({
378373
const isStreaming = streamStatus !== 'idle'
379374

380375
const handleTimerEvent = useCallback(
381-
(event: SendMessageTimerEvent) => {
382-
const payload = {
383-
event: 'cli_main_agent_timer',
384-
timerEventType: event.type,
385-
agentId: agentId ?? 'main',
386-
messageId: event.messageId,
387-
startedAt: event.startedAt,
388-
...(event.type === 'stop'
389-
? {
390-
finishedAt: event.finishedAt,
391-
elapsedMs: event.elapsedMs,
392-
outcome: event.outcome,
393-
}
394-
: {}),
395-
}
396-
const message =
397-
event.type === 'start'
398-
? 'Main agent timer started'
399-
: `Main agent timer stopped (${event.outcome})`
400-
},
376+
(event: SendMessageTimerEvent) => {},
401377
[agentId],
402378
)
403379

@@ -544,16 +520,22 @@ export const Chat = ({
544520
const previewWidth = Math.max(30, separatorWidth - 20)
545521
return formatQueuedPreview(queuedMessages, previewWidth)
546522
}, [queuedMessages, separatorWidth, shouldShowQueuePreview])
547-
const hasSlashSuggestions = slashContext.active && slashSuggestionItems.length > 0
523+
const hasSlashSuggestions =
524+
slashContext.active && slashSuggestionItems.length > 0
548525
const hasMentionSuggestions =
549-
!slashContext.active && mentionContext.active && agentSuggestionItems.length > 0
526+
!slashContext.active &&
527+
mentionContext.active &&
528+
agentSuggestionItems.length > 0
550529
const hasSuggestionMenu = hasSlashSuggestions || hasMentionSuggestions
551530
const showAgentStatusLine = showAgentDisplayName && loadedAgentsData
552531

553532
const inputLayoutMetrics = useMemo(() => {
554533
const text = inputValue ?? ''
555534
const layoutContent = text.length > 0 ? text : ' '
556-
const safeCursor = Math.max(0, Math.min(cursorPosition, layoutContent.length))
535+
const safeCursor = Math.max(
536+
0,
537+
Math.min(cursorPosition, layoutContent.length),
538+
)
557539
const cursorProbe =
558540
safeCursor >= layoutContent.length
559541
? layoutContent
@@ -591,7 +573,10 @@ export const Chat = ({
591573
)
592574

593575
const elapsedTimeNode = (
594-
<StatusElapsedTime streamStatus={streamStatus} timerStartTime={timerStartTime} />
576+
<StatusElapsedTime
577+
streamStatus={streamStatus}
578+
timerStartTime={timerStartTime}
579+
/>
595580
)
596581

597582
const validationBanner = useValidationBanner({
@@ -749,7 +734,6 @@ export const Chat = ({
749734
<text style={{ wrapMode: 'none' }}>{elapsedTimeNode}</text>
750735
</box>
751736
</box>
752-
753737
</box>
754738
)}
755739

@@ -802,7 +786,9 @@ export const Chat = ({
802786
<box
803787
style={{
804788
flexDirection: 'row',
805-
alignItems: shouldCenterInputVertically ? 'center' : 'flex-start',
789+
alignItems: shouldCenterInputVertically
790+
? 'center'
791+
: 'flex-start',
806792
width: '100%',
807793
}}
808794
>

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

Lines changed: 1 addition & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -60,101 +60,6 @@ export const AgentBranchItem = ({
6060
const showCollapsedPreview =
6161
(isStreaming && !!streamingPreview) || (!isStreaming && !!finishedPreview)
6262

63-
const isTextRenderable = (value: ReactNode): boolean => {
64-
if (value === null || value === undefined || typeof value === 'boolean') {
65-
return false
66-
}
67-
68-
if (typeof value === 'string' || typeof value === 'number') {
69-
return true
70-
}
71-
72-
if (Array.isArray(value)) {
73-
return value.every((child) => isTextRenderable(child))
74-
}
75-
76-
if (React.isValidElement(value)) {
77-
if (value.type === React.Fragment) {
78-
return isTextRenderable(value.props.children)
79-
}
80-
81-
if (typeof value.type === 'string') {
82-
if (
83-
value.type === 'span' ||
84-
value.type === 'strong' ||
85-
value.type === 'em'
86-
) {
87-
return isTextRenderable(value.props.children)
88-
}
89-
90-
return false
91-
}
92-
}
93-
94-
return false
95-
}
96-
97-
const renderExpandedContent = (value: ReactNode): ReactNode => {
98-
if (
99-
value === null ||
100-
value === undefined ||
101-
value === false ||
102-
value === true
103-
) {
104-
return null
105-
}
106-
107-
if (isTextRenderable(value)) {
108-
return (
109-
<text
110-
fg={theme.foreground}
111-
key="expanded-text"
112-
attributes={getAttributes()}
113-
>
114-
{value}
115-
</text>
116-
)
117-
}
118-
119-
if (React.isValidElement(value)) {
120-
if (value.key === null || value.key === undefined) {
121-
return (
122-
<box key="expanded-node" style={{ flexDirection: 'column', gap: 0 }}>
123-
{value}
124-
</box>
125-
)
126-
}
127-
return value
128-
}
129-
130-
if (Array.isArray(value)) {
131-
return (
132-
<box key="expanded-array" style={{ flexDirection: 'column', gap: 0 }}>
133-
{value.map((child, idx) => (
134-
<box
135-
key={`expanded-array-${idx}`}
136-
style={{ flexDirection: 'column', gap: 0 }}
137-
>
138-
{child}
139-
</box>
140-
))}
141-
</box>
142-
)
143-
}
144-
145-
// Check if value is a plain object (not a React element)
146-
if (typeof value === 'object' && value !== null && !React.isValidElement(value)) {
147-
console.warn('Attempted to render plain object in agent content:', value)
148-
return null
149-
}
150-
151-
return (
152-
<box key="expanded-unknown" style={{ flexDirection: 'column', gap: 0 }}>
153-
{value}
154-
</box>
155-
)
156-
}
157-
15863
return (
15964
<box
16065
style={{
@@ -280,7 +185,7 @@ export const AgentBranchItem = ({
280185
</box>
281186
</box>
282187
)}
283-
{renderExpandedContent(content)}
188+
{content}
284189
{onToggle && (
285190
<box
286191
style={{

cli/src/components/message-block.tsx

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,69 @@ export const MessageBlock = memo(
296296
)
297297
}
298298

299+
const normalizeAgentContent = (
300+
nodes: React.ReactNode[],
301+
): React.ReactNode | null => {
302+
const normalizedChildren: React.ReactNode[] = []
303+
let fallbackKey = 0
304+
305+
const appendNode = (value: React.ReactNode): void => {
306+
if (
307+
value === null ||
308+
value === undefined ||
309+
typeof value === 'boolean'
310+
) {
311+
return
312+
}
313+
314+
if (Array.isArray(value)) {
315+
value.forEach(appendNode)
316+
return
317+
}
318+
319+
if (React.isValidElement(value)) {
320+
if (value.type === React.Fragment) {
321+
appendNode(value.props.children)
322+
return
323+
}
324+
normalizedChildren.push(value)
325+
return
326+
}
327+
328+
if (typeof value === 'string' || typeof value === 'number') {
329+
normalizedChildren.push(
330+
<text
331+
key={`agent-node-${fallbackKey++}`}
332+
fg={theme.foreground}
333+
style={{ wrapMode: 'word' }}
334+
>
335+
{value}
336+
</text>,
337+
)
338+
return
339+
}
340+
341+
if (process.env.NODE_ENV !== 'production') {
342+
console.warn(
343+
'Dropping unsupported agent content before render:',
344+
value,
345+
)
346+
}
347+
}
348+
349+
nodes.forEach(appendNode)
350+
351+
if (normalizedChildren.length === 0) {
352+
return null
353+
}
354+
355+
return (
356+
<box style={{ flexDirection: 'column', gap: 0 }}>
357+
{normalizedChildren}
358+
</box>
359+
)
360+
}
361+
299362
function renderAgentBranch(
300363
agentBlock: Extract<ContentBlock, { type: 'agent' }>,
301364
indentLevel: number,
@@ -333,10 +396,7 @@ export const MessageBlock = memo(
333396
isStreaming,
334397
)
335398

336-
const displayContent =
337-
childNodes.length > 0 ? (
338-
<box style={{ flexDirection: 'column', gap: 0 }}>{childNodes}</box>
339-
) : null
399+
const displayContent = normalizeAgentContent(childNodes)
340400
const isActive = isStreaming || agentBlock.status === 'running'
341401
const statusLabel = isActive
342402
? 'running'

0 commit comments

Comments
 (0)