mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-19 22:09:37 +00:00
[backport cloud/1.42] fix: migrate V1 tab state pointers during V1->V2 draft migration (#10007) (#10908)
Backport of #10007 to cloud/1.42. Clean cherry-pick.
This commit is contained in:
@@ -53,8 +53,8 @@ export function useWorkflowPersistenceV2() {
|
||||
const toast = useToast()
|
||||
const { onUserLogout } = useCurrentUser()
|
||||
|
||||
// Run migration on module load
|
||||
migrateV1toV2()
|
||||
// Run migration on module load, passing clientId for tab state migration
|
||||
migrateV1toV2(undefined, api.clientId ?? api.initialClientId ?? undefined)
|
||||
|
||||
// Clear workflow persistence storage when user signs out (cloud only)
|
||||
onUserLogout(() => {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { hashPath } from '../base/hashUtil'
|
||||
import { readOpenPaths } from '../base/storageIO'
|
||||
import {
|
||||
cleanupV1Data,
|
||||
getMigrationStatus,
|
||||
@@ -212,6 +213,85 @@ describe('migrateV1toV2', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('V1 tab state migration', () => {
|
||||
it('migrates V1 tab state pointers to V2 format', () => {
|
||||
// Simulate V1 state: user had 3 workflows open, 2nd was active
|
||||
const v1Drafts = {
|
||||
'workflows/a.json': {
|
||||
data: '{"nodes":[1]}',
|
||||
updatedAt: 1000,
|
||||
name: 'a',
|
||||
isTemporary: true
|
||||
},
|
||||
'workflows/b.json': {
|
||||
data: '{"nodes":[2]}',
|
||||
updatedAt: 2000,
|
||||
name: 'b',
|
||||
isTemporary: true
|
||||
},
|
||||
'workflows/c.json': {
|
||||
data: '{"nodes":[3]}',
|
||||
updatedAt: 3000,
|
||||
name: 'c',
|
||||
isTemporary: false
|
||||
}
|
||||
}
|
||||
setV1Data(v1Drafts, [
|
||||
'workflows/a.json',
|
||||
'workflows/b.json',
|
||||
'workflows/c.json'
|
||||
])
|
||||
|
||||
// V1 tab state stored by setStorageValue (localStorage fallback keys)
|
||||
localStorage.setItem(
|
||||
'Comfy.OpenWorkflowsPaths',
|
||||
JSON.stringify([
|
||||
'workflows/a.json',
|
||||
'workflows/b.json',
|
||||
'workflows/c.json'
|
||||
])
|
||||
)
|
||||
localStorage.setItem('Comfy.ActiveWorkflowIndex', JSON.stringify(1))
|
||||
|
||||
// Run migration (simulating upgrade from pre-V2 to V2)
|
||||
const clientId = 'client-123'
|
||||
const result = migrateV1toV2(workspaceId, clientId)
|
||||
expect(result).toBe(3)
|
||||
|
||||
// V2 tab state should be readable via the V2 API
|
||||
const openPaths = readOpenPaths(clientId, workspaceId)
|
||||
|
||||
// This is the bug: V1 tab state is NOT migrated, so openPaths is null
|
||||
expect(openPaths).not.toBeNull()
|
||||
expect(openPaths!.paths).toEqual([
|
||||
'workflows/a.json',
|
||||
'workflows/b.json',
|
||||
'workflows/c.json'
|
||||
])
|
||||
expect(openPaths!.activeIndex).toBe(1)
|
||||
})
|
||||
|
||||
it('does not migrate tab state when V1 tab state keys are absent', () => {
|
||||
const v1Drafts = {
|
||||
'workflows/a.json': {
|
||||
data: '{}',
|
||||
updatedAt: 1000,
|
||||
name: 'a',
|
||||
isTemporary: true
|
||||
}
|
||||
}
|
||||
setV1Data(v1Drafts, ['workflows/a.json'])
|
||||
|
||||
// No V1 tab state keys in localStorage
|
||||
migrateV1toV2(workspaceId)
|
||||
|
||||
const openPaths = readOpenPaths('any-client-id', workspaceId)
|
||||
|
||||
// No tab state to migrate — should remain null
|
||||
expect(openPaths).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('getMigrationStatus', () => {
|
||||
it('reports correct status', () => {
|
||||
setV1Data(
|
||||
|
||||
@@ -10,7 +10,12 @@ import type { DraftIndexV2 } from '../base/draftTypes'
|
||||
import { upsertEntry, createEmptyIndex } from '../base/draftCacheV2'
|
||||
import { hashPath } from '../base/hashUtil'
|
||||
import { getWorkspaceId } from '../base/storageKeys'
|
||||
import { readIndex, writeIndex, writePayload } from '../base/storageIO'
|
||||
import {
|
||||
readIndex,
|
||||
writeIndex,
|
||||
writeOpenPaths,
|
||||
writePayload
|
||||
} from '../base/storageIO'
|
||||
|
||||
/**
|
||||
* V1 draft snapshot structure (from draftCache.ts)
|
||||
@@ -27,7 +32,9 @@ interface V1DraftSnapshot {
|
||||
*/
|
||||
const V1_KEYS = {
|
||||
drafts: (workspaceId: string) => `Comfy.Workflow.Drafts:${workspaceId}`,
|
||||
order: (workspaceId: string) => `Comfy.Workflow.DraftOrder:${workspaceId}`
|
||||
order: (workspaceId: string) => `Comfy.Workflow.DraftOrder:${workspaceId}`,
|
||||
openPaths: 'Comfy.OpenWorkflowsPaths',
|
||||
activeIndex: 'Comfy.ActiveWorkflowIndex'
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -64,7 +71,10 @@ function readV1Drafts(
|
||||
*
|
||||
* @returns Number of drafts migrated, or -1 if migration not needed/failed
|
||||
*/
|
||||
export function migrateV1toV2(workspaceId: string = getWorkspaceId()): number {
|
||||
export function migrateV1toV2(
|
||||
workspaceId: string = getWorkspaceId(),
|
||||
clientId?: string
|
||||
): number {
|
||||
// Check if V2 already exists
|
||||
if (isV2MigrationComplete(workspaceId)) {
|
||||
return -1
|
||||
@@ -116,12 +126,48 @@ export function migrateV1toV2(workspaceId: string = getWorkspaceId()): number {
|
||||
return -1
|
||||
}
|
||||
|
||||
// Migrate V1 tab state pointers to V2 sessionStorage format.
|
||||
// V1 used setStorageValue which stored tab state in localStorage as fallback.
|
||||
// V2 uses sessionStorage keyed by clientId. Without this migration,
|
||||
// users upgrading from V1 lose their open tab list.
|
||||
migrateV1TabState(workspaceId, clientId)
|
||||
|
||||
if (migrated > 0) {
|
||||
console.warn(`[V2 Migration] Migrated ${migrated} drafts from V1 to V2`)
|
||||
}
|
||||
return migrated
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrates V1 tab state (open paths + active index) to V2 format.
|
||||
* V1 stored these in localStorage via setStorageValue fallback.
|
||||
* V2 uses sessionStorage keyed by clientId.
|
||||
*/
|
||||
function migrateV1TabState(workspaceId: string, clientId?: string): void {
|
||||
if (!clientId) return
|
||||
|
||||
try {
|
||||
const pathsJson = localStorage.getItem(V1_KEYS.openPaths)
|
||||
if (!pathsJson) return
|
||||
|
||||
const paths = JSON.parse(pathsJson)
|
||||
if (!Array.isArray(paths) || paths.length === 0) return
|
||||
|
||||
const indexJson = localStorage.getItem(V1_KEYS.activeIndex)
|
||||
let activeIndex = 0
|
||||
if (indexJson !== null) {
|
||||
const parsed = JSON.parse(indexJson)
|
||||
if (typeof parsed === 'number' && Number.isFinite(parsed)) {
|
||||
activeIndex = Math.min(Math.max(0, parsed), paths.length - 1)
|
||||
}
|
||||
}
|
||||
|
||||
writeOpenPaths(clientId, { workspaceId, paths, activeIndex })
|
||||
} catch {
|
||||
// Best effort - don't block draft migration on tab state errors
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up V1 data after successful migration.
|
||||
* Should NOT be called until 2026-07-15 to allow rollback.
|
||||
|
||||
Reference in New Issue
Block a user