From db014cf7a2f09c672ad72511fb1b67311864e72e Mon Sep 17 00:00:00 2001 From: Nicholas Tindle Date: Fri, 17 Apr 2026 16:00:23 -0500 Subject: [PATCH] fix(frontend/copilot): close artifact panel on copilot page unmount MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The artifact panel lived in the Zustand store, so its `isOpen` state survived copilot page unmounts. Navigating to /profile, /home, or a new chat and coming back would re-render the panel open with the prior session's artifact. Tickets SECRT-2254, SECRT-2223, SECRT-2220 all trace to this. Reset the panel in a useAutoOpenArtifacts unmount cleanup so leaving the copilot page (or session-less limbo) always returns users to a clean default-closed panel. Session-change reset was already handled; this covers the nav-away → nav-back case. Two new failing-first tests drive it: - SECRT-2254 repro: open panel → unmount hook → panel must be closed. - SECRT-2220 repro: seed store with a stale `isOpen: true`, mount+unmount, remount → panel must be closed. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../useAutoOpenArtifacts.test.ts | 49 +++++++++++++++++++ .../ChatContainer/useAutoOpenArtifacts.ts | 9 ++++ 2 files changed, 58 insertions(+) diff --git a/autogpt_platform/frontend/src/app/(platform)/copilot/components/ChatContainer/useAutoOpenArtifacts.test.ts b/autogpt_platform/frontend/src/app/(platform)/copilot/components/ChatContainer/useAutoOpenArtifacts.test.ts index 8ff3046d55..0b730b29ac 100644 --- a/autogpt_platform/frontend/src/app/(platform)/copilot/components/ChatContainer/useAutoOpenArtifacts.test.ts +++ b/autogpt_platform/frontend/src/app/(platform)/copilot/components/ChatContainer/useAutoOpenArtifacts.test.ts @@ -88,4 +88,53 @@ describe("useAutoOpenArtifacts", () => { expect(s.activeArtifact?.id).toBe("c"); expect(s.history).toEqual([]); }); + + // SECRT-2254: "had agent panel open then went to profile then went to home + // and agent panel was still open". Nav-away unmounts the copilot page; if + // the panel state persists in the store, coming back re-renders it open. + it("closes the panel on unmount so nav-away → nav-back doesn't resurrect it (SECRT-2254)", () => { + useCopilotUIStore.getState().openArtifact(makeArtifact(A_ID, "a.txt")); + expect(useCopilotUIStore.getState().artifactPanel.isOpen).toBe(true); + + const { unmount } = renderHook(() => + useAutoOpenArtifacts({ sessionId: "s1" }), + ); + + act(() => { + unmount(); + }); + + const s = useCopilotUIStore.getState().artifactPanel; + expect(s.isOpen).toBe(false); + expect(s.activeArtifact).toBeNull(); + expect(s.history).toEqual([]); + }); + + // SECRT-2220: "keep closed by default" — a fresh mount (e.g. user returns to + // /copilot) must start with a closed panel even if the store somehow carries + // stale state from a prior life. + it("does not re-open a panel whose store state is stale on fresh mount (SECRT-2220)", () => { + // Simulate the store being left in an open state by a previous page life. + useCopilotUIStore.setState({ + artifactPanel: { + isOpen: true, + isMinimized: false, + isMaximized: false, + width: 600, + activeArtifact: makeArtifact(A_ID, "stale.txt"), + history: [], + }, + }); + + const { unmount } = renderHook(() => + useAutoOpenArtifacts({ sessionId: "s1" }), + ); + act(() => { + unmount(); + }); + + // Next mount of the page should see a clean store. + renderHook(() => useAutoOpenArtifacts({ sessionId: "s1" })); + expect(useCopilotUIStore.getState().artifactPanel.isOpen).toBe(false); + }); }); diff --git a/autogpt_platform/frontend/src/app/(platform)/copilot/components/ChatContainer/useAutoOpenArtifacts.ts b/autogpt_platform/frontend/src/app/(platform)/copilot/components/ChatContainer/useAutoOpenArtifacts.ts index a8b867009c..04ef7d2631 100644 --- a/autogpt_platform/frontend/src/app/(platform)/copilot/components/ChatContainer/useAutoOpenArtifacts.ts +++ b/autogpt_platform/frontend/src/app/(platform)/copilot/components/ChatContainer/useAutoOpenArtifacts.ts @@ -26,4 +26,13 @@ export function useAutoOpenArtifacts({ resetArtifactPanel(); } }, [sessionId, resetArtifactPanel]); + + // Reset on unmount so navigating away from /copilot (to /profile, /home, + // etc.) can't leave the panel open in the Zustand store, which would then + // render the panel re-open when the user returns. See SECRT-2254/2220. + useEffect(() => { + return () => { + resetArtifactPanel(); + }; + }, [resetArtifactPanel]); }