FN-8436: remove reconnecting status from Planning Mode
Keep Planning Mode progress feedback in the loading pane without showing transient reconnecting text. - Remove reconnecting UI state and stream-state callback handling. - Preserve loading controls and generation progress during stream recovery. - Cover recovery behavior without the reconnecting hint. Files changed: .../FN-8436-planning-no-reconnecting-hint.md | 7 ++++++ .../dashboard/app/components/PlanningModeModal.tsx | 25 ++++------------------ .../PlanningModeModal.planning-flow.test.tsx | 6 ++++-- 3 files changed, 15 insertions(+), 23 deletions(-) Fusion-Task-Id: FN-8436 Fusion-Task-Lineage: 344eb8bd-fc5c-4cb1-aed0-27453a0cb701 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
7
.changeset/FN-8436-planning-no-reconnecting-hint.md
Normal file
7
.changeset/FN-8436-planning-no-reconnecting-hint.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
"@runfusion/fusion": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
summary: Stop showing Reconnecting status text in Planning Mode.
|
||||||
|
category: fix
|
||||||
|
dev: Remove PlanningModeModal isReconnecting UI state and the planning.reconnecting form-hint; generation progress remains via the loading pane only.
|
||||||
@@ -350,7 +350,6 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat
|
|||||||
const hasLoadedPersistedRef = useRef(false);
|
const hasLoadedPersistedRef = useRef(false);
|
||||||
const [streamingOutput, setStreamingOutput] = useState<string>("");
|
const [streamingOutput, setStreamingOutput] = useState<string>("");
|
||||||
const [showThinking, setShowThinking] = useState(true);
|
const [showThinking, setShowThinking] = useState(true);
|
||||||
const [isReconnecting, setIsReconnecting] = useState(false);
|
|
||||||
const [isRetrying, setIsRetrying] = useState(false);
|
const [isRetrying, setIsRetrying] = useState(false);
|
||||||
const [isAutoRetrying, setIsAutoRetrying] = useState(false);
|
const [isAutoRetrying, setIsAutoRetrying] = useState(false);
|
||||||
const [autoRetryAttempt, setAutoRetryAttempt] = useState(0);
|
const [autoRetryAttempt, setAutoRetryAttempt] = useState(0);
|
||||||
@@ -817,7 +816,6 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat
|
|||||||
setBranchName("");
|
setBranchName("");
|
||||||
setBaseBranch("");
|
setBaseBranch("");
|
||||||
setStreamingOutput("");
|
setStreamingOutput("");
|
||||||
setIsReconnecting(false);
|
|
||||||
setIsRetrying(false);
|
setIsRetrying(false);
|
||||||
resetPlanningAutoRetryBudget();
|
resetPlanningAutoRetryBudget();
|
||||||
setIsRefiningSummary(false);
|
setIsRefiningSummary(false);
|
||||||
@@ -930,7 +928,6 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat
|
|||||||
passive stream catch-up event that overwrites a newer awaiting-input question.
|
passive stream catch-up event that overwrites a newer awaiting-input question.
|
||||||
*/
|
*/
|
||||||
if (isAnsweredQuestion && editingQuestionIdRef.current !== normalizedQuestion.id) return;
|
if (isAnsweredQuestion && editingQuestionIdRef.current !== normalizedQuestion.id) return;
|
||||||
setIsReconnecting(false);
|
|
||||||
setIsRetrying(false);
|
setIsRetrying(false);
|
||||||
resetPlanningAutoRetryBudget();
|
resetPlanningAutoRetryBudget();
|
||||||
setIsRefiningSummary(false);
|
setIsRefiningSummary(false);
|
||||||
@@ -962,7 +959,6 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat
|
|||||||
onSummary: (summary) => {
|
onSummary: (summary) => {
|
||||||
if (isStaleEvent()) return;
|
if (isStaleEvent()) return;
|
||||||
const normalizedSummary = normalizePlanningSummary(summary);
|
const normalizedSummary = normalizePlanningSummary(summary);
|
||||||
setIsReconnecting(false);
|
|
||||||
setIsRetrying(false);
|
setIsRetrying(false);
|
||||||
resetPlanningAutoRetryBudget();
|
resetPlanningAutoRetryBudget();
|
||||||
setIsRefiningSummary(false);
|
setIsRefiningSummary(false);
|
||||||
@@ -1004,7 +1000,6 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat
|
|||||||
// permanent error view. Refetch the session state — if the server
|
// permanent error view. Refetch the session state — if the server
|
||||||
// still has it in a recoverable state, silently reconnect; only
|
// still has it in a recoverable state, silently reconnect; only
|
||||||
// surface the error if the server actually persisted one.
|
// surface the error if the server actually persisted one.
|
||||||
setIsReconnecting(true);
|
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
const session = await fetchAiSession(sessionId);
|
const session = await fetchAiSession(sessionId);
|
||||||
@@ -1019,7 +1014,6 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat
|
|||||||
// fall through to error view below
|
// fall through to error view below
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsReconnecting(false);
|
|
||||||
/*
|
/*
|
||||||
FNXC:PlanningRetry 2026-07-15-00:00:
|
FNXC:PlanningRetry 2026-07-15-00:00:
|
||||||
FN-8332 limits the stuck-turn retry budget to generations started by
|
FN-8332 limits the stuck-turn retry budget to generations started by
|
||||||
@@ -1053,16 +1047,12 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat
|
|||||||
})();
|
})();
|
||||||
},
|
},
|
||||||
onComplete: () => {
|
onComplete: () => {
|
||||||
setIsReconnecting(false);
|
|
||||||
setIsRetrying(false);
|
setIsRetrying(false);
|
||||||
resetPlanningAutoRetryBudget();
|
resetPlanningAutoRetryBudget();
|
||||||
setIsRefiningSummary(false);
|
setIsRefiningSummary(false);
|
||||||
refineSummaryInFlightRef.current = false;
|
refineSummaryInFlightRef.current = false;
|
||||||
currentSessionIdRef.current = null;
|
currentSessionIdRef.current = null;
|
||||||
},
|
},
|
||||||
onConnectionStateChange: (state) => {
|
|
||||||
setIsReconnecting(state === "reconnecting");
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
streamConnectionRef.current = connection;
|
streamConnectionRef.current = connection;
|
||||||
@@ -1155,7 +1145,6 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat
|
|||||||
setIsAutoRetrying(false);
|
setIsAutoRetrying(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsReconnecting(false);
|
|
||||||
return;
|
return;
|
||||||
} catch (sessionRefreshError) {
|
} catch (sessionRefreshError) {
|
||||||
retryError = sessionRefreshError;
|
retryError = sessionRefreshError;
|
||||||
@@ -1169,7 +1158,6 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat
|
|||||||
session: retryTarget,
|
session: retryTarget,
|
||||||
errorMessage: getErrorMessage(retryError) || t("planning.retryFailed", "Retry failed. Please try again."),
|
errorMessage: getErrorMessage(retryError) || t("planning.retryFailed", "Retry failed. Please try again."),
|
||||||
});
|
});
|
||||||
setIsReconnecting(false);
|
|
||||||
setIsAutoRetrying(false);
|
setIsAutoRetrying(false);
|
||||||
} finally {
|
} finally {
|
||||||
if (!options.auto) {
|
if (!options.auto) {
|
||||||
@@ -1220,7 +1208,6 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat
|
|||||||
setStreamingOutput("");
|
setStreamingOutput("");
|
||||||
setConversationHistory([]);
|
setConversationHistory([]);
|
||||||
setResponseHistory([]);
|
setResponseHistory([]);
|
||||||
setIsReconnecting(false);
|
|
||||||
resetPlanningAutoRetryBudget();
|
resetPlanningAutoRetryBudget();
|
||||||
setIsRefiningSummary(false);
|
setIsRefiningSummary(false);
|
||||||
refineSummaryInFlightRef.current = false;
|
refineSummaryInFlightRef.current = false;
|
||||||
@@ -1251,7 +1238,6 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat
|
|||||||
connectToPlanningStream(sessionId);
|
connectToPlanningStream(sessionId);
|
||||||
setResponseHistory([]);
|
setResponseHistory([]);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setIsReconnecting(false);
|
|
||||||
setError(getErrorMessage(err) || t("planning.failedStartSession", "Failed to start planning session"));
|
setError(getErrorMessage(err) || t("planning.failedStartSession", "Failed to start planning session"));
|
||||||
setView({ type: "initial" });
|
setView({ type: "initial" });
|
||||||
currentSessionIdRef.current = null;
|
currentSessionIdRef.current = null;
|
||||||
@@ -1846,7 +1832,6 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat
|
|||||||
if (!isOpen) {
|
if (!isOpen) {
|
||||||
hasAutoStartedRef.current = false;
|
hasAutoStartedRef.current = false;
|
||||||
hasLoadedPersistedRef.current = false;
|
hasLoadedPersistedRef.current = false;
|
||||||
setIsReconnecting(false);
|
|
||||||
setIsRetrying(false);
|
setIsRetrying(false);
|
||||||
}
|
}
|
||||||
}, [isOpen]);
|
}, [isOpen]);
|
||||||
@@ -1940,7 +1925,6 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat
|
|||||||
}
|
}
|
||||||
streamConnectionRef.current?.close();
|
streamConnectionRef.current?.close();
|
||||||
streamConnectionRef.current = null;
|
streamConnectionRef.current = null;
|
||||||
setIsReconnecting(false);
|
|
||||||
setIsRetrying(false);
|
setIsRetrying(false);
|
||||||
setIsRefiningSummary(false);
|
setIsRefiningSummary(false);
|
||||||
refineSummaryInFlightRef.current = false;
|
refineSummaryInFlightRef.current = false;
|
||||||
@@ -2090,7 +2074,6 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat
|
|||||||
|
|
||||||
streamConnectionRef.current?.close();
|
streamConnectionRef.current?.close();
|
||||||
streamConnectionRef.current = null;
|
streamConnectionRef.current = null;
|
||||||
setIsReconnecting(false);
|
|
||||||
setIsRetrying(false);
|
setIsRetrying(false);
|
||||||
setIsAutoRetrying(false);
|
setIsAutoRetrying(false);
|
||||||
setIsRefiningSummary(false);
|
setIsRefiningSummary(false);
|
||||||
@@ -2456,11 +2439,11 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat
|
|||||||
<div className="planning-detail">
|
<div className="planning-detail">
|
||||||
{error && <div className="form-error planning-error">{error}</div>}
|
{error && <div className="form-error planning-error">{error}</div>}
|
||||||
{/*
|
{/*
|
||||||
FNXC:PlanningMode 2026-07-15-00:00:
|
FNXC:PlanningMode 2026-07-20-12: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.
|
FN-8436 supersedes FN-8002's loading-only reconnect hint: Planning Mode never
|
||||||
|
surfaces a user-visible reconnecting status. The loading pane's generating/thinking,
|
||||||
|
elapsed-time, and Stop controls are the sole progress feedback while SSE recovers.
|
||||||
*/}
|
*/}
|
||||||
{isReconnecting && view.type === "loading" && <div className="form-hint text-muted">{t("planning.reconnecting", "Reconnecting…")}</div>}
|
|
||||||
|
|
||||||
{view.type === "initial" && (
|
{view.type === "initial" && (
|
||||||
<div className="planning-initial">
|
<div className="planning-initial">
|
||||||
<div className="planning-view-scroll">
|
<div className="planning-view-scroll">
|
||||||
|
|||||||
@@ -1932,7 +1932,7 @@ describe("PlanningModeModal", () => {
|
|||||||
expect(screen.queryByText("Reconnecting…")).toBeNull();
|
expect(screen.queryByText("Reconnecting…")).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("shows the reconnecting hint while active generation is loading", async () => {
|
it("keeps generation chrome visible without a reconnecting hint during stream recovery", async () => {
|
||||||
let streamHandlers: any;
|
let streamHandlers: any;
|
||||||
mockConnectPlanningStream.mockImplementationOnce((_sessionId: string, _projectId: string | undefined, handlers: any) => {
|
mockConnectPlanningStream.mockImplementationOnce((_sessionId: string, _projectId: string | undefined, handlers: any) => {
|
||||||
streamHandlers = handlers;
|
streamHandlers = handlers;
|
||||||
@@ -1955,11 +1955,13 @@ describe("PlanningModeModal", () => {
|
|||||||
fireEvent.click(screen.getByText("Start Planning"));
|
fireEvent.click(screen.getByText("Start Planning"));
|
||||||
await waitFor(() => expect(mockConnectPlanningStream).toHaveBeenCalledTimes(1));
|
await waitFor(() => expect(mockConnectPlanningStream).toHaveBeenCalledTimes(1));
|
||||||
|
|
||||||
|
expect(screen.getByText("Generating next question...")).toBeInTheDocument();
|
||||||
act(() => {
|
act(() => {
|
||||||
streamHandlers.onConnectionStateChange?.("reconnecting");
|
streamHandlers.onConnectionStateChange?.("reconnecting");
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(screen.getByText("Reconnecting…")).toBeInTheDocument();
|
expect(screen.getByText("Generating next question...")).toBeInTheDocument();
|
||||||
|
expect(screen.queryByText("Reconnecting…")).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("shows summary view when resuming a complete persisted session", async () => {
|
it("shows summary view when resuming a complete persisted session", async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user