diff --git a/CHANGELOG.md b/CHANGELOG.md index ab6c4e8..6b2333d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,22 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.2.1] - 2026-11-03 + +Fixed: +- Dynamic input nodes (e.g. "create DICT"): clearing a key/value pair could + leave the node with input names and widget names out of sync. The prompt was + then built from the stale widget names, so the container was created empty + and the connection was drawn next to the wrong row. Removed widgets are now + also removed from ComfyUI's widget value store, which keeps both in sync. +- Dynamic input nodes (e.g. "create DICT" and "create LIST") lost their extra + key/value pairs when the workflow was recreated by dragging a produced image + or video back into ComfyUI: the workflow that ComfyUI embeds into those files + drops unconnected widget inputs, and for these nodes that slot is what keeps + a pair alive, so the values ended up on the wrong rows and the container was + created with only its first entry. The dynamic inputs are now kept in that + embedded workflow, also for workflows that were saved before this fix. + ## [2.2.0] - 2026-10-16 Added: diff --git a/pyproject.toml b/pyproject.toml index 7b45cb8..6f8a935 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "basic_data_handling" -version = "2.2.0" +version = "2.2.1" description = """Basic Python functions for manipulating data that every programmer is used to, lightweight with no additional dependencies. Supported data types: diff --git a/web/js/dynamicnode.js b/web/js/dynamicnode.js index fdb9aa4..7c8eae1 100644 --- a/web/js/dynamicnode.js +++ b/web/js/dynamicnode.js @@ -266,6 +266,11 @@ app.registerExtension({ if (isWidgetFlag) { addStandardWidget.call(this, name, widgetGuiType, widgetDefaultVal, widgetConfOpts); this.addInput(name, comfyInputType, { shape, widget: { name } }); + // ComfyUI drops widget inputs that are not connected from the workflow it + // embeds into saved images/videos (compressWidgetInputSlots) unless the slot + // carries a label. For dynamic inputs the slot is what keeps the whole + // key/value pair alive, so label it with its own name to survive the round trip. + this.inputs[this.inputs.length - 1].label = name; } else { this.addInput(name, comfyInputType, { shape }); } @@ -312,10 +317,12 @@ app.registerExtension({ const widget = this.widgets.find(w => w.name === widgetName); if (widget) { widget.name = newName; + if (widget.label) widget.label = newName; } if (inputRef.widget) inputRef.widget.name = newName; } inputRef.name = newName; + if (inputRef.label) inputRef.label = newName; } updateSlotIndices(this); }; @@ -500,7 +507,7 @@ app.registerExtension({ this.setDirtyCanvas(true, true); }; - // --- Event Handlers (onConnectionsChange, onConnectInput, onRemoved, onWidgetChanged) --- + // --- Event Handlers (onConnectionsChange, onConnectInput, onConfigure, onRemoved, onWidgetChanged) --- const onConnectionsChange = nodeType.prototype.onConnectionsChange; nodeType.prototype.onConnectionsChange = function (type, slotIndex, isConnected, link, ioSlot) { const originalReturn = onConnectionsChange?.apply(this, arguments); @@ -532,6 +539,22 @@ app.registerExtension({ return result; } + const onConfigure = nodeType.prototype.onConfigure; + nodeType.prototype.onConfigure = function () { + const originalReturn = onConfigure?.apply(this, arguments); + // Inputs restored from a workflow may not carry the label yet, e.g. when the + // workflow was saved before it was introduced. Without the label ComfyUI + // drops the slot again when the workflow is embedded into an image/video, so + // stamp it here for every dynamic widget input. + for (const input of this.inputs ?? []) { + if (!input.widget || input.label || !isDynamicInput(input.name)) continue; + input.label = input.name; + const widget = this.widgets?.find((w) => w.name === input.widget.name); + if (widget && !widget.label) widget.label = input.name; + } + return originalReturn; + }; + const onRemoved = nodeType.prototype.onRemoved; nodeType.prototype.onRemoved = function () { if(!isProcessingConnection){