feat(track-changes): add pairReplacements mode matching Word/ECMA-376 (SD-2607)#2849
Conversation
… (SD-2607)
when track changes are on and a user replaces text, SuperDoc groups the
insertion and deletion under one shared id so accepting or rejecting takes
one click (Google-Docs-like). Microsoft Word and ECMA-376 §17.13.5 treat
every <w:ins> and <w:del> as an independent revision with its own required
w:id. a consumer wants their UI to match Word.
this adds modules.trackChanges.pairReplacements (default true preserves
current behaviour). when false, each insertion and each deletion is an
independent change, addressable and resolvable on its own.
- importer: buildTrackedChangeIdMap accepts { pairReplacements }; when
false, skips the adjacent w:del+w:ins pairing so each Word w:id maps to
its own UUID
- insertTrackedChange: no shared id on replacements in unpaired mode
- getChangesByIdToResolve: returns just the single matching mark in
unpaired mode (no neighbor walk)
- wiring: SuperDoc.vue -> editor.options.trackedChanges -> Editor.ts ->
SuperConverter.trackedChangesOptions -> docxImporter
no exporter change needed — <w:ins>/<w:del> are already written per-mark
with their own w:id in both modes. no public API shape change.
|
Status: PASS The changed files handle Elements and attributes ( All three attributes read from
All twelve elements are real OOXML cross-structure annotation markers: bookmark start/end (Annex L §L.1.14.8), permission start/end (§L.1.14.9),
The spec (§17.13.5) does treat each
Nothing here misuses an OOXML element or attribute. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d2e8af3a32
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
…607)
- always walk adjacent same-id marks in getChangesByIdToResolve so a single
logical revision split across multiple segments resolves as one unit; the
unpaired case is handled implicitly because ins/del now have distinct ids
- align changeId with the insertion mark's id so comment threads and the
optional comment reply attach to the same thread in unpaired mode
- simplify id-minting: one primary id anchors the operation; the deletion
mints its own fresh id only when unpaired AND it's a replacement. the
Document API write adapter now gets unpaired revisions when the flag is
off without any adapter-level change
- add trackedChanges?: {...} to EditorOptions so consumers don't need casts
- add an unpaired-mode example snippet to the docs
- extension test now covers the headline guarantee: in unpaired mode,
acceptTrackedChangeById(insertionId) resolves only the insertion, and the
deletion is still independently rejectable by its own id
when track changes are on and a user replaces text, SuperDoc groups the insertion and deletion under one shared id so accepting or rejecting takes one click (Google-Docs-like). Microsoft Word and ECMA-376 §17.13.5 treat every
<w:ins>and<w:del>as an independent revision with its own requiredw:id. a consumer asked us to match the Word model.this adds
modules.trackChanges.pairReplacements(defaulttruepreserves current behavior). whenfalse, each insertion and each deletion is an independent change, addressable and resolvable on its own.buildTrackedChangeIdMapaccepts{ pairReplacements }; whenfalse, skips the adjacentw:del+w:inspairing so each Wordw:idmaps to its own UUIDeditor.options.trackedChanges→Editor.ts→SuperConverter.trackedChangesOptions→docxImporterMust stay the same: paired-mode behavior is the default; existing consumers see no change.
Rejected: making unpaired mode the global default — that would turn one-click resolve into two for every existing consumer, regressing UX for the Google-Docs-like crowd.
Review: check the two new code paths in
track-changes.js(pairReplacements read at insert time and at accept/reject time), and the mapper's conditional pairing.Verified:
pnpm --filter @superdoc/super-editor test→ 11358 pass (13 skipped),pnpm --filter superdoc test→ 877 pass (same pre-existingcollab-server.test.tsmodule-resolution failure as main).no exporter change needed —
<w:ins>/<w:del>are already written per-mark with their ownw:idin both modes. public API (trackChanges.list/get/decide) keeps the same shape —decide({ id })just resolves one mark instead of two in unpaired mode.closes SD-2607, unblocks IT-935. stacked on #2847 — the base will auto-retarget to
mainonce that merges.