Repository navigation
Conversation
The Monaco editor was driven as a controlled component via `value={code}`.
@monaco-editor/react syncs that prop from a passive effect, and whenever the
prop and the live model disagree it rewrites the entire document:
editor.executeEdits("", [{ range: model.getFullModelRange(),
text: value, forceMoveMarkers: true }])
`onChange` is invoked from Monaco's `onDidChangeModelContent` rather than from
a React event, so `setCode` is scheduled at default priority and the effect
only runs after the next paint. A keystroke landing inside that window leaves
the committed `value` behind the model, the full-range rewrite kicks in, and
`forceMoveMarkers` pushes the caret to the end of the replaced text, dropping
the characters typed in between.
Make the editor uncontrolled instead: `path` gives each task its own model and
view state, `defaultValue` seeds it, and the live buffer is read back from the
editor instance when a submission is sent. Typing no longer re-renders `Home`,
so the problem description and its KaTeX are no longer re-parsed per keystroke.
Measured by driving 40 keystrokes one macrotask apart, caret starting on line 4:
before: 45 change events, 2 buffer rewrites, caret ended on line 9 col 37,
2 characters lost
after: 40 change events, 0 rewrites, caret ended on line 4 col 52, none lost
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Typing in the code editor intermittently throws the caret onto a different line and swallows the characters typed around the jump.
Cause
The editor is driven as a controlled component:
@monaco-editor/reactsynchronises that prop from a passive effect, and when the prop and the live model disagree it rewrites the whole document (dist/index.js):The two can disagree on any keystroke:
onChangeis dispatched from Monaco'sonDidChangeModelContent, not from a React event, sosetCodeis scheduled at default priority instead of being flushed synchronously.valuebehind the live model. The effect then replaces the full model range with the stale text,forceMoveMarkers: truepushes the caret to the end of the replaced text, and the characters typed in between are lost.Because
codelives in React state, every keystroke also re-rendersHomeand re-parses the problem description and its KaTeX, which widens the window that triggers the jump.Fix
Make the editor uncontrolled:
path={`${taskId}.py`}gives each task its own Monaco model, so switching problems swaps models (and restores their view state) instead of rewriting one shared buffer.defaultValueseeds a task's contents. Path and contents come from a single piece of state, so they can never disagree.optionsmoved to module scope so the wrapper stops callingupdateOptions()on every render, andonChangeis wrapped inuseCallbackso the content-change listener is no longer disposed and re-subscribed on every render.Verification
Same harness on both builds: 40 keystrokes dispatched one macrotask apart, caret starting on line 4, column 12.
Homere-renders while typingChecked manually in Chrome against the local FastAPI backend:
localStoragenpx tsc --noEmit,npx eslint srcandnext buildall pass.🤖 Generated with Claude Code