Skip to content

fix(editor): scope external disk syncs and encoding reloads to one model - #3194

Merged
wgqqqqq merged 1 commit into
GCWing:mainfrom
wgqqqqq:wgq/editor-sync-followup
Sep 22, 2026
Merged

wgqqqqq merged 1 commit into
GCWing:mainfrom
wgqqqqq:wgq/editor-sync-followup

Conversation

@wgqqqqq

@wgqqqqq wgqqqqq commented Sep 22, 2026

Copy link
Copy Markdown
Collaborator

Summary

Follow-up to #3185. Two dirty-state correctness gaps around programmatic disk writes in the code editor.

  1. Root cause: external-sync suppression was process-wide. MonacoModelManager tracked it with a global depth counter, so a bracketed disk write on one model also muted dirty tracking for every other model that a content listener edited synchronously during that write. The bracket is now tracked per model (WeakMap), and a disk write still emits monaco-model-content-changed while dirty tracking is suppressed.

  2. Root cause: the encoding reload had no request ownership. handleEncodingConfirm awaited readFileContent and then wrote the result unconditionally, so a slow read could overwrite a newer view, a newer encoding selection, or edits made while the read was in flight. It now guards on a request id, a live model/view check, and the model's alternative version id, and commits the decoded buffer together with its saved baseline (applyDiskSnapshotToEditor) before awaiting file metadata.

Follow-up to #3185, related to #3165.

Type and Areas

Type: regression fix

Areas: web UI (src/web-ui/src/tools/editor)

Motivation / Impact

The global counter made the #3165 fix too broad: any model edited by a content listener while another model was being synced from disk silently skipped its dirty recompute and its dirty-state broadcast, so a tab could keep showing a stale modified/unmodified state. Scoping the bracket to the synchronized model keeps the original guarantee - no transient "modified" flash for a disk sync - without suppressing unrelated edits.

The encoding reload guard closes a race where a slow read resolved after the user had switched view, picked another encoding, or typed; the resolved content then replaced newer state. No user-facing strings change.

Verification

  • pnpm --dir src/web-ui run test:run src/tools/editor/components/CodeEditor.test.tsx src/tools/editor/services/MonacoModelManager.test.ts src/tools/editor/services/EditorDocument.test.ts src/tools/editor/utils/diskFileVersion.test.ts -> 4 files, 39 tests passed
  • pnpm run type-check:web -> passed
  • node scripts/check-git-object-sizes.mjs --base upstream/main --head HEAD -> no unapproved objects

Not covered by these checks: live SSH and peer-device transport for the editor, and manual interaction in a running editor.

Reviewer Notes

  • Prepared with AI assistance in a worktree; testing level: focused tests plus type-check, not manually exercised in a running editor.
  • The new CodeEditor.test.tsx renders the real CodeEditor against the real MonacoModelManager and EditorDocument, with fake Monaco rendering and host IO. It covers delayed reads, stale requests, surface changes, and view remounts.
  • The branch is one commit on top of 0be594d8b (current upstream/main), so the PR diff is only that commit.

Checklist

  • This PR is focused and does not include secrets, temporary prompts, generated scratch files, or unrelated artifacts.
  • Relevant verification is recorded above, or skipped checks are explained.
  • User-facing strings, docs, and locales are updated where applicable.

Generated with OpenBitFun

The external-sync suppression was a process-wide depth counter, so a
bracketed disk write on one model also muted dirty tracking for every other
model that a content listener edited synchronously. Track the bracket per
model instead, and keep emitting the content-changed notification for disk
writes even while dirty tracking is suppressed.

The encoding reload had no request ownership either: a slow read could
overwrite a newer view, a newer encoding selection, or edits made while it
was in flight. Guard it with a request id, a live view check, and a buffer
version comparison, and commit the decoded buffer with its saved baseline
before awaiting file metadata.

Cover both paths with the new component test and the extended model manager
tests, and record the focused verification command in the editor guide.

Co-authored-by: OpenBitFun <318544290+bitfun-ai@users.noreply.github.com>
@wgqqqqq
wgqqqqq force-pushed the wgq/editor-sync-followup branch from b197fc1 to 211d4dd Compare September 22, 2026 09:11
@wgqqqqq
wgqqqqq merged commit f70d3b8 into GCWing:main Sep 22, 2026
9 checks passed
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.

1 participant