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:
7
.changeset/fn-8445-planning-compact-switcher-top.md
Normal file
7
.changeset/fn-8445-planning-compact-switcher-top.md
Normal 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).
|
||||
@@ -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;
|
||||
|
||||
@@ -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*;/);
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user