[Markdown Agent] Apply document edits deterministically - #2967
Merged
George Ng (GeorgeNgMsft) merged 2 commits intoSep 4, 2026
Merged
Conversation
George Ng (GeorgeNgMsft)
force-pushed
the
georgengmsft-markdown-safe-updates
branch
from
September 3, 2026 22:41
c3b1974 to
2424a5c
Compare
George Ng (GeorgeNgMsft)
marked this pull request as ready for review
September 4, 2026 05:58
jebrans
approved these changes
Sep 4, 2026
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: dd89289c-6602-452d-b8c5-9221494be8f7
George Ng (GeorgeNgMsft)
force-pushed
the
georgengmsft-markdown-safe-updates
branch
from
September 4, 2026 21:57
ef02d96 to
333ee62
Compare
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.
What changed
This PR adds the raw-Markdown edit engine that later persistence layers can call. It does not yet change where documents are saved or add browser synchronization.
Before
A model can return several edits whose character offsets were all calculated from the same original Markdown. The previous application path processed those edits sequentially against an already-mutated string, so an early edit could move the text targeted by every later edit.
For example, starting with
ABCDE:Xat offset1[3, 5)(DE)Applying those operations in request order produces
AXBCDE, then deletes offsets[3, 5)from that changed string (CD), yielding the incorrect resultAXBE.The previous path could also reverse multiple inserts at one position, silently clamp invalid ranges, and apply intersecting edits whose meaning became ambiguous after the first mutation.
After
applyDocumentOperationstreats the operations as one edit batch against one original Markdown snapshot:For the example above, the delete at
[3, 5)runs first and removesDE; the insert at1then produces the intendedAXBC.This follows the established simultaneous text-edit model used by language tooling. The LSP contract describes text edits against a document range, while Microsoft’s reference Node implementation sorts edits and applies them from the end toward the beginning, rejecting overlap:
TextEditvscode-languageserver-nodereverse edit applicationWhy this is a separate stack layer
The original replacement change combined edit semantics with file persistence, stale-write protection, and browser synchronization. Isolating the operation engine makes its coordinate contract and edge cases reviewable before later layers use it for durable updates.
Deferred upward: durable workspace persistence, revision and binding conflict checks, streaming exactly-once persistence, view-service state, browser synchronization/autosave, same-file editor rebinding, and loopback-only binding.
Validation
markdownOperationSchema.tsanddocumentOperations.tsgit diff --checkpassedThe normal package build/Jest suite and PR ratchets could not run because this worktree had no
node_modules; the single time-boxed restore did not complete while reaching unavailable Azure feed artifacts. The commit hook was bypassed after the equivalent cached Prettier, type, and smoke checks above.