From 3fce53640e8a4b603b249eb3d35c70c8704082f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20Can=C3=B4?= Date: Tue, 14 Jul 2026 12:15:57 -0300 Subject: [PATCH] fix(dashboard): keep completed Planning Mode sessions in history after multi-task creation (#2079) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 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). ## 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. Co-authored-by: Claude --- .changeset/fix-planning-history-multitask.md | 7 ++ .../src/__tests__/routes-planning.test.ts | 71 +++++++++++++++++++ .../register-planning-subtask-routes.ts | 12 +++- 3 files changed, 87 insertions(+), 3 deletions(-) create mode 100644 .changeset/fix-planning-history-multitask.md diff --git a/.changeset/fix-planning-history-multitask.md b/.changeset/fix-planning-history-multitask.md new file mode 100644 index 0000000000..5bb2f58e90 --- /dev/null +++ b/.changeset/fix-planning-history-multitask.md @@ -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. diff --git a/packages/dashboard/src/__tests__/routes-planning.test.ts b/packages/dashboard/src/__tests__/routes-planning.test.ts index 88d5147e65..c839680464 100644 --- a/packages/dashboard/src/__tests__/routes-planning.test.ts +++ b/packages/dashboard/src/__tests__/routes-planning.test.ts @@ -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[0]); + + (store.createTask as ReturnType) + .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).mockResolvedValue({}); + (store.logEntry as ReturnType).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).mockResolvedValue({ id: "FN-100", diff --git a/packages/dashboard/src/routes/register-planning-subtask-routes.ts b/packages/dashboard/src/routes/register-planning-subtask-routes.ts index d73de7b701..d17464c254 100644 --- a/packages/dashboard/src/routes/register-planning-subtask-routes.ts +++ b/packages/dashboard/src/routes/register-planning-subtask-routes.ts @@ -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 }, );