Skip to content

Commit 5604e83

Browse files
kubeclaude
andcommitted
H-6281: Fix stale closure in multi-node drags and deduplicate input checks
Use the setState updater form in mutatePetriNetDefinition so multiple calls before a re-render each see the latest state, fixing lost position updates during multi-node drag end. Merge duplicated isTextInput and isInputFocused checks into a single variable to prevent drift. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 1f65204 commit 5604e83

2 files changed

Lines changed: 27 additions & 26 deletions

File tree

libs/@hashintel/petrinaut/demo-site/main/app.tsx

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -113,23 +113,33 @@ export const DevApp = () => {
113113
const mutatePetriNetDefinition = (
114114
definitionMutationFn: (draft: SDCPN) => void,
115115
) => {
116-
if (!currentNetId || !currentNet || isOldFormatInLocalStorage(currentNet)) {
116+
if (!currentNetId) {
117117
return;
118118
}
119119

120-
// Compute the new SDCPN synchronously so we can push it to history
121-
// without relying on async state reads.
122-
const newSDCPN = produce(currentNet.sdcpn, definitionMutationFn);
120+
let newSDCPN: SDCPN | undefined;
123121

124-
setStoredSDCPNs((prev) =>
125-
produce(prev, (draft) => {
126-
if (draft[currentNetId]) {
127-
draft[currentNetId].sdcpn = newSDCPN;
128-
}
129-
}),
130-
);
122+
// Use the updater form so that multiple calls before a re-render
123+
// (e.g. multi-node drag end) each see the latest state.
124+
setStoredSDCPNs((prev) => {
125+
const net = prev[currentNetId];
126+
if (!net || isOldFormatInLocalStorage(net)) {
127+
return prev;
128+
}
129+
const updatedSDCPN = produce(net.sdcpn, definitionMutationFn);
130+
newSDCPN = updatedSDCPN;
131+
return {
132+
...prev,
133+
[currentNetId]: {
134+
...net,
135+
sdcpn: updatedSDCPN,
136+
},
137+
};
138+
});
131139

132-
pushState(newSDCPN);
140+
if (newSDCPN) {
141+
pushState(newSDCPN);
142+
}
133143
};
134144

135145
const prevNetIdRef = useRef(currentNetId);

libs/@hashintel/petrinaut/src/views/Editor/components/BottomBar/use-keyboard-shortcuts.ts

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,17 @@ export function useKeyboardShortcuts(
1717
function handleKeyDown(event: KeyboardEvent) {
1818
const target = event.target as HTMLElement;
1919

20-
// Handle undo/redo shortcuts, but let Monaco editors, inputs,
21-
// textareas, and contentEditable elements handle their own undo/redo.
22-
const isTextInput =
20+
const isInputFocused =
2321
target.tagName === "INPUT" ||
2422
target.tagName === "TEXTAREA" ||
2523
target.isContentEditable ||
26-
target.closest(".monaco-editor") !== null;
24+
target.closest(".monaco-editor") !== null ||
25+
target.closest("#sentry-feedback") !== null;
2726

27+
// Handle undo/redo shortcuts, but let inputs handle their own undo/redo.
2828
if (
2929
undoRedo &&
30-
!isTextInput &&
30+
!isInputFocused &&
3131
(event.metaKey || event.ctrlKey) &&
3232
event.key.toLowerCase() === "z"
3333
) {
@@ -40,15 +40,6 @@ export function useKeyboardShortcuts(
4040
return;
4141
}
4242

43-
// Don't trigger if focus is in an input, textarea, contentEditable, or Monaco editor
44-
const isInputFocused =
45-
target.tagName === "INPUT" ||
46-
target.tagName === "TEXTAREA" ||
47-
target.isContentEditable ||
48-
// Check if we're inside a Monaco editor
49-
target.closest(".monaco-editor") !== null ||
50-
target.closest("#sentry-feedback") !== null;
51-
5243
if (isInputFocused) {
5344
return;
5445
}

0 commit comments

Comments
 (0)