Browse Source

fix: replace JSON deep-clone with clonePreservingStrings in clipboard and snapshot

copySelectedNodes, pasteNodes, and AI captureSnapshot were still using
JSON.parse(JSON.stringify(...)) which duplicates multi-MB base64 blobs.
Switched to clonePreservingStrings which shares immutable string refs.
Also clarified docstring re: toJSON not being called.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
handoff-20260429-1057
shrimbly 3 months ago
parent
commit
1c785aa039
  1. 6
      src/store/undoHistory.ts
  2. 16
      src/store/workflowStore.ts

6
src/store/undoHistory.ts

@ -57,9 +57,13 @@ export class UndoManager {
* snapshots, but strings (immutable in JS) are returned by reference.
* This avoids duplicating multi-MB base64 blobs across undo history.
*
* Matches JSON.parse(JSON.stringify()) semantics:
* Matches JSON.parse(JSON.stringify()) semantics for plain JSON-like
* objects/arrays:
* - `undefined` values are dropped from objects, become `null` in arrays
* - functions are dropped from objects, become `null` in arrays
*
* Does NOT call toJSON() on objects. Objects with custom toJSON methods
* are treated as plain objects (their enumerable own properties are cloned).
*/
export function clonePreservingStrings<T>(value: T): T {
if (value === null || typeof value !== "object") {

16
src/store/workflowStore.ts

@ -907,8 +907,8 @@ const workflowStoreImpl: StateCreator<WorkflowStore> = (set, get) => ({
);
// Deep clone the nodes and edges to avoid reference issues
const clonedNodes = JSON.parse(JSON.stringify(selectedNodes)) as WorkflowNode[];
const clonedEdges = JSON.parse(JSON.stringify(connectedEdges)) as WorkflowEdge[];
const clonedNodes = clonePreservingStrings(selectedNodes) as WorkflowNode[];
const clonedEdges = clonePreservingStrings(connectedEdges) as WorkflowEdge[];
set({ clipboard: { nodes: clonedNodes, edges: clonedEdges } });
},
@ -946,7 +946,7 @@ const workflowStoreImpl: StateCreator<WorkflowStore> = (set, get) => ({
width: undefined,
height: undefined,
measured: undefined,
data: JSON.parse(JSON.stringify(node.data)),
data: clonePreservingStrings(node.data),
};
});
@ -2536,12 +2536,12 @@ const workflowStoreImpl: StateCreator<WorkflowStore> = (set, get) => ({
captureSnapshot: () => {
const state = get();
// Deep copy the current workflow state to avoid reference sharing
const snapshot = {
nodes: JSON.parse(JSON.stringify(state.nodes)),
edges: JSON.parse(JSON.stringify(state.edges)),
groups: JSON.parse(JSON.stringify(state.groups)),
const snapshot = clonePreservingStrings({
nodes: state.nodes,
edges: state.edges,
groups: state.groups,
edgeStyle: state.edgeStyle,
};
});
set({
previousWorkflowSnapshot: snapshot,
manualChangeCount: 0,

Loading…
Cancel
Save