From 660ccb923680601381f4d1d9445c5faef2f6176d Mon Sep 17 00:00:00 2001 From: bymyself Date: Mon, 23 Feb 2026 04:19:57 +0000 Subject: [PATCH] fix: promoted textarea widgets in subgraphs no longer permanently read-only Skip disabled override for promoted widgets whose internal slot is linked to SubgraphInput. Extract isPromotedOnOwningNode variable and reuse it for the existing borderStyle check. Amp-Thread-ID: https://ampcode.com/threads/T-019c88b8-a377-754b-b624-db394d59f5b5 --- .../vueNodes/components/NodeWidgets.vue | 24 +++++++------ .../widgets/components/WidgetTextarea.test.ts | 35 ++++++++++++++----- 2 files changed, 41 insertions(+), 18 deletions(-) diff --git a/src/renderer/extensions/vueNodes/components/NodeWidgets.vue b/src/renderer/extensions/vueNodes/components/NodeWidgets.vue index 85895a01e1..2b9270adfd 100644 --- a/src/renderer/extensions/vueNodes/components/NodeWidgets.vue +++ b/src/renderer/extensions/vueNodes/components/NodeWidgets.vue @@ -192,19 +192,23 @@ const processedWidgets = computed((): ProcessedWidget[] => { // Get value from store (falls back to undefined if not registered) const value = widgetState?.value as WidgetValue - // Build options from store state, with slot-linked override for disabled + // Build options from store state, with slot-linked override for disabled. + // Promoted widgets inside a subgraph are always linked to SubgraphInput, + // but should remain interactive — skip the disabled override for them. const storeOptions = widgetState?.options ?? {} - const widgetOptions = slotMetadata?.linked - ? { ...storeOptions, disabled: true } - : storeOptions + const isPromotedOnOwningNode = + widgetState?.promoted && String(widgetState?.nodeId) === String(nodeId) + const widgetOptions = + slotMetadata?.linked && !isPromotedOnOwningNode + ? { ...storeOptions, disabled: true } + : storeOptions // Derive border style from store metadata - const borderStyle = - widgetState?.promoted && String(widgetState?.nodeId) === String(nodeId) - ? 'ring ring-component-node-widget-promoted' - : widget.options?.advanced - ? 'ring ring-component-node-widget-advanced' - : undefined + const borderStyle = isPromotedOnOwningNode + ? 'ring ring-component-node-widget-promoted' + : widget.options?.advanced + ? 'ring ring-component-node-widget-advanced' + : undefined const simplified: SimplifiedWidget = { name: widget.name, diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetTextarea.test.ts b/src/renderer/extensions/vueNodes/widgets/components/WidgetTextarea.test.ts index 15115917c0..fe2b28d707 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/WidgetTextarea.test.ts +++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetTextarea.test.ts @@ -22,14 +22,12 @@ function createMockWidget( function mountComponent( widget: SimplifiedWidget, modelValue: string, - readonly = false, placeholder?: string ) { return mount(WidgetTextarea, { props: { widget, modelValue, - readonly, placeholder } }) @@ -179,18 +177,39 @@ describe('WidgetTextarea Value Binding', () => { it('uses provided placeholder when specified', () => { const widget = createMockWidget('test') - const wrapper = mountComponent( - widget, - 'test', - false, - 'Custom placeholder' - ) + const wrapper = mountComponent(widget, 'test', 'Custom placeholder') const textarea = wrapper.find('textarea') expect(textarea.attributes('placeholder')).toBe('Custom placeholder') }) }) + describe('Read-Only Behavior', () => { + it('is readonly when options.read_only is true', () => { + const widget = createMockWidget('test', { read_only: true }) + const wrapper = mountComponent(widget, 'test') + expect(wrapper.find('textarea').attributes('readonly')).toBeDefined() + }) + + it('is readonly when options.disabled is true', () => { + const widget = createMockWidget('test', { disabled: true }) + const wrapper = mountComponent(widget, 'test') + expect(wrapper.find('textarea').attributes('readonly')).toBeDefined() + }) + + it('is editable when neither read_only nor disabled is set', () => { + const widget = createMockWidget('test', {}) + const wrapper = mountComponent(widget, 'test') + expect(wrapper.find('textarea').attributes('readonly')).toBeUndefined() + }) + + it('is editable when disabled is explicitly false', () => { + const widget = createMockWidget('test', { disabled: false }) + const wrapper = mountComponent(widget, 'test') + expect(wrapper.find('textarea').attributes('readonly')).toBeUndefined() + }) + }) + describe('Edge Cases', () => { it('handles very long text', async () => { const widget = createMockWidget('short')