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;
|
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 {
|
.planning-compact-pane-switcher {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
@@ -1513,6 +1523,15 @@ Tablet embedded Planning keeps the desktop two-pane shell, so the summary footer
|
|||||||
flex-direction: column;
|
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 {
|
.planning-compact-pane-switcher {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
|
|||||||
@@ -75,12 +75,19 @@ describe("PlanningModeModal CSS responsive action contract", () => {
|
|||||||
const tabletCss = getMediaBlocks(css, TABLET_SUMMARY_ACTIONS_QUERY).join("\n");
|
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-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(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-question .planning-running-plan");
|
||||||
expect(shortShellCss).toContain(".planning-modal-body--compact-plan .planning-detail");
|
expect(shortShellCss).toContain(".planning-modal-body--compact-plan .planning-detail");
|
||||||
expect(shortShellCss).toContain(".planning-modal-body--compact-history .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")).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");
|
expect(tabletCss).toContain(".planning-modal-body--compact-question .planning-running-plan");
|
||||||
|
|
||||||
expectSomeRule(css, ".planning-running-plan", /flex\s*:\s*0 1 24rem\s*;/);
|
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) {
|
FNXC:PlanningModeCompactSwitcher 2026-07-20-11:00:
|
||||||
if (viewport === "tablet") mockViewport("tablet");
|
FN-8445 preserves the mounted compact switcher across all three interview panes and every
|
||||||
else mockShortLandscapePhone();
|
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({
|
mockFetchAiSession.mockResolvedValueOnce({
|
||||||
id: `session-${viewport}`,
|
id: `session-${viewport}`,
|
||||||
type: "planning",
|
type: "planning",
|
||||||
status: "awaiting_input",
|
status: "awaiting_input",
|
||||||
title: "Responsive planning session",
|
title: "Responsive planning session",
|
||||||
inputPayload: JSON.stringify({ initialPlan: "Responsive plan prompt" }),
|
inputPayload: JSON.stringify({ initialPlan: "Responsive plan prompt" }),
|
||||||
conversationHistory: "[]",
|
conversationHistory: JSON.stringify([{ question: mockQuestion, response: { [mockQuestion.id]: "Small" } }]),
|
||||||
result: JSON.stringify(mockSummary),
|
result: JSON.stringify(mockSummary),
|
||||||
thinkingOutput: "",
|
thinkingOutput: "",
|
||||||
projectId: null,
|
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}`} />);
|
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("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: "Running plan" })).toBeDefined();
|
||||||
expect(screen.getByRole("button", { name: "Answered questions" })).toBeDefined();
|
expect(screen.getByRole("button", { name: "Answered questions" })).toBeDefined();
|
||||||
expect(screen.getByRole("button", { name: "Next question" })).toBeDefined();
|
expect(screen.getByRole("button", { name: "Next question" })).toBeDefined();
|
||||||
@@ -3868,7 +3874,8 @@ describe("PlanningModeModal", () => {
|
|||||||
|
|
||||||
fireEvent.click(screen.getByRole("button", { name: "Answered questions" }));
|
fireEvent.click(screen.getByRole("button", { name: "Answered questions" }));
|
||||||
expect(rendered.container.querySelector(".planning-modal-body")).toHaveClass("planning-modal-body--compact-history");
|
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" }));
|
fireEvent.click(screen.getByRole("button", { name: "Question" }));
|
||||||
expect(rendered.container.querySelector(".planning-modal-body")).toHaveClass("planning-modal-body--compact-question");
|
expect(rendered.container.querySelector(".planning-modal-body")).toHaveClass("planning-modal-body--compact-question");
|
||||||
|
|||||||
Reference in New Issue
Block a user