@@ -242,6 +242,7 @@ export default function ChatView({ threadId }: ChatViewProps) {
242242 ( store ) => store . draftThreadsByThreadId [ threadId ] ?? null ,
243243 ) ;
244244 const promptRef = useRef ( prompt ) ;
245+ const [ showScrollToBottom , setShowScrollToBottom ] = useState ( false ) ;
245246 const [ isDragOverComposer , setIsDragOverComposer ] = useState ( false ) ;
246247 const [ expandedImage , setExpandedImage ] = useState < ExpandedImagePreview | null > ( null ) ;
247248 const [ optimisticUserMessages , setOptimisticUserMessages ] = useState < ChatMessage [ ] > ( [ ] ) ;
@@ -1587,6 +1588,7 @@ export default function ChatView({ threadId }: ChatViewProps) {
15871588 }
15881589 }
15891590
1591+ setShowScrollToBottom ( ! shouldAutoScrollRef . current ) ;
15901592 lastKnownScrollTopRef . current = currentScrollTop ;
15911593 } , [ ] ) ;
15921594 const onMessagesWheel = useCallback ( ( event : React . WheelEvent < HTMLDivElement > ) => {
@@ -3259,45 +3261,62 @@ export default function ChatView({ threadId }: ChatViewProps) {
32593261 < div className = "flex min-h-0 min-w-0 flex-1" >
32603262 { /* Chat column */ }
32613263 < div className = "flex min-h-0 min-w-0 flex-1 flex-col" >
3262- { /* Messages */ }
3263- < div
3264- ref = { setMessagesScrollContainerRef }
3265- className = "min-h-0 flex-1 overflow-x-hidden overflow-y-auto overscroll-y-contain px-3 py-3 sm:px-5 sm:py-4"
3266- onScroll = { onMessagesScroll }
3267- onClickCapture = { onMessagesClickCapture }
3268- onWheel = { onMessagesWheel }
3269- onPointerDown = { onMessagesPointerDown }
3270- onPointerUp = { onMessagesPointerUp }
3271- onPointerCancel = { onMessagesPointerCancel }
3272- onTouchStart = { onMessagesTouchStart }
3273- onTouchMove = { onMessagesTouchMove }
3274- onTouchEnd = { onMessagesTouchEnd }
3275- onTouchCancel = { onMessagesTouchEnd }
3276- >
3277- < MessagesTimeline
3278- key = { activeThread . id }
3279- hasMessages = { timelineEntries . length > 0 }
3280- isWorking = { isWorking }
3281- activeTurnInProgress = { isWorking || ! latestTurnSettled }
3282- activeTurnStartedAt = { activeWorkStartedAt }
3283- scrollContainer = { messagesScrollElement }
3284- timelineEntries = { timelineEntries }
3285- completionDividerBeforeEntryId = { completionDividerBeforeEntryId }
3286- completionSummary = { completionSummary }
3287- turnDiffSummaryByAssistantMessageId = { turnDiffSummaryByAssistantMessageId }
3288- nowIso = { nowIso }
3289- expandedWorkGroups = { expandedWorkGroups }
3290- onToggleWorkGroup = { onToggleWorkGroup }
3291- onOpenTurnDiff = { onOpenTurnDiff }
3292- revertTurnCountByUserMessageId = { revertTurnCountByUserMessageId }
3293- onRevertUserMessage = { onRevertUserMessage }
3294- isRevertingCheckpoint = { isRevertingCheckpoint }
3295- onImageExpand = { onExpandTimelineImage }
3296- markdownCwd = { gitCwd ?? undefined }
3297- resolvedTheme = { resolvedTheme }
3298- timestampFormat = { timestampFormat }
3299- workspaceRoot = { activeProject ?. cwd ?? undefined }
3300- />
3264+ { /* Messages Wrapper */ }
3265+ < div className = "relative flex min-h-0 flex-1 flex-col" >
3266+ { /* Messages */ }
3267+ < div
3268+ ref = { setMessagesScrollContainerRef }
3269+ className = "min-h-0 flex-1 overflow-x-hidden overflow-y-auto overscroll-y-contain px-3 py-3 sm:px-5 sm:py-4"
3270+ onScroll = { onMessagesScroll }
3271+ onClickCapture = { onMessagesClickCapture }
3272+ onWheel = { onMessagesWheel }
3273+ onPointerDown = { onMessagesPointerDown }
3274+ onPointerUp = { onMessagesPointerUp }
3275+ onPointerCancel = { onMessagesPointerCancel }
3276+ onTouchStart = { onMessagesTouchStart }
3277+ onTouchMove = { onMessagesTouchMove }
3278+ onTouchEnd = { onMessagesTouchEnd }
3279+ onTouchCancel = { onMessagesTouchEnd }
3280+ >
3281+ < MessagesTimeline
3282+ key = { activeThread . id }
3283+ hasMessages = { timelineEntries . length > 0 }
3284+ isWorking = { isWorking }
3285+ activeTurnInProgress = { isWorking || ! latestTurnSettled }
3286+ activeTurnStartedAt = { activeWorkStartedAt }
3287+ scrollContainer = { messagesScrollElement }
3288+ timelineEntries = { timelineEntries }
3289+ completionDividerBeforeEntryId = { completionDividerBeforeEntryId }
3290+ completionSummary = { completionSummary }
3291+ turnDiffSummaryByAssistantMessageId = { turnDiffSummaryByAssistantMessageId }
3292+ nowIso = { nowIso }
3293+ expandedWorkGroups = { expandedWorkGroups }
3294+ onToggleWorkGroup = { onToggleWorkGroup }
3295+ onOpenTurnDiff = { onOpenTurnDiff }
3296+ revertTurnCountByUserMessageId = { revertTurnCountByUserMessageId }
3297+ onRevertUserMessage = { onRevertUserMessage }
3298+ isRevertingCheckpoint = { isRevertingCheckpoint }
3299+ onImageExpand = { onExpandTimelineImage }
3300+ markdownCwd = { gitCwd ?? undefined }
3301+ resolvedTheme = { resolvedTheme }
3302+ timestampFormat = { timestampFormat }
3303+ workspaceRoot = { activeProject ?. cwd ?? undefined }
3304+ />
3305+ </ div >
3306+
3307+ { /* scroll to bottom pill — shown when user has scrolled away from the bottom */ }
3308+ { showScrollToBottom && (
3309+ < div className = "pointer-events-none absolute bottom-1 left-1/2 z-30 flex -translate-x-1/2 justify-center py-1.5" >
3310+ < button
3311+ type = "button"
3312+ onClick = { ( ) => scrollMessagesToBottom ( "smooth" ) }
3313+ className = "pointer-events-auto flex items-center gap-1.5 rounded-full border border-border/60 bg-card px-3 py-1 text-muted-foreground text-xs shadow-sm transition-colors hover:bg-accent hover:text-accent-foreground"
3314+ >
3315+ < ChevronDownIcon className = "size-3.5" />
3316+ Scroll to bottom
3317+ </ button >
3318+ </ div >
3319+ ) }
33013320 </ div >
33023321
33033322 { /* Input bar */ }
@@ -3328,7 +3347,7 @@ export default function ChatView({ threadId }: ChatViewProps) {
33283347 < div className = "rounded-t-[19px] border-b border-border/65 bg-muted/20" >
33293348 < ComposerPendingUserInputPanel
33303349 pendingUserInputs = { pendingUserInputs }
3331- respondingRequestIds = { respondingUserInputRequestIds }
3350+ respondingRequestIds = { respondingRequestIds }
33323351 answers = { activePendingDraftAnswers }
33333352 questionIndex = { activePendingQuestionIndex }
33343353 onSelectOption = { onSelectActivePendingUserInputOption }
0 commit comments