@@ -181,8 +181,8 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
181181 const [ showAnnouncementModal , setShowAnnouncementModal ] = useState ( false )
182182 const everVisibleMessagesTsRef = useRef < LRUCache < number , boolean > > (
183183 new LRUCache ( {
184- max : 250 ,
185- ttl : 1000 * 60 * 15 , // 15 minutes TTL for long-running tasks
184+ max : 100 ,
185+ ttl : 1000 * 60 * 5 ,
186186 } ) ,
187187 )
188188 const autoApproveTimeoutRef = useRef < NodeJS . Timeout | null > ( null )
@@ -458,7 +458,26 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
458458 }
459459 } , [ isHidden ] )
460460
461- useEffect ( ( ) => ( ) => everVisibleMessagesTsRef . current . clear ( ) , [ ] )
461+ useEffect ( ( ) => {
462+ return ( ) => {
463+ everVisibleMessagesTsRef . current . clear ( )
464+ }
465+ } , [ ] )
466+
467+ useEffect ( ( ) => {
468+ const cleanupInterval = setInterval ( ( ) => {
469+ const cache = everVisibleMessagesTsRef . current
470+ const currentMessageIds = new Set ( modifiedMessages . map ( ( m : ClineMessage ) => m . ts ) )
471+
472+ cache . forEach ( ( value : boolean , key : number ) => {
473+ if ( ! currentMessageIds . has ( key ) ) {
474+ cache . delete ( key )
475+ }
476+ } )
477+ } , 60000 )
478+
479+ return ( ) => clearInterval ( cleanupInterval )
480+ } , [ modifiedMessages ] )
462481
463482 useEffect ( ( ) => {
464483 const prev = prevExpandedRowsRef . current
@@ -502,7 +521,10 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
502521 if ( isLastMessagePartial ) {
503522 return true
504523 } else {
505- const lastApiReqStarted = findLast ( modifiedMessages , ( message ) => message . say === "api_req_started" )
524+ const lastApiReqStarted = findLast (
525+ modifiedMessages ,
526+ ( message : ClineMessage ) => message . say === "api_req_started" ,
527+ )
506528
507529 if (
508530 lastApiReqStarted &&
@@ -522,7 +544,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
522544 } , [ modifiedMessages , clineAsk , enableButtons , primaryButtonText ] )
523545
524546 const markFollowUpAsAnswered = useCallback ( ( ) => {
525- const lastFollowUpMessage = messagesRef . current . findLast ( ( msg ) => msg . ask === "followup" )
547+ const lastFollowUpMessage = messagesRef . current . findLast ( ( msg : ClineMessage ) => msg . ask === "followup" )
526548 if ( lastFollowUpMessage ) {
527549 setCurrentFollowUpTs ( lastFollowUpMessage . ts )
528550 }
@@ -564,7 +586,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
564586 if ( sendingDisabled && ! fromQueue ) {
565587 // Generate a more unique ID using timestamp + random component
566588 const messageId = `${ Date . now ( ) } -${ Math . random ( ) . toString ( 36 ) . substr ( 2 , 9 ) } `
567- setMessageQueue ( ( prev ) => [ ...prev , { id : messageId , text, images } ] )
589+ setMessageQueue ( ( prev : QueuedMessage [ ] ) => [ ...prev , { id : messageId , text, images } ] )
568590 setInputValue ( "" )
569591 setSelectedImages ( [ ] )
570592 return
@@ -660,7 +682,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
660682 if ( retryCount < MAX_RETRY_ATTEMPTS ) {
661683 retryCountRef . current . set ( nextMessage . id , retryCount + 1 )
662684 // Re-add the message to the end of the queue
663- setMessageQueue ( ( current ) => [ ...current , nextMessage ] )
685+ setMessageQueue ( ( current : QueuedMessage [ ] ) => [ ...current , nextMessage ] )
664686 } else {
665687 console . error ( `Message ${ nextMessage . id } failed after ${ MAX_RETRY_ATTEMPTS } attempts, discarding` )
666688 retryCountRef . current . delete ( nextMessage . id )
@@ -834,7 +856,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
834856 // Only handle selectedImages if it's not for editing context
835857 // When context is "edit", ChatRow will handle the images
836858 if ( message . context !== "edit" ) {
837- setSelectedImages ( ( prevImages ) =>
859+ setSelectedImages ( ( prevImages : string [ ] ) =>
838860 appendImages ( prevImages , message . images , MAX_IMAGES_PER_MESSAGE ) ,
839861 )
840862 }
@@ -901,10 +923,12 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
901923 )
902924
903925 const visibleMessages = useMemo ( ( ) => {
904- const newVisibleMessages = modifiedMessages . filter ( ( message ) => {
926+ const currentMessageCount = modifiedMessages . length
927+ const startIndex = Math . max ( 0 , currentMessageCount - 500 )
928+ const recentMessages = modifiedMessages . slice ( startIndex )
929+
930+ const newVisibleMessages = recentMessages . filter ( ( message : ClineMessage ) => {
905931 if ( everVisibleMessagesTsRef . current . has ( message . ts ) ) {
906- // If it was ever visible, and it's not one of the types that should always be hidden once processed, keep it.
907- // This helps prevent flickering for messages like 'api_req_retry_delayed' if they are no longer the absolute last.
908932 const alwaysHiddenOnceProcessedAsk : ClineAsk [ ] = [
909933 "api_req_failed" ,
910934 "resume_task" ,
@@ -918,14 +942,12 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
918942 ]
919943 if ( message . ask && alwaysHiddenOnceProcessedAsk . includes ( message . ask ) ) return false
920944 if ( message . say && alwaysHiddenOnceProcessedSay . includes ( message . say ) ) return false
921- // Also, re-evaluate empty text messages if they were previously visible but now empty (e.g. partial stream ended)
922945 if ( message . say === "text" && ( message . text ?? "" ) === "" && ( message . images ?. length ?? 0 ) === 0 ) {
923946 return false
924947 }
925948 return true
926949 }
927950
928- // Original filter logic
929951 switch ( message . ask ) {
930952 case "completion_result" :
931953 if ( message . text === "" ) return false
@@ -944,9 +966,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
944966 const last1 = modifiedMessages . at ( - 1 )
945967 const last2 = modifiedMessages . at ( - 2 )
946968 if ( last1 ?. ask === "resume_task" && last2 === message ) {
947- // This specific sequence should be visible
948969 } else if ( message !== last1 ) {
949- // If not the specific sequence above, and not the last message, hide it.
950970 return false
951971 }
952972 break
@@ -959,8 +979,10 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
959979 return true
960980 } )
961981
962- // Update the set of ever-visible messages (LRUCache automatically handles cleanup)
963- newVisibleMessages . forEach ( ( msg ) => everVisibleMessagesTsRef . current . set ( msg . ts , true ) )
982+ const viewportStart = Math . max ( 0 , newVisibleMessages . length - 100 )
983+ newVisibleMessages
984+ . slice ( viewportStart )
985+ . forEach ( ( msg : ClineMessage ) => everVisibleMessagesTsRef . current . set ( msg . ts , true ) )
964986
965987 return newVisibleMessages
966988 } , [ modifiedMessages ] )
@@ -1240,7 +1262,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
12401262 }
12411263 }
12421264
1243- visibleMessages . forEach ( ( message ) => {
1265+ visibleMessages . forEach ( ( message : ClineMessage ) => {
12441266 if ( message . ask === "browser_action_launch" ) {
12451267 // Complete existing browser session if any.
12461268 endBrowserSession ( )
@@ -1333,7 +1355,10 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
13331355
13341356 const handleSetExpandedRow = useCallback (
13351357 ( ts : number , expand ?: boolean ) => {
1336- setExpandedRows ( ( prev ) => ( { ...prev , [ ts ] : expand === undefined ? ! prev [ ts ] : expand } ) )
1358+ setExpandedRows ( ( prev : Record < number , boolean > ) => ( {
1359+ ...prev ,
1360+ [ ts ] : expand === undefined ? ! prev [ ts ] : expand ,
1361+ } ) )
13371362 } ,
13381363 [ setExpandedRows ] , // setExpandedRows is stable
13391364 )
@@ -1362,7 +1387,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
13621387 )
13631388
13641389 useEffect ( ( ) => {
1365- let timer : NodeJS . Timeout | undefined
1390+ let timer : ReturnType < typeof setTimeout > | undefined
13661391 if ( ! disableAutoScrollRef . current ) {
13671392 timer = setTimeout ( ( ) => scrollToBottomSmooth ( ) , 50 )
13681393 }
@@ -1425,7 +1450,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
14251450 )
14261451
14271452 const handleSuggestionClickInRow = useCallback (
1428- ( suggestion : SuggestionItem , event ?: React . MouseEvent ) => {
1453+ ( suggestion : SuggestionItem , event ?: MouseEvent ) => {
14291454 // Mark that user has responded if this is a manual click (not auto-approval)
14301455 if ( event ) {
14311456 userRespondedRef . current = true
@@ -1448,7 +1473,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
14481473
14491474 if ( event ?. shiftKey ) {
14501475 // Always append to existing text, don't overwrite
1451- setInputValue ( ( currentValue ) => {
1476+ setInputValue ( ( currentValue : string ) => {
14521477 return currentValue !== "" ? `${ currentValue } \n${ suggestion . answer } ` : suggestion . answer
14531478 } )
14541479 } else {
@@ -1482,7 +1507,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
14821507 isStreaming = { isStreaming }
14831508 isExpanded = { ( messageTs : number ) => expandedRows [ messageTs ] ?? false }
14841509 onToggleExpand = { ( messageTs : number ) => {
1485- setExpandedRows ( ( prev ) => ( {
1510+ setExpandedRows ( ( prev : Record < number , boolean > ) => ( {
14861511 ...prev ,
14871512 [ messageTs ] : ! prev [ messageTs ] ,
14881513 } ) )
@@ -1842,20 +1867,19 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
18421867 < div className = "grow flex" ref = { scrollContainerRef } >
18431868 < Virtuoso
18441869 ref = { virtuosoRef }
1845- key = { task . ts } // trick to make sure virtuoso re-renders when task changes, and we use initialTopMostItemIndex to start at the bottom
1870+ key = { task . ts }
18461871 className = "scrollable grow overflow-y-scroll mb-1"
1847- // increasing top by 3_000 to prevent jumping around when user collapses a row
1848- increaseViewportBy = { { top : 3_000 , bottom : Number . MAX_SAFE_INTEGER } } // hack to make sure the last message is always rendered to get truly perfect scroll to bottom animation when new messages are added (Number.MAX_SAFE_INTEGER is safe for arithmetic operations, which is all virtuoso uses this value for in src/sizeRangeSystem.ts)
1849- data = { groupedMessages } // messages is the raw format returned by extension, modifiedMessages is the manipulated structure that combines certain messages of related type, and visibleMessages is the filtered structure that removes messages that should not be rendered
1872+ increaseViewportBy = { { top : 3_000 , bottom : 1000 } }
1873+ data = { groupedMessages }
18501874 itemContent = { itemContent }
1851- atBottomStateChange = { ( isAtBottom ) => {
1875+ atBottomStateChange = { ( isAtBottom : boolean ) => {
18521876 setIsAtBottom ( isAtBottom )
18531877 if ( isAtBottom ) {
18541878 disableAutoScrollRef . current = false
18551879 }
18561880 setShowScrollToBottom ( disableAutoScrollRef . current && ! isAtBottom )
18571881 } }
1858- atBottomThreshold = { 10 } // anything lower causes issues with followOutput
1882+ atBottomThreshold = { 10 }
18591883 initialTopMostItemIndex = { groupedMessages . length - 1 }
18601884 />
18611885 </ div >
0 commit comments