feat: navigate to previously active tab when closing current tab (#6624)

When closing a tab, the UI now returns to the most recently active tab
instead of always going to the first/next tab. This matches standard
browser tab behavior and prevents accidental edits in the wrong
workflow. Implementation uses a lazy-cleanup history array (max 32
entries) that tracks tab activations and skips closed tabs when finding
the previous tab to switch to. Fixes #6599


https://github.com/user-attachments/assets/0bb87969-fd01-4e6b-96e8-c0f741f23ff8

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6624-feat-navigate-to-previously-active-tab-when-closing-current-tab-2a36d73d365081f5be95db51ff7a03f6)
by [Unito](https://www.unito.io)
This commit is contained in:
Christian Byrne
2025-11-11 10:03:35 -08:00
committed by GitHub
parent 879cb8f1a8
commit 0be1da2041
3 changed files with 156 additions and 4 deletions

View File

@@ -3,9 +3,11 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
import { nextTick } from 'vue'
import type { Subgraph } from '@/lib/litegraph/src/litegraph'
import type { ComfyWorkflow } from '@/platform/workflow/management/stores/workflowStore'
import type {
ComfyWorkflow,
LoadedComfyWorkflow
} from '@/platform/workflow/management/stores/workflowStore'
import {
type LoadedComfyWorkflow,
useWorkflowBookmarkStore,
useWorkflowStore
} from '@/platform/workflow/management/stores/workflowStore'
@@ -723,4 +725,93 @@ describe('useWorkflowStore', () => {
})
})
})
describe('Tab Activation History', () => {
let workflowA: ComfyWorkflow
let workflowB: ComfyWorkflow
let workflowC: ComfyWorkflow
beforeEach(async () => {
await syncRemoteWorkflows(['a.json', 'b.json', 'c.json'])
workflowA = store.getWorkflowByPath('workflows/a.json')!
workflowB = store.getWorkflowByPath('workflows/b.json')!
workflowC = store.getWorkflowByPath('workflows/c.json')!
vi.mocked(api.getUserData).mockResolvedValue({
status: 200,
text: () => Promise.resolve(defaultGraphJSON)
} as Response)
})
it('should return most recently active workflow', async () => {
// Open workflows in order: A -> B -> C
await store.openWorkflow(workflowA)
await store.openWorkflow(workflowB)
await store.openWorkflow(workflowC)
// C is current, B should be most recent
const mostRecent = store.getMostRecentWorkflow()
expect(mostRecent?.path).toBe(workflowB.path)
})
it('should skip closed workflows (lazy cleanup)', async () => {
// Open workflows: A -> B -> C
await store.openWorkflow(workflowA)
await store.openWorkflow(workflowB)
await store.openWorkflow(workflowC)
// Close B (the most recent before C)
await store.closeWorkflow(workflowB)
// C is current, B is closed, so A should be returned
const mostRecent = store.getMostRecentWorkflow()
expect(mostRecent?.path).toBe(workflowA.path)
})
it('should return null when no valid history exists', async () => {
// Open only one workflow
await store.openWorkflow(workflowA)
// No previous workflows, should return null
const mostRecent = store.getMostRecentWorkflow()
expect(mostRecent).toBeNull()
})
it('should track history when opening workflows', async () => {
// Open A, then B, then A again
await store.openWorkflow(workflowA)
await store.openWorkflow(workflowB)
await store.openWorkflow(workflowA)
// A is current, B should be most recent
const mostRecent = store.getMostRecentWorkflow()
expect(mostRecent?.path).toBe(workflowB.path)
})
it('should handle workflow activated multiple times', async () => {
// Open: A -> B -> A -> C
await store.openWorkflow(workflowA)
await store.openWorkflow(workflowB)
await store.openWorkflow(workflowA)
await store.openWorkflow(workflowC)
// C is current, A should be most recent (not B)
const mostRecent = store.getMostRecentWorkflow()
expect(mostRecent?.path).toBe(workflowA.path)
})
it('should clean up history when all previous workflows are closed', async () => {
// Open: A -> B -> C
await store.openWorkflow(workflowA)
await store.openWorkflow(workflowB)
await store.openWorkflow(workflowC)
// Close A and B
await store.closeWorkflow(workflowA)
await store.closeWorkflow(workflowB)
// C is current, no valid history
const mostRecent = store.getMostRecentWorkflow()
expect(mostRecent).toBeNull()
})
})
})