fix(site/src/pages/AgentsPage): preserve chat input typed while loading (#26415)

This commit is contained in:
Danielle Maywood
2026-06-17 09:58:34 +01:00
committed by GitHub
parent d638b1aaed
commit cf0c9ce16b
4 changed files with 84 additions and 1 deletions
@@ -324,6 +324,9 @@ describe("useConversationEditingState", () => {
);
});
expect(localStorage.getItem(expectedKey)).toBe("work in progress");
// handleContentChange persists only; it must not advance the seed.
expect(result.current.editorInitialValue).toBe("");
expect(result.current.initialEditorState).toBeUndefined();
act(() => {
// Even though the serialized state is non-empty (Lexical always
@@ -336,6 +339,27 @@ describe("useConversationEditingState", () => {
unmount();
});
it("carries a draft typed during loading into the seed for the loaded editor", () => {
const { result, unmount } = renderEditing();
// handleContentChange persists but does not advance the seed.
const editorState =
'{"root":{"children":[{"text":"typed while loading"}]}}';
act(() => {
result.current.handleLoadingDraftChange(
"typed while loading",
editorState,
false,
);
});
expect(localStorage.getItem(expectedKey)).toBe(editorState);
expect(result.current.editorInitialValue).toBe("typed while loading");
expect(result.current.initialEditorState).toBe(editorState);
unmount();
});
it("loads edit text into the composer and restores the prior draft on cancel without refocusing", () => {
const { result, unmount } = renderEditing();
@@ -569,6 +569,21 @@ export function useConversationEditingState(deps: {
}
};
// Separate from handleContentChange, which avoids setState to prevent
// per-keystroke re-renders. The loading editor is a different instance
// that unmounts on load, so the seed must advance here.
const handleLoadingDraftChange = (
content: string,
serializedEditorState: string,
hasFileReferences: boolean,
) => {
handleContentChange(content, serializedEditorState, hasFileReferences);
setDraftState({
editorInitialValue: content,
initialEditorState: serializedEditorState,
});
};
return {
inputValueRef,
chatInputRef,
@@ -584,6 +599,7 @@ export function useConversationEditingState(deps: {
handleCancelQueueEdit,
handleSendFromInput,
handleContentChange,
handleLoadingDraftChange,
};
}
@@ -1548,6 +1564,11 @@ const AgentChatPage: FC = () => {
preferencesQuery.isLoading,
)}
titleElement={titleElement}
inputRef={editing.chatInputRef}
initialValue={editing.editorInitialValue}
initialEditorState={editing.initialEditorState}
remountKey={editing.remountKey}
onContentChange={editing.handleLoadingDraftChange}
isInputDisabled={isInputDisabled}
effectiveSelectedModel={effectiveSelectedModel}
setSelectedModel={setSelectedModel}
@@ -748,6 +748,11 @@ export const Loading: Story = {
<AgentChatPageLoadingView
sendShortcut="enter"
titleElement={<title>Loading — Agents</title>}
inputRef={{ current: null }}
initialValue=""
initialEditorState={undefined}
remountKey={0}
onContentChange={fn()}
isInputDisabled
effectiveSelectedModel={defaultModelConfigID}
setSelectedModel={fn()}
@@ -767,6 +772,11 @@ export const LoadingWithModelOptions: Story = {
<AgentChatPageLoadingView
sendShortcut="enter"
titleElement={<title>Loading — Agents</title>}
inputRef={{ current: null }}
initialValue=""
initialEditorState={undefined}
remountKey={0}
onContentChange={fn()}
isInputDisabled={false}
effectiveSelectedModel={defaultModelConfigID}
setSelectedModel={fn()}
@@ -785,6 +795,11 @@ export const LoadingWithRightPanel: Story = {
<AgentChatPageLoadingView
sendShortcut="enter"
titleElement={<title>Loading — Agents</title>}
inputRef={{ current: null }}
initialValue=""
initialEditorState={undefined}
remountKey={0}
onContentChange={fn()}
isInputDisabled
effectiveSelectedModel={defaultModelConfigID}
setSelectedModel={fn()}
@@ -804,6 +819,11 @@ export const LoadingSidebarCollapsed: Story = {
<AgentChatPageLoadingView
sendShortcut="enter"
titleElement={<title>Loading — Agents</title>}
inputRef={{ current: null }}
initialValue=""
initialEditorState={undefined}
remountKey={0}
onContentChange={fn()}
isInputDisabled
effectiveSelectedModel={defaultModelConfigID}
setSelectedModel={fn()}
@@ -1016,6 +1016,15 @@ export const AgentChatPageView: FC<AgentChatPageViewProps> = ({
interface AgentChatPageLoadingViewProps {
sendShortcut: AgentChatSendShortcut;
titleElement: React.ReactNode;
inputRef: RefObject<ChatMessageInputRef | null>;
initialValue: string;
initialEditorState: string | undefined;
remountKey: number;
onContentChange: (
content: string,
serializedEditorState: string,
hasFileReferences: boolean,
) => void;
isInputDisabled: boolean;
effectiveSelectedModel: string;
setSelectedModel: (model: string) => void;
@@ -1033,6 +1042,11 @@ interface AgentChatPageLoadingViewProps {
export const AgentChatPageLoadingView: FC<AgentChatPageLoadingViewProps> = ({
sendShortcut,
titleElement,
inputRef,
initialValue,
initialEditorState,
remountKey,
onContentChange,
isInputDisabled,
effectiveSelectedModel,
setSelectedModel,
@@ -1085,7 +1099,11 @@ export const AgentChatPageLoadingView: FC<AgentChatPageLoadingViewProps> = ({
<AgentChatInput
onSend={() => {}}
sendShortcut={sendShortcut}
initialValue=""
inputRef={inputRef}
initialValue={initialValue}
initialEditorState={initialEditorState}
remountKey={remountKey}
onContentChange={onContentChange}
isDisabled={isInputDisabled}
isLoading={false}
selectedModel={effectiveSelectedModel}