From 3620587641cb8737103cb36e140b8dab47a26b35 Mon Sep 17 00:00:00 2001 From: Taranum01 Date: Sat, 15 Aug 2026 05:48:59 +0530 Subject: [PATCH] fix(web): re-add handleChangeConversation to handleNewConversation deps Supersedes #39865 (closed as superseded; the original branch drifted against main while the useConversationSelection refactor landed and the diff was no longer mergeable). Rebuilt on `upstream/main` (dfac3e524e). The bug is still present on main: `useEmbeddedChatbot` calls `handleChangeConversation('')` inside `handleNewConversation` but only declares it through the closure instead of in the useCallback dep array. The reset button therefore captures a stale `handleChangeConversation` identity after any state change that invalidates its reference (e.g. `userId`). Adds the missing dep and a regression test that asserts both handlers regenerate together after an identity-invalidation rerender, while preserving identity across a no-prop rerender. --- .../embedded-chatbot/__tests__/hooks.spec.tsx | 39 +++++++++++++++++++ .../base/chat/embedded-chatbot/hooks.tsx | 1 + 2 files changed, 40 insertions(+) diff --git a/web/app/components/base/chat/embedded-chatbot/__tests__/hooks.spec.tsx b/web/app/components/base/chat/embedded-chatbot/__tests__/hooks.spec.tsx index 0f022253500..1b204fdd54d 100644 --- a/web/app/components/base/chat/embedded-chatbot/__tests__/hooks.spec.tsx +++ b/web/app/components/base/chat/embedded-chatbot/__tests__/hooks.spec.tsx @@ -608,6 +608,45 @@ describe('useEmbeddedChatbot', () => { expect(result.current.clearChatList).toBe(true) }) + // Regression for #38573: handleNewConversation must include handleChangeConversation + // in its useCallback deps so the reset button always invokes the freshest + // handleConversationIdInfoChange. Without the dep, the reset click captures a + // stale closure over the original handleChangeConversation identity. + it('handleNewConversation tracks handleChangeConversation identity', async () => { + const { result, rerender } = await renderWithClient(() => + useEmbeddedChatbot(AppSourceType.webApp), + ) + + const firstHandleNewConversation = result.current.handleNewConversation + const firstHandleChangeConversation = result.current.handleChangeConversation + expect(typeof firstHandleNewConversation).toBe('function') + expect(typeof firstHandleChangeConversation).toBe('function') + + // No-prop rerender: useCallback must short-circuit and preserve identity + // because no dependency reference changed. + await act(async () => { + rerender() + }) + + expect(result.current.handleNewConversation).toBe(firstHandleNewConversation) + expect(result.current.handleChangeConversation).toBe(firstHandleChangeConversation) + + // Flip the resolved userId via the store mock so handleChangeConversation's + // identity is invalidated. With the fix, handleNewConversation regenerates + // alongside it; without the fix, handleNewConversation would still reference + // the stale closure. + mockStoreState.embeddedUserId = 'embedded-user-2' + + await act(async () => { + rerender() + }) + + await waitFor(() => { + expect(result.current.handleChangeConversation).not.toBe(firstHandleChangeConversation) + }) + expect(result.current.handleNewConversation).not.toBe(firstHandleNewConversation) + }) + it('handleChangeConversation updates current conversation and refetches chat list', async () => { mockStoreState.embeddedConversationId = null const { result } = await renderWithClient(() => useEmbeddedChatbot(AppSourceType.webApp)) diff --git a/web/app/components/base/chat/embedded-chatbot/hooks.tsx b/web/app/components/base/chat/embedded-chatbot/hooks.tsx index 0c7950f562c..2f96f4dc566 100644 --- a/web/app/components/base/chat/embedded-chatbot/hooks.tsx +++ b/web/app/components/base/chat/embedded-chatbot/hooks.tsx @@ -393,6 +393,7 @@ export const useEmbeddedChatbot = (appSourceType: AppSourceType, tryAppId?: stri }, [ isTryApp, setShowNewConversationItemInList, + handleChangeConversation, handleNewConversationInputsChange, setClearChatList, ])