fix(super-editor): stop export from stripping tracked marks off the caller's tree - #3949
leonidkuznetsov18 wants to merge 1 commit into
Conversation
…aller's tree The w:ins/w:del decoders removed the tracked-change mark from the node they were handed by assigning the filtered marks array back onto it. That node belongs to the caller: the body export starts from a fresh getUpdatedJson() snapshot each time, but once a header/footer sub-editor is registered, #exportProcessHeadersFooters serializes the converter's persistent import-time tree (this.headers[id] / this.footers[id]) by reference. The first exportDocx() therefore stripped trackInsert/trackDelete permanently, and every later export wrote the header/footer redline as accepted plain text — a counterparty's tracked deletion silently became regular content on the second save, with no user edit involved. Strip the mark on a shallow copy of the node instead, leaving the caller's tree intact, so repeated exports with no intervening edit produce the same tracked changes every time. Fixes superdoc#3893 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Qodo reviews are paused for this user.Troubleshooting steps vary by plan Learn more → On a Teams plan? Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center? |
|
All contributors have signed the CLA ✍️ ✅ |
|
recheck |
|
Thanks, Leonid, for the careful fix and regression tests. We confirmed that V2 preserves the header’s tracked insertions and deletions across repeated exports, including after opening the header, consistent with the resolution of #3893. We’re focusing maintenance on V2, so we’re closing this V1-specific PR. For applications still using V1, please follow the V2 migration guide. We appreciate the work you put into isolating the mutation. |
What
Once a header (or footer) sub-editor exists, the first
exportDocx()writes the part's importedw:ins/w:delcorrectly and every later export writes it with the tracked changes gone — text inside aw:delcomes back as an ordinary run, so a counterparty's tracked deletion silently becomes accepted content on the second save, with no user edit involved (#3893).Mechanism
The
w:ins/w:deldecoders (v3/handlers/w/ins/ins-translator.js,v3/handlers/w/del/del-translator.js) removed the tracked-change mark by assigning the filtered marks array back onto the node they were handed:That node belongs to the caller. The body path never notices because it starts from a fresh
getUpdatedJson()snapshot each export, but#exportProcessHeadersFooterspasses the converter's persistent import-time tree (this.headers[id]/this.footers[id]) by reference once a sub-editor is registered — which happens as soon as a user clicks into the header. Export 1 strips the marks off that tree permanently; export 2 re-serializes the already-stripped tree.Fix
Strip the mark on a shallow copy of the node instead (
{ ...node, marks: filtered }), leaving the caller's tree intact. This fixes the root cause for everyexportToXmlJsoncaller, including theheader-footer-syncfallback path that also passes the persistent tree by reference — repeated exports with no intervening edit now produce the same tracked changes every time. These two assignments are the only in-place mutations of caller data on the export path (auditednode.marks =/node.attrs =/ splice sites in the decode chain).Testing
headerTrackedChangesDoubleExport.test.jswith a synthetic fixture (header-tracked-changes.docx, derived from the in-treeheader-alternating-odd-even.docxwith onew:ins+ onew:delspliced intoword/header1.xml). It registers a header sub-editor the wayHeaderFooterEditorManagerdoes, exports twice, and assertsword/header1.xmlstill carriesins=1 del=1on the second export. Red without the fix (second export drops toins=0 del=0, exactly the issue's output), green with it.marksuntouched while the stripped copy is what reachesexportSchemaToJson.import-export+super-convertersuites: 3643/3643 pass.Fixes #3893