@@ -5,7 +5,7 @@ import BrandedEmptyState from "./branded-empty-state"
55import MessageBlock from "./message-block"
66import { getMessageAnchorId } from "./message-anchors"
77import MessageTimeline , { buildTimelineSegments , type TimelineSegment } from "./message-timeline"
8- import VirtualFollowList , { type VirtualFollowBottomIntent , type VirtualFollowListApi , type VirtualFollowListState , type VirtualFollowScrollSnapshot } from "./virtual-follow-list"
8+ import VirtualFollowList , { type VirtualExplicitBottomPinIntent , type VirtualFollowListApi , type VirtualFollowListState , type VirtualFollowScrollSnapshot } from "./virtual-follow-list"
99import { isSnapshotAutoFollowing } from "./virtual-follow-behavior"
1010import { useConfig } from "../stores/preferences"
1111import { getSessionInfo } from "../stores/sessions"
@@ -24,7 +24,6 @@ import { buildSessionSearchMatches } from "../lib/session-search"
2424import type { SessionSearchMatch } from "../lib/session-search"
2525import { resolveThinkingExpansionDefault } from "./tool-call/tool-registry"
2626
27- const SCROLL_SENTINEL_MARGIN_PX = 8
2827const MESSAGE_SCROLL_CACHE_SCOPE = "message-stream"
2928const QUOTE_SELECTION_MAX_LENGTH = 2000
3029const STREAMING_TEXT_HOLD_TOP_THRESHOLD_PX = 8
@@ -49,7 +48,8 @@ export interface MessageSectionProps {
4948 onReloadMessages ?: ( ) => void
5049 isActive ?: boolean
5150 sessionStreamingActive ?: boolean
52- bottomFollowIntent ?: VirtualFollowBottomIntent | null
51+ explicitBottomPinIntent ?: VirtualExplicitBottomPinIntent | null
52+ onExplicitBottomPinCancelled ?: ( ) => void
5353}
5454
5555export default function MessageSection ( props : MessageSectionProps ) {
@@ -735,16 +735,6 @@ export default function MessageSection(props: MessageSectionProps) {
735735 return
736736 }
737737
738- const element = streamElement ( )
739- if ( ! allowCapture || ! canCapture ) return
740- if ( ! element ) return
741- const scrollTop = element . scrollTop
742- const maxScrollTop = Math . max ( element . scrollHeight - element . clientHeight , 0 )
743- const scrollRatio = maxScrollTop > 0 ? scrollTop / maxScrollTop : 0
744- const atBottom = element . scrollHeight - ( element . scrollTop + element . clientHeight ) <= 48
745- const snapshot = { scrollTop, scrollRatio, maxScrollTop, atBottom }
746- setLastGoodScrollSnapshot ( sessionId , snapshot )
747- store ( ) . setScrollSnapshot ( sessionId , MESSAGE_SCROLL_CACHE_SCOPE , snapshot )
748738 }
749739
750740 // Persist scroll position when switching sessions. This effect's cleanup runs
@@ -783,7 +773,7 @@ export default function MessageSection(props: MessageSectionProps) {
783773 setScrollControlsOpen ( false )
784774 }
785775
786- function openScrollControlsFromTrigger ( event : PointerEvent ) {
776+ function openScrollControlsFromTrigger ( event : MouseEvent ) {
787777 event . preventDefault ( )
788778 event . stopPropagation ( )
789779 if ( scrollControlsOpen ( ) ) return
@@ -795,7 +785,7 @@ export default function MessageSection(props: MessageSectionProps) {
795785 event . preventDefault ( )
796786 event . stopPropagation ( )
797787 action ( )
798- setScrollControlsHoverSuppressed ( event . pointerType !== "mouse" )
788+ setScrollControlsHoverSuppressed ( false )
799789 closeScrollControls ( )
800790 }
801791
@@ -836,7 +826,7 @@ export default function MessageSection(props: MessageSectionProps) {
836826 const api = listApi ( )
837827 if ( ! api ) return
838828 if ( props . registerScrollToBottom ) {
839- props . registerScrollToBottom ( ( ) => api . scrollToBottom ( { immediate : true , suppressHold : true } ) )
829+ props . registerScrollToBottom ( ( ) => api . scrollToBottom ( { immediate : true } ) )
840830 onCleanup ( ( ) => props . registerScrollToBottom ?.( null ) )
841831 }
842832 } )
@@ -1264,7 +1254,7 @@ export default function MessageSection(props: MessageSectionProps) {
12641254 if ( ! match || ! isSearchOpen ( ) ) return
12651255 if ( match . id === lastScrolledSearchMatchId ) return
12661256 lastScrolledSearchMatchId = match . id
1267- listApi ( ) ?. scrollToKey ( match . messageId , { behavior : "smooth" , block : "start" , setAutoScroll : false } )
1257+ listApi ( ) ?. scrollToKey ( match . messageId , { behavior : "smooth" , block : "start" } )
12681258 } )
12691259
12701260
@@ -1367,16 +1357,15 @@ export default function MessageSection(props: MessageSectionProps) {
13671357 getKey = { ( messageId ) => messageId }
13681358 getAnchorId = { getMessageAnchorId }
13691359 overscanPx = { 800 }
1370- scrollSentinelMarginPx = { SCROLL_SENTINEL_MARGIN_PX }
1371- suspendMeasurements = { ( ) => ! isActive ( ) }
13721360 streamingActive = { streamingActive }
13731361 isActive = { isActive }
13741362 scrollToBottomOnActivate = { ( ) => false }
13751363 initialScrollToBottom = { ( ) => false }
13761364 initialAutoScroll = { initialAutoScroll }
13771365 resetKey = { ( ) => props . sessionId }
13781366 followToken = { followToken }
1379- forceBottomFollowIntent = { ( ) => props . bottomFollowIntent ?? null }
1367+ explicitBottomPinIntent = { ( ) => props . explicitBottomPinIntent ?? null }
1368+ onExplicitBottomPinCancelled = { props . onExplicitBottomPinCancelled }
13801369 autoPinHoldEnabled = { holdLongAssistantRepliesEnabled }
13811370 autoPinHoldTargetKey = { autoPinHoldTargetKey }
13821371 autoPinHoldTopThresholdPx = { STREAMING_TEXT_HOLD_TOP_THRESHOLD_PX }
@@ -1429,7 +1418,7 @@ export default function MessageSection(props: MessageSectionProps) {
14291418 < button
14301419 type = "button"
14311420 class = "message-scroll-button message-scroll-controls-trigger"
1432- onPointerUp = { openScrollControlsFromTrigger }
1421+ onClick = { openScrollControlsFromTrigger }
14331422 aria-label = { t ( "messageSection.scroll.showControlsAriaLabel" ) }
14341423 title = { t ( "messageSection.scroll.showControlsAriaLabel" ) }
14351424 >
@@ -1472,7 +1461,7 @@ export default function MessageSection(props: MessageSectionProps) {
14721461 < button
14731462 type = "button"
14741463 class = "message-scroll-button"
1475- onPointerUp = { ( event ) => runScrollControlAction ( event , ( ) => api . scrollToBottom ( { suppressHold : true } ) ) }
1464+ onPointerUp = { ( event ) => runScrollControlAction ( event , ( ) => api . scrollToBottom ( ) ) }
14761465 aria-label = { t ( "messageSection.scroll.toLatestAriaLabel" ) }
14771466 >
14781467 < span class = "message-scroll-icon" aria-hidden = "true" >
0 commit comments