FN-6958: stop auto-focusing Planning Mode composer

Planning Mode now opens without forcing focus into the initial text area.\n\n- Remove the open-time autofocus effect while preserving explicit textarea focus behavior.\n- Cover mobile, embedded desktop, and initialPlan handoff behavior with regression tests.\n- Add a patch changeset for the published CLI package.\n\nFiles changed:\n .changeset/FN-6958-planning-focus.md               |  5 ++\n .../dashboard/app/components/PlanningModeModal.tsx | 10 ++--\n .../__tests__/PlanningModeModal.initial.test.tsx   | 69 ++++++++++++++++++++++\n 3 files changed, 78 insertions(+), 6 deletions(-)

Fusion-Task-Id: FN-6958

Fusion-Task-Lineage: df030f3f-c9ca-4420-8618-bc3ec44d6e12
This commit is contained in:
gsxdsm
2026-06-23 10:40:37 -07:00
parent 54be35f537
commit f6e9deb13f
3 changed files with 78 additions and 6 deletions

View File

@@ -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.

View File

@@ -821,12 +821,10 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat
projectId, projectId,
]); ]);
// Focus textarea when opening /*
useEffect(() => { FNXC:PlanningFocus 2026-06-23-00:00:
if (isOpen && view.type === "initial") { 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.
textareaRef.current?.focus(); */
}
}, [isOpen, view.type]);
useEffect(() => { useEffect(() => {
if (!isOpen) { if (!isOpen) {

View File

@@ -216,6 +216,75 @@ describe("PlanningModeModal", () => {
expect(screen.queryByText("Planning Mode")).toBeNull(); 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(
<PlanningModeModal
isOpen={true}
onClose={mockOnClose}
onTaskCreated={mockOnTaskCreated}
onTasksCreated={vi.fn()}
tasks={mockTasks}
/>
);
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(
<PlanningModeModal
isOpen={true}
onClose={mockOnClose}
onTaskCreated={mockOnTaskCreated}
onTasksCreated={vi.fn()}
tasks={mockTasks}
initialPlan={undefined}
presentation="embedded"
/>
);
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(
<PlanningModeModal
isOpen={true}
onClose={mockOnClose}
onTaskCreated={mockOnTaskCreated}
onTasksCreated={vi.fn()}
tasks={mockTasks}
initialPlan="Build a login system from handoff"
/>
);
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", () => { it("mobile close path blurs focused input and resets viewport scroll", () => {
mockViewport("mobile"); mockViewport("mobile");
const scrollToSpy = vi.spyOn(window, "scrollTo").mockImplementation(() => undefined); const scrollToSpy = vi.spyOn(window, "scrollTo").mockImplementation(() => undefined);