diff --git a/apps/web/src/components/PreviewDrawOverlay.tsx b/apps/web/src/components/PreviewDrawOverlay.tsx
index 9c8955faa8..7d3750cb6d 100644
--- a/apps/web/src/components/PreviewDrawOverlay.tsx
+++ b/apps/web/src/components/PreviewDrawOverlay.tsx
@@ -364,6 +364,9 @@ export function PreviewDrawOverlay({
const shouldCapture = hasInk || hasTarget || captureViewport;
const canSubmit = shouldCapture || Boolean(note.trim());
if (sending || !canSubmit) return;
+ // While a task is running the primary Send is disabled (use Queue instead).
+ // The note/attachment is not lost: Queue still stages it for the next turn.
+ if (action === 'send' && sendDisabled) return;
setCaptureWarning(null);
setPendingAction(action);
try {
@@ -425,7 +428,7 @@ export function PreviewDrawOverlay({
const overlayPointer = active ? 'auto' : 'none';
const showCanvas = active || hasInk;
const canSubmit = hasInk || Boolean(captureTarget) || captureViewport || Boolean(note.trim());
- const canSend = canSubmit;
+ const canSend = canSubmit && !sendDisabled;
const canUndo = undoCount > 0 && !sending;
const canRedo = redoCount > 0 && !sending;
diff --git a/apps/web/tests/components/FileViewer.test.tsx b/apps/web/tests/components/FileViewer.test.tsx
index 76f5856e29..d520c99cd6 100644
--- a/apps/web/tests/components/FileViewer.test.tsx
+++ b/apps/web/tests/components/FileViewer.test.tsx
@@ -1722,7 +1722,7 @@ describe('FileViewer tweaks toolbar', () => {
expect((screen.getByTestId('artifact-preview-frame') as HTMLIFrameElement).srcdoc).toBe(frame.srcdoc);
});
- it('lets Draw direct send emit a queued annotation while a task is running', async () => {
+ it('disables Draw direct send during a run but keeps Queue available so the annotation is not lost', async () => {
const annotationSpy = vi.fn();
window.addEventListener(ANNOTATION_EVENT, annotationSpy);
@@ -1738,17 +1738,22 @@ describe('FileViewer tweaks toolbar', () => {
target: { value: 'mark this' },
});
+ // While a task is running the primary Send is disabled; Queue stays available
+ // so the annotation is staged for the next turn rather than sent mid-run.
+ const send = screen.getByRole('button', { name: 'Send' }) as HTMLButtonElement;
+ expect(send.disabled).toBe(true);
const queue = screen.getByRole('button', { name: 'Queue' }) as HTMLButtonElement;
expect(queue.disabled).toBe(false);
- const send = screen.getByRole('button', { name: 'Send' }) as HTMLButtonElement;
- expect(send.disabled).toBe(false);
fireEvent.click(send);
+ expect(annotationSpy).not.toHaveBeenCalled();
+
+ fireEvent.click(queue);
await waitFor(() => expect(annotationSpy).toHaveBeenCalledTimes(1));
expect(annotationSpy.mock.calls[0]?.[0]).toMatchObject({
detail: {
- action: 'send',
+ action: 'queue',
note: 'mark this',
filePath: 'preview.html',
},
diff --git a/apps/web/tests/components/PreviewDrawOverlay.test.tsx b/apps/web/tests/components/PreviewDrawOverlay.test.tsx
index 6152ddf3b8..61c1f14526 100644
--- a/apps/web/tests/components/PreviewDrawOverlay.test.tsx
+++ b/apps/web/tests/components/PreviewDrawOverlay.test.tsx
@@ -61,6 +61,65 @@ describe('PreviewDrawOverlay', () => {
}
});
+ it('does not direct-send via Enter while a task is running', () => {
+ const annotation = vi.fn();
+ window.addEventListener('opendesign:annotation', annotation);
+
+ try {
+ const { container } = render(
+
+
+ ,
+ );
+
+ const input = container.querySelector('.preview-draw-note-input');
+ expect(input).toBeTruthy();
+
+ fireEvent.change(input!, { target: { value: 'Please inspect this panel.' } });
+ fireEvent.keyDown(input!, { key: 'Enter' });
+
+ expect(annotation).not.toHaveBeenCalled();
+ } finally {
+ window.removeEventListener('opendesign:annotation', annotation);
+ }
+ });
+
+ it('disables the primary Send action while a task is running', () => {
+ const { getByRole } = render(
+
+
+ ,
+ );
+
+ const sendButton = getByRole('button', { name: 'Send' });
+ expect((sendButton as HTMLButtonElement).disabled).toBe(true);
+ });
+
+ it('keeps Queue available so an annotation is not lost while a task is running', async () => {
+ const annotation = vi.fn();
+ window.addEventListener('opendesign:annotation', annotation);
+
+ try {
+ const { container, getByRole } = render(
+
+
+ ,
+ );
+
+ const input = container.querySelector('.preview-draw-note-input');
+ fireEvent.change(input!, { target: { value: 'Queue this up.' } });
+
+ const queueButton = getByRole('button', { name: 'Queue' });
+ expect((queueButton as HTMLButtonElement).disabled).toBe(false);
+ fireEvent.click(queueButton);
+
+ await waitFor(() => expect(annotation).toHaveBeenCalledTimes(1));
+ expect(annotation.mock.calls[0]?.[0].detail).toMatchObject({ action: 'queue' });
+ } finally {
+ window.removeEventListener('opendesign:annotation', annotation);
+ }
+ });
+
it('clears transient ink when draw mode exits', async () => {
const { container, rerender } = render(