fix(site): isolate draft prompts per conversation (#23469)

This commit is contained in:
Danielle Maywood
2026-03-24 01:05:19 +00:00
committed by GitHub
parent 82f965a0ae
commit a8e7cc10b6
2 changed files with 40 additions and 19 deletions
@@ -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");
+22 -19
View File
@@ -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<string>).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 <AgentDetail key={agentId} />;
};
export default KeyedAgentDetail;