Skip to content

fix(track-changes): correct format change description for already-formatted text (SD-2077)#2253

Merged
harbournick merged 3 commits intomainfrom
tadeu/sd-2077-bug-tracked-change-displays-incorrect-changes-when-applied
Mar 3, 2026
Merged

fix(track-changes): correct format change description for already-formatted text (SD-2077)#2253
harbournick merged 3 commits intomainfrom
tadeu/sd-2077-bug-tracked-change-displays-incorrect-changes-when-applied

Conversation

@tupizz
Copy link
Contributor

@tupizz tupizz commented Mar 3, 2026

Summary

Fixes the tracked change comment showing incorrect "removed" descriptions when adding formatting to text that already has different formatting in suggesting mode.

Example: Text has highlight applied. User switches to suggesting mode and bolds it. The tracked change comment said "Format: bold, removed highlight" when it should say "Format: bold". The highlight was never removed.

Root cause

In addMarkStep.js, when a format mark is added in suggesting mode, the track changes system records a before/after snapshot to describe what changed. The display logic diffs these arrays: marks in before but not after show as "removed", marks in after but not before show as "added".

The bug was in the else branch (no existing trackFormat mark on the text): the code dumped all existing non-tracking marks into before, regardless of whether they were related to the change. So when adding bold to highlighted text, highlight ended up in before and bold in after, making it look like highlight was removed and bold was added.

// Before (bug): captures ALL marks as "before" state
before = liveMarks
  .filter((mark) => ![TrackDeleteMarkName, TrackFormatMarkName].includes(mark.type.name))
  .map((mark) => ({ type: mark.type.name, attrs: { ...mark.attrs } }));

Fix

Only capture a mark of the same type as the one being added into before. This correctly handles:

  • Pure additions (bold on highlighted text): before = [], after = [bold] → shows "bold"
  • Type replacements (changing textStyle color): before = [textStyle{old}], after = [textStyle{new}] → shows "color"
  • No false removals: unrelated marks (highlight, italic, etc.) are never included in before
// After (fix): only captures same-type mark being replaced
const existingMarkOfSameType = liveMarks.find(
  (mark) =>
    mark.type.name === step.mark.type.name &&
    ![TrackDeleteMarkName, TrackFormatMarkName].includes(mark.type.name),
);
before = existingMarkOfSameType
  ? [{ type: existingMarkOfSameType.type.name, attrs: { ...existingMarkOfSameType.attrs } }]
  : [];

This matches the pattern already used by removeMarkStep.js, which correctly scopes before to only the specific mark being removed.

Why this approach

The before/after arrays represent the diff, not the full state. They should only contain marks that are actually changing. The removeMarkStep handler already follows this principle (it only puts the removed mark in before). This fix aligns addMarkStep with the same semantic.

Alternatives considered:

  • Fixing at the display layer (translateFormatChangesToEnglish): would only mask the symptom. The before/after data would still be wrong, affecting any future consumer of these arrays (e.g., accept/reject, collaboration sync).
  • Filtering unrelated marks in before by comparing against after: more complex and fragile. The root issue is that unrelated marks should never enter before in the first place.

Test plan

  • Unit test: adding bold to highlighted text → before is empty, after has bold
  • Unit test: changing textStyle on text with highlight → before has only textStyle, not highlight
  • Behavior test (Chromium/Firefox/WebKit): bold on highlighted text shows "Format: bold", no "removed"
  • Behavior test: italic on bold text shows "Format: italic", no "removed bold"
  • Behavior test: color change on highlighted text shows "Format: color", no "removed highlight"
  • All 736 existing unit test files pass
  • All 41 existing comment/track-change behavior tests pass

… to formatted text (SD-2077)

When applying formatting (e.g. bold) to text that already had different
formatting (e.g. highlight) in suggesting mode, the tracked change
comment incorrectly stated the original formatting was removed.

Root cause: addMarkStep populated the `before` array with ALL existing
marks on the text, rather than only marks of the same type being
replaced. The display logic diffs `before` vs `after` to generate the
description, so unrelated marks in `before` appeared as removals.

Fix: only capture an existing mark of the same type as the step mark
into `before` (for replacement cases like changing textStyle color).
For pure additions, `before` is now empty. This matches the pattern
already used by removeMarkStep.
@linear
Copy link

linear bot commented Mar 3, 2026

Copy link
Contributor

@caio-pizzol caio-pizzol left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tupizz clean fix, no correctness issues. tests look good. one unused import and a minor cleanup suggestion — left inline comments.

Copy link
Collaborator

@harbournick harbournick left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@harbournick harbournick merged commit b2ffc0d into main Mar 3, 2026
7 checks passed
@harbournick harbournick deleted the tadeu/sd-2077-bug-tracked-change-displays-incorrect-changes-when-applied branch March 3, 2026 21:20
@superdoc-bot
Copy link
Contributor

superdoc-bot bot commented Mar 3, 2026

🎉 This PR is included in superdoc v1.17.0-next.69

The release is available on GitHub release

@superdoc-bot
Copy link
Contributor

superdoc-bot bot commented Mar 3, 2026

🎉 This PR is included in superdoc-cli v0.2.0-next.66

The release is available on GitHub release

harbournick pushed a commit that referenced this pull request Mar 3, 2026
# [1.17.0](v1.16.0...v1.17.0) (2026-03-03)

### Bug Fixes

* active track change ([#2163](#2163)) ([108c14d](108c14d))
* add currentTotalPages getter and pagination-update event ([#2202](#2202)) ([95b4579](95b4579)), closes [#958](#958)
* always call resolveComment after custom TC bubble handlers (SD-2049) ([#2204](#2204)) ([34fb4e0](34fb4e0))
* backward replace insert text ([#2172](#2172)) ([66f0849](66f0849))
* before paragraph spacing inside table cells ([#1842](#1842)) ([c7efa85](c7efa85))
* **collaboration:** deduplicate updateYdocDocxData during replaceFile (SD-1920) ([#2162](#2162)) ([52962fc](52962fc))
* **comments:** cross-page collision avoidance for floating comment bubbles (SD-1998) ([#2180](#2180)) ([6cfbeca](6cfbeca))
* **comments:** emit empty comment positions so undo clears orphan bubbles ([#2235](#2235)) ([12ba727](12ba727))
* **comments:** improve multiline comment input styling ([#2242](#2242)) ([e6a0dab](e6a0dab))
* **comments:** prevent comment mark from extending to adjacent typed text ([#2241](#2241)) ([07fecd8](07fecd8))
* **comments:** reduce sidebar jitter when clicking comments (SD-2034) ([#2250](#2250)) ([c3568d2](c3568d2))
* **comments:** remove synchronous dispatch from plugin apply() (SD-1940) ([#2157](#2157)) ([887175b](887175b))
* **css:** scope ProseMirror CSS to prevent bleeding into host apps (SD-1850) ([#2134](#2134)) ([b9d98fa](b9d98fa))
* document-api improvements, plan mode, query.match, mutations ([6221580](6221580))
* **document-api:** delete table cell fix ([#2209](#2209)) ([5e5c43f](5e5c43f))
* **document-api:** distribute columns command fixes ([#2207](#2207)) ([8f4eaf7](8f4eaf7))
* **document-api:** fix cell shading in document api ([#2215](#2215)) ([456f60e](456f60e))
* **document-api:** insert table cell ([#2210](#2210)) ([357ee90](357ee90))
* **document-api:** plan-engine reliability fixes and error diagnostics ([#2185](#2185)) ([abfd81b](abfd81b))
* **document-api:** split table cell command ([#2217](#2217)) ([0b3e2b4](0b3e2b4))
* **document-api:** split table command ([#2214](#2214)) ([ec31699](ec31699))
* **editor:** render styles applied inside SDT fields (SD-2011) ([#2188](#2188)) ([9c34be3](9c34be3))
* **editor:** selection highlight flickers when dragging across mark boundaries (SD-2024) ([#2205](#2205)) ([ba03e76](ba03e76))
* extract duplicate block identity normalization from docxImporter ([7f7ff93](7f7ff93))
* improve backspace behavior near run boundaries for tracked changes ([#2175](#2175)) ([6c9c7a3](6c9c7a3))
* **layout:** per-section footer constraints for multi-section docs (SD-1837) ([#2022](#2022)) ([e11acc5](e11acc5))
* markdown block-separator blank lines and heading split style-mark normalization ([e988adc](e988adc))
* normalize review namespace into trackChanges, harden input validation ([33e907b](33e907b))
* outside click for toolbar dropdown ([#2174](#2174)) ([5f859c7](5f859c7))
* prefer full decoration range ([#2239](#2239)) ([ac15e31](ac15e31)), closes [#collectDesiredState](https://github.com/superdoc-dev/superdoc/issues/collectDesiredState) [#resolveEffectiveRanges](https://github.com/superdoc-dev/superdoc/issues/resolveEffectiveRanges) [#setPreviousRanges](https://github.com/superdoc-dev/superdoc/issues/setPreviousRanges)
* preserve line spacing and indentation on Google Docs paste ([#2183](#2183)) ([b9a7357](b9a7357)), closes [#2151](#2151)
* preserve text-align on paste from Google Docs ([#2208](#2208)) ([762231b](762231b))
* rollback comments colors / ui ([#2216](#2216)) ([a99b5ab](a99b5ab))
* **scroll:** wait for virtualized page mount and center text element ([#2221](#2221)) ([95f634e](95f634e))
* **shapes:** render grouped DrawingML shapes with custom geometry (SD-1877) ❇️ ([#2105](#2105)) ([14985a5](14985a5))
* splitting run with header adds empty row ([#2229](#2229)) ([e1965fc](e1965fc))
* **super-converter:** handle empty pic:spPr in image import ([#2254](#2254)) ([2b8dbce](2b8dbce))
* **super-editor:** backspace across run boundaries without splitting list items ([#2258](#2258)) ([27ccb64](27ccb64))
* support cell spacing ([#1879](#1879)) ([1639967](1639967))
* **tables:** defaultTableStyle support, cell fixes ([#2246](#2246)) ([74fca9c](74fca9c))
* **tables:** expand auto-width tables to fill available page width ([#2109](#2109)) ([15f36bc](15f36bc))
* **tables:** preserve TableGrid defaults and style-driven spacing/bor… ([#2230](#2230)) ([b0a482f](b0a482f))
* text highlight on export ([#2189](#2189)) ([9cbd022](9cbd022))
* track highlight changes ([#2192](#2192)) ([e164625](e164625))
* **track-changes:** correct format change description for already-formatted text (SD-2077) ([#2253](#2253)) ([b2ffc0d](b2ffc0d))
* **track-changes:** handle ReplaceAroundStep in tracked changes mode (SD-2061) ([#2225](#2225)) ([8f3cbe4](8f3cbe4))
* **track-changes:** remove ghost TrackFormat on multi-node format cancel ([#2233](#2233)) ([e925ef9](e925ef9))
* undo/redo actions ([#2161](#2161)) ([495e92f](495e92f))
* **virtualization:** correct scroll mapping and viewport sizing at non-100% zoom ([#2171](#2171)) ([84af4c0](84af4c0)), closes [#registryKey](https://github.com/superdoc-dev/superdoc/issues/registryKey)

### Features

* allow custom accept/reject handlers for TC bubbles ([#1921](#1921)) ([e30abf6](e30abf6))
* **comments:** improve floating comments ui ([#2195](#2195)) ([e870cfb](e870cfb))
* **document-api:** add format operations font size alignment color font family ([#2179](#2179)) ([f19c688](f19c688))
* **document-api:** add get markdown to sdks ([e42b56d](e42b56d))
* **document-api:** add plan-based mutation engine with query.match and style capture ([#2160](#2160)) ([365293a](365293a))
* **document-api:** default table style setting ([#2248](#2248)) ([3ad4e9f](3ad4e9f))
* **document-api:** default target-less insert to document end ([#2244](#2244)) ([c717e2b](c717e2b))
* **document-api:** doc default initial styles ([#2184](#2184)) ([f25e41f](f25e41f))
* **document-api:** format.paragraph for w:pPr formatting ([#2218](#2218)) ([32c9991](32c9991))
* **document-api:** history name space ([#2219](#2219)) ([41dea37](41dea37))
* **document-api:** include anchored text in comments list response ([#2177](#2177)) ([b3a2912](b3a2912))
* **document-api:** inline formatting parity core end-to-end ([#2197](#2197)) ([b405b03](b405b03))
* **document-api:** inline formatting rpr parity ([#2198](#2198)) ([41ab771](41ab771))
* **document-api:** lists namespace  ([#2223](#2223)) ([09ebfcb](09ebfcb))
* **document-api:** section commands ([#2199](#2199)) ([ec4abe3](ec4abe3))
* **document-api:** support deleting entire block nodes not only text ([#2181](#2181)) ([2897246](2897246))
* **document-api:** table of contents commands ([#2200](#2200)) ([baa72c4](baa72c4))
* **document-api:** tables namespace and commands ([#2182](#2182)) ([b80ee31](b80ee31))
* **document-api:** toc commands ([#2220](#2220)) ([767e010](767e010))
* **images:** allow drag-and-drop for images in editor ([#2227](#2227)) ([4b36780](4b36780))
* **layout-engine:** render table headers, tblLook support ([#2256](#2256)) ([db6a2ff](db6a2ff))
* **link-popover:** custom link popovers ([#2222](#2222)) ([070190f](070190f))
* **markdown:** add markdown override to sdk, improve conversion ([#2196](#2196)) ([04a1c71](04a1c71))
* preserve w:view setting through DOCX round-trip ([#2190](#2190)) ([48b4210](48b4210)), closes [#2070](#2070)
* real time collab in python sdk ([#2243](#2243)) ([dc3b4fd](dc3b4fd))
* **tables:** allow resizing table rows ([#2226](#2226)) ([2c6da10](2c6da10))
* **tables:** improve cell color application (context), column dragging, table pasting ([#2228](#2228)) ([066b9eb](066b9eb))
* **table:** toggle header row sets both cell types and repeatHeader atomically ([#2245](#2245)) ([2f5899d](2f5899d))
* **track-changes:** clear comment bubbles when bulk accept or reject TCs ([#2159](#2159)) ([27fbe8e](27fbe8e))

### Performance Improvements

* **comments:** batch tracked change comment creation on load ([#2166](#2166)) ([0c2eca5](0c2eca5))
* **comments:** batch tracked change creation and virtualize floating bubbles (SD-1997) ([#2168](#2168)) ([70fd7d9](70fd7d9))
@harbournick
Copy link
Collaborator

🎉 This PR is included in superdoc v1.17.0

The release is available on GitHub release

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants