Skip to content

Commit 66ad0c1

Browse files
committed
Improve CLI scroll UX and refactor timer handling
- Add isUserCollapsingRef to prevent auto-scroll during user-initiated collapse actions - Refactor timer handling from simple timerStartTime to comprehensive timer object - Make timer prop optional in MessageBlock for better null safety - Pass timer object through component hierarchy for consistent elapsed time tracking - Fix scroll-to-latest button to use function call syntax
1 parent fd1f8d0 commit 66ad0c1

File tree

3 files changed

+32
-15
lines changed

3 files changed

+32
-15
lines changed

cli/src/chat.tsx

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -221,10 +221,17 @@ export const Chat = ({
221221
}, [activeSubagents])
222222

223223
const abortControllerRef = useRef<AbortController | null>(null)
224+
const isUserCollapsingRef = useRef<boolean>(false)
225+
226+
// Reset the collapse flag after collapse state changes
227+
useEffect(() => {
228+
isUserCollapsingRef.current = false
229+
}, [collapsedAgents])
224230

225231
const { scrollToLatest, scrollboxProps, isAtBottom } = useChatScrollbox(
226232
scrollRef,
227233
messages,
234+
isUserCollapsingRef,
228235
)
229236

230237
const inertialScrollAcceleration = useMemo(
@@ -619,7 +626,7 @@ export const Chat = ({
619626
const hasStatus = useHasStatus({
620627
isActive: isStatusActive,
621628
clipboardMessage,
622-
timerStartTime,
629+
timer: mainAgentTimer,
623630
nextCtrlCWillExit,
624631
})
625632

@@ -737,7 +744,7 @@ export const Chat = ({
737744
<StatusIndicator
738745
clipboardMessage={clipboardMessage}
739746
isActive={isStatusActive}
740-
timerStartTime={timerStartTime}
747+
timer={mainAgentTimer}
741748
nextCtrlCWillExit={nextCtrlCWillExit}
742749
/>
743750
)
@@ -813,11 +820,12 @@ export const Chat = ({
813820
collapsedAgents={collapsedAgents}
814821
streamingAgents={streamingAgents}
815822
isWaitingForResponse={isWaitingForResponse}
816-
timerStartTime={timerStartTime}
823+
timer={mainAgentTimer}
817824
setCollapsedAgents={setCollapsedAgents}
818825
setFocusedAgentId={setFocusedAgentId}
819826
userOpenedAgents={userOpenedAgents}
820827
setUserOpenedAgents={setUserOpenedAgents}
828+
isUserCollapsingRef={isUserCollapsingRef}
821829
/>
822830
</scrollbox>
823831
</box>
@@ -855,7 +863,7 @@ export const Chat = ({
855863
{/* Center section - scroll indicator (always centered) */}
856864
<box style={{ flexShrink: 0 }}>
857865
{!isAtBottom && (
858-
<text onMouseDown={scrollToLatest}>
866+
<text onMouseDown={() => scrollToLatest()}>
859867
<span fg={theme.info} attributes={TextAttributes.BOLD}>
860868
861869
</span>

cli/src/components/message-block.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ interface MessageBlockProps {
3535
isComplete?: boolean
3636
completionTime?: string
3737
credits?: number
38-
timer: ElapsedTimeTracker
38+
timer?: ElapsedTimeTracker
3939
textColor?: ThemeColor
4040
timestampColor: string
4141
markdownOptions: { codeBlockWidth: number; palette: MarkdownPalette }
@@ -73,7 +73,7 @@ export const MessageBlock = ({
7373
const resolvedTextColor = textColor ?? theme.foreground
7474

7575
// Get elapsed time from timer for streaming AI messages
76-
const elapsedSeconds = timer.elapsedSeconds
76+
const elapsedSeconds = timer?.elapsedSeconds ?? 0
7777

7878
const renderContentWithMarkdown = (
7979
rawContent: string,

cli/src/components/message-renderer.tsx

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,12 @@ interface MessageRendererProps {
2424
collapsedAgents: Set<string>
2525
streamingAgents: Set<string>
2626
isWaitingForResponse: boolean
27-
timerStartTime: number | null
27+
timer: any
2828
setCollapsedAgents: React.Dispatch<React.SetStateAction<Set<string>>>
2929
setFocusedAgentId: React.Dispatch<React.SetStateAction<string | null>>
3030
userOpenedAgents: Set<string>
3131
setUserOpenedAgents: React.Dispatch<React.SetStateAction<Set<string>>>
32+
isUserCollapsingRef: React.MutableRefObject<boolean>
3233
}
3334

3435
export const MessageRenderer = (props: MessageRendererProps): ReactNode => {
@@ -42,15 +43,22 @@ export const MessageRenderer = (props: MessageRendererProps): ReactNode => {
4243
collapsedAgents,
4344
streamingAgents,
4445
isWaitingForResponse,
45-
timerStartTime,
46+
timer,
4647
setCollapsedAgents,
4748
setFocusedAgentId,
4849
setUserOpenedAgents,
50+
isUserCollapsingRef,
4951
} = props
5052

53+
const timerStartTime = timer?.startTime ?? null
54+
5155
const onToggleCollapsed = useCallback(
5256
(id: string) => {
5357
const wasCollapsed = collapsedAgents.has(id)
58+
59+
// Set flag to prevent auto-scroll during user-initiated collapse
60+
isUserCollapsingRef.current = true
61+
5462
setCollapsedAgents((prev) => {
5563
const next = new Set(prev)
5664
if (next.has(id)) {
@@ -70,7 +78,7 @@ export const MessageRenderer = (props: MessageRendererProps): ReactNode => {
7078
return next
7179
})
7280
},
73-
[collapsedAgents, setCollapsedAgents, setUserOpenedAgents],
81+
[collapsedAgents, setCollapsedAgents, setUserOpenedAgents, isUserCollapsingRef],
7482
)
7583

7684
return (
@@ -94,7 +102,7 @@ export const MessageRenderer = (props: MessageRendererProps): ReactNode => {
94102
setUserOpenedAgents={setUserOpenedAgents}
95103
setFocusedAgentId={setFocusedAgentId}
96104
isWaitingForResponse={isWaitingForResponse}
97-
timerStartTime={timerStartTime}
105+
timer={timer}
98106
onToggleCollapsed={onToggleCollapsed}
99107
/>
100108
)
@@ -118,7 +126,7 @@ interface MessageWithAgentsProps {
118126
setUserOpenedAgents: React.Dispatch<React.SetStateAction<Set<string>>>
119127
setFocusedAgentId: React.Dispatch<React.SetStateAction<string | null>>
120128
isWaitingForResponse: boolean
121-
timerStartTime: number | null
129+
timer: any
122130
onToggleCollapsed: (id: string) => void
123131
}
124132

@@ -138,11 +146,12 @@ const MessageWithAgents = memo(
138146
setUserOpenedAgents,
139147
setFocusedAgentId,
140148
isWaitingForResponse,
141-
timerStartTime,
149+
timer,
142150
onToggleCollapsed,
143151
}: MessageWithAgentsProps): ReactNode => {
144152
const SIDE_GUTTER = 1
145153
const isAgent = message.variant === 'agent'
154+
const timerStartTime = timer?.startTime ?? null
146155

147156
if (isAgent) {
148157
return (
@@ -271,7 +280,7 @@ const MessageWithAgents = memo(
271280
isComplete={message.isComplete}
272281
completionTime={message.completionTime}
273282
credits={message.credits}
274-
timerStartTime={timerStartTime}
283+
timer={timer}
275284
textColor={textColor}
276285
timestampColor={timestampColor}
277286
markdownOptions={markdownOptions}
@@ -342,7 +351,7 @@ const MessageWithAgents = memo(
342351
setUserOpenedAgents={setUserOpenedAgents}
343352
setFocusedAgentId={setFocusedAgentId}
344353
isWaitingForResponse={isWaitingForResponse}
345-
timerStartTime={timerStartTime}
354+
timer={timer}
346355
onToggleCollapsed={onToggleCollapsed}
347356
/>
348357
</box>
@@ -580,7 +589,7 @@ const AgentMessage = memo(
580589
setUserOpenedAgents={setUserOpenedAgents}
581590
setFocusedAgentId={setFocusedAgentId}
582591
isWaitingForResponse={isWaitingForResponse}
583-
timerStartTime={timerStartTime}
592+
timer={timer}
584593
onToggleCollapsed={onToggleCollapsed}
585594
/>
586595
</box>

0 commit comments

Comments
 (0)