From 116a81e06ec9f3b3478759003f0cb2aef380aec6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Almeida?= Date: Fri, 16 Oct 2026 09:04:20 +0000 Subject: [PATCH] fix(editor): Keep pending navigation when workflows list query is unchanged (#41125) Co-authored-by: Claude Opus 5.5 --- .../src/app/views/WorkflowsView.test.ts | 27 +++++++++++++++++++ .../editor-ui/src/app/views/WorkflowsView.vue | 10 ++++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/packages/frontend/editor-ui/src/app/views/WorkflowsView.test.ts b/packages/frontend/editor-ui/src/app/views/WorkflowsView.test.ts index 9ff8e0ca89c..a530185e398 100644 --- a/packages/frontend/editor-ui/src/app/views/WorkflowsView.test.ts +++ b/packages/frontend/editor-ui/src/app/views/WorkflowsView.test.ts @@ -430,6 +430,33 @@ describe('WorkflowsView', () => { await waitFor(() => expect(router.currentRoute.value.query).toStrictEqual({})); }); + it('should not replace the route when the query does not change', async () => { + await router.replace({ query: { search: 'one' } }); + const replaceSpy = vi.spyOn(router, 'replace'); + renderComponent({ pinia }); + await waitAllPromises(); + + expect(replaceSpy).not.toHaveBeenCalled(); + replaceSpy.mockRestore(); + }); + + it('should not replace the route when the route changes while tags load', async () => { + await router.replace({ query: { tags: 'non-existing-tag' } }); + workflowsListStore.fetchWorkflowsPage.mockResolvedValue([]); + const tagStore = mockedStore(useTagsStore); + tagStore.fetchAll.mockImplementation(async () => { + await router.push({ query: { search: 'new' } }); + return []; + }); + const replaceSpy = vi.spyOn(router, 'replace'); + renderComponent({ pinia }); + await waitAllPromises(); + + expect(replaceSpy).not.toHaveBeenCalled(); + expect(router.currentRoute.value.query).toStrictEqual({ search: 'new' }); + replaceSpy.mockRestore(); + }); + it('should show archived only hint', async () => { foldersStore.totalWorkflowCount = 1; workflowsListStore.fetchWorkflowsPage.mockResolvedValue([]); diff --git a/packages/frontend/editor-ui/src/app/views/WorkflowsView.vue b/packages/frontend/editor-ui/src/app/views/WorkflowsView.vue index 39e463dc759..49753caa087 100644 --- a/packages/frontend/editor-ui/src/app/views/WorkflowsView.vue +++ b/packages/frontend/editor-ui/src/app/views/WorkflowsView.vue @@ -96,6 +96,7 @@ import { useI18n } from '@n8n/i18n'; import { getResourcePermissions } from '@n8n/permissions'; import { createEventBus } from '@n8n/utils/event-bus'; import debounce from 'lodash/debounce'; +import isEqual from 'lodash/isEqual'; import { type IUser, PROJECT_ROOT } from 'n8n-workflow'; import { computed, onBeforeUnmount, onMounted, ref, useTemplateRef, watch } from 'vue'; import { type LocationQueryRaw, useRoute, useRouter } from 'vue-router'; @@ -1069,6 +1070,7 @@ const saveFiltersOnQueryString = () => { }; const setFiltersFromQueryString = async () => { + const { fullPath } = route; const newQuery: LocationQueryRaw = { ...route.query }; const { tags, status, search, homeProject, sort, showArchived } = route.query ?? {}; @@ -1139,7 +1141,13 @@ const setFiltersFromQueryString = async () => { filters.value.showArchived = false; } - void router.replace({ query: newQuery }); + // The route changed while the stores loaded. Do not write this query onto the new route. + if (route.fullPath !== fullPath) return; + + // A replace cancels any pending navigation, e.g. a tab click made while this view loads. + if (!isEqual(newQuery, route.query)) { + void router.replace({ query: newQuery }); + } }; // Misc methods