From 9dde50d55e48a1ca4ef437df814ec17a8b66dd37 Mon Sep 17 00:00:00 2001 From: Martin Vu <22mvu7@gmail.com> Date: Fri, 17 Jul 2026 01:27:41 -0700 Subject: [PATCH 1/3] fix(sync): handle missing position events without breaking sync stream --- .../model/joint-graph-wrapper.spec.ts | 41 +++++++++++++++++++ .../model/joint-graph-wrapper.ts | 8 +++- 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/workspace/service/workflow-graph/model/joint-graph-wrapper.spec.ts b/frontend/src/app/workspace/service/workflow-graph/model/joint-graph-wrapper.spec.ts index c212722314d..cb297c0d2d6 100644 --- a/frontend/src/app/workspace/service/workflow-graph/model/joint-graph-wrapper.spec.ts +++ b/frontend/src/app/workspace/service/workflow-graph/model/joint-graph-wrapper.spec.ts @@ -818,4 +818,45 @@ describe("JointGraphWrapperService", () => { expect(emitted).toEqual([false, true, false]); }); }); + + it( + "should ignore position change events when old position is missing and continue emitting future events", + marbles(m => { + const operator = jointUIService.getJointOperatorElement(mockScanPredicate, mockPoint); + jointGraph.addCell(operator); + + const events: any[] = []; + const subscription = jointGraphWrapper + .getElementPositionChangeEvent() + .subscribe(event => events.push(event)); + + // remove operator position from the internal map to force bug condition + (jointGraphWrapper as any).elementPositions.delete(mockScanPredicate.operatorID); + + // trigger position change with missing oldPosition + operator.position(100, 100); + + // restore position tracking + (jointGraphWrapper as any).elementPositions.set( + mockScanPredicate.operatorID, + { + currPos: { x: 100, y: 100 }, + lastPos: undefined, + } + ); + + // trigger another valid position change + operator.position(200, 200); + + // the first event should be skipped,but the second event should still emit + expect(events.length).toBe(1); + expect(events[0].elementID).toBe(mockScanPredicate.operatorID); + expect(events[0].newPosition).toEqual({ + x: 200, + y: 200, + }); + + subscription.unsubscribe(); + }) + ); }); diff --git a/frontend/src/app/workspace/service/workflow-graph/model/joint-graph-wrapper.ts b/frontend/src/app/workspace/service/workflow-graph/model/joint-graph-wrapper.ts index b7a2bf05b51..f2561d928d0 100644 --- a/frontend/src/app/workspace/service/workflow-graph/model/joint-graph-wrapper.ts +++ b/frontend/src/app/workspace/service/workflow-graph/model/joint-graph-wrapper.ts @@ -307,7 +307,7 @@ export class JointGraphWrapper { const oldPosition = this.elementPositions.get(elementID); const newPosition = { x: e[1].x, y: e[1].y }; if (!oldPosition) { - throw new Error(`internal error: cannot find element position for ${elementID}`); + return undefined; } if ( !oldPosition.lastPos || @@ -325,7 +325,11 @@ export class JointGraphWrapper { oldPosition: oldPosition.lastPos, newPosition: newPosition, }; - }) + }), + filter( + (event): event is { elementID: string; oldPosition: Point; newPosition: Point } => + event !== undefined + ) ); } From c0286ffada59e8fe8e727121f58ec27e591673b2 Mon Sep 17 00:00:00 2001 From: Martin Vu <22mvu7@gmail.com> Date: Fri, 17 Jul 2026 02:27:32 -0700 Subject: [PATCH 2/3] style: format files --- .../model/joint-graph-wrapper.spec.ts | 15 +++++---------- .../workflow-graph/model/joint-graph-wrapper.ts | 5 +---- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/frontend/src/app/workspace/service/workflow-graph/model/joint-graph-wrapper.spec.ts b/frontend/src/app/workspace/service/workflow-graph/model/joint-graph-wrapper.spec.ts index cb297c0d2d6..b90fc1eb9ed 100644 --- a/frontend/src/app/workspace/service/workflow-graph/model/joint-graph-wrapper.spec.ts +++ b/frontend/src/app/workspace/service/workflow-graph/model/joint-graph-wrapper.spec.ts @@ -826,9 +826,7 @@ describe("JointGraphWrapperService", () => { jointGraph.addCell(operator); const events: any[] = []; - const subscription = jointGraphWrapper - .getElementPositionChangeEvent() - .subscribe(event => events.push(event)); + const subscription = jointGraphWrapper.getElementPositionChangeEvent().subscribe(event => events.push(event)); // remove operator position from the internal map to force bug condition (jointGraphWrapper as any).elementPositions.delete(mockScanPredicate.operatorID); @@ -837,13 +835,10 @@ describe("JointGraphWrapperService", () => { operator.position(100, 100); // restore position tracking - (jointGraphWrapper as any).elementPositions.set( - mockScanPredicate.operatorID, - { - currPos: { x: 100, y: 100 }, - lastPos: undefined, - } - ); + (jointGraphWrapper as any).elementPositions.set(mockScanPredicate.operatorID, { + currPos: { x: 100, y: 100 }, + lastPos: undefined, + }); // trigger another valid position change operator.position(200, 200); diff --git a/frontend/src/app/workspace/service/workflow-graph/model/joint-graph-wrapper.ts b/frontend/src/app/workspace/service/workflow-graph/model/joint-graph-wrapper.ts index f2561d928d0..4816a5dc588 100644 --- a/frontend/src/app/workspace/service/workflow-graph/model/joint-graph-wrapper.ts +++ b/frontend/src/app/workspace/service/workflow-graph/model/joint-graph-wrapper.ts @@ -326,10 +326,7 @@ export class JointGraphWrapper { newPosition: newPosition, }; }), - filter( - (event): event is { elementID: string; oldPosition: Point; newPosition: Point } => - event !== undefined - ) + filter((event): event is { elementID: string; oldPosition: Point; newPosition: Point } => event !== undefined) ); } From 963d9dd64228634e958428027cef67096dccebda Mon Sep 17 00:00:00 2001 From: Martin Vu <22mvu7@gmail.com> Date: Fri, 17 Jul 2026 18:12:12 -0700 Subject: [PATCH 3/3] test(workflow-graph): add coverage for missing position sync events --- .../service/workflow-graph/model/joint-graph-wrapper.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/workspace/service/workflow-graph/model/joint-graph-wrapper.spec.ts b/frontend/src/app/workspace/service/workflow-graph/model/joint-graph-wrapper.spec.ts index b90fc1eb9ed..663f4a7e49e 100644 --- a/frontend/src/app/workspace/service/workflow-graph/model/joint-graph-wrapper.spec.ts +++ b/frontend/src/app/workspace/service/workflow-graph/model/joint-graph-wrapper.spec.ts @@ -832,7 +832,7 @@ describe("JointGraphWrapperService", () => { (jointGraphWrapper as any).elementPositions.delete(mockScanPredicate.operatorID); // trigger position change with missing oldPosition - operator.position(100, 100); + operator.position(150, 150); // restore position tracking (jointGraphWrapper as any).elementPositions.set(mockScanPredicate.operatorID, {