From 43f69066af9a088c24fce961b71777df6e4b184a Mon Sep 17 00:00:00 2001 From: znetsixe Date: Mon, 13 Apr 2026 14:50:45 +0200 Subject: [PATCH] fix(asset-menu): supplier->type->model cascade lost the model dropdown MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reproduction (any node using assetMenu — measurement, rotatingMachine, pumpingStation, monster, …): open node -> pick Vega supplier -> pick Pressure type -> model dropdown stays "Awaiting Type Selection" Root cause: two interacting bugs in the chained dropdown wiring. 1. populate() inside both wireEvents() and loadData() auto-dispatched a synthetic 'change' event whenever the value of the rebuilt with placeholder + options and assigns the requested value. - New cascadeFromSupplier / cascadeFromType / cascadeFromModel helpers read the *current DOM value* of each upstream 's native 'change' listener is just the corresponding cascade function. Same code path runs for user picks AND for initial load, so saved-node restore behaves identically to a fresh pick. - The cascades are exposed under window.EVOLV.nodes..assetMenu._cascade so loadData (or future sync code) can re-run them after async data arrives without duplicating logic. No new DOM dependencies, no test framework changes. Existing generalFunctions tests still 52/61 (same 9 pre-existing failures unrelated to this change). Co-Authored-By: Claude Opus 4.6 (1M context) --- src/menu/asset.js | 193 ++++++++++++++++++++++++++-------------------- 1 file changed, 111 insertions(+), 82 deletions(-) diff --git a/src/menu/asset.js b/src/menu/asset.js index d84bd81..1f664b4 100644 --- a/src/menu/asset.js +++ b/src/menu/asset.js @@ -193,8 +193,13 @@ class AssetMenu { return normalizeApiCategory(key, node.softwareType || key, payload.data); } + // Non-dispatching populate (matches the wireEvents version). The + // load path below explicitly walks supplier -> type -> model -> + // unit in order using saved node.* values, so auto-dispatched + // change events (which previously cascaded through wireEvents' + // listeners and double-populated everything) are no longer needed. function populate(selectEl, items = [], selectedValue, mapFn, placeholderText = 'Select...') { - const previous = selectEl.value; + if (!selectEl) return; const mapper = typeof mapFn === 'function' ? mapFn : (value) => ({ value, label: value }); @@ -227,9 +232,6 @@ class AssetMenu { } else { selectEl.value = ''; } - if (selectEl.value !== previous) { - selectEl.dispatchEvent(new Event('change')); - } } const categoryKey = resolveCategoryKey(); @@ -305,6 +307,28 @@ class AssetMenu { getEventInjectionCode(nodeName) { return ` // Asset event wiring for ${nodeName} + // + // The supplier -> type -> model -> unit chain is a strict downward + // cascade: each select rebuilds the next based on the currently + // selected value above it. Two earlier bugs in this code: + // + // 1. populate() auto-dispatched a synthetic 'change' event whenever + // the value of the rebuilt select differed from before the + // rebuild. That triggered the *child* select's listener mid-way + // through the *parent* listener, which then continued and + // blindly overwrote the child select with empty content. Net + // effect: model dropdown showed 'Awaiting Type Selection' even + // though a type was clearly selected. + // + // 2. Each downstream wipe ran unconditionally inside the parent + // handler, instead of being driven by the actual current value + // of the child select. + // + // Fix: populate() no longer dispatches change. Cascade is explicit + // via cascadeFromSupplier() / cascadeFromType() / cascadeFromModel() + // which are called from each handler. The same helpers run on + // initial load so behaviour is identical whether the user picked the + // value or it came from a saved node. window.EVOLV.nodes.${nodeName}.assetMenu.wireEvents = function(node) { const menuAsset = window.EVOLV.nodes.${nodeName}.menuData.asset || {}; const categories = menuAsset.categories || {}; @@ -316,11 +340,17 @@ class AssetMenu { unit: document.getElementById('node-input-unit') }; - function populate(selectEl, items = [], selectedValue, mapFn, placeholderText = 'Select...') { - const previous = selectEl.value; + // populate(): rebuild a