@@ -112,6 +112,7 @@ interface ChatState {
112112 currentStreamingMessageId ?: string
113113 currentlyStreamingNodeId ?: string
114114 inputBarFocused : boolean
115+ shouldScrollToFocusedToggle : boolean
115116}
116117
117118// State
@@ -128,6 +129,7 @@ let chatState: ChatState = {
128129 currentStreamingMessageId : undefined ,
129130 currentlyStreamingNodeId : undefined ,
130131 inputBarFocused : true , // Start with input bar focused
132+ shouldScrollToFocusedToggle : false ,
131133}
132134
133135// Cached date formatter for performance
@@ -219,6 +221,43 @@ function scrollToBottom(): void {
219221 chatState . userHasScrolled = false
220222}
221223
224+ function scrollToToggle ( toggleNodeId : string , messageId : string ) : void {
225+ // We'll find the focused toggle after content is updated by looking for the highlighted toggle
226+ // Set a flag to scroll to the focused toggle after the next render
227+ chatState . shouldScrollToFocusedToggle = true
228+ }
229+
230+ function scrollToFocusedToggle ( ) : void {
231+ if ( ! chatState . shouldScrollToFocusedToggle ) return
232+
233+ const metrics = getTerminalMetrics ( )
234+
235+ // Find the line number where the highlighted toggle appears
236+ let toggleLineIndex = - 1
237+ for ( let i = 0 ; i < chatState . contentLines . length ; i ++ ) {
238+ const line = chatState . contentLines [ i ]
239+ // Look for the highlighted toggle pattern (\x1b[7m[+]\x1b[27m or \x1b[7m[-]\x1b[27m)
240+ if ( line . includes ( '\x1b[7m[' ) && line . includes ( ']\x1b[27m' ) ) {
241+ toggleLineIndex = i
242+ break
243+ }
244+ }
245+
246+ if ( toggleLineIndex !== - 1 ) {
247+ // Position the toggle near the top (about 3-4 lines down from the visible area)
248+ const targetTopOffset = 4
249+ const newScrollOffset = Math . max ( 0 , toggleLineIndex - targetTopOffset )
250+
251+ // Clamp the scroll offset to valid bounds
252+ const maxScrollOffset = computeMaxScrollOffset ( metrics )
253+ chatState . scrollOffset = Math . min ( newScrollOffset , maxScrollOffset )
254+ chatState . userHasScrolled = true
255+ }
256+
257+ // Clear the flag
258+ chatState . shouldScrollToFocusedToggle = false
259+ }
260+
222261function formatQueuePreview (
223262 message : string ,
224263 queueCount : string ,
@@ -248,6 +287,7 @@ function resetChatState(): void {
248287 currentStreamingMessageId : undefined ,
249288 currentlyStreamingNodeId : undefined ,
250289 inputBarFocused : true , // Start with input bar focused
290+ shouldScrollToFocusedToggle : false ,
251291 }
252292}
253293
@@ -631,6 +671,9 @@ function renderChat() {
631671 const maxContentLines = computeMaxContentLines ( metrics )
632672 const maxScrollOffset = computeMaxScrollOffset ( metrics )
633673
674+ // Handle scroll to focused toggle if requested
675+ scrollToFocusedToggle ( )
676+
634677 // Auto-scroll to bottom to show latest messages only if user hasn't manually scrolled
635678 if ( ! chatState . userHasScrolled ) {
636679 chatState . scrollOffset = maxScrollOffset
@@ -1626,6 +1669,9 @@ function handleTabNavigation(key: any): boolean {
16261669 )
16271670 if ( targetMessage && targetMessage . subagentUIState ) {
16281671 targetMessage . subagentUIState . focusNodeId = targetNode . nodeId
1672+
1673+ // Auto-scroll to position the focused toggle near the top of the chat
1674+ scrollToToggle ( targetNode . nodeId , targetMessage . id )
16291675 }
16301676 }
16311677
0 commit comments