diff --git a/.changeset/planning-thinking-all-steps.md b/.changeset/planning-thinking-all-steps.md
new file mode 100644
index 0000000000..da5fa72332
--- /dev/null
+++ b/.changeset/planning-thinking-all-steps.md
@@ -0,0 +1,7 @@
+---
+"@runfusion/fusion": patch
+---
+
+summary: Every Planning Mode generation step now streams AI thinking/output, not just the first turn.
+category: fix
+dev: The planning workspace loader (follow-up turns — next question, refine, contextual comments, question regeneration) reuses the initial loading view's thinking container/toggle and mirrors the generation-activity label.
diff --git a/packages/dashboard/app/components/PlanningModeModal.tsx b/packages/dashboard/app/components/PlanningModeModal.tsx
index cf0a36499b..8ea9be9d94 100644
--- a/packages/dashboard/app/components/PlanningModeModal.tsx
+++ b/packages/dashboard/app/components/PlanningModeModal.tsx
@@ -3975,12 +3975,38 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat
{view.type === "loading" && (
- {t("planning.generatingPlan", "Generating plan…")}
+
+ {generationActivity === "question"
+ ? t("planning.generatingQuestion", "Generating next question…")
+ : t("planning.generatingPlan", "Generating plan…")}
+
{generationStartTime && {t("planning.thinkingElapsed", "Thinking… ({{seconds}}s)", { seconds: elapsedSeconds })}}
+ {/*
+ FNXC:PlanningThinkingVisibility 2026-07-23-22:45:
+ Every Planning Mode generation step must stream the model's thinking/output to
+ the operator, not only the first (pre-summary) turn. This workspace loader
+ covers all follow-up turns — next question, refine, contextual comments, and
+ question regeneration — and previously showed only a spinner with elapsed
+ time. Reuse the same thinking container + toggle as the initial loading view.
+ */}
+
+
+ {showThinking && streamingOutput && (
+
+
{streamingOutput}
+
+ )}
+
)}
diff --git a/packages/dashboard/app/components/__tests__/PlanningModeModal.planning-flow.test.tsx b/packages/dashboard/app/components/__tests__/PlanningModeModal.planning-flow.test.tsx
index 95dd7315a3..2ec81efe4a 100644
--- a/packages/dashboard/app/components/__tests__/PlanningModeModal.planning-flow.test.tsx
+++ b/packages/dashboard/app/components/__tests__/PlanningModeModal.planning-flow.test.tsx
@@ -842,6 +842,30 @@ describe("PlanningModeModal sequential flow", () => {
expect(await screen.findByText("Thinking… (7s)")).toBeInTheDocument();
dateNow.mockRestore();
});
+ /*
+ FNXC:PlanningThinkingVisibility 2026-07-23-22:45:
+ Every generation step must stream thinking/output to the operator. Follow-up turns render
+ the workspace loader (summary present), which previously showed only a spinner + elapsed
+ time; this pins the streamed thinking pane there too.
+ */
+ it("streams thinking in the workspace loader during follow-up generations", async () => {
+ mockFetchAiSession.mockResolvedValue({
+ ...base,
+ status: "generating",
+ currentQuestion: null,
+ result: JSON.stringify(summaryWithRefinements),
+ inputPayload: JSON.stringify({ generationPurpose: "plan_update", generationStartedAt: new Date().toISOString() }),
+ });
+ renderSession();
+
+ await waitFor(() => expect(mockConnectPlanningStream).toHaveBeenCalledWith("session-1", "project-1", expect.any(Object)));
+ const handlers = mockConnectPlanningStream.mock.calls[0]?.[2];
+ act(() => handlers?.onThinking?.("Weighing the tradeoffs between approaches…"));
+
+ expect(await screen.findByText("Weighing the tradeoffs between approaches…")).toBeInTheDocument();
+ fireEvent.click(screen.getByRole("button", { name: "Hide thinking" }));
+ expect(screen.queryByText("Weighing the tradeoffs between approaches…")).toBeNull();
+ });
it("returns to the prior question without an error when generation is stopped", async () => {
const priorQuestion = { id: "q-prior", type: "text", question: "What should change?" };
mockFetchAiSession.mockResolvedValue({