From a747f86daafd3118590627ad0a7bd8e651d35e6c Mon Sep 17 00:00:00 2001 From: Ethanfel Date: Mon, 23 Feb 2026 16:16:50 +0100 Subject: [PATCH] Persist output types across save/load via hidden output_types widget Types were lost on workflow reload because only key names were stored. Now both keys and types are saved in hidden widgets and restored by onConfigure, so colored connector dots persist without needing Refresh. Co-Authored-By: Claude Opus 4.6 --- json_loader.py | 1 + web/json_dynamic.js | 30 +++++++++++++++++++----------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/json_loader.py b/json_loader.py index bea59e0..25412c0 100644 --- a/json_loader.py +++ b/json_loader.py @@ -106,6 +106,7 @@ class JSONLoaderDynamic: }, "optional": { "output_keys": ("STRING", {"default": ""}), + "output_types": ("STRING", {"default": ""}), }, } diff --git a/web/json_dynamic.js b/web/json_dynamic.js index f2b4304..81e11f1 100644 --- a/web/json_dynamic.js +++ b/web/json_dynamic.js @@ -11,11 +11,10 @@ app.registerExtension({ nodeType.prototype.onNodeCreated = function () { origOnNodeCreated?.apply(this, arguments); - // Hide the output_keys widget (managed internally by JS) - const okWidget = this.widgets?.find(w => w.name === "output_keys"); - if (okWidget) { - okWidget.type = "hidden"; - okWidget.computeSize = () => [0, -4]; + // Hide internal widgets (managed by JS) + for (const name of ["output_keys", "output_types"]) { + const w = this.widgets?.find(w => w.name === name); + if (w) { w.type = "hidden"; w.computeSize = () => [0, -4]; } } // Remove all 32 default outputs from Python RETURN_TYPES @@ -42,9 +41,11 @@ app.registerExtension({ ); const { keys, types } = await resp.json(); - // Update output_keys widget for Python to read at execution time + // Store keys and types in hidden widgets for persistence const okWidget = this.widgets?.find(w => w.name === "output_keys"); if (okWidget) okWidget.value = keys.join(","); + const otWidget = this.widgets?.find(w => w.name === "output_types"); + if (otWidget) otWidget.value = types.join(","); // Build a map of current output names to slot indices const oldSlots = {}; @@ -105,20 +106,27 @@ app.registerExtension({ nodeType.prototype.onConfigure = function (info) { origOnConfigure?.apply(this, arguments); - const okWidget = this.widgets?.find(w => w.name === "output_keys"); - if (okWidget) { - okWidget.type = "hidden"; - okWidget.computeSize = () => [0, -4]; + // Hide internal widgets + for (const name of ["output_keys", "output_types"]) { + const w = this.widgets?.find(w => w.name === name); + if (w) { w.type = "hidden"; w.computeSize = () => [0, -4]; } } + const okWidget = this.widgets?.find(w => w.name === "output_keys"); + const otWidget = this.widgets?.find(w => w.name === "output_types"); + const keys = okWidget?.value ? okWidget.value.split(",").filter(k => k.trim()) : []; + const types = otWidget?.value + ? otWidget.value.split(",") + : []; // On load, LiteGraph already restored serialized outputs with links. - // Just rename to match keys (preserves links) and trim excess. + // Rename and set types to match stored state (preserves links). for (let i = 0; i < this.outputs.length && i < keys.length; i++) { this.outputs[i].name = keys[i].trim(); + if (types[i]) this.outputs[i].type = types[i]; } // Remove any extra outputs beyond the key count