diff --git a/.changeset/FN-6958-planning-focus.md b/.changeset/FN-6958-planning-focus.md new file mode 100644 index 0000000000..4e2c405b4e --- /dev/null +++ b/.changeset/FN-6958-planning-focus.md @@ -0,0 +1,5 @@ +--- +"@runfusion/fusion": patch +--- + +Stop Planning Mode from automatically focusing the initial text entry when it opens, preventing mobile keyboards from appearing until the user explicitly focuses the textarea. diff --git a/packages/dashboard/app/components/PlanningModeModal.tsx b/packages/dashboard/app/components/PlanningModeModal.tsx index f86a01efb1..49bcdb204a 100644 --- a/packages/dashboard/app/components/PlanningModeModal.tsx +++ b/packages/dashboard/app/components/PlanningModeModal.tsx @@ -821,12 +821,10 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat projectId, ]); - // Focus textarea when opening - useEffect(() => { - if (isOpen && view.type === "initial") { - textareaRef.current?.focus(); - } - }, [isOpen, view.type]); + /* + FNXC:PlanningFocus 2026-06-23-00:00: + Viewing Planning Mode must not auto-focus the initial composer because mobile browsers open the keyboard before the user chooses to type. Keep the textarea ref for autosize and explicit user focus only; populated initialPlan handoffs still auto-start through the separate effect below. + */ useEffect(() => { if (!isOpen) { diff --git a/packages/dashboard/app/components/__tests__/PlanningModeModal.initial.test.tsx b/packages/dashboard/app/components/__tests__/PlanningModeModal.initial.test.tsx index 938854a6e0..0675fd0274 100644 --- a/packages/dashboard/app/components/__tests__/PlanningModeModal.initial.test.tsx +++ b/packages/dashboard/app/components/__tests__/PlanningModeModal.initial.test.tsx @@ -216,6 +216,75 @@ describe("PlanningModeModal", () => { expect(screen.queryByText("Planning Mode")).toBeNull(); }); + it("does not auto-focus the initial textarea on mobile open until the user focuses it", () => { + mockViewport("mobile"); + + render( + + ); + + const textarea = screen.getByLabelText("What do you want to build?") as HTMLTextAreaElement; + expect(document.activeElement).not.toBe(textarea); + + act(() => { + textarea.focus(); + }); + + expect(document.activeElement).toBe(textarea); + }); + + it("does not auto-focus the initial textarea in embedded desktop presentation", () => { + render( + + ); + + const textarea = screen.getByLabelText("What do you want to build?") as HTMLTextAreaElement; + expect(textarea.value).toBe(""); + expect(document.activeElement).not.toBe(textarea); + }); + + it("auto-starts populated initialPlan handoffs without focusing the initial textarea", async () => { + const focusSpy = vi.spyOn(HTMLTextAreaElement.prototype, "focus"); + + try { + render( + + ); + + await waitFor(() => { + expect(mockStartPlanningStreaming).toHaveBeenCalledWith("Build a login system from handoff", undefined, undefined, { + planningDepth: "medium", + customQuestionCount: undefined, + }, undefined); + }); + + expect(focusSpy).not.toHaveBeenCalled(); + } finally { + focusSpy.mockRestore(); + } + }); + it("mobile close path blurs focused input and resets viewport scroll", () => { mockViewport("mobile"); const scrollToSpy = vi.spyOn(window, "scrollTo").mockImplementation(() => undefined);