Skip to content

Commit dcdecff

Browse files
committed
fix: working on duplicate node field history line item bug
1 parent 1342c1d commit dcdecff

4 files changed

Lines changed: 82 additions & 18 deletions

File tree

src/NodeCanvas.jsx

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ function NodeCanvas() {
132132
const wrapperRef = useRef(null);
133133
const [orbitData, setOrbitData] = useState({ inner: [], outer: [], all: [] });
134134
const wasDraggingRef = useRef(false); // Track if a drag just occurred to prevent click events
135+
const dragHistoryRecordedRef = useRef(false); // Guard against double recording of drag events
135136

136137
// Helper to measure text width accurately for the group labels
137138
// We use a cached canvas context to avoid creating it repeatedly
@@ -5060,10 +5061,11 @@ function NodeCanvas() {
50605061
primaryId: instanceId
50615062
});
50625063

5064+
dragHistoryRecordedRef.current = false; // Reset history guard
50635065
triggerDragZoomOut(clientX, clientY);
50645066

50655067
selectedInstanceIds.forEach(id => {
5066-
storeActions.updateNodeInstance(activeGraphId, id, draft => { draft.scale = 1.15; }, { isDragging: true, phase: 'start' });
5068+
storeActions.updateNodeInstance(activeGraphId, id, draft => { draft.scale = 1.15; }, { isDragging: true, phase: 'start', ignore: true });
50675069
});
50685070
return true;
50695071
}
@@ -5075,11 +5077,9 @@ function NodeCanvas() {
50755077
const offset = { x: mouseCanvasX - nodeData.x, y: mouseCanvasY - nodeData.y };
50765078
setDraggingNodeInfo({ instanceId, offset, initialPos: { x: nodeData.x, y: nodeData.y } });
50775079

5078-
5079-
5080+
dragHistoryRecordedRef.current = false; // Reset history guard
50805081
triggerDragZoomOut(clientX, clientY);
5081-
5082-
storeActions.updateNodeInstance(activeGraphId, instanceId, draft => { draft.scale = 1.15; }, { isDragging: true, phase: 'start' });
5082+
storeActions.updateNodeInstance(activeGraphId, instanceId, draft => { draft.scale = 1.15; }, { isDragging: true, phase: 'start', ignore: true });
50835083
return true;
50845084
}, [
50855085
activeGraphId,
@@ -6738,7 +6738,7 @@ function NodeCanvas() {
67386738
}
67396739

67406740
// Reset scale for dragged nodes
6741-
if (draggingNodeInfo) {
6741+
if (draggingNodeInfo && !dragHistoryRecordedRef.current) {
67426742
// --- Manual History Recording for Drag ---
67436743
const patches = [];
67446744
const inversePatches = [];
@@ -6780,7 +6780,13 @@ function NodeCanvas() {
67806780
patches,
67816781
inversePatches
67826782
});
6783+
dragHistoryRecordedRef.current = true; // Mark as recorded for this drag session
67836784
}
6785+
}
6786+
6787+
// Common finalization logic (always run even if history recorded)
6788+
if (draggingNodeInfo) {
6789+
// -----------------------------------------
67846790
// -----------------------------------------
67856791

67866792
const instanceIdsToReset = new Set();

src/components/panel/views/LeftHistoryView.css

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
background-color: #bdb5b5;
77
color: #260000;
88
overflow: hidden;
9+
font-family: 'EmOne', sans-serif;
910
}
1011

1112
/* Header - matches other panels */
@@ -23,12 +24,14 @@
2324
font-weight: 600;
2425
color: #260000;
2526
letter-spacing: -0.01em;
27+
font-family: 'EmOne', sans-serif;
2628
}
2729

2830
.history-stats {
2931
font-size: 11px;
3032
opacity: 0.5;
3133
font-weight: 500;
34+
font-family: 'EmOne', sans-serif;
3235
}
3336

3437
/* Filter Tabs */
@@ -98,14 +101,39 @@
98101

99102
.history-item {
100103
display: flex;
101-
padding: 8px 16px;
102-
transition: background-color 0.1s ease;
103-
align-items: flex-start;
104-
gap: 10px;
104+
align-items: center;
105+
padding: 7px 12px;
106+
gap: 12px;
107+
/* Use transparent border to reserve space for the active state/head */
108+
border: 3px solid transparent;
109+
border-bottom: 3px solid transparent;
110+
/* Override bottom separataor? */
111+
border-radius: 8px;
112+
/* Always rounded */
113+
114+
margin: 3px 12px;
115+
/* Increased margin for better clearance */
116+
/* Consistent margin inside container, added vertical spacing */
117+
box-sizing: border-box;
118+
/* Ensure padding/border don't overflow width */
119+
width: auto;
120+
/* Let it fill available space minus margins */
121+
122+
transition: background-color 0.15s ease, border-color 0.15s ease;
123+
cursor: pointer;
124+
/* Interactive */
125+
position: relative;
126+
user-select: none;
105127
}
106128

