From 1e0ee101b1a93a2f1ab8ad6a6065396c9e62a74e Mon Sep 17 00:00:00 2001 From: Kevin Ushey Date: Wed, 26 Aug 2026 14:42:56 -0700 Subject: [PATCH] don't treat programmatic selection changes as cursor movement AceNodeView.setSelection() sets this.updating, focuses the Ace editor and applies the selection range; that fires 'changeCursor', so cursorDirty was set even though the user never moved the cursor. The next Ace render then called scrollCursorIntoView() and scrolled the editing root to that node view. ProseMirror calls setSelection() from selectionToDOM() on every EditorView.focus(), so any focus restore scrolled the document to whichever embedded editor held the selection -- for a freshly opened document that is the YAML front matter block, at the very top. Addresses #1099. --- packages/editor/src/optional/ace/ace.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/editor/src/optional/ace/ace.ts b/packages/editor/src/optional/ace/ace.ts index eda54f514..eb511e734 100644 --- a/packages/editor/src/optional/ace/ace.ts +++ b/packages/editor/src/optional/ace/ace.ts @@ -530,9 +530,11 @@ export class AceNodeView implements NodeView { // If the cursor moves and we're in focus, ensure that the cursor is // visible. Ace's own cursor visiblity mechanisms don't work in embedded - // editors. + // editors. Selection changes we make ourselves (this.updating) don't count: + // those come from ProseMirror syncing its selection into the editor, not + // from the user moving the cursor. this.aceEditor.getSelection().on('changeCursor', () => { - if (this.dom.contains(document.activeElement) && !this.mouseDown) { + if (!this.updating && this.dom.contains(document.activeElement) && !this.mouseDown) { this.cursorDirty = true; } });