Skip to content

fix: prevent SourceEditor recreation on every keystroke#624

Open
hobostay wants to merge 1 commit intoheygen-com:mainfrom
hobostay:fix/source-editor-recreation
Open

fix: prevent SourceEditor recreation on every keystroke#624
hobostay wants to merge 1 commit intoheygen-com:mainfrom
hobostay:fix/source-editor-recreation

Conversation

@hobostay
Copy link
Copy Markdown

@hobostay hobostay commented May 5, 2026

Summary

  • Fix SourceEditor being destroyed and recreated on every content change, losing cursor position, undo history, and focus
  • The mountEditor callback had content in its dependency array and was used as a React ref callback — every keystroke changed content, giving mountEditor a new identity and triggering full editor recreation

Details

Affected file: packages/studio/src/components/editor/SourceEditor.tsx

The fix:

  1. Removes content from the mountEditor dependency array — the editor is now only recreated when filePath, language, or readOnly change
  2. Uses a contentRef to pass initial content to the editor without subscribing to changes
  3. Adds a useEffect that syncs external content changes (file switches, server refreshes) into the existing editor via view.dispatch(), without recreating it

Test plan

  • Open a file in the source editor and type — verify cursor position is maintained between keystrokes
  • Verify undo/redo (Ctrl+Z/Ctrl+Y) still works
  • Switch files and verify the editor content updates correctly
  • Verify syntax highlighting works after file switch

🤖 Generated with Claude Code

The mountEditor callback had content in its dependency array and was
used as a React ref callback. Every content change (keystroke) gave
mountEditor a new identity, causing React to destroy and recreate
the entire CodeMirror editor — losing cursor position, undo history,
and focus.

Remove content from the dependency array and use a separate useEffect
to push external content updates to the existing editor via
dispatch(). The editor is now only recreated when filePath, language,
or readOnly change.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copy link
Copy Markdown
Collaborator

@jrusso1020 jrusso1020 left a comment

Choose a reason for hiding this comment

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

Verified — mountEditor is used as a ref callback (<div ref={mountEditor}>) with content in its dep array, so every keystroke gives it a new identity. React then invokes the old callback with null (destroying the editor) and the new one with the node (rebuilding it), losing cursor/undo/focus. The fix is the canonical pattern: drop content from the callback deps, seed via a ref, and sync external changes through a separate useEffect that dispatches a single replace transaction. The current !== content guard inside the effect correctly avoids a feedback loop with the updateListener (which would otherwise re-fire onChange after every external sync). LGTM.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants