@@ -39,6 +39,15 @@ export function ChatPanel({
3939
4040 const { scrollRef, contentRef } = useStickToBottom ( { initial : 'smooth' , resize : 'smooth' } )
4141 const initialLoaded = useRef ( false )
42+ // `onMessagesChange` is a fresh inline function on every parent render (it
43+ // closes over `doc.idbKey`). Depending on it directly in the effect below
44+ // would re-fire on every App render, not just when `messages` actually
45+ // changes — and since the parent's setState always builds a new object
46+ // (there's no single shared value to bail out on via Object.is anymore),
47+ // that turns into an infinite render loop. Reading it through a ref keeps
48+ // the effect keyed on the one thing that should trigger it: new messages.
49+ const onMessagesChangeRef = useRef ( onMessagesChange )
50+ onMessagesChangeRef . current = onMessagesChange
4251
4352 useEffect ( ( ) => { if ( triggerRef ) triggerRef . current = sendMessage } , [ triggerRef , sendMessage ] )
4453 useEffect ( ( ) => { if ( silentContextRef ) silentContextRef . current = addSilentContext } , [ silentContextRef , addSilentContext ] )
@@ -56,8 +65,8 @@ export function ChatPanel({
5665 } , [ initialMessages , setMessages , documentName , onDeferredTrigger ] )
5766
5867 useEffect ( ( ) => {
59- if ( messages . length > 0 ) onMessagesChange ?.( messages )
60- } , [ messages , onMessagesChange ] )
68+ if ( messages . length > 0 ) onMessagesChangeRef . current ?.( messages )
69+ } , [ messages ] )
6170
6271 const handleOptionSelect = useCallback ( ( msgId : string , optId : string , label : string ) => {
6372 setMessages ( messages . map ( ( m : Msg ) =>
0 commit comments