fix(widget): proxy hidden to options.hidden (ADR 0010)

Change widget.hidden from a plain property to a getter/setter that
proxies to options.hidden. This ensures Vue and canvas renderers
see the same visibility state.

Fixes the dual-hidden bug where Vue renderer reads options.hidden
and canvas renderer reads widget.hidden, causing visibility desync.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Connor Byrne
2026-05-13 13:45:36 -07:00
committed by bymyself
parent baa5af0ac8
commit b371bd97f1
2 changed files with 38 additions and 3 deletions

View File

@@ -67,6 +67,26 @@ describe('BaseWidget store integration', () => {
expect(widget.disabled).toBe(true)
expect(widget.advanced).toBe(true)
})
// ADR 0010: widget.hidden proxies to options.hidden to ensure
// Vue and canvas renderers see the same visibility state
it('syncs widget.hidden with options.hidden (ADR 0010)', () => {
const widget = createTestWidget(node, { options: { min: 0 } })
// Initially false
expect(widget.hidden).toBe(false)
expect(widget.options.hidden).toBeUndefined()
// Setting widget.hidden writes to options.hidden
widget.hidden = true
expect(widget.hidden).toBe(true)
expect(widget.options.hidden).toBe(true)
// Setting options.hidden is reflected in widget.hidden
widget.options.hidden = false
expect(widget.hidden).toBe(false)
expect(widget.options.hidden).toBe(false)
})
})
describe('metadata properties after registration', () => {
@@ -136,7 +156,8 @@ describe('BaseWidget store integration', () => {
expect(state?.value).toBe(100)
expect(state?.label).toBe('Auto Label')
expect(state?.disabled).toBe(true)
expect(state?.options).toEqual({ min: 0, max: 100 })
// hidden is now proxied to options.hidden (ADR 0010)
expect(state?.options).toEqual({ min: 0, max: 100, hidden: true })
expect(widget.hidden).toBe(true)
expect(widget.advanced).toBe(true)
@@ -151,7 +172,8 @@ describe('BaseWidget store integration', () => {
expect(state?.disabled).toBe(false)
expect(state?.label).toBeUndefined()
expect(widget.hidden).toBeUndefined()
// hidden now returns false when not set (proxied to options.hidden, ADR 0010)
expect(widget.hidden).toBe(false)
expect(widget.advanced).toBeUndefined()
})

View File

@@ -96,7 +96,20 @@ export abstract class BaseWidget<TWidget extends IBaseWidget = IBaseWidget>
this._state.label = value
}
hidden?: boolean
/**
* Whether this widget is hidden from the node UI.
* Proxies to `options.hidden` to ensure Vue and canvas renderers agree.
* See ADR 0010 (Widget State Categories).
*/
get hidden(): boolean {
return (this.options as Record<string, unknown>)?.hidden === true
}
set hidden(value: boolean) {
if (this.options) {
;(this.options as Record<string, unknown>).hidden = value
}
}
advanced?: boolean
get disabled(): boolean | undefined {