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:
7
.changeset/fix-planning-history-multitask.md
Normal file
7
.changeset/fix-planning-history-multitask.md
Normal 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.
|
||||
@@ -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",
|
||||
|
||||
@@ -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 },
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user