diff --git a/.changeset/fix-mobile-planning-summary-spaces.md b/.changeset/fix-mobile-planning-summary-spaces.md new file mode 100644 index 0000000000..fdcbce5b66 --- /dev/null +++ b/.changeset/fix-mobile-planning-summary-spaces.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": patch +--- + +summary: Fix mobile Planning Mode description editing so spaces can be entered. +category: fix +dev: Guards Planning Mode summary normalization so editable fields keep normal text input. diff --git a/packages/dashboard/app/components/PlanningModeModal.tsx b/packages/dashboard/app/components/PlanningModeModal.tsx index 29d2aac418..8012151ab7 100644 --- a/packages/dashboard/app/components/PlanningModeModal.tsx +++ b/packages/dashboard/app/components/PlanningModeModal.tsx @@ -142,8 +142,13 @@ function normalizePlanningSummary(summary: PlanningSummary): PlanningSummary { const title = typeof raw.title === "string" && raw.title.trim().length > 0 ? raw.title.trim() : "Untitled planning task"; - const description = typeof raw.description === "string" && raw.description.trim().length > 0 - ? raw.description.trim() + /* + FNXC:PlanningMode 2026-06-30-12:54: + Final summary description is an editable draft. Preserve internal and trailing whitespace during render so mobile text entry can insert a space before the next word; only whitespace-only server payloads fall back to the normalized title. + */ + const rawDescription = typeof raw.description === "string" ? raw.description : ""; + const description = rawDescription.trim().length > 0 + ? rawDescription : title; return { ...summary, diff --git a/packages/dashboard/app/components/__tests__/PlanningModeModal.ui-interactions.test.tsx b/packages/dashboard/app/components/__tests__/PlanningModeModal.ui-interactions.test.tsx index e41feeeb87..b7ab1d76fa 100644 --- a/packages/dashboard/app/components/__tests__/PlanningModeModal.ui-interactions.test.tsx +++ b/packages/dashboard/app/components/__tests__/PlanningModeModal.ui-interactions.test.tsx @@ -25,6 +25,7 @@ vi.mock("../../hooks/useNavigationHistory", async (importOriginal) => { }; }); import { act, render, renderHook, screen, fireEvent, waitFor, within } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; import * as api from "../../api"; import { PlanningModeModal } from "../PlanningModeModal"; import { TaskDetailModal } from "../TaskDetailModal"; @@ -997,6 +998,25 @@ describe("PlanningModeModal", () => { }); }); + it.each([ + { viewport: "mobile" as const, typedDescription: "Mobile summary accepts spaces" }, + { viewport: "desktop" as const, typedDescription: "Desktop summary accepts spaces" }, + ])("preserves typed spaces in the $viewport summary description textarea", async ({ viewport, typedDescription }) => { + mockViewport(viewport); + const user = userEvent.setup(); + await renderPlanningSummary("Generated summary with existing words"); + + const textarea = screen.getByLabelText("Description") as HTMLTextAreaElement; + textarea.focus(); + textarea.setSelectionRange(0, textarea.value.length); + await user.type(textarea, typedDescription); + + expect(textarea.value).toContain(typedDescription); + expect(textarea.value).not.toContain(typedDescription.replaceAll(" ", "")); + expect(screen.getByRole("button", { name: /Create Single Task/i })).toBeDefined(); + expect(screen.getByRole("button", { name: /Break into Tasks/i })).toBeDefined(); + }); + it("expands and collapses the mobile summary description without breaking the adjacent markdown toggle", async () => { mockViewport("mobile"); const { container } = await renderPlanningSummary(