https://sonarly.com/issue/33779?type=bug
The workflow email editor's multi-recipient field crashes with RangeError when `convertTextToTag` runs inside a `setTimeout` callback with a stale text length, computing a negative `from` position for ProseMirror's `deleteRange`.
Fix: Changed `convertTextToTag` to read the text before cursor from the editor's current state at execution time, instead of accepting a pre-captured `text` parameter from the caller's closure.
**What changed:**
1. **`convertTextToTag` signature**: Changed from `(editor: Editor, text: string)` to `(editor: Editor)`. The function now calls `getTextBeforeCursor(editor)` internally to get the current text before cursor, ensuring it always operates on the editor's actual state rather than a stale closure value.
2. **Safety guard**: Added `if (deleteFrom < 0) return false` check before calling `deleteRange`, as defense-in-depth against any remaining edge case where position arithmetic could produce a negative value.
3. **All 4 call sites updated**:
- `handleTextInput` setTimeout callback (line 77)
- `handleKeyDown` setTimeout callback (line 90)
- `onBlur` handler (line 159) — also simplified since the empty-text check is now inside `convertTextToTag`
**Why this fixes the bug:**
The original code captured `textBefore` via `getTextBeforeCursor()` at event time, then passed it to `convertTextToTag` inside a `setTimeout`. By the time the setTimeout fired, the `onBlur` handler (or another setTimeout) could have already converted that text to a tag node, shrinking the document. The function then used the stale `text.length` against the current `$from.pos`, producing `deleteFrom = small_pos - large_stale_length = negative`, which caused ProseMirror's `Node.resolve(negative)` to throw `RangeError: Position -N out of range`.
Now, `convertTextToTag` reads the current text at execution time. If a prior handler already converted the text, `getTextBeforeCursor` returns an empty string, and the function safely returns `false`.