feat(FN-3209): fix refine continuation flow in planning mode and add local

Merges fixes for the planning refine continuation flow (FN-3209) alongside a new local startup script for development environments. The changes include updates to `PlanningModeModal.tsx`, new and updated tests for the planning system, route handler improvements in `chat.ts` and `planning.ts`, and do

Fusion-Task-Id: FN-3209
This commit is contained in:
Fusion
2026-05-05 11:26:25 -07:00
committed by gsxdsm
parent 1f50be85a1
commit f2accb736f
8 changed files with 282 additions and 22 deletions

View File

@@ -1307,6 +1307,33 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat
[projectId, sessionTabId, view]
);
const handleRefineFurther = useCallback(async () => {
if (view.type !== "summary") {
return;
}
const { session, summary } = view;
const sessionId = session.sessionId;
currentSessionIdRef.current = sessionId;
setLockSessionId(sessionId);
setError(null);
setIsRetrying(false);
setStreamingOutput("");
setView({ type: "loading" });
connectToPlanningStream(sessionId);
try {
await respondToPlanning(sessionId, { refine: true }, projectId, sessionTabId);
} catch (err) {
streamConnectionRef.current?.close();
streamConnectionRef.current = null;
setError(getErrorMessage(err) || "Failed to refine plan");
setView({ type: "summary", session, summary: editedSummary ?? summary });
}
}, [connectToPlanningStream, editedSummary, projectId, sessionTabId, view]);
const handleStopGeneration = useCallback(async () => {
const sessionId = currentSessionIdRef.current;
if (!sessionId) {
@@ -1935,8 +1962,7 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat
onCreateTask={handleCreateTask}
onBreakIntoTasks={handleStartBreakdown}
onRefine={() => {
// Reset to question mode for more refinement
setView({ type: "question", session: view.session });
void handleRefineFurther();
}}
isLoading={false}
/>

View File

@@ -809,6 +809,84 @@ describe("PlanningModeModal", () => {
expect(mockCreateTaskFromPlanning).toHaveBeenCalledWith("session-complete-2", resumedSummary, undefined);
});
});
it("refines a resumed complete session without blank question view", async () => {
const resumedSummary: PlanningSummary = {
title: "Resume-and-refine",
description: "Recovered summary for refine",
suggestedSize: "M",
suggestedDependencies: [],
keyDeliverables: ["Implement", "Verify"],
};
const refinedQuestion: PlanningQuestion = {
id: "q-refine",
type: "text",
question: "Which part should we refine?",
description: "Refine follow-up",
};
mockFetchAiSession.mockResolvedValueOnce({
id: "session-complete-refine",
type: "planning",
status: "complete",
title: "Resume-and-refine",
inputPayload: JSON.stringify({ initialPlan: "Recover and refine" }),
conversationHistory: "[]",
currentQuestion: null,
result: JSON.stringify(resumedSummary),
thinkingOutput: "",
error: null,
projectId: null,
createdAt: "2026-01-01T00:00:00.000Z",
updatedAt: "2026-01-01T00:00:00.000Z",
});
let streamHandlers: any;
mockConnectPlanningStream.mockImplementationOnce((_sessionId: string, _projectId: string | undefined, handlers: any) => {
streamHandlers = handlers;
return {
close: vi.fn(),
isConnected: vi.fn().mockReturnValue(true),
};
});
mockRespondToPlanning.mockImplementationOnce(async () => {
setTimeout(() => {
streamHandlers?.onQuestion?.(refinedQuestion);
}, 10);
return { type: "question", data: refinedQuestion };
});
render(
<PlanningModeModal
isOpen={true}
onClose={mockOnClose}
onTaskCreated={mockOnTaskCreated}
onTasksCreated={vi.fn()}
tasks={mockTasks}
resumeSessionId="session-complete-refine"
/>
);
await waitFor(() => {
expect(screen.getByRole("button", { name: "Refine Further" })).toBeDefined();
});
fireEvent.click(screen.getByRole("button", { name: "Refine Further" }));
await waitFor(() => {
expect(mockRespondToPlanning).toHaveBeenCalledWith(
"session-complete-refine",
{ refine: true },
undefined,
expect.any(String),
);
});
await waitFor(() => {
expect(screen.getByText("Which part should we refine?")).toBeDefined();
});
expect(screen.queryByText("No active question in session")).toBeNull();
});
});
describe("Conversation history", () => {