From 9112027ee63259178687b8a939285411ec565247 Mon Sep 17 00:00:00 2001 From: Mitsu13Ion <50143759+Mitsu13Ion@users.noreply.github.com> Date: Sat, 21 Feb 2026 18:54:30 +0100 Subject: [PATCH 1/2] fix: prevent roadmap.json cross-project contamination on project switch Race condition: when switching projects, the roadmap store holds stale data during async load. Any save triggered in that gap writes Project A's roadmap to Project B's file. Fix by validating roadmap.projectId matches the target projectId before every save, and clearing the store immediately on load. --- apps/frontend/src/renderer/hooks/useIpc.ts | 18 +++++++++++------- .../src/renderer/stores/roadmap-store.ts | 5 ++++- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/apps/frontend/src/renderer/hooks/useIpc.ts b/apps/frontend/src/renderer/hooks/useIpc.ts index 1dab10bc5e..1ad2fea8e4 100644 --- a/apps/frontend/src/renderer/hooks/useIpc.ts +++ b/apps/frontend/src/renderer/hooks/useIpc.ts @@ -222,14 +222,18 @@ export function useIpcListeners(): void { // Sync roadmap feature when task completes if (status === 'done' || status === 'pr_created') { - useRoadmapStore.getState().markFeatureDoneBySpecId(taskId); - // Re-read state after mutation to get updated roadmap + // Use event's projectId (from callback param) — NOT the store's activeProjectId. + // During project switches, the store may hold a different project's roadmap. + const eventProjectId = projectId; const rm = useRoadmapStore.getState().roadmap; - const currentProjectId = useProjectStore.getState().activeProjectId || useProjectStore.getState().selectedProjectId; - if (rm && currentProjectId) { - window.electronAPI.saveRoadmap(currentProjectId, rm).catch((err) => { - console.error('[useIpc] Failed to persist roadmap after task completion:', err); - }); + if (rm && eventProjectId && rm.projectId === eventProjectId) { + useRoadmapStore.getState().markFeatureDoneBySpecId(taskId); + const updatedRm = useRoadmapStore.getState().roadmap; + if (updatedRm && updatedRm.projectId === eventProjectId) { + window.electronAPI.saveRoadmap(eventProjectId, updatedRm).catch((err) => { + console.error('[useIpc] Failed to persist roadmap after task completion:', err); + }); + } } } } diff --git a/apps/frontend/src/renderer/stores/roadmap-store.ts b/apps/frontend/src/renderer/stores/roadmap-store.ts index 05a9fce067..c1933b92ea 100644 --- a/apps/frontend/src/renderer/stores/roadmap-store.ts +++ b/apps/frontend/src/renderer/stores/roadmap-store.ts @@ -713,7 +713,9 @@ async function reconcileLinkedFeatures(projectId: string, roadmap: Roadmap): Pro if (hasChanges) { const updatedRoadmap = useRoadmapStore.getState().roadmap; - if (updatedRoadmap) { + // Guard: only save if the store still holds the same project's roadmap. + // A project switch during async reconciliation could replace the roadmap. + if (updatedRoadmap && updatedRoadmap.projectId === projectId) { console.log('[Roadmap] Reconciled linked features with task states'); window.electronAPI.saveRoadmap(projectId, updatedRoadmap).catch((err) => { console.error('[Roadmap] Failed to save reconciled roadmap:', err); @@ -729,6 +731,7 @@ export async function loadRoadmap(projectId: string): Promise { // Always set current project ID first - this ensures event handlers // only process events for the currently viewed project store.setCurrentProjectId(projectId); + store.setRoadmap(null); // Clear immediately to prevent stale cross-project saves // Query if roadmap generation is currently running for this project // This restores the generation status when switching back to a project From 71d24580c25ed9c4f4551b9f38fb26bcec422771 Mon Sep 17 00:00:00 2001 From: Mitsu13Ion <50143759+Mitsu13Ion@users.noreply.github.com> Date: Sat, 21 Feb 2026 19:01:07 +0100 Subject: [PATCH 2/2] fix: also clear competitorAnalysis and generationStatus on project switch Prevents stale competitor analysis and generation progress from the previous project showing during async load of the new project's data. --- apps/frontend/src/renderer/stores/roadmap-store.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/frontend/src/renderer/stores/roadmap-store.ts b/apps/frontend/src/renderer/stores/roadmap-store.ts index c1933b92ea..e93274744b 100644 --- a/apps/frontend/src/renderer/stores/roadmap-store.ts +++ b/apps/frontend/src/renderer/stores/roadmap-store.ts @@ -732,6 +732,8 @@ export async function loadRoadmap(projectId: string): Promise { // only process events for the currently viewed project store.setCurrentProjectId(projectId); store.setRoadmap(null); // Clear immediately to prevent stale cross-project saves + store.setCompetitorAnalysis(null); + store.setGenerationStatus({ phase: 'idle', progress: 0, message: '' }); // Query if roadmap generation is currently running for this project // This restores the generation status when switching back to a project