Skip to content

Keep a valid selection when the editor value is replaced - #1165

Draft
bmesuere wants to merge 1 commit into
mainfrom
fix/editor-replace-selection
Draft

bmesuere wants to merge 1 commit into
mainfrom
fix/editor-replace-selection

Conversation

@bmesuere

@bmesuere bmesuere commented Sep 25, 2026 •

Copy link
Copy Markdown
Member

TODO (author) — drafted by Claude: proofread, verify and remove this line.

This pull request passes an explicit cursor when CodeMirrorEditor replaces its whole document, so an edit made after an external value update no longer throws RangeError: Invalid change range 482 to 0 (in doc of length 482).

Sentry: DODONA-FRONTEND-2CM, thrown from @codemirror/state after clicks in p-code-runner.

Cause. dispatchChange dispatches {from: 0, to: doc.length, insert} without a selection, so CodeMirror maps the current selection through the change. SelectionRange.map maps a non-empty range's from with assoc 1 and its to with assoc -1; for a range strictly inside the replaced text that gives from = new length and to = 0. The next paste, delete or typed character at that selection builds a change from the inverted range and throws. An empty cursor is not affected, which is why this only shows up with text selected. @codemirror/state 6.7.6 (latest) maps ranges the same way.

dispatchChange runs for every value that does not come from the editor itself: code set by the host page, and effectiveCode changing when test code is added or removed. Typing does not reach it, since the setter returns early when the value equals the document.

  • The dispatch now sets the cursor to the old selection head, clamped to the new length. That keeps the cursor where it was when the host sends back nearly the same text, and matches CodeMirror's own behaviour for an empty cursor otherwise.
  • The fix is in CodeMirrorEditor, so the code editor, the batch input editor and the file editor (CodeEditor, BatchInputEditor, FileEditor) all get it.

Manual testing instructions

  • In the scratchpad, select a few characters in the middle of the code, then make the code change from outside the editor (for example by adding or removing test code, or setting papyros.runner.code from the console). Type or paste: the text is inserted at the cursor and nothing is logged to the console.

Checklist

  • Tests: one case in CodeEditor.test.ts that selects text inside the document, replaces the value and inserts at the selection. It throws the Sentry error before this change.
  • AI: Claude Code investigated this during Dodona's weekly Sentry triage and wrote the fix.

Reaches production once Dodona bumps @dodona/papyros.

CodeMirrorEditor.dispatchChange replaces the whole document without
passing a selection, so CodeMirror maps the old one through the change.
SelectionRange.map maps a non-empty range's from with assoc 1 and its
to with assoc -1, which for a range strictly inside the replaced text
gives from = new length and to = 0. The next edit at that selection
throws "RangeError: Invalid change range".

The dispatch now places the cursor at the old head, clamped to the new
length.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155VQF6bMAnbEaGnc1haWMp
@bmesuere bmesuere added the bug Something isn't working label Sep 25, 2026
@bmesuere bmesuere self-assigned this Sep 25, 2026

@bmesuere bmesuere left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Reviewed. This fixes the same concrete CodeMirror selection corruption seen in Dodona, at the shared Papyros editor layer. Preserving the old head, clamped to the new document, is a sensible host-update behavior and prevents the next edit from using an inverted range. Focused tests, lint, and typecheck pass locally. No blocking findings.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant