fix(mobile-chat): restore composer draft on genuine send error (mc#2908 F7) #3034
@@ -219,6 +219,9 @@ export function MobileChat({
|
||||
const composerRef = useRef<HTMLTextAreaElement>(null);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const [pendingFiles, setPendingFiles] = useState<File[]>([]);
|
||||
// Preserve composer state so a genuine send error can restore the draft
|
||||
// and attachments instead of silently dropping them (mc#2908 F7).
|
||||
const lastComposerRef = useRef<{ text: string; files: File[] } | null>(null);
|
||||
|
||||
const {
|
||||
messages,
|
||||
@@ -254,6 +257,17 @@ export function MobileChat({
|
||||
},
|
||||
});
|
||||
|
||||
// Restore draft + attachments when a genuine send error surfaces, so the
|
||||
// user can retry without retyping (mc#2908 F7). Clear the snapshot after
|
||||
// restoring to avoid replaying on unrelated error changes.
|
||||
useEffect(() => {
|
||||
if (sendError && lastComposerRef.current) {
|
||||
setDraft(lastComposerRef.current.text);
|
||||
setPendingFiles(lastComposerRef.current.files);
|
||||
lastComposerRef.current = null;
|
||||
}
|
||||
}, [sendError, setDraft, setPendingFiles]);
|
||||
|
||||
// The agent is "thinking" when the user's send is in flight OR the workspace
|
||||
// reports an in-flight task — either way it's reachable, so clear any stale
|
||||
// "unreachable" banner the moment it's working (core#2745). Mirrors ChatTab.
|
||||
@@ -364,6 +378,9 @@ export function MobileChat({
|
||||
// shipped this; mobile reused the hook but still blocked here.)
|
||||
if ((!text && pendingFiles.length === 0) || !reachable) return;
|
||||
clearError();
|
||||
// Snapshot the composer so a subsequent genuine send error can restore
|
||||
// the draft and attachments instead of leaving the input empty (mc#2908 F7).
|
||||
lastComposerRef.current = { text, files: [...pendingFiles] };
|
||||
setDraft("");
|
||||
const files = pendingFiles;
|
||||
setPendingFiles([]);
|
||||
|
||||
@@ -762,6 +762,27 @@ describe("MobileChat — multi-send tap path (CR2 #2762)", () => {
|
||||
expect(container.textContent ?? "").not.toContain("unreachable");
|
||||
});
|
||||
});
|
||||
|
||||
it("restores composer draft after a genuine send error (mc#2908 F7)", async () => {
|
||||
vi.spyOn(api, "post").mockRejectedValueOnce(new Error("boom"));
|
||||
|
||||
const { container } = renderChat(mockAgentId);
|
||||
const ta = container.querySelector("textarea") as HTMLTextAreaElement;
|
||||
const sendBtn = container.querySelector('[aria-label="Send"]') as HTMLButtonElement;
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.change(ta, { target: { value: "retry me" } });
|
||||
});
|
||||
await act(async () => {
|
||||
sendBtn.click();
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(container.textContent ?? "").toContain("unreachable");
|
||||
});
|
||||
|
||||
// Composer should be restored so the user can retry without retyping.
|
||||
expect(ta.value).toBe("retry me");
|
||||
});
|
||||
});
|
||||
|
||||
describe("MobileChat — tool-call chain (#231 desktop parity)", () => {
|
||||
|
||||
Reference in New Issue
Block a user