@@ -60,8 +60,6 @@ export default function ChatPanel({ onNewMessage }: ChatPanelProps) {
6060 const [ showTypingIndicator , setShowTypingIndicator ] = useState ( false ) ;
6161 const [ deliveredMessageIds , setDeliveredMessageIds ] = useState < Set < string > > ( new Set ( ) ) ;
6262 const [ readMessageIds , setReadMessageIds ] = useState < Set < string > > ( new Set ( ) ) ;
63- const deliveredTimersRef = useRef < NodeJS . Timeout [ ] > ( [ ] ) ;
64- const readTimersRef = useRef < NodeJS . Timeout [ ] > ( [ ] ) ;
6563 const loggedMessagePartsRef = useRef < Set < string > > ( new Set ( ) ) ;
6664 const hasInitializedRef = useRef ( false ) ;
6765 const followupSentRef = useRef ( false ) ;
@@ -254,6 +252,8 @@ export default function ChatPanel({ onNewMessage }: ChatPanelProps) {
254252 // Track user messages for delivered/read status (uses useChat's message IDs for UI)
255253 const trackedUserMessageIdsRef = useRef < Set < string > > ( new Set ( ) ) ;
256254 useEffect ( ( ) => {
255+ const deliveredTimers : NodeJS . Timeout [ ] = [ ] ;
256+ const readTimers : NodeJS . Timeout [ ] = [ ] ;
257257 messages . forEach ( ( message ) => {
258258 if ( message . role !== 'user' || ! message . id || message . id === 'initial-user-message' ) return ;
259259 if ( trackedUserMessageIdsRef . current . has ( message . id ) ) return ;
@@ -264,15 +264,21 @@ export default function ChatPanel({ onNewMessage }: ChatPanelProps) {
264264 const deliveredTimer = setTimeout ( ( ) => {
265265 setDeliveredMessageIds ( ( prev ) => new Set ( prev ) . add ( message . id ) ) ;
266266 } , deliveredDelay ) ;
267- deliveredTimersRef . current . push ( deliveredTimer ) ;
267+ deliveredTimers . push ( deliveredTimer ) ;
268268
269269 // Mark message as read after a short delay to feel more human
270270 const readDelay = 3000 + Math . random ( ) * 5000 ; // 3-8 seconds
271271 const readTimer = setTimeout ( ( ) => {
272272 setReadMessageIds ( ( prev ) => new Set ( prev ) . add ( message . id ) ) ;
273273 } , readDelay ) ;
274- readTimersRef . current . push ( readTimer ) ;
274+ readTimers . push ( readTimer ) ;
275275 } ) ;
276+
277+ // Cleanup the timers on unmount
278+ return ( ) => {
279+ deliveredTimers . forEach ( ( timer ) => void clearTimeout ( timer ) ) ;
280+ readTimers . forEach ( ( timer ) => void clearTimeout ( timer ) ) ;
281+ } ;
276282 } , [ messages ] ) ;
277283
278284 // Log assistant message part event - called when a new part becomes visible
@@ -349,9 +355,7 @@ export default function ChatPanel({ onNewMessage }: ChatPanelProps) {
349355 onNewMessage ( ) ;
350356 }
351357 } ) ;
352-
353- /* Removed internal notification state as requested */
354-
358+
355359 // Show notification when a new assistant message part appears
356360 useEffect ( ( ) => {
357361 if ( messages . length === 0 ) return ;
@@ -361,14 +365,6 @@ export default function ChatPanel({ onNewMessage }: ChatPanelProps) {
361365 }
362366 } , [ messages , visibleMessagePartCount ] ) ;
363367
364- // Cleanup any pending read timers on unmount
365- useEffect ( ( ) => {
366- return ( ) => {
367- deliveredTimersRef . current . forEach ( ( timer ) => clearTimeout ( timer ) ) ;
368- readTimersRef . current . forEach ( ( timer ) => clearTimeout ( timer ) ) ;
369- } ;
370- } , [ ] ) ;
371-
372368 return (
373369 < div className = "h-full flex flex-col overflow-hidden" >
374370 < div className = "flex items-center gap-2.5 bg-gray-50 border-b border-gray-300 px-3 py-2.5" >
0 commit comments