diff --git a/site/src/pages/AgentsPage/AgentDetail.test.ts b/site/src/pages/AgentsPage/AgentDetail.test.ts index 85d9e4f3ac..ca0df3ca9b 100644 --- a/site/src/pages/AgentsPage/AgentDetail.test.ts +++ b/site/src/pages/AgentsPage/AgentDetail.test.ts @@ -120,6 +120,24 @@ describe("useConversationEditingState", () => { unmount(); }); + it("initializes with the correct draft for each chatID", () => { + const chatA = "chat-aaa"; + const chatB = "chat-bbb"; + localStorage.setItem(`${draftInputStorageKeyPrefix}${chatA}`, "draft A"); + localStorage.setItem(`${draftInputStorageKeyPrefix}${chatB}`, "draft B"); + + // Each chatID should initialize with its own draft — this is + // what the key={agentId} wrapper guarantees at the component + // level (a new chatID means a full remount). + const hookA = renderEditing(chatA); + expect(hookA.result.current.editorInitialValue).toBe("draft A"); + hookA.unmount(); + + const hookB = renderEditing(chatB); + expect(hookB.result.current.editorInitialValue).toBe("draft B"); + hookB.unmount(); + }); + it("clears the draft from localStorage on successful send", async () => { localStorage.setItem(expectedKey, "draft to clear"); diff --git a/site/src/pages/AgentsPage/AgentDetail.tsx b/site/src/pages/AgentsPage/AgentDetail.tsx index 8406fff4ec..3671018c95 100644 --- a/site/src/pages/AgentsPage/AgentDetail.tsx +++ b/site/src/pages/AgentsPage/AgentDetail.tsx @@ -92,22 +92,17 @@ export function useConversationEditingState(deps: { ? `${draftInputStorageKeyPrefix}${chatID}` : null; const [editorInitialValue, setEditorInitialValue] = useState(() => { - if (typeof window === "undefined" || !draftStorageKey) { + if (!draftStorageKey) { return ""; } return localStorage.getItem(draftStorageKey) ?? ""; }); - // Sync the ref with the initial draft value so callers that - // read inputValueRef.current see the persisted draft. Uses a - // layout effect so the value is available before paint. - const initialSyncDone = useRef(false); + // Sync the ref with the editor value so callers that read + // inputValueRef.current see the persisted draft. Uses a layout + // effect so the value is available before paint. useLayoutEffect(() => { - if (!initialSyncDone.current && editorInitialValue) { - initialSyncDone.current = true; - (inputValueRef as React.MutableRefObject).current = - editorInitialValue; - } + inputValueRef.current = editorInitialValue; }, [editorInitialValue, inputValueRef]); // -- History editing state -- @@ -119,6 +114,14 @@ export function useConversationEditingState(deps: { readonly ChatMessagePart[] >([]); + // -- Queue editing state -- + const [editingQueuedMessageID, setEditingQueuedMessageID] = useState< + number | null + >(null); + const [draftBeforeQueueEdit, setDraftBeforeQueueEdit] = useState< + string | null + >(null); + const handleEditUserMessage = ( messageId: number, text: string, @@ -145,14 +148,6 @@ export function useConversationEditingState(deps: { } }; - // -- Queue editing state -- - const [editingQueuedMessageID, setEditingQueuedMessageID] = useState< - number | null - >(null); - const [draftBeforeQueueEdit, setDraftBeforeQueueEdit] = useState< - string | null - >(null); - const handleStartQueueEdit = ( id: number, text: string, @@ -918,4 +913,12 @@ const AgentDetail: FC = () => { ); }; -export default AgentDetail; +// Keyed wrapper so that navigating between agents (changing the +// :agentId param) fully remounts the component, resetting all +// internal state — drafts, editing, queries — cleanly. +const KeyedAgentDetail: FC = () => { + const { agentId } = useParams<{ agentId: string }>(); + return ; +}; + +export default KeyedAgentDetail;