fix(control-ui): keep new draft through delayed replay

This commit is contained in:
张贵萍0668001030
2026-06-21 14:38:40 +08:00
committed by Vincent Koc
parent 825fc6b106
commit 32e5fd9cc3
2 changed files with 54 additions and 16 deletions

View File

@@ -2009,6 +2009,59 @@ describe("chat slash menu accessibility", () => {
expect(onDraftChange).toHaveBeenCalledTimes(1);
});
it("keeps a new same-session draft when a delayed stale replay arrives", () => {
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();
const textarea = container.querySelector<HTMLTextAreaElement>("textarea");
expect(textarea?.value).toBe("");
textarea!.dispatchEvent(
new InputEvent("beforeinput", {
bubbles: true,
data: "new draft",
inputType: "insertText",
}),
);
textarea!.value = "new draft";
textarea!.dispatchEvent(
new InputEvent("input", {
bubbles: true,
data: "new draft",
inputType: "insertText",
}),
);
expect(textarea?.value).toBe("new draft");
textarea!.value = "submitted message";
textarea!.dispatchEvent(
new InputEvent("input", {
bubbles: true,
data: "submitted message",
inputType: "insertText",
}),
);
expect(textarea?.value).toBe("new draft");
expect(onDraftChange).toHaveBeenCalledTimes(1);
});
it("does not apply a stale submitted draft replay to another session", () => {
const drafts: Record<string, string> = {
"stale-replay-a": "",

View File

@@ -509,7 +509,6 @@ function renderRealtimeTalkConversation(props: ChatProps) {
type PendingClearedSubmittedDraft = {
key: string;
value: string;
inputVersion: number;
};
interface ChatEphemeralState {
@@ -526,7 +525,6 @@ interface ChatEphemeralState {
pinnedExpanded: boolean;
composerComposing: boolean;
composerInputIntentKey: string | null;
composerInputVersions: Map<string, number>;
pendingClearedSubmittedDraft: PendingClearedSubmittedDraft | null;
historyRenderSessionKey: string | null;
historyRenderMessagesRef: unknown[] | null;
@@ -556,7 +554,6 @@ function createChatEphemeralState(): ChatEphemeralState {
pinnedExpanded: false,
composerComposing: false,
composerInputIntentKey: null,
composerInputVersions: new Map(),
pendingClearedSubmittedDraft: null,
historyRenderSessionKey: null,
historyRenderMessagesRef: null,
@@ -614,12 +611,7 @@ function commitComposerDraft(props: ChatProps, value: string): void {
props.onDraftChange(value);
}
function currentComposerInputVersion(key: string): number {
return vs.composerInputVersions.get(key) ?? 0;
}
function markComposerInputIntent(key: string): void {
vs.composerInputVersions.set(key, currentComposerInputVersion(key) + 1);
vs.composerInputIntentKey = key;
}
@@ -651,12 +643,7 @@ function suppressStaleSubmittedDraftReplay(
if (!pending) {
return false;
}
if (
target.value !== pending.value ||
pending.inputVersion !== currentComposerInputVersion(pending.key) ||
hasInputIntent ||
isExplicitComposerInsertion(event)
) {
if (target.value !== pending.value || hasInputIntent || isExplicitComposerInsertion(event)) {
return false;
}
@@ -2338,7 +2325,6 @@ export function renderChat(props: ChatProps) {
vs.pendingClearedSubmittedDraft = {
key: mirrorKey,
value: submittedDraft,
inputVersion: currentComposerInputVersion(mirrorKey),
};
} else {
clearPendingClearedSubmittedDraft(mirrorKey);
@@ -2513,7 +2499,6 @@ export function renderChat(props: ChatProps) {
if (suppressStaleSubmittedDraftReplay(target, e, draftMirror, hasInputIntent)) {
return;
}
clearPendingClearedSubmittedDraft(mirrorKey);
syncComposerValue(target);
};
const handleCompositionEnd = (e: CompositionEvent) => {