FN-8002: suppress reconnecting hint on planning question screen

Gate Planning Mode's "Reconnecting…" indicator to the loading view so persisted awaiting-input questions stay free of transient SSE reconnect noise.

- Show planning.reconnecting only when view.type is "loading"
- Cover desktop and mobile resumed question screens without the hint
- Keep the hint during active generation loading reconnects
- Add patch changeset for the user-facing fix

Files changed:
 .changeset/FN-8002-planning-reconnecting-hint.md   |  7 +++
 .../dashboard/app/components/PlanningModeModal.tsx |  6 +-
 .../PlanningModeModal.planning-flow.test.tsx       | 73 ++++++++++++++++++++++
 3 files changed, 85 insertions(+), 1 deletion(-)

Fusion-Task-Id: FN-8002

Fusion-Task-Lineage: 0a8682ee-922d-4acd-b7ce-bbf4f25dfce7

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
gsxdsm
2026-07-15 17:12:32 -07:00
parent 22fde62510
commit 7daa16fe30
3 changed files with 85 additions and 1 deletions

View File

@@ -0,0 +1,7 @@
---
"@runfusion/fusion": patch
---
summary: Suppress the Planning Mode reconnecting hint on persisted question screens.
category: fix
dev: Gate the `planning.reconnecting` indicator in `PlanningModeModal` to `view.type === "loading"` so idle `awaiting_input`/DB-backed views render purely from persisted state.

View File

@@ -2174,7 +2174,11 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat
<div className="planning-detail">
{error && <div className="form-error planning-error">{error}</div>}
{isReconnecting && <div className="form-hint text-muted">{t("planning.reconnecting", "Reconnecting…")}</div>}
{/*
FNXC:PlanningMode 2026-07-15-00:00:
Awaiting-input questions are persisted database state, so transient idle SSE reconnects must not imply that the question is being regenerated. Reserve this hint for the active loading view, where live generation genuinely depends on the stream.
*/}
{isReconnecting && view.type === "loading" && <div className="form-hint text-muted">{t("planning.reconnecting", "Reconnecting…")}</div>}
{view.type === "initial" && (
<div className="planning-initial">

View File

@@ -1935,6 +1935,79 @@ describe("PlanningModeModal", () => {
expect(screen.queryByRole("button", { name: "Refine Further" })).toBeNull();
});
it.each(["desktop", "mobile"] as const)("keeps persisted awaiting-input questions free of reconnecting hints on %s", async (viewportMode) => {
mockViewport(viewportMode);
let streamHandlers: any;
mockConnectPlanningStream.mockImplementationOnce((_sessionId: string, _projectId: string | undefined, handlers: any) => {
streamHandlers = handlers;
return { close: vi.fn(), isConnected: vi.fn().mockReturnValue(true) };
});
mockFetchAiSession.mockResolvedValueOnce({
id: `session-reconnect-question-${viewportMode}`,
type: "planning",
status: "awaiting_input",
title: "Persisted question",
inputPayload: JSON.stringify({ initialPlan: "Resume persisted question" }),
conversationHistory: "[]",
currentQuestion: JSON.stringify(mockQuestion),
result: null,
thinkingOutput: "",
error: null,
projectId: null,
createdAt: "2026-01-01T00:00:00.000Z",
updatedAt: "2026-01-01T00:00:00.000Z",
});
render(
<PlanningModeModal
isOpen={true}
onClose={mockOnClose}
onTaskCreated={mockOnTaskCreated}
onTasksCreated={vi.fn()}
tasks={mockTasks}
resumeSessionId={`session-reconnect-question-${viewportMode}`}
/>,
);
expect(await screen.findByText(mockQuestion.question)).toBeInTheDocument();
act(() => {
streamHandlers.onConnectionStateChange?.("reconnecting");
});
expect(screen.getByText(mockQuestion.question)).toBeInTheDocument();
expect(screen.queryByText("Reconnecting…")).toBeNull();
});
it("shows the reconnecting hint while active generation is loading", async () => {
let streamHandlers: any;
mockConnectPlanningStream.mockImplementationOnce((_sessionId: string, _projectId: string | undefined, handlers: any) => {
streamHandlers = handlers;
return { close: vi.fn(), isConnected: vi.fn().mockReturnValue(true) };
});
render(
<PlanningModeModal
isOpen={true}
onClose={mockOnClose}
onTaskCreated={mockOnTaskCreated}
onTasksCreated={vi.fn()}
tasks={mockTasks}
/>,
);
fireEvent.change(screen.getByPlaceholderText(/e.g., Build a user authentication/), {
target: { value: "Generate a planning task" },
});
fireEvent.click(screen.getByText("Start Planning"));
await waitFor(() => expect(mockConnectPlanningStream).toHaveBeenCalledTimes(1));
act(() => {
streamHandlers.onConnectionStateChange?.("reconnecting");
});
expect(screen.getByText("Reconnecting…")).toBeInTheDocument();
});
it("shows summary view when resuming a complete persisted session", async () => {
const resumedSummary: PlanningSummary = {
title: "Resume-ready planning output",