diff --git a/frontend/components/chat/chat-input.tsx b/frontend/components/chat/chat-input.tsx index 480aa44..1fcc16f 100644 --- a/frontend/components/chat/chat-input.tsx +++ b/frontend/components/chat/chat-input.tsx @@ -85,12 +85,17 @@ export function ChatInput({ const handleFilesSelected = useCallback( (event: ChangeEvent) => { - 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]); + } }, [] ); diff --git a/frontend/components/messages/messages-view.tsx b/frontend/components/messages/messages-view.tsx index 6658ffe..ac194eb 100644 --- a/frontend/components/messages/messages-view.tsx +++ b/frontend/components/messages/messages-view.tsx @@ -283,12 +283,15 @@ export function MessagesView() { // Open the file picker; on select, upload each and stage it. const onPickFiles = async (event: ChangeEvent) => { - 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"),