mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-21 21:22:11 -03:00
Remove multiple sources of truth and async sync chains that caused values to be lost during load/switch workflow or reload page. Changes: - Remove internalValue state variable from main.ts - Update getValue/setValue to read/write DOM directly via widget.inputEl - Remove textValue reactive ref and v-model from Vue component - Remove serializeValue, onSetValue, and watch callbacks - Register textarea reference on mount, clean up on unmount - Simplify AutocompleteTextWidgetInterface Follows ComfyUI built-in addMultilineWidget pattern: - Single source of truth (DOM element value only) - Direct sync (no intermediate variables or async chains) Also adds documentation: - docs/dom-widgets/value-persistence-best-practices.md - docs/dom-widgets/README.md - Update docs/dom_widget_dev_guide.md with reference
3.5 KiB
3.5 KiB
DOM Widget Value Persistence - Best Practices
Problem
DOM widgets with text inputs failed to persist values after:
- Loading workflows
- Switching workflows
- Reloading pages
Root Cause
Multiple sources of truth causing sync issues:
- Internal state variable (
internalValuein main.ts) - Vue reactive ref (
textValuein component) - DOM element value (actual textarea)
- ComfyUI widget value (
props.widget.value)
Broken sync chains:
getValue() → internalValue (not actual DOM value)
setValue(v) → internalValue → onSetValue() → textValue.value (async chain)
serializeValue() → textValue.value (different from getValue)
watch() → another sync layer
Solution
Follow ComfyUI built-in addMultilineWidget pattern:
✅ Do
-
Single source of truth: Use the DOM element directly
// main.ts const widget = node.addDOMWidget(name, type, container, { getValue() { return widget.inputEl?.value ?? '' }, setValue(v: string) { if (widget.inputEl) { widget.inputEl.value = v ?? '' } } }) -
Register DOM reference when component mounts
// Vue component onMounted(() => { if (textareaRef.value) { props.widget.inputEl = textareaRef.value } }) -
Clean up reference on unmount
onUnmounted(() => { if (props.widget.inputEl === textareaRef.value) { props.widget.inputEl = undefined } }) -
Simplify interface - only expose what's needed
export interface MyWidgetInterface { inputEl?: HTMLTextAreaElement callback?: (v: string) => void }
❌ Don't
-
Don't create internal state variables
// Wrong let internalValue = '' getValue() { return internalValue } -
Don't use v-model for text inputs in DOM widgets
<!-- Wrong --> <textarea v-model="textValue" /> <!-- Right --> <textarea ref="textareaRef" @input="onInput" /> -
Don't add serializeValue - getValue/setValue handle it
// Wrong props.widget.serializeValue = async () => textValue.value -
Don't add onSetValue callback
// Wrong setValue(v: string) { internalValue = v widget.onSetValue?.(v) // Unnecessary layer } -
Don't watch props.widget.value - creates race conditions
// Wrong watch(() => props.widget.value, (newValue) => { textValue.value = newValue }) -
Don't restore from props.widget.value in onMounted
// Wrong onMounted(() => { if (props.widget.value) { textValue.value = props.widget.value } })
Key Principles
- One source of truth: DOM element value only
- Direct sync: getValue/setValue read/write DOM directly
- No async chains: Eliminate intermediate variables
- Match built-in patterns: Study ComfyUI's
addMultilineWidgetimplementation - Minimal interface: Only expose
inputElandcallback
Testing Checklist
- Load workflow - value restores correctly
- Switch workflow - value persists
- Reload page - value persists
- Type in widget - callback fires
- No console errors
References
- ComfyUI built-in:
/home/miao/code/ComfyUI_frontend/src/renderer/extensions/vueNodes/widgets/composables/useStringWidget.ts - Example fix:
vue-widgets/src/components/AutocompleteTextWidget.vue(after fix)