fix(ui): clear chat composer after send (#89106)

This commit is contained in:
Sally O'Malley
2026-06-01 10:42:35 -04:00
committed by GitHub
parent 1b928592ef
commit c1ce51546e
3 changed files with 59 additions and 1 deletions

View File

@@ -297,6 +297,11 @@ describeControlUiE2e("Control UI mocked Gateway E2E", () => {
await page.getByRole("button", { name: "Send message" }).click();
const sendRequest = await gateway.waitForRequest("chat.send");
await expect
.poll(() => page.locator(".agent-chat__composer-combobox textarea").inputValue(), {
timeout: 10_000,
})
.toBe("");
const params = requireRecord(sendRequest.params);
expect(params.message).toBe(prompt);
expect(params.sessionKey).toBe("global");
@@ -377,6 +382,11 @@ describeControlUiE2e("Control UI mocked Gateway E2E", () => {
await page.getByRole("button", { name: "Send message" }).click();
const sendRequest = await gateway.waitForRequest("chat.send");
await expect
.poll(() => page.locator(".agent-chat__composer-combobox textarea").inputValue(), {
timeout: 10_000,
})
.toBe("");
const params = requireRecord(sendRequest.params);
const runId = requireString(params.idempotencyKey, "chat send idempotency key");

View File

@@ -1144,6 +1144,31 @@ describe("chat slash menu accessibility", () => {
expect(onSend).toHaveBeenCalledTimes(1);
});
it("clears the visible local draft immediately when send clears the host draft", () => {
let draft = "";
const container = document.createElement("div");
const onDraftChange = vi.fn((next: string) => {
draft = next;
});
const onSend = vi.fn(() => {
draft = "";
});
const renderWithDraft = () => {
render(
renderChat(createChatProps({ draft, getDraft: () => draft, onDraftChange, onSend })),
container,
);
};
renderWithDraft();
inputDraft(container, "submitted message");
container.querySelector<HTMLButtonElement>(".chat-send-btn")!.click();
expect(onDraftChange).toHaveBeenCalledWith("submitted message");
expect(onSend).toHaveBeenCalledTimes(1);
expect(container.querySelector<HTMLTextAreaElement>("textarea")?.value).toBe("");
});
it("commits local draft input before Enter sends", () => {
const onDraftChange = vi.fn();
const onSend = vi.fn();

View File

@@ -1388,6 +1388,7 @@ export function renderChat(props: ChatProps) {
};
const draftMirror = getComposerDraftMirror(props);
const visibleDraft = draftMirror.value;
let composerTextarea: HTMLTextAreaElement | null = null;
const pinned = getPinnedMessages(props.sessionKey);
const deleted = getDeletedMessages(props.sessionKey);
const hasAttachments = (props.attachments?.length ?? 0) > 0;
@@ -1624,6 +1625,21 @@ export function renderChat(props: ChatProps) {
</div>
`;
const syncComposerDraftAfterSend = (target: HTMLTextAreaElement | null) => {
const hostDraft = props.getDraft?.();
if (typeof hostDraft !== "string") {
return;
}
// Sends can clear the host draft synchronously before Lit rerenders; keep
// the local mirror aligned so the submitted text does not stay editable.
draftMirror.hostDraft = hostDraft;
draftMirror.value = hostDraft;
if (target && target.value !== hostDraft) {
target.value = hostDraft;
adjustTextareaHeight(target);
}
};
const handleKeyDown = (e: KeyboardEvent) => {
// Slash menu navigation — arg mode
if (vs.slashMenuOpen && vs.slashMenuMode === "args" && vs.slashMenuArgItems.length > 0) {
@@ -1743,6 +1759,7 @@ export function renderChat(props: ChatProps) {
const target = e.target as HTMLTextAreaElement;
commitComposerDraft(props, target.value);
props.onSend();
syncComposerDraftAfterSend(target);
}
}
};
@@ -1764,6 +1781,7 @@ export function renderChat(props: ChatProps) {
const handleSend = () => {
commitComposerDraft(props, draftMirror.value);
props.onSend();
syncComposerDraftAfterSend(composerTextarea);
};
const slashMenuVisible = isSlashMenuVisible();
const activeSlashMenuOptionId = getActiveSlashMenuOptionId();
@@ -1899,7 +1917,12 @@ export function renderChat(props: ChatProps) {
<div class="agent-chat__composer-combobox">
<textarea
${ref((el) => el && adjustTextareaHeight(el as HTMLTextAreaElement))}
${ref((el) => {
composerTextarea = el instanceof HTMLTextAreaElement ? el : null;
if (composerTextarea) {
adjustTextareaHeight(composerTextarea);
}
})}
.value=${visibleDraft}
dir=${detectTextDirection(visibleDraft)}
?disabled=${!props.connected}