Operator position sync silently stops for a tab: a single error in `getElementPositionChangeEvent()` permanently kills the drag-broadcast stream
- Dominant language
- Scala
- Stars
- 314
- Forks
- 187
- Avg merge
- 1d 21h
- Merged PRs (30d)
- 214
Description
### What happened?
A tab can **silently stop broadcasting operator position changes** to collaborators, while operator/link add/delete keep syncing. Local dragging still works, so the affected user sees nothing wrong; only other collaborators notice ("new operators show up but moves don't"). A page reload "fixes" it.
Root cause: `JointGraphWrapper.getElementPositionChangeEvent()` throws inside its `map()` operator when an element is missing from its `elementPositions` bookkeeping:
https://github.com/apache/texera/blob/master/frontend/src/app/workspace/service/workflow-graph/model/joint-graph-wrapper.ts#L304-L311
```ts
return fromEvent(this.jointGraph, "change:position").pipe(
map(e => {
const elementID = e[0].id.toString();
const oldPosition = this.elementPositions.get(elementID);
...
if (!oldPosition) {
throw new Error(`internal error: cannot find element position for ${elementID}`);
}
```
In RxJS, an error that reaches a subscriber **permanently unsubscribes the pipeline**. The main consumer, `WorkflowActionService.handleJointElementDrag()`, subscribes exactly once per tab at service construction (`workflow-action.service.ts:124` → `:856`) and is the only path that writes local drags into `sharedModel.elementPositionMap` for shared editing. So a single `change:position` event for an unknown element (e.g. delivered during a window where the element was added/removed by another code path, undo/redo, or a remote peer) kills the stream for the rest of the tab's lifetime. Add/delete/link sync are separate streams, which is why they keep working.
**Expected:** one bad event should not permanently disable position sync.
**Suggested fix:** make the stream survive bad events instead of dying — return `undefined` for unknown elements and `filter()` them out (or `catchError` in the consumers). Same consideration applies to the other subscriber at `workflow-action.service.ts:691` (undo/redo debounce merge).
### How to reproduce?
Deterministic repro of the kill mechanism:
1. Open a workflow in two browsers as two users (shared editing).
2. In one tab, cause `getElementPositionChangeEvent()` to error once — any `change:position` for an element absent from `elementPositions` triggers the `internal error: cannot find element position` throw.
3. From then on, drags in that tab no longer propagate to the other browser, while adding operators/links still syncs. Reloading the tab restores position sync.
Observed organically while testing #6240 on a local minikube deployment with two browsers on the same workflow: one tab's position sync stopped while adds/links kept flowing; the receive path was verified healthy (positions written to `elementPositionMap` by an external Yjs client rendered in both tabs), fresh Playwright-driven sessions always synced 10/10 position updates, and a reload restored broadcast — consistent with a long-lived subscription dying rather than a happy-path logic bug.
### Version/Branch
1.3.0-incubating-SNAPSHOT (main)
### Commit Hash (Optional)
_No response_
### What browsers are you seeing the problem on?
Chrome
### Relevant log output
```shell
Error: internal error: cannot find element position for
at joint-graph-wrapper.ts getElementPositionChangeEvent map()
(after this error, the handleJointElementDrag subscription is permanently unsubscribed)
```
Contributor guide
Assessment
This issue has not been assessed yet.