Skip to content

Commit d9201d8

Browse files
committed
feat(undo-redo): migrate persistence from localStorage to IndexedDB
Replace inline safeStorageAdapter with indexedDBStorage. Workflow undo/ redo persistence moves from localStorage (~5MB origin cap) to IndexedDB (~GB), eliminating QuotaExceededError that can surface from any small persisted Zustand store (notification-storage, panel-editor-state, etc.) once workflow-undo-redo occupies the bulk of the origin's localStorage budget. Same pattern as PR #2812 (terminal-console -> IndexedDB) and aligns with PR #497's policy of minimizing localStorage usage. The earlier safeStorageAdapter (PR #2079) silently swallowed quota throws in this store but did not free the origin budget that the other small UI stores share, leaving them to throw uncaught QuotaExceededError. Fixes #4737 Signed-off-by: JaeHyung Jang <jaehyung.jang@navercorp.com>
1 parent fe854cb commit d9201d8

1 file changed

Lines changed: 2 additions & 36 deletions

File tree

apps/sim/stores/undo-redo/store.ts

Lines changed: 2 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { UNDO_REDO_OPERATIONS } from '@sim/realtime-protocol/constants'
33
import type { Edge } from 'reactflow'
44
import { create } from 'zustand'
55
import { createJSONStorage, persist } from 'zustand/middleware'
6+
import { indexedDBStorage } from '@/stores/undo-redo/storage'
67
import type {
78
BatchAddBlocksOperation,
89
BatchAddEdgesOperation,
@@ -47,41 +48,6 @@ function getStackKey(workflowId: string, userId: string): string {
4748
return `${workflowId}:${userId}`
4849
}
4950

50-
/**
51-
* Custom storage adapter for Zustand's persist middleware.
52-
* We need this wrapper to gracefully handle 'QuotaExceededError' when localStorage is full,
53-
* Without this, the default storage engine would throw and crash the application.
54-
* and to properly handle SSR/Node.js environments.
55-
*/
56-
const safeStorageAdapter = {
57-
getItem: (name: string): string | null => {
58-
if (typeof localStorage === 'undefined') return null
59-
try {
60-
return localStorage.getItem(name)
61-
} catch (e) {
62-
logger.warn('Failed to read from localStorage', e)
63-
return null
64-
}
65-
},
66-
setItem: (name: string, value: string): void => {
67-
if (typeof localStorage === 'undefined') return
68-
try {
69-
localStorage.setItem(name, value)
70-
} catch (e) {
71-
// Log warning but don't crash - this handles QuotaExceededError
72-
logger.warn('Failed to save to localStorage', e)
73-
}
74-
},
75-
removeItem: (name: string): void => {
76-
if (typeof localStorage === 'undefined') return
77-
try {
78-
localStorage.removeItem(name)
79-
} catch (e) {
80-
logger.warn('Failed to remove from localStorage', e)
81-
}
82-
},
83-
}
84-
8551
function isOperationApplicable(
8652
operation: Operation,
8753
graph: { blocksById: Record<string, BlockState>; edgesById: Record<string, Edge> }
@@ -501,7 +467,7 @@ export const useUndoRedoStore = create<UndoRedoState>()(
501467
}),
502468
{
503469
name: 'workflow-undo-redo',
504-
storage: createJSONStorage(() => safeStorageAdapter),
470+
storage: createJSONStorage(() => indexedDBStorage),
505471
partialize: (state) => ({
506472
stacks: state.stacks,
507473
capacity: state.capacity,

0 commit comments

Comments
 (0)