fix(subgraph): trust persisted previewExposures, stop re-auto-exposing on load

Auto-exposed previews would reappear after reload even when the user had
explicitly hidden them. The host's `properties.previewExposures` already
captures user intent, but `LGraph.configure` was unconditionally running
`autoExposeKnownPreviewNodes` after hydration, second-guessing the
serialized state.

- Gate `autoExposeKnownPreviewNodes` on `properties.previewExposures`
  being undefined. A node that has never been saved (or comes from a
  pre-PR workflow with no `previewExposures` property) still gets the
  convenience auto-expose pass; once the property exists in any form,
  including an empty array, the serialized state is authoritative.
- Always serialize `previewExposures` (including as `[]` when empty)
  so "user cleared everything" is distinguishable from "never touched".

Amp-Thread-ID: https://ampcode.com/threads/T-019e4671-bc67-7039-b6c6-9f5b11a07e42
Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
DrJKL
2026-05-20 12:37:50 -07:00
parent fb973e231b
commit 7674251742
4 changed files with 75 additions and 9 deletions

View File

@@ -28,6 +28,7 @@ vi.mock('@/services/litegraphService', () => ({
import {
CANVAS_IMAGE_PREVIEW_WIDGET,
autoExposeKnownPreviewNodes,
demoteWidget,
getPromotableWidgets,
hasUnpromotedWidgets,
@@ -341,6 +342,74 @@ describe('promoteRecommendedWidgets', () => {
})
})
describe('autoExposeKnownPreviewNodes', () => {
beforeEach(() => {
setActivePinia(createTestingPinia({ stubActions: false }))
updatePreviewsMock.mockReset()
})
it('auto-exposes previews when host has no persisted previewExposures property', () => {
const subgraph = createTestSubgraph()
const subgraphNode = createTestSubgraphNode(subgraph)
const glslNode = new LGraphNode('GLSLShader')
glslNode.type = 'GLSLShader'
subgraph.add(glslNode)
autoExposeKnownPreviewNodes(subgraphNode)
expect(
usePreviewExposureStore().getExposures(
subgraphNode.rootGraph.id,
String(subgraphNode.id)
)
).toHaveLength(1)
})
it('does not auto-expose when host has empty persisted previewExposures (user cleared)', () => {
const subgraph = createTestSubgraph()
const subgraphNode = createTestSubgraphNode(subgraph)
subgraphNode.properties.previewExposures = []
const glslNode = new LGraphNode('GLSLShader')
glslNode.type = 'GLSLShader'
subgraph.add(glslNode)
autoExposeKnownPreviewNodes(subgraphNode)
expect(
usePreviewExposureStore().getExposures(
subgraphNode.rootGraph.id,
String(subgraphNode.id)
)
).toEqual([])
})
it('does not auto-expose when host has non-empty persisted previewExposures', () => {
const subgraph = createTestSubgraph()
const subgraphNode = createTestSubgraphNode(subgraph)
const glslNode = new LGraphNode('GLSLShader')
glslNode.type = 'GLSLShader'
subgraph.add(glslNode)
const otherNode = new LGraphNode('OtherShader')
otherNode.type = 'GLSLShader'
subgraph.add(otherNode)
subgraphNode.properties.previewExposures = [
{
name: CANVAS_IMAGE_PREVIEW_WIDGET,
sourceNodeId: String(otherNode.id),
sourcePreviewName: CANVAS_IMAGE_PREVIEW_WIDGET
}
]
autoExposeKnownPreviewNodes(subgraphNode)
expect(
usePreviewExposureStore()
.getExposures(subgraphNode.rootGraph.id, String(subgraphNode.id))
.map((e) => e.sourceNodeId)
).not.toContain(String(glslNode.id))
})
})
describe('hasUnpromotedWidgets', () => {
beforeEach(() => {
setActivePinia(createTestingPinia({ stubActions: false }))

View File

@@ -470,6 +470,7 @@ function nodeWidgets(n: LGraphNode): WidgetItem[] {
}
export function autoExposeKnownPreviewNodes(subgraphNode: SubgraphNode): void {
if (subgraphNode.properties.previewExposures !== undefined) return
const { updatePreviews } = useLitegraphService()
const interiorNodes = subgraphNode.subgraph.nodes
for (const node of interiorNodes) {

View File

@@ -1031,13 +1031,9 @@ export class SubgraphNode extends LGraphNode implements BaseLGraph {
rootGraphId,
hostLocator
)
if (previewExposures.length > 0) {
serializedProperties.previewExposures = previewExposures.map((entry) => ({
...entry
}))
} else {
delete serializedProperties.previewExposures
}
serializedProperties.previewExposures = previewExposures.map((entry) => ({
...entry
}))
const quarantine = parseProxyWidgetErrorQuarantine(
this.properties.proxyWidgetErrorQuarantine

View File

@@ -1175,12 +1175,12 @@ describe('SubgraphWidgetPromotion', () => {
expected: [named12, named14]
},
{
name: 'omits previewExposures when the store has no entries for the host',
name: 'writes empty previewExposures when the store has no entries for the host',
addExposures: [],
staleProperty: [
{ name: 'stale', sourceNodeId: '0', sourcePreviewName: CANVAS }
],
expected: undefined,
expected: [],
expectLiveUnchanged: true
}
]