From d8f3cc64aec9e43355f509a418505b37d549ac21 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Wed, 1 Jul 2026 01:04:02 -0700 Subject: [PATCH] FN-7356: fix mobile chat send taps Make task chat and planner chat submit reliably from mobile send buttons. - Submit touch and pen send-button interactions on pointer down before soft-keyboard blur can consume the tap. - Add synchronous duplicate-send guards for task chat and planner chat send flows. - Cover one-tap mobile send behavior and duplicate prevention in chat component tests. - Add a patch changeset for the published Fusion package. Files changed: .changeset/fn-7356-mobile-chat-send.md | 7 +++ packages/dashboard/app/components/TaskChatTab.tsx | 25 +++++++- .../app/components/TaskPlannerChatTab.tsx | 36 +++++++++++- .../app/components/__tests__/TaskChatTab.test.tsx | 65 +++++++++++++++++++++ .../__tests__/TaskPlannerChatTab.test.tsx | 67 +++++++++++++++++++++- 5 files changed, 194 insertions(+), 6 deletions(-) Fusion-Task-Id: FN-7356 Fusion-Task-Lineage: 90b243e0-ae36-459d-bf01-112219e7865b Co-authored-by: Fusion (runfusion.ai) --- .changeset/fn-7356-mobile-chat-send.md | 7 ++ .../dashboard/app/components/TaskChatTab.tsx | 25 ++++++- .../app/components/TaskPlannerChatTab.tsx | 36 +++++++++- .../components/__tests__/TaskChatTab.test.tsx | 65 ++++++++++++++++++ .../__tests__/TaskPlannerChatTab.test.tsx | 67 ++++++++++++++++++- 5 files changed, 194 insertions(+), 6 deletions(-) create mode 100644 .changeset/fn-7356-mobile-chat-send.md diff --git a/.changeset/fn-7356-mobile-chat-send.md b/.changeset/fn-7356-mobile-chat-send.md new file mode 100644 index 0000000000..9cd3b79f5d --- /dev/null +++ b/.changeset/fn-7356-mobile-chat-send.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": patch +--- + +summary: Make mobile task-detail chat Send buttons submit on the first tap. +category: fix +dev: Adds touch-first send handling for Activity steering, done-task refinement, and planner Chat composers. diff --git a/packages/dashboard/app/components/TaskChatTab.tsx b/packages/dashboard/app/components/TaskChatTab.tsx index 3671e39de3..cd41fa2211 100644 --- a/packages/dashboard/app/components/TaskChatTab.tsx +++ b/packages/dashboard/app/components/TaskChatTab.tsx @@ -569,6 +569,7 @@ export function TaskChatTab({ task, projectId, active, addToast, onTaskUpdated, const { entries, loading, loadMore, hasMore, loadingMore } = useAgentLogs(task.id, active, projectId); const [draft, setDraft] = useState(""); const [sending, setSending] = useState(false); + const sendingRef = useRef(false); const [optimisticMessages, setOptimisticMessages] = useState([]); const [isTranscriptAtBottom, setIsTranscriptAtBottom] = useState(true); const transcriptRef = useRef(null); @@ -782,7 +783,8 @@ export function TaskChatTab({ task, projectId, active, addToast, onTaskUpdated, const handleSubmit = useCallback(async (event?: React.FormEvent) => { event?.preventDefault(); const text = draft.trim(); - if (!text || sending) return; + if (!text || sendingRef.current) return; + sendingRef.current = true; const latestTimestampMs = getLatestTranscriptTimestampMs(entries, userMessages); const optimisticCreatedAtMs = Math.max(Date.now(), latestTimestampMs + 1); @@ -826,9 +828,10 @@ export function TaskChatTab({ task, projectId, active, addToast, onTaskUpdated, setOptimisticMessages((current) => current.filter((message) => message.id !== optimisticMessage.id)); addToast(`Unable to send message: ${getErrorMessage(error)}`, "error"); } finally { + sendingRef.current = false; setSending(false); } - }, [addToast, draft, entries, isDoneTask, onTaskUpdated, projectId, sending, task.id, userMessages]); + }, [addToast, draft, entries, isDoneTask, onTaskUpdated, projectId, task.id, userMessages]); /** * FNXC:TaskDetailChat 2026-06-13-19:05: @@ -843,6 +846,22 @@ export function TaskChatTab({ task, projectId, active, addToast, onTaskUpdated, void handleSubmit(); }, [handleSubmit]); + /* + FNXC:TaskDetailChat 2026-07-01-00:00: + Mobile soft keyboards can blur the focused composer textarea before the Send button receives a click, consuming the first tap. Touch/pen pointer-down submits immediately while the synchronous sendingRef guard preserves empty/disabled and duplicate-send behavior; mouse down only preserves focus so desktop click and keyboard submit semantics remain unchanged. + */ + const handleSendPointerDown = useCallback((event: React.PointerEvent) => { + if (event.pointerType === "mouse") return; + if (!canSend) return; + event.preventDefault(); + void handleSubmit(); + }, [canSend, handleSubmit]); + + const handleSendMouseDown = useCallback((event: React.MouseEvent) => { + if (!canSend) return; + event.preventDefault(); + }, [canSend]); + return (
{onToggleExpanded ? ( @@ -956,6 +975,8 @@ export function TaskChatTab({ task, projectId, active, addToast, onTaskUpdated, disabled={!canSend} aria-label={sending ? t("taskChat.sending", "Sending") : t("common:actions.send", "Send")} title={sending ? t("taskChat.sending", "Sending") : t("common:actions.send", "Send")} + onPointerDown={handleSendPointerDown} + onMouseDown={handleSendMouseDown} > {sending ?