frontend: fix file attach dropping the picked file (live FileList race)

The file pickers read event.target.files but reset event.target.value = ""
before the file was actually used: in the AI composer the value reset ran
before React's deferred setState updater read the (now-empty) live FileList,
so no chip appeared; in Messages the reset ran before the length check, so it
returned early. Snapshot the files into a plain array synchronously, before the
reset. This is why attaching seemed to fail (no badge) — especially while typing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Khalid Abdi
2026-06-16 21:09:15 +03:00
parent 700b461618
commit 705d4b5719
2 changed files with 15 additions and 7 deletions
+9 -4
View File
@@ -85,12 +85,17 @@ export function ChatInput({
const handleFilesSelected = useCallback(
(event: ChangeEvent<HTMLInputElement>) => {
const selected = event.target.files;
if (selected && selected.length > 0) {
setFiles((prev) => [...prev, ...Array.from(selected)]);
}
// Copy the files out NOW: `event.target.files` is a live FileList, and the
// `event.target.value = ""` reset below empties it. React runs the
// functional setState updater during a later render, so reading the
// FileList inside the updater would see it already cleared (no file added,
// no chip). Snapshot to a plain array first.
const picked = Array.from(event.target.files ?? []);
// Reset so picking the same file again still fires onChange.
event.target.value = "";
if (picked.length > 0) {
setFiles((prev) => [...prev, ...picked]);
}
},
[]
);
@@ -283,12 +283,15 @@ export function MessagesView() {
// Open the file picker; on select, upload each and stage it.
const onPickFiles = async (event: ChangeEvent<HTMLInputElement>) => {
const files = event.target.files;
// Snapshot before resetting: `event.target.files` is a live FileList that
// `event.target.value = ""` empties, so reading it afterwards (or in a later
// tick) yields nothing.
const picked = Array.from(event.target.files ?? []);
event.target.value = "";
if (!files?.length) return;
if (picked.length === 0) return;
setUploading(true);
try {
for (const file of Array.from(files)) {
for (const file of picked) {
if (file.size > MAX_ATTACHMENT_BYTES) {
notify.error(
t("messages.attach.tooLargeTitle"),