From 9d8ba7ad7c17c65d5e2c0ea7e480f4d7320d365a Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 20 Mar 2026 22:26:50 +0000 Subject: [PATCH] fix: group MODE field change and disconnect events in if_clause for correct undo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the user changes an if_clause block's MODE dropdown (e.g. IF → ELSE) while a following block needs to be disconnected, validateAndDisconnectInvalidChain_ was firing BLOCK_MOVE events before Blockly fired the BLOCK_CHANGE for the field. With no active event group, these landed in separate undo entries, requiring two Ctrl+Z presses to undo a single user action. Fix: the dropdown validator now opens an event group when none is already active, and the onchange handler closes it once the corresponding BLOCK_CHANGE fires. This ensures the disconnect and the field change are always a single undo entry. https://claude.ai/code/session_013VxihhypLXT63EyPWiBk6j --- blocks/control.js | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/blocks/control.js b/blocks/control.js index 29a88e1f..d952c0fe 100644 --- a/blocks/control.js +++ b/blocks/control.js @@ -461,6 +461,10 @@ export function defineControlBlocks() { // True while Blockly is constructing/rehydrating the block graph. this._isRestoring = true; + // Tracks a group we opened in the MODE validator so we can close it + // once Blockly fires the corresponding BLOCK_CHANGE event. + this._modeChangeGroupId = null; + this.appendDummyInput("HEAD").appendField( new Blockly.FieldDropdown( [ @@ -469,6 +473,14 @@ export function defineControlBlocks() { ["%{BKY_CONTROLS_IF_MSG_ELSE}", MODE.ELSE], ], (newValue) => { + // If there's no active event group, open one so that any + // BLOCK_MOVE events fired by validateAndDisconnectInvalidChain_ + // land in the same undo entry as the BLOCK_CHANGE that Blockly + // fires for this field after the validator returns. + if (!Blockly.Events.getGroup() && !this._modeChangeGroupId) { + Blockly.Events.setGroup(true); + this._modeChangeGroupId = Blockly.Events.getGroup(); + } this.updateShape_(newValue); return newValue; }, @@ -585,6 +597,22 @@ export function defineControlBlocks() { return; } + // Close the event group we opened in the MODE validator once Blockly + // has fired the corresponding BLOCK_CHANGE, so that the field change + // and any prior disconnect events are a single undo entry. + if ( + this._modeChangeGroupId && + event.type === Blockly.Events.BLOCK_CHANGE && + event.element === "field" && + event.name === "MODE" && + event.blockId === this.id + ) { + if (Blockly.Events.getGroup() === this._modeChangeGroupId) { + Blockly.Events.setGroup(false); + } + this._modeChangeGroupId = null; + } + const isRelevantEvent = event.type === Blockly.Events.BLOCK_MOVE || event.type === Blockly.Events.BLOCK_CHANGE ||