Compare commits

...

1 Commits

Author SHA1 Message Date
Nicholas Tindle
db014cf7a2 fix(frontend/copilot): close artifact panel on copilot page unmount
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) <noreply@anthropic.com>
2026-04-17 16:00:23 -05:00
2 changed files with 58 additions and 0 deletions

View File

@@ -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);
});
});

View File

@@ -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]);
}