Environment: superdoc@2.12.0 + @superdoc/react@2.7.0 (@superdoc/docx-engine@0.11.0), browser adapter.
Follow-up to #3709, which fixed the serious half: moved text no longer disappears on import. Thank you for that. This is about the other half.
What happens now
A tracked move imported from a .docx renders as an unrelated deletion at the source and an unrelated insertion at the destination. Nothing links them and nothing marks them as a move, so a reader cannot tell "this passage moved" from "this passage was deleted and a similar one was added somewhere else".
Why this looks like a small step rather than a new feature
Most of it already exists:
--sd-tracked-changes-move-from-* and --sd-tracked-changes-move-to-* theme variables, with #00853d as their default.
modules.trackChanges.semanticColors accepts move, move-from and move-to as override keys, and move is documented as a group key for both sides.
- The types model the pairing in full:
TrackChangeMoveSides { source, destination }, TrackChangeMoveSide, TrackChangeMoveTarget, role: 'move-source' | 'move-destination', moveDirection: 'up' | 'down'.
- The published
docx-engine bundle carries the runtime plumbing for it, not only the declarations (movePair, move-source, move-destination, moveDirection all appear as runtime strings).
What appears to be missing is the pairing at render time. The layout path looks the move wrapper up in a map keyed by w:id plus 'delete' | 'insert', and when the lookup misses it renders the side with plain insert or delete visibility and no semantic metadata, then pushes a diagnostic:
render.tracked-move.annotation-unmatched
Tracked move wrapper w:moveFrom|w:moveTo has no matching annotation;
rendered with <insert|delete> visibility and no semantic metadata
With no semanticColorKey on the layer, the move colours above are never reached.
Ask
When the pair does resolve, stamp the move semantic keys on both sides so the existing colours and the existing semanticColors config apply. That would make a move visually distinguishable from a delete plus an insert without any new theming surface.
This looks adjacent to the unpaired-move follow-up mentioned when #3709 was closed, so it may already be in scope there.
A related question
Does trackChanges.list() populate move, role and moveDirection today for an imported move, even while the render does not use them? If it does, a host can build its own affordance in the meantime, which would take the pressure off. We could not confirm it either way: the headless SDK did not finish opening our test document within five minutes (which may itself be #3900), and a hand-built minimal .docx was rejected with "source loading is not applicable for this document".
Steps to reproduce
The synthetic body from #3709 reproduces it. A pending move whose two sides share a w:name on the range markers:
<w:p>
<w:r><w:t xml:space="preserve">Alpha one shall provide the services to beta two.</w:t></w:r>
</w:p>
<w:p>
<w:moveFromRangeStart w:id="102" w:author="A. Author" w:date="2026-04-05T12:39:00Z" w:name="move1"/>
<w:moveFrom w:id="103" w:author="A. Author" w:date="2026-04-05T12:39:00Z">
<w:r><w:t xml:space="preserve">Gamma three means delta four and epsilon five acting on its behalf.</w:t></w:r>
</w:moveFrom>
<w:moveFromRangeEnd w:id="102"/>
</w:p>
<w:p>
<w:moveToRangeStart w:id="105" w:author="A. Author" w:date="2026-04-05T12:39:00Z" w:name="move1"/>
<w:moveTo w:id="106" w:author="A. Author" w:date="2026-04-05T12:39:00Z">
<w:r><w:t xml:space="preserve">Gamma three means delta four and epsilon five acting on its behalf.</w:t></w:r>
</w:moveTo>
<w:moveToRangeEnd w:id="105"/>
</w:p>
Observed: the source side renders as a pending deletion, the destination as a pending insertion, neither in the move colours. Expected: both sides carry the move semantic keys, so move-from and move-to colour them and the two are recognisable as one move.
Environment:
superdoc@2.12.0+@superdoc/react@2.7.0(@superdoc/docx-engine@0.11.0), browser adapter.Follow-up to #3709, which fixed the serious half: moved text no longer disappears on import. Thank you for that. This is about the other half.
What happens now
A tracked move imported from a
.docxrenders as an unrelated deletion at the source and an unrelated insertion at the destination. Nothing links them and nothing marks them as a move, so a reader cannot tell "this passage moved" from "this passage was deleted and a similar one was added somewhere else".Why this looks like a small step rather than a new feature
Most of it already exists:
--sd-tracked-changes-move-from-*and--sd-tracked-changes-move-to-*theme variables, with#00853das their default.modules.trackChanges.semanticColorsacceptsmove,move-fromandmove-toas override keys, andmoveis documented as a group key for both sides.TrackChangeMoveSides { source, destination },TrackChangeMoveSide,TrackChangeMoveTarget,role: 'move-source' | 'move-destination',moveDirection: 'up' | 'down'.docx-enginebundle carries the runtime plumbing for it, not only the declarations (movePair,move-source,move-destination,moveDirectionall appear as runtime strings).What appears to be missing is the pairing at render time. The layout path looks the move wrapper up in a map keyed by
w:idplus'delete' | 'insert', and when the lookup misses it renders the side with plain insert or delete visibility and no semantic metadata, then pushes a diagnostic:With no
semanticColorKeyon the layer, the move colours above are never reached.Ask
When the pair does resolve, stamp the move semantic keys on both sides so the existing colours and the existing
semanticColorsconfig apply. That would make a move visually distinguishable from a delete plus an insert without any new theming surface.This looks adjacent to the unpaired-move follow-up mentioned when #3709 was closed, so it may already be in scope there.
A related question
Does
trackChanges.list()populatemove,roleandmoveDirectiontoday for an imported move, even while the render does not use them? If it does, a host can build its own affordance in the meantime, which would take the pressure off. We could not confirm it either way: the headless SDK did not finish opening our test document within five minutes (which may itself be #3900), and a hand-built minimal.docxwas rejected with "source loading is not applicable for this document".Steps to reproduce
The synthetic body from #3709 reproduces it. A pending move whose two sides share a
w:nameon the range markers:Observed: the source side renders as a pending deletion, the destination as a pending insertion, neither in the move colours. Expected: both sides carry the move semantic keys, so
move-fromandmove-tocolour them and the two are recognisable as one move.