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:
gsxdsm
2026-07-20 11:46:57 -07:00
parent 02e297aab5
commit baf88931d4
3 changed files with 15 additions and 23 deletions

View 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.

View File

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

View File

@@ -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 () => {