129+
/* We need a separator line, but border-radius messes with border-bottom full width.
130+
So we might need a pseudo-element for the separator line if we want it?
131+
Or just accept the spaced styling.
132+
Let's remove the old border-bottom separator and just rely on spacing or hover.
133+
*/
134+
107135
.history-item:hover {
108-
background-color: rgba(38, 0, 0, 0.03);
136+
background-color: rgba(255, 255, 255, 0.4);
109137
}
110138

111139
/* Subtly distinguish global actions */
@@ -118,20 +146,21 @@
118146
}
119147

120148
.history-item.undone {
121-
opacity: 0.5;
122-
background-color: rgba(0, 0, 0, 0.02);
149+
opacity: 0.4;
150+
background-color: transparent;
123151
}
124152

125153
.history-item.head {
126-
background-color: rgba(255, 255, 255, 0.7);
127-
border-left: 3px solid #8B0000;
128-
padding-left: 13px;
129-
/* Adjust for border width */
154+
background-color: transparent;
155+
border-color: #8B0000;
156+
/* Padding/margin already handled by base class */
130157
}
131158

132159
/* Head indicator dot on the right? Or border left? Doing border left above.
133160
Also can add the dot. */
134161
.history-head-indicator {
162+
display: none;
163+
/* Hide dot since we have the box outline */
135164
position: absolute;
136165
right: 12px;
137166
top: 50%;
@@ -171,10 +200,11 @@
171200
white-space: nowrap;
172201
overflow: hidden;
173202
text-overflow: ellipsis;
203+
font-family: 'EmOne', sans-serif;
174204
}
175205

176206
.history-item.undone .history-item-description {
177-
text-decoration: line-through;
207+
text-decoration: none;
178208
opacity: 0.7;
179209
}
180210

src/store/graphStore.jsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,11 @@ const saveCoordinatorMiddleware = (config) => {
189189
return (set, get, api) => {
190190
// Enhance the set function to track change context and capture patches
191191
const enhancedSet = (...args) => {
192+
// DEBUG: Trace why actions are duplicated
193+
if (changeContext.type !== 'node_update') { // Filter out noise if needed, or just log all
194+
console.log(`[GraphStore] Action: ${changeContext.type}`, new Error().stack.split('\n')[2]);
195+
}
196+
192197
// 1. Capture patches via the global listener (hooked into our custom produce wrapper)
193198
let currentPatches = null;
194199
let currentInverse = null;

src/store/historyStore.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,29 @@ const useHistoryStore = create((set, get) => ({
1919
? state.history
2020
: state.history.slice(0, state.history.length + state.currentIndex + 1);
2121

22+
// Deduplication Logic:
23+
// Check if the new entry is identical to the last one (double-fire protection)
24+
const lastAction = newHistory[newHistory.length - 1];
25+
console.log('[History] pushAction called:', entry.actionType, entry.description);
26+
if (lastAction) {
27+
const isSameType = lastAction.actionType === entry.actionType;
28+
const isSameDesc = lastAction.description === entry.description;
29+
const isRecent = (Date.now() - lastAction.timestamp) < 500; // Within 500ms
30+
const timeDiff = Date.now() - lastAction.timestamp;
31+
32+
console.log('[History] Dedup check:', { isSameType, isSameDesc, isRecent, timeDiff });
33+
34+
if (isSameType && isSameDesc && isRecent) {
35+
// deep check patches
36+
const isSamePatches = JSON.stringify(lastAction.patches) === JSON.stringify(entry.patches);
37+
console.log('[History] Patches comparison:', { isSamePatches, lastPatchCount: lastAction.patches?.length, newPatchCount: entry.patches?.length });
38+
if (isSamePatches) {
39+
console.warn('[History] Duplicate action ignored:', entry.description);
40+
return state; // No change
41+
}
42+
}
43+
}
44+
2245
// Flatten domain if it's an object (just in case)
2346
const domain = typeof entry.domain === 'object' ? 'global' : entry.domain;
2447

0 commit comments

Comments
 (0)