diff --git a/src/platform/workflow/management/stores/workflowStore.test.ts b/src/platform/workflow/management/stores/workflowStore.test.ts index 7dd549e08..5b66b2777 100644 --- a/src/platform/workflow/management/stores/workflowStore.test.ts +++ b/src/platform/workflow/management/stores/workflowStore.test.ts @@ -54,6 +54,13 @@ describe('useWorkflowStore', () => { return await store.syncWorkflows() } + const syncRemoteWorkflowsWithMeta = async ( + files: Array<{ path: string; modified: number; size: number }> + ) => { + vi.mocked(api.listUserDataFullInfo).mockResolvedValue(files) + return await store.syncWorkflows() + } + beforeEach(() => { setActivePinia(createPinia()) store = useWorkflowStore() @@ -82,6 +89,78 @@ describe('useWorkflowStore', () => { expect(store.workflows.length).toBe(3) expect(store.workflows.filter((w) => w.isTemporary)).toEqual([workflow]) }) + + it('should not unload the active workflow during sync', async () => { + await syncRemoteWorkflowsWithMeta([ + { path: 'a.json', modified: 100, size: 1 } + ]) + const workflow = store.getWorkflowByPath('workflows/a.json')! + + vi.mocked(api.getUserData).mockResolvedValue({ + status: 200, + text: () => Promise.resolve(defaultGraphJSON) + } as Response) + + await store.openWorkflow(workflow) + expect(store.activeWorkflow?.path).toBe('workflows/a.json') + expect(store.activeWorkflow?.isLoaded).toBe(true) + + await syncRemoteWorkflowsWithMeta([ + { path: 'a.json', modified: 200, size: 2 } + ]) + + expect(store.activeWorkflow?.isLoaded).toBe(true) + expect(workflow.lastModified).toBe(200) + expect(workflow.size).toBe(2) + }) + + it('should not unload a loaded workflow if metadata has not changed', async () => { + await syncRemoteWorkflowsWithMeta([ + { path: 'a.json', modified: 100, size: 1 } + ]) + const workflow = store.getWorkflowByPath('workflows/a.json')! + + vi.mocked(api.getUserData).mockResolvedValue({ + status: 200, + text: () => Promise.resolve(defaultGraphJSON) + } as Response) + + await workflow.load() + expect(workflow.isLoaded).toBe(true) + + await syncRemoteWorkflowsWithMeta([ + { path: 'a.json', modified: 100, size: 1 } + ]) + expect(workflow.isLoaded).toBe(true) + }) + + it('should unload a loaded workflow if metadata has changed and it is not active', async () => { + await syncRemoteWorkflowsWithMeta([ + { path: 'a.json', modified: 100, size: 1 } + ]) + const workflow = store.getWorkflowByPath('workflows/a.json')! + + vi.mocked(api.getUserData).mockResolvedValue({ + status: 200, + text: () => Promise.resolve(defaultGraphJSON) + } as Response) + + await workflow.load() + expect(workflow.isLoaded).toBe(true) + expect(workflow.content).toBe(defaultGraphJSON) + expect(workflow.originalContent).toBe(defaultGraphJSON) + + // Metadata change should invalidate cached content for non-active workflows. + await syncRemoteWorkflowsWithMeta([ + { path: 'a.json', modified: 200, size: 2 } + ]) + + expect(workflow.lastModified).toBe(200) + expect(workflow.size).toBe(2) + expect(workflow.isLoaded).toBe(false) + expect(workflow.content).toBeNull() + expect(workflow.originalContent).toBeNull() + }) }) describe('createTemporary', () => { diff --git a/src/platform/workflow/management/stores/workflowStore.ts b/src/platform/workflow/management/stores/workflowStore.ts index 4b8b64f3d..6f47c8445 100644 --- a/src/platform/workflow/management/stores/workflowStore.ts +++ b/src/platform/workflow/management/stores/workflowStore.ts @@ -513,8 +513,33 @@ export const useWorkflowStore = defineStore('workflow', () => { size: file.size }), (existingWorkflow, file) => { - existingWorkflow.lastModified = file.modified - existingWorkflow.size = file.size + const isActiveWorkflow = + activeWorkflow.value?.path === existingWorkflow.path + + const nextLastModified = Math.max( + existingWorkflow.lastModified, + file.modified + ) + + const isMetadataUnchanged = + nextLastModified === existingWorkflow.lastModified && + file.size === existingWorkflow.size + + if (!isMetadataUnchanged) { + existingWorkflow.lastModified = nextLastModified + existingWorkflow.size = file.size + } + + // Never unload the active workflow - it may contain unsaved in-memory edits. + if (isActiveWorkflow) { + return + } + + // If nothing changed, keep any loaded content cached. + if (isMetadataUnchanged) { + return + } + existingWorkflow.unload() }, /* exclude */ (workflow) => workflow.isTemporary