fix(dashboard): keep completed Planning Mode sessions in history after multi-task creation (#2079)

## Problem
In the dashboard **Planning Mode** screen, a planning session that runs
to completion **and creates multiple tasks** disappears from the "saved
sessions" history panel ("No saved sessions yet").

## Root cause
The multi-task route `POST /api/planning/create-tasks` called
`cleanupSession(planningSessionId)` → `unpersistSession` →
`_aiSessionStore.delete`, **deleting the persisted `ai_sessions` row**.
The single-task route `POST /api/planning/create-task` deliberately uses
`releaseSession` instead — it releases the in-memory runtime but
**keeps** the persisted completed row, which is what the history list
reads (`listAll` includes completed sessions). So multi-task creation
erased its own history entry.

## Fix
Switch the multi-task route to `releaseSession`, matching the
single-task path. The completed `type: "planning"` session row now
survives task creation and appears in history.

## Tests
Adds a regression test in `routes-planning.test.ts` asserting the
persisted planning row survives multi-task creation (verified it fails
against the old `cleanupSession` behavior). Merge gate green locally;
changeset included.

Made with Claude (see `Co-Authored-By` trailer).

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Fixed an issue where Planning Mode multi-task sessions could be
removed from planning history after task creation.
* Completed multi-task planning sessions are now reliably retained with
their completed status.
* **Tests**
* Added a regression test for the multi-task Planning Mode flow to
confirm all tasks are created and the planning session remains persisted
in history.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Victor Canô
2026-07-14 12:15:57 -03:00
committed by GitHub
parent bc348345a4
commit 3fce53640e
3 changed files with 87 additions and 3 deletions

View File

@@ -0,0 +1,7 @@
---
"@runfusion/fusion": patch
---
summary: Completed Planning Mode sessions that create multiple tasks now stay in planning history.
category: fix
dev: The multi-task create-tasks route now uses releaseSession instead of cleanupSession, retaining the persisted ai_sessions row like the single-task path.

View File

@@ -2594,6 +2594,77 @@ describe("Planning Mode Routes", () => {
expect(res.body.tasks).toHaveLength(2);
});
it("keeps the completed planning session in history after multi-task creation", async () => {
// Bug C: /planning/create-tasks used cleanupSession() which deleted the
// persisted ai_sessions row, so a session that ran to completion AND
// created tasks vanished from the saved-sessions history. It must instead
// release only the in-memory runtime (like single-task create-task) and
// keep the persisted completed row.
const mockStore = new MockAiSessionStore();
setAiSessionStore(mockStore as unknown as Parameters<typeof setAiSessionStore>[0]);
(store.createTask as ReturnType<typeof vi.fn>)
.mockResolvedValueOnce({
id: "FN-270",
description: "First",
column: "triage",
dependencies: [],
createdAt: "2026-01-01T00:00:00.000Z",
updatedAt: "2026-01-01T00:00:00.000Z",
})
.mockResolvedValueOnce({
id: "FN-271",
description: "Second",
column: "triage",
dependencies: [],
createdAt: "2026-01-01T00:00:00.000Z",
updatedAt: "2026-01-01T00:00:00.000Z",
});
(store.updateTask as ReturnType<typeof vi.fn>).mockResolvedValue({});
(store.logEntry as ReturnType<typeof vi.fn>).mockResolvedValue(undefined);
const planningSessionId = await createCompletedPlanningSession();
// Precondition: the completed planning session is persisted as history.
const persistedBefore = mockStore.get(planningSessionId);
expect(persistedBefore).not.toBeNull();
expect(persistedBefore?.type).toBe("planning");
expect(persistedBefore?.status).toBe("complete");
const breakdownRes = await REQUEST(
buildApp(),
"POST",
"/api/planning/start-breakdown",
JSON.stringify({ sessionId: planningSessionId }),
{ "Content-Type": "application/json" }
);
const generatedSubtasks = breakdownRes.body.subtasks as Array<{ id: string }>;
const res = await REQUEST(
buildApp(),
"POST",
"/api/planning/create-tasks",
JSON.stringify({
planningSessionId,
subtasks: [
{ id: generatedSubtasks[0]!.id, title: "Auth backend", description: "Implement backend", suggestedSize: "L", dependsOn: [] },
{ id: generatedSubtasks[1]!.id, title: "Auth frontend", description: "Implement frontend", dependsOn: [generatedSubtasks[0]!.id] },
],
}),
{ "Content-Type": "application/json" }
);
expect(res.status).toBe(201);
expect(res.body.tasks).toHaveLength(2);
// Regression assertion: the completed planning session row must survive
// task creation so it remains listable/restorable in history.
const persistedAfter = mockStore.get(planningSessionId);
expect(persistedAfter).not.toBeNull();
expect(persistedAfter?.type).toBe("planning");
expect(persistedAfter?.status).toBe("complete");
});
it("creates task with explicit summary priority", async () => {
(store.createTask as ReturnType<typeof vi.fn>).mockResolvedValue({
id: "FN-100",

View File

@@ -1333,7 +1333,7 @@ export function registerPlanningSubtaskRoutes(ctx: ApiRoutesContext, deps: Plann
}
const { store: scopedStore } = await getProjectContext(req);
const { getSession, cleanupSession, formatInterviewQA, mergePlanningSubtaskDrafts } = await import("../planning.js");
const { getSession, releaseSession, formatInterviewQA, mergePlanningSubtaskDrafts } = await import("../planning.js");
const session = await getSession(planningSessionId);
if (!session) {
@@ -1494,9 +1494,15 @@ export function registerPlanningSubtaskRoutes(ctx: ApiRoutesContext, deps: Plann
);
}
// FNXC:PlanningMode 2026-07-13-00:00: release the live in-memory planning
// runtime but KEEP the persisted completed row so the multi-task path
// matches single-task create-task — completed planning sessions must remain
// listable/restorable in the saved-sessions history. Using cleanupSession
// here deleted the ai_sessions row, so a session that ran to completion and
// created tasks vanished from history (GET /ai-sessions returned it no more).
await runPlanningCreateSideEffect(
"Planning create-tasks session cleanup failed",
() => cleanupSession(planningSessionId),
"Planning create-tasks session release failed",
() => releaseSession(planningSessionId),
{ planningSessionId },
);