diff --git a/.changeset/fn-8445-planning-compact-switcher-top.md b/.changeset/fn-8445-planning-compact-switcher-top.md new file mode 100644 index 0000000000..ad15ef7b69 --- /dev/null +++ b/.changeset/fn-8445-planning-compact-switcher-top.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": patch +--- + +summary: Keep Planning Mode compact interview view tabs pinned to the top on Answered questions. +category: fix +dev: Compact column order/CSS pin for .planning-compact-pane-switcher under history pane (FN-8445). diff --git a/packages/dashboard/app/components/PlanningModeModal.css b/packages/dashboard/app/components/PlanningModeModal.css index f4451ea142..8d4f469643 100644 --- a/packages/dashboard/app/components/PlanningModeModal.css +++ b/packages/dashboard/app/components/PlanningModeModal.css @@ -550,6 +550,16 @@ The New session button must look EXACTLY like Missions' primary sidebar create b flex-direction: column; } + /* + FNXC:PlanningModeCompactSwitcher 2026-07-20-11:00: + FN-8445 requires compact interview tabs to remain directly below the Planning header for + Question, Running plan, and Answered questions. History renders before the switcher in DOM, + so visual flex order pins the shared chrome above its scrollable sibling on mobile and short shells. + */ + .planning-modal-body--compact-interview .planning-compact-pane-switcher { + order: -1; + } + .planning-compact-pane-switcher { display: flex; flex: 0 0 auto; @@ -1513,6 +1523,15 @@ Tablet embedded Planning keeps the desktop two-pane shell, so the summary footer flex-direction: column; } + /* + FNXC:PlanningModeCompactSwitcher 2026-07-20-11:00: + FN-8445 keeps every compact interview pane's shared tabs above Answered questions on tablet, + where history otherwise grows before the later DOM sibling in the column. + */ + .planning-modal-body--compact-interview .planning-compact-pane-switcher { + order: -1; + } + .planning-compact-pane-switcher { display: flex; flex: 0 0 auto; diff --git a/packages/dashboard/app/components/__tests__/PlanningModeModal.css.test.ts b/packages/dashboard/app/components/__tests__/PlanningModeModal.css.test.ts index 2ee80b0a45..04d0d13a1e 100644 --- a/packages/dashboard/app/components/__tests__/PlanningModeModal.css.test.ts +++ b/packages/dashboard/app/components/__tests__/PlanningModeModal.css.test.ts @@ -75,12 +75,19 @@ describe("PlanningModeModal CSS responsive action contract", () => { const tabletCss = getMediaBlocks(css, TABLET_SUMMARY_ACTIONS_QUERY).join("\n"); expect(findRule(shortShellCss, ".planning-modal-body--compact-interview")).toMatch(/flex-direction\s*:\s*column\s*;/); - expect(findRule(shortShellCss, ".planning-compact-pane-switcher")).toMatch(/display\s*:\s*flex\s*;/); + expectSomeRule(shortShellCss, ".planning-compact-pane-switcher", /display\s*:\s*flex\s*;/); + /* + FNXC:PlanningModeCompactSwitcher 2026-07-20-11:00: + FN-8445 keeps the tabs above Answered questions despite its earlier DOM position. jsdom + cannot measure flex layout, so both compact media contracts assert the visual-order pin. + */ + expect(findRule(shortShellCss, ".planning-modal-body--compact-interview .planning-compact-pane-switcher")).toMatch(/order\s*:\s*-1\s*;/); expect(findRule(shortShellCss, ".planning-compact-pane-switcher .btn")).toMatch(/min-height\s*:\s*calc\(var\(--space-md\) \* 2\.25\)\s*;/); expect(shortShellCss).toContain(".planning-modal-body--compact-question .planning-running-plan"); expect(shortShellCss).toContain(".planning-modal-body--compact-plan .planning-detail"); expect(shortShellCss).toContain(".planning-modal-body--compact-history .planning-detail"); expect(findRule(tabletCss, ".planning-modal-body--compact-interview")).toMatch(/flex-direction\s*:\s*column\s*;/); + expect(findRule(tabletCss, ".planning-modal-body--compact-interview .planning-compact-pane-switcher")).toMatch(/order\s*:\s*-1\s*;/); expect(tabletCss).toContain(".planning-modal-body--compact-question .planning-running-plan"); expectSomeRule(css, ".planning-running-plan", /flex\s*:\s*0 1 24rem\s*;/); diff --git a/packages/dashboard/app/components/__tests__/PlanningModeModal.planning-flow.test.tsx b/packages/dashboard/app/components/__tests__/PlanningModeModal.planning-flow.test.tsx index 41f5ce373b..68cbb34a6f 100644 --- a/packages/dashboard/app/components/__tests__/PlanningModeModal.planning-flow.test.tsx +++ b/packages/dashboard/app/components/__tests__/PlanningModeModal.planning-flow.test.tsx @@ -3832,17 +3832,22 @@ describe("PlanningModeModal", () => { }); }); - it("uses progressive interview controls on tablet and short-landscape phone sessions", async () => { - for (const viewport of ["tablet", "short-landscape"] as const) { - if (viewport === "tablet") mockViewport("tablet"); - else mockShortLandscapePhone(); + /* + FNXC:PlanningModeCompactSwitcher 2026-07-20-11:00: + FN-8445 preserves the mounted compact switcher across all three interview panes and every + compact shell. CSS source assertions prove flex order; this test proves each rendered surface. + */ + it("keeps progressive interview controls available across tablet, mobile, and short-landscape panes", async () => { + for (const viewport of ["tablet", "mobile", "short-landscape"] as const) { + if (viewport === "short-landscape") mockShortLandscapePhone(); + else mockViewport(viewport); mockFetchAiSession.mockResolvedValueOnce({ id: `session-${viewport}`, type: "planning", status: "awaiting_input", title: "Responsive planning session", inputPayload: JSON.stringify({ initialPlan: "Responsive plan prompt" }), - conversationHistory: "[]", + conversationHistory: JSON.stringify([{ question: mockQuestion, response: { [mockQuestion.id]: "Small" } }]), result: JSON.stringify(mockSummary), thinkingOutput: "", projectId: null, @@ -3850,8 +3855,9 @@ describe("PlanningModeModal", () => { }); const rendered = render(); - await screen.findByText("What is the scope?"); + await screen.findByTestId("planning-question-text"); expect(screen.getByRole("button", { name: "Question" })).toHaveAttribute("aria-pressed", "true"); + expect(screen.getByRole("navigation", { name: "Planning interview panels" })).toBeVisible(); expect(screen.getByRole("button", { name: "Running plan" })).toBeDefined(); expect(screen.getByRole("button", { name: "Answered questions" })).toBeDefined(); expect(screen.getByRole("button", { name: "Next question" })).toBeDefined(); @@ -3868,7 +3874,8 @@ describe("PlanningModeModal", () => { fireEvent.click(screen.getByRole("button", { name: "Answered questions" })); expect(rendered.container.querySelector(".planning-modal-body")).toHaveClass("planning-modal-body--compact-history"); - expect(screen.getByRole("complementary", { name: "Answered questions" })).toBeVisible(); + expect(screen.getByRole("navigation", { name: "Planning interview panels" })).toBeVisible(); + expect(within(screen.getByRole("complementary", { name: "Answered questions" })).getByText(mockQuestion.question)).toBeVisible(); fireEvent.click(screen.getByRole("button", { name: "Question" })); expect(rendered.container.querySelector(".planning-modal-body")).toHaveClass("planning-modal-body--compact-question");