From 234e74022446b7af581b8bb0db8e8bc824e8efb6 Mon Sep 17 00:00:00 2001 From: Claude Belizaire <139932571+Claudesaul@users.noreply.github.com> Date: Fri, 9 Oct 2026 00:49:09 -0400 Subject: [PATCH] fix(mobile): allow starting a task with only an image --- .../features/threads/NewTaskDraftScreen.tsx | 4 ++-- .../threads/new-task-flow-provider.tsx | 3 ++- apps/mobile/src/state/thread-outbox-model.ts | 3 ++- apps/mobile/src/state/thread-outbox.test.ts | 20 +++++++++++++++++++ 4 files changed, 26 insertions(+), 4 deletions(-) diff --git a/apps/mobile/src/features/threads/NewTaskDraftScreen.tsx b/apps/mobile/src/features/threads/NewTaskDraftScreen.tsx index 181c257cbc17..cfb12bfd72a8 100644 --- a/apps/mobile/src/features/threads/NewTaskDraftScreen.tsx +++ b/apps/mobile/src/features/threads/NewTaskDraftScreen.tsx @@ -1244,7 +1244,7 @@ export function NewTaskDraftScreen(props: { if ( attachmentBlockReason !== null || !modelSelection || - initialMessageText.length === 0 || + (initialMessageText.length === 0 && draft.attachments.length === 0) || flow.submitting || (workspaceMode === "worktree" && !selectedBranchName) ) { @@ -1397,7 +1397,7 @@ export function NewTaskDraftScreen(props: { !modelUnavailable && Boolean(flow.selectedProject) && Boolean(flow.selectedModel) && - flow.prompt.trim().length > 0 && + (flow.prompt.trim().length > 0 || flow.attachments.length > 0) && isIncomingShareReady && !isImportingShare && !flow.submitting && diff --git a/apps/mobile/src/features/threads/new-task-flow-provider.tsx b/apps/mobile/src/features/threads/new-task-flow-provider.tsx index 9a523c6bb0b0..9b0699c0f740 100644 --- a/apps/mobile/src/features/threads/new-task-flow-provider.tsx +++ b/apps/mobile/src/features/threads/new-task-flow-provider.tsx @@ -1105,7 +1105,8 @@ export function NewTaskFlowProvider(props: React.PropsWithChildren) { selectedEnvironmentServerConfig, draft.modelSelection ?? null, ) ?? selectedModel; - if (text.length === 0 || !draftModelSelection) { + // A shared image or file is a task on its own; text is optional. + if ((text.length === 0 && draft.attachments.length === 0) || !draftModelSelection) { return null; } // A saved choice from before the project went no-project must not diff --git a/apps/mobile/src/state/thread-outbox-model.ts b/apps/mobile/src/state/thread-outbox-model.ts index 57a788cf5188..3c1527e04131 100644 --- a/apps/mobile/src/state/thread-outbox-model.ts +++ b/apps/mobile/src/state/thread-outbox-model.ts @@ -247,7 +247,8 @@ export function isQueuedThreadCreationSendable(message: QueuedThreadMessage): bo if (!message.creation) { return false; } - if (message.text.trim().length === 0 || message.modelSelection === undefined) { + const hasContent = message.text.trim().length > 0 || message.attachments.length > 0; + if (!hasContent || message.modelSelection === undefined) { return false; } return message.creation.workspaceMode !== "worktree" || Boolean(message.creation.branch); diff --git a/apps/mobile/src/state/thread-outbox.test.ts b/apps/mobile/src/state/thread-outbox.test.ts index cbaffbbe5041..7cf10884f293 100644 --- a/apps/mobile/src/state/thread-outbox.test.ts +++ b/apps/mobile/src/state/thread-outbox.test.ts @@ -1430,6 +1430,26 @@ describe("thread outbox", () => { false, ); expect(isQueuedThreadCreationSendable(base)).toBe(false); + expect(isQueuedThreadCreationSendable({ ...creationMessage, text: " " })).toBe(false); + expect( + isQueuedThreadCreationSendable({ + ...creationMessage, + text: "", + attachments: [ + { + id: "image-1", + type: "image", + name: "photo.png", + mimeType: "image/png", + sizeBytes: 3, + fileUri: "file:///documents/t3-composer-attachments/photo.png", + previewUri: "file:///documents/t3-composer-attachments/photo.png", + uploadedAttachmentId: "pending-photo-png", + uploadEnvironmentId: EnvironmentId.make("environment-1"), + }, + ], + }), + ).toBe(true); }); it("retries transport failures but drops deterministic command failures", () => {