diff --git a/apps/web/src/components/ai-elements/prompt-input.test.tsx b/apps/web/src/components/ai-elements/prompt-input.test.tsx
index 1aafcdffd..838e54ece 100644
--- a/apps/web/src/components/ai-elements/prompt-input.test.tsx
+++ b/apps/web/src/components/ai-elements/prompt-input.test.tsx
@@ -151,6 +151,34 @@ describe('PromptInput', () => {
expect(textarea).toHaveFocus();
});
+ it('reports unsupported files in a mixed selection', () => {
+ const onError = vi.fn();
+ const prompt = (
+ {}} onError={onError}>
+
+
+ );
+ const { container } = render(prompt);
+
+ fireEvent.change(container.querySelector('input[type="file"]')!, {
+ target: {
+ files: [
+ new File(['notes'], 'notes.txt', { type: 'text/plain' }),
+ new File(['unsupported'], 'attachment.bin', {
+ type: 'application/octet-stream',
+ }),
+ ],
+ },
+ });
+
+ expect(screen.getByTestId('attachment-count')).toHaveTextContent('1');
+ expect(onError).toHaveBeenCalledWith({
+ code: 'accept',
+ message:
+ 'Some files were not added because their types are not supported.',
+ });
+ });
+
it('accepts attachments that match extension-based filters', () => {
const { container } = render(
{}}>
diff --git a/apps/web/src/components/ai-elements/prompt-input.tsx b/apps/web/src/components/ai-elements/prompt-input.tsx
index 3fa573a8b..a7d056bd3 100644
--- a/apps/web/src/components/ai-elements/prompt-input.tsx
+++ b/apps/web/src/components/ai-elements/prompt-input.tsx
@@ -347,13 +347,15 @@ export const PromptInput = ({
const incoming = Array.from(fileList);
const accepted = incoming.filter((f) => matchesAccept(f));
- if (incoming.length && accepted.length === 0) {
+ if (incoming.length > accepted.length) {
onError?.({
code: 'accept',
- message: 'No files match the accepted types.',
+ message: accepted.length
+ ? 'Some files were not added because their types are not supported.'
+ : 'No files match the accepted types.',
});
- return;
+ if (accepted.length === 0) return;
}
const withinSize = (f: File) =>
@@ -424,13 +426,15 @@ export const PromptInput = ({
const incoming = Array.from(fileList);
const accepted = incoming.filter((f) => matchesAccept(f));
- if (incoming.length && accepted.length === 0) {
+ if (incoming.length > accepted.length) {
onError?.({
code: 'accept',
- message: 'No files match the accepted types.',
+ message: accepted.length
+ ? 'Some files were not added because their types are not supported.'
+ : 'No files match the accepted types.',
});
- return;
+ if (accepted.length === 0) return;
}
const withinSize = (f: File) =>
diff --git a/apps/web/src/components/tasks/TaskPromptInput.client.test.tsx b/apps/web/src/components/tasks/TaskPromptInput.client.test.tsx
index f84c92eac..2e8f267e5 100644
--- a/apps/web/src/components/tasks/TaskPromptInput.client.test.tsx
+++ b/apps/web/src/components/tasks/TaskPromptInput.client.test.tsx
@@ -1,4 +1,5 @@
import { useState } from 'react';
+import { toast } from 'sonner';
import {
createEvent,
fireEvent,
@@ -47,6 +48,30 @@ function KeyboardPrompt({
}
describe('TaskPromptInput', () => {
+ it('reports rejected attachments without clearing the prompt', () => {
+ const errorToast = vi.spyOn(toast, 'error').mockReturnValue(0);
+ try {
+ const { container } = renderPromptInput();
+ fireEvent.change(container.querySelector('input[type="file"]')!, {
+ target: {
+ files: [
+ new File(['unsupported'], 'attachment.bin', {
+ type: 'application/octet-stream',
+ }),
+ ],
+ },
+ });
+
+ expect(errorToast).toHaveBeenCalledWith(
+ 'No files match the accepted types.',
+ );
+ expect(screen.getByRole('textbox')).toHaveValue('Fix the login bug');
+ expect(screen.queryByText('attachment.bin')).not.toBeInTheDocument();
+ } finally {
+ errorToast.mockRestore();
+ }
+ });
+
it('explains why the send button is disabled when hovering it', async () => {
const reason = 'Create an environment before starting a task.';
diff --git a/apps/web/src/components/tasks/TaskPromptInput.tsx b/apps/web/src/components/tasks/TaskPromptInput.tsx
index 02d14119e..bbfcc27ba 100644
--- a/apps/web/src/components/tasks/TaskPromptInput.tsx
+++ b/apps/web/src/components/tasks/TaskPromptInput.tsx
@@ -1,4 +1,5 @@
import type { ReactNode } from 'react';
+import { toast } from 'sonner';
import { BasicTooltip, SendHorizontal } from '@/components/system';
import {
@@ -162,6 +163,7 @@ export function TaskPromptInput({
toast.error(error.message)}
clearOnSubmit={false}
accept={ROOMOTE_FILE_ATTACHMENT_ACCEPT}
multiple