FN-8445: pin compact planning switcher above history

Keep compact Planning Mode interview tabs above the answered-question history across responsive layouts.

- Pin the shared compact pane switcher ahead of history in short-shell and tablet flex layouts.
- Cover the visual-order contract and mounted controls across tablet, mobile, and short-landscape panes.
- Add a patch changeset for the Planning Mode fix.

Files changed:
 .changeset/fn-8445-planning-compact-switcher-top.md |  7 +++++++
 .../dashboard/app/components/PlanningModeModal.css  | 19 +++++++++++++++++++
 .../__tests__/PlanningModeModal.css.test.ts         |  9 ++++++++-
 .../PlanningModeModal.planning-flow.test.tsx        | 21 ++++++++++++++-------
 4 files changed, 48 insertions(+), 8 deletions(-)

Fusion-Task-Id: FN-8445

Fusion-Task-Lineage: 7aa99bc7-1ad6-46ff-8ff9-4628ccefd705

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
gsxdsm
2026-07-20 14:09:26 -07:00
parent 8c347981e4
commit 22db40cba5
4 changed files with 48 additions and 8 deletions

View File

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

View File

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

View File

@@ -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*;/);

View File

@@ -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(<PlanningModeModal isOpen={true} onClose={mockOnClose} onTaskCreated={mockOnTaskCreated} onTasksCreated={vi.fn()} tasks={mockTasks} resumeSessionId={`session-${viewport}`} />);
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");