fix: gate add-to-link picker on kind and combo choices compatibility

Adversarial review of the previous commit found two ways an
incompatible node input could silently corrupt an existing linked
input's shared value: no kind check at all, and (after fixing that) a
same-kind combo with a different choices list still slipping through
and getting overwritten via the widget's own stale-value self-heal,
which then broadcast to every other member of the link.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
digital-garbage
2026-09-17 20:03:03 +03:00
co-authored by Claude Sonnet 5
parent 1f663a3ce1
commit 3022a341b0
+15 -1
View File
@@ -191,6 +191,20 @@
function ensureLinks() { if (!config.links) config.links = []; return config.links; }
function linkOf(slotId, input) { return (config.links || []).find((l) => (l.members || []).some((m) => m.slotId === slotId && m.input === input)); }
function linkSelHas(slotId, input) { return linkSel.some((s) => s.slotId === slotId && s.input === input); }
// When adding to an already-existing link, only offer inputs whose kind — and, for a
// combo, whose exact option set — that link's shared value could actually hold. A
// same-kind combo with a DIFFERENT choices list still corrupts the link: the new
// member's own stale-value self-heal (widgetField's "value not in choices" branch)
// fires on next render and pushes its default back out to every other member.
function linkPickKindOk(kind, choices) {
if (!linkTarget) return true;
const link = (config.links || []).find((l) => l.id === linkTarget);
if (!link || !link.kind) return true;
if (link.kind !== kind) return false;
if (kind !== "combo") return true;
const a = link.choices || [], b = choices || [];
return a.length === b.length && a.every((c) => b.includes(c));
}
function applyLinkValue(link, value) {
link.value = value;
(link.members || []).forEach((m) => { const s = slotById(m.slotId); if (s) { s.inputs = s.inputs || {}; s.inputs[m.input] = value; } });
@@ -904,7 +918,7 @@
f.classList.add("linked");
const ctrl = f.querySelector("input,select"); if (ctrl) ctrl.disabled = true;
f.append(el("span", "link-tag", "🔗 " + lk.name));
} else if (linkMode && !isList) {
} else if (linkMode && !isList && linkPickKindOk(spec.kind, spec.choices)) {
const chk = el("button", "eye-btn link-pick" + (linkSelHas(slot.id, spec.name) ? " on" : ""), "+");
chk.type = "button"; chk.title = "Add to link selection";
chk.onclick = (e) => {