fix(word-addin): resolve tracked changes by revision content so edit cards stay linked on Word for the web

↗ view on GitHub · Amal · 2026-08-16 · 26e91dba

WHY THIS MATTERS
On Word for the web, clicking Accept or Reject on a Mike edit card did
nothing: the document's redlines stayed pending and the card errored with
"The revisions in this passage changed after Mike applied the edit." The
card and its tracked change were effectively unlinked, which breaks the
core review loop of the add-in. Replicated live against real Word online
before fixing (two same-paragraph spelling fixes; neither could be
resolved from its card).

WHAT WORD ACTUALLY DOES
Office.js ranges do not scope getTrackedChanges() the way the previous
code assumed. Live probing showed three separate behaviors on Word for
the web:
1. Inside the Word.run batch that queues the replacement, the generated
   revisions may not be visible at all (collections come back empty).
2. Afterwards, the search-match range, the inserted-text range, and even
   the containing paragraph each report only the ADDED revision - the
   DELETED strikethrough run sits just outside every range we retain.
3. A retained (tracked) paragraph proxy can later report zero revisions.
Only context.document.body.getTrackedChanges() reliably lists all
pending revisions. The old resolution logic demanded the full
Added+Deleted pair through ONE range proxy (strict whole-set equality in
trackedChangesMatchEdit), so on Word online it always refused.

HOW THE FIX WORKS
Resolution now identifies the edit by revision CONTENT rather than by
proxy identity, in three layers, most precise first:
1. Exact retained child revisions (unchanged fast path; still what the
   mock host and Word desktop hit).
2. Per-anchor, per-side matching: pickEditRevisionSubset() selects, from
   whatever revision set a range reports, exactly one Added revision
   whose text equals toWordText(replacement) and one Deleted revision
   whose text equals the original. A read-only scan pass locates which
   anchor can supply each side before anything mutates.
3. An atomic document-level fallback (resolveEditThroughBody) that finds
   the edit's unambiguous Added/Deleted pair in the body collection and
   resolves both sides in a single batch.
Every layer aborts untouched on any ambiguity (two pending revisions
with identical text), so Mike can never resolve a revision that is not
provably its own. restoreTrackedEdit() gets the same subset matching
plus the body fallback, so cards re-link after a task-pane reload, and
the persistent bookmark now spans the whole edited paragraph when
per-revision ranges are unavailable. Apply-time also re-reads the edited
paragraph once before giving up on exact review controls.

The e2e Office mock gains body.getTrackedChanges() and range.paragraphs
to mirror the real host, and the persistence specs now assert the new
contract: a sibling revision inside the bookmark no longer severs review
controls, while genuinely ambiguous revision sets stay view-only.

Verified live on Word for the web: Accept finalizes the text in the
document, Reject restores the original, and the pane console shows
"[tracked-edit/resolve] body fallback resolved".

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Repository open-legal-products/mike
Author Amal <mamalanand3@gmail.com>
Authored
Committed
Parents 64e2865a
Stats 4 files changed , +515 , -81
Part of Make Word edit approval reliable across hosts

Capture this commit into my fork

Download a Markdown prompt that tells Claude how to port this exact commit into your working tree. Run it via claude -p < capture-commit-26e91dba.md from inside the repo you want the change in.

⬇ Download capture-commit-26e91dba.md