From 0c64850b7d78796c3a4bd629d5b43fcf65e43510 Mon Sep 17 00:00:00 2001 From: Lucio Lelii Date: Mon, 18 May 2026 12:49:31 +0200 Subject: [PATCH] fix: preserve expanded node mode on boolean toggles --- src/app/models/nodes.ts | 1 + .../container-node/container-node.spec.ts | 20 +++++++++++++++++++ .../nodes/container-node/container-node.ts | 17 +++++++++++++++- .../nodes/generic-node/generic-node.spec.ts | 14 +++++++++++++ .../shared/nodes/generic-node/generic-node.ts | 17 +++++++++++++++- src/app/utilities/rete-editor.ts | 8 +++++++- 6 files changed, 74 insertions(+), 3 deletions(-) diff --git a/src/app/models/nodes.ts b/src/app/models/nodes.ts index 234c57c..bb3558c 100644 --- a/src/app/models/nodes.ts +++ b/src/app/models/nodes.ts @@ -12,6 +12,7 @@ export type HFNodeData = FlowNode & { __needsServerCreate?: boolean; __createdOnServer?: boolean; __isCreatingOnServer?: boolean; + __focusOpen?: boolean; __updateBlockError?: string | null; __containerValidationErrors?: unknown[]; __containerAssignmentError?: string | null; diff --git a/src/app/shared/nodes/container-node/container-node.spec.ts b/src/app/shared/nodes/container-node/container-node.spec.ts index da9c6b9..7fc6b94 100644 --- a/src/app/shared/nodes/container-node/container-node.spec.ts +++ b/src/app/shared/nodes/container-node/container-node.spec.ts @@ -57,6 +57,26 @@ describe('ContainerNodeComponent', () => { expect(component.richContentFields.map((field) => field.path)).toContain('prompt'); }); + it('restores and syncs persisted expanded-mode state', () => { + component.data = { + data: { + __focusOpen: true, + specificConfiguration: {}, + inputs: [], + outputs: [] + } + }; + + (component as any).restorePersistedFocusState(); + + expect(component.focusOpen).toBe(true); + expect(component.data.data.__focusOpen).toBe(true); + + component.toggleFocus(); + expect(component.focusOpen).toBe(false); + expect(component.data.data.__focusOpen).toBe(false); + }); + it('loads retriever-backed options for nested LLM descriptor fields', async () => { const schema = { type: 'object', diff --git a/src/app/shared/nodes/container-node/container-node.ts b/src/app/shared/nodes/container-node/container-node.ts index a36333e..020f238 100644 --- a/src/app/shared/nodes/container-node/container-node.ts +++ b/src/app/shared/nodes/container-node/container-node.ts @@ -131,6 +131,7 @@ export class ContainerNodeComponent implements OnDestroy { } ngOnInit() { + this.restorePersistedFocusState(); void this.loadSchemaContext(); } @@ -175,6 +176,7 @@ export class ContainerNodeComponent implements OnDestroy { private setFocusOpen(value: boolean) { if (this.focusOpen === value) return; this.focusOpen = value; + this.syncPersistedFocusState(); if (value) { this.attachHostToModalLayer(); this.applyPageScrollLock(); @@ -185,6 +187,18 @@ export class ContainerNodeComponent implements OnDestroy { this.cdr.markForCheck(); } + private restorePersistedFocusState() { + const nodeData = this.data?.data as Record | undefined; + if (nodeData?.['__focusOpen'] !== true) return; + this.setFocusOpen(true); + } + + private syncPersistedFocusState() { + const nodeData = this.data?.data as Record | undefined; + if (!nodeData) return; + nodeData['__focusOpen'] = this.focusOpen; + } + private applyPageScrollLock() { if (this.pageScrollLocked) return; const body = document.body; @@ -1063,7 +1077,8 @@ export class ContainerNodeComponent implements OnDestroy { if (typeof replaceNode === 'function') { await replaceNode({ ...createdContainer, - position: (current['position'] as { x: number; y: number } | undefined) ?? createdContainer.position + position: (current['position'] as { x: number; y: number } | undefined) ?? createdContainer.position, + __focusOpen: current['__focusOpen'] === true }); return; } diff --git a/src/app/shared/nodes/generic-node/generic-node.spec.ts b/src/app/shared/nodes/generic-node/generic-node.spec.ts index 78147b7..e2c472a 100644 --- a/src/app/shared/nodes/generic-node/generic-node.spec.ts +++ b/src/app/shared/nodes/generic-node/generic-node.spec.ts @@ -75,4 +75,18 @@ describe('GenericNodeComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + it('restores and syncs persisted expanded-mode state', () => { + const nodeData = component.data.data as Record; + nodeData['__focusOpen'] = true; + + (component as any).restorePersistedFocusState(); + + expect(component.focusOpen).toBe(true); + expect(nodeData['__focusOpen']).toBe(true); + + component.toggleFocus(); + expect(component.focusOpen).toBe(false); + expect(nodeData['__focusOpen']).toBe(false); + }); }); diff --git a/src/app/shared/nodes/generic-node/generic-node.ts b/src/app/shared/nodes/generic-node/generic-node.ts index 3e367c4..3b8257f 100644 --- a/src/app/shared/nodes/generic-node/generic-node.ts +++ b/src/app/shared/nodes/generic-node/generic-node.ts @@ -242,6 +242,7 @@ export class GenericNodeComponent implements OnDestroy { this.refreshValidationState(); this.refreshParameterFields(); + this.restorePersistedFocusState(); void this.loadSchemaContext(); } @@ -290,6 +291,7 @@ export class GenericNodeComponent implements OnDestroy { private setFocusOpen(value: boolean) { if (this.focusOpen === value) return; this.focusOpen = value; + this.syncPersistedFocusState(); if (value) { this.attachHostToModalLayer(); this.applyPageScrollLock(); @@ -300,6 +302,18 @@ export class GenericNodeComponent implements OnDestroy { this.cdr.markForCheck(); } + private restorePersistedFocusState() { + const nodeData = this.data?.data as Record | undefined; + if (nodeData?.['__focusOpen'] !== true) return; + this.setFocusOpen(true); + } + + private syncPersistedFocusState() { + const nodeData = this.data?.data as Record | undefined; + if (!nodeData) return; + nodeData['__focusOpen'] = this.focusOpen; + } + private applyPageScrollLock() { if (this.pageScrollLocked) return; const body = document.body; @@ -1868,7 +1882,8 @@ export class GenericNodeComponent implements OnDestroy { if (typeof replaceNode === 'function') { void replaceNode({ ...createdBlock, - position: (current['position'] as { x: number; y: number } | undefined) ?? createdBlock.position + position: (current['position'] as { x: number; y: number } | undefined) ?? createdBlock.position, + __focusOpen: current['__focusOpen'] === true }); return; } diff --git a/src/app/utilities/rete-editor.ts b/src/app/utilities/rete-editor.ts index 1262a14..36da7e9 100644 --- a/src/app/utilities/rete-editor.ts +++ b/src/app/utilities/rete-editor.ts @@ -409,6 +409,7 @@ export async function addBlockToEditor( }; const replaceWithCreatedNode = async (createdBlock: FlowNode) => { if (!editor.getNode(node.id)) return; + const createdBlockData = createdBlock as FlowNode & Record; const previousConnections = editor.getConnections() .filter((connection) => connection.source === node.id || connection.target === node.id) .map((connection) => ({ @@ -422,7 +423,11 @@ export async function addBlockToEditor( const replacementNode = await addBlockToEditor( editor, area, - { ...createdBlock, position: currentPosition }, + { + ...createdBlockData, + position: currentPosition, + __focusOpen: node.data?.['__focusOpen'] === true || createdBlockData['__focusOpen'] === true + } as FlowNode & Record, currentPosition, resolvedRuntime ); @@ -493,6 +498,7 @@ export async function addBlockToEditor( __needsServerCreate: sourceData['nodeFamily'] === 'container' ? false : true, __createdOnServer: false, __isCreatingOnServer: false, + __focusOpen: false, __updateBlockError: null } as FlowNode & Record;