Commit ad5a8cc
authored
fix(super-editor): keep comment range markers around unpaired tracked changes (SD-2528) (#3239)
* fix(super-editor): keep comment range markers as siblings of unpaired tracked changes (SD-2528)
mergeConsecutiveTrackedChanges greedily absorbed trailing w:commentRangeEnd
and w:r->w:commentReference elements into a w:del/w:ins wrapper even when no
same-id wrapper followed to actually merge into. This produced a lopsided
structure where w:commentRangeStart sat outside the wrapper but
w:commentRangeEnd ended up inside it, breaking comment round-trip on redlined
text.
Buffer comment markers during the forward scan and only commit them inside
the wrapper when a same-id merge actually happens. Otherwise emit them as
siblings, matching Word's expected OOXML.
SD-1519 merge behavior is unchanged and covered by the new tests.
* fix(super-editor): fold commentRangeStart into TC wrapper for round-trip (SD-2528)
The previous fix left commentRangeStart as a sibling of w:ins/w:del.
documentCommentsImporter.js' extractCommentRangesFromDocument only
associates a comment with a tracked change when commentRangeStart sits
inside the wrapper, so the sibling shape silently dropped the comment
to TC link on re-import.
Fold any leading commentRangeStart sibling into the immediately following
w:ins/w:del as its first child, matching the shape Word produces. The
existing SD-1519 same-id merge for trailing commentRangeEnd and
w:r/w:commentReference stays unchanged.
Adds an end-to-end test that loads the Google-Docs TC+comment fixture,
exports it, re-imports the exported XML, and asserts every comment that
was originally inside a tracked change still carries
trackedChangeParentId after the round-trip.
* fix: cascade accept/reject of a tracked change to its anchored comments (SD-2528)
A user comment anchored to a tracked change carries trackedChangeParentId
pointing at the TC. Two bugs broke the link end-to-end:
1. docxImporter built two tracked-change id maps independently
(trackedChangeIdMap and trackedChangeIdMapsByPart), each minting fresh
UUIDs for the same Word w:id. The comments importer used the global
map; ins/del translators used the per-part map. The two never matched,
so trackedChangeParentId on a comment never pointed at the actual TC
mark id in the PM doc. Fix: build the per-part maps first and reuse
the document.xml entry as the global map.
2. The comments-store resolve handler only resolved the TC's own
redline-display entry. User comments with trackedChangeParentId === the
resolved TC stayed open. Fix: after resolving the TC entity, iterate
commentsList and resolve every comment whose trackedChangeParentId
matches. Defer via Promise.resolve so the cascading resolveComment
doesn't dispatch into a still-running accept/rejectTrackedChangeById
loop and collide with the loop's mutable tr.
E2E browser repro on the real Google-Docs TC+comment fixture: accept TC
by id, both the TC and its anchored user comments resolve in one user
action. Same for reject. No mismatched-tr errors.
The export-side round-trip test also asserts the two id maps are
aligned and every comment trackedChangeParentId matches a real
tracked-change mark id in the PM doc.
* fix(super-editor): thread reply-to-TC under its tracked-change bubble on re-import (SD-2528)
A reply that the user typed under a tracked-change bubble has
parentCommentId pointing at the synthetic TC entity in the comments
store. On export the TC parent is filtered out of comments.xml
(TC entries are not real comments), so the reply lands in the file
without any paraIdParent. On re-import the reply gets trackedChangeParentId
via the document.xml walker (the commentRange wraps the TC text) but
parentCommentId was left undefined — the sidebar then renders the reply
as a separate top-level bubble next to the TC instead of nested under it,
matching the user-reported regression in image 1 of SD-2528.
Promote trackedChangeParentId to parentCommentId when no explicit
parent is set. CommentDialog already threads via direct
parentCommentId === trackedChangeId (line 321), so this is the
cheapest path to restore the live pre-export state.
Round-trip stable: re-export still filters TC parents but re-emits the
commentRange inside the wrapper, which gets re-detected on the next
import via extractCommentRangesFromDocument and re-establishes the
linkage.
* fix(comments): thread tracked-change replies regardless of file origin (SD-2528)
The UI guarded TC reply threading with isRangeThreadedComment, which is
true only when the source DOCX has no commentsExtended.xml (Google Docs
style). SuperDoc-exported DOCX files always write commentsExtended.xml,
so on re-import the guard short-circuited and the reply rendered as a
top-level bubble next to its TC instead of nested under it.
Drop the file-origin guard from the two sites that threaded TC replies:
collectTrackedChangeThread in CommentDialog.vue and
shouldThreadWithTrackedChange in comments-store.js.
trackedChangeParentId pointing at a tracked-change entity is sufficient
to thread; file origin should not change whether a comment threads under
its TC.
Reverts the earlier importer-side patch that promoted
trackedChangeParentId into parentCommentId. That patch violated the
comment-diffing contract (parentCommentId is diffed; trackedChangeParentId
is intentionally ignored because it is regenerated across imports) and
broke six existing tests. The UI-side change is surgical and breaks no
tests.
* fix(comments): preserve TC color on anchored comments + clean up IMPORTED/resolve gates (SD-2528)
Three visual round-trip regressions after the SD-2528 fix made TC replies
thread again:
1. CommentHighlightDecorator painted its pink (external) / green (internal)
inline background on every element with the superdoc-comment-highlight
class — including text that already carries a track-insert-dec /
track-delete-dec decoration. The inline style won over the TC's own CSS
class background, so a green trackInsert came back pink after re-import.
Skip the BG override when the element is also a tracked-change decoration:
the TC color (green for insert, red for delete) is the right signal for
the user, and the comment range is still visually identified by its
dashed border + sidebar bubble.
2. CommentHeader's IMPORTED tag fired whenever comment.origin or
importedAuthor was set — including comments authored by the current user
in a previous session. Round-tripping a file you exported then re-opened
should not relabel your own comments as imported. Suppress the tag when
the comment's creatorEmail matches the current user's email.
3. CommentHeader's allowResolve guard treated parentCommentId as the only
marker of a child comment. A TC-anchored reply on re-import keeps the
linkage through trackedChangeParentId only (parentCommentId is left
undefined to preserve the comment-diffing contract). The resolve check
affordance therefore appeared on re-imported replies even though the
pre-export state had no parentCommentId either. Treat
trackedChangeParentId as an equivalent child signal.
All three are surgical render-side gates — no converter / data-model
changes. 1369 super-editor presentation tests pass.
* fix(comments): scope cascade to active doc + tighten TC-anchored gates (SD-2528)
Addresses Codex's 3 P2 review findings from PR #3239:
P2 #1 — comments-store.js cascade scope
The new cascade-resolve scan introduced in aa88a58 didn't honour the
resolve event's documentId. findTrackedChangeById (line 591) correctly
scopes its match by belongsToTrackedChangeSyncDocument; the cascade six
lines lower did not. In multi-document sessions where imported
tracked-change ids collide across files (each w:id space is local),
accepting a change in document A would also resolve comments anchored on
the same id in document B. Mirror the same per-document filter when a
documentId is provided; single-document callers (no documentId on the
event) keep the legacy global behaviour.
P2 #2 — CommentHighlightDecorator.ts visual gate
The earlier suppression triggered on any of `track-insert-dec`,
`track-delete-dec`, or `track-format-dec`. Per layout-engine styles.ts
only `.track-insert-dec.highlighted` and `.track-delete-dec.highlighted`
paint a background; `.track-format-dec` only paints a border-bottom, and
the `.highlighted` modifier is only applied in "review" / All Markup mode
(renderer.ts:909-928). In Original / Final modes, and on format-only
changes, the suppression cleared the comment fill with nothing to replace
it, making the bubble invisible. Tighten the gate to require both
`.highlighted` and one of the bg-painting base classes.
P2 #3 — collectTrackedChangeThread parent shadowing
documentCommentsImporter can produce a comment with BOTH a non-TC
`parentCommentId` and a `trackedChangeParentId`: the comment's range
lives inside a TC, but its conversational thread starts at a regular
comment outside the TC. The previous unconditional pull on
trackedChangeParentId placed such replies in both threads. Restrict the
direct seed to roots (no parentCommentId) and let the BFS step pick up
same-TC-anchored chains via parent links. Extract the helper to a
sibling module so the BFS logic can be unit-tested in isolation —
previously trapped inside CommentDialog.vue's <script setup>.
Verification
- 8 new unit tests covering each P2 case (3 in
collect-tracked-change-thread.test.js, 4+ in
CommentHighlightDecorator.test.ts, 1 cross-doc + 1 single-doc
regression in comments-store.test.js).
- SD-2528 integration round-trip test still passes (1/1).
- super-editor: 12 850 / 12 850 unit tests pass.
- superdoc: 966 unit tests pass (1 pre-existing collab-server import
failure, unrelated, present on main and other branches).
- Browser repro on the corpus fixture: accepting an imported TC still
cascade-resolves both anchored user comments end-to-end.1 parent 87368de commit ad5a8cc
13 files changed
Lines changed: 798 additions & 95 deletions
File tree
- packages
- super-editor/src/editors/v1
- core
- presentation-editor/dom
- super-converter
- v2/importer
- v3/handlers/w/p/helpers
- tests/export
- superdoc/src
- components/CommentsLayer
- stores
Lines changed: 99 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
352 | 352 | | |
353 | 353 | | |
354 | 354 | | |
| 355 | + | |
| 356 | + | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
| 370 | + | |
| 371 | + | |
| 372 | + | |
| 373 | + | |
| 374 | + | |
| 375 | + | |
| 376 | + | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
| 385 | + | |
| 386 | + | |
| 387 | + | |
| 388 | + | |
| 389 | + | |
| 390 | + | |
| 391 | + | |
| 392 | + | |
| 393 | + | |
| 394 | + | |
| 395 | + | |
| 396 | + | |
| 397 | + | |
| 398 | + | |
| 399 | + | |
| 400 | + | |
| 401 | + | |
| 402 | + | |
| 403 | + | |
| 404 | + | |
| 405 | + | |
| 406 | + | |
| 407 | + | |
| 408 | + | |
| 409 | + | |
| 410 | + | |
| 411 | + | |
| 412 | + | |
| 413 | + | |
| 414 | + | |
| 415 | + | |
| 416 | + | |
| 417 | + | |
| 418 | + | |
| 419 | + | |
| 420 | + | |
| 421 | + | |
| 422 | + | |
| 423 | + | |
| 424 | + | |
| 425 | + | |
| 426 | + | |
| 427 | + | |
| 428 | + | |
| 429 | + | |
| 430 | + | |
| 431 | + | |
| 432 | + | |
| 433 | + | |
| 434 | + | |
| 435 | + | |
| 436 | + | |
| 437 | + | |
| 438 | + | |
| 439 | + | |
| 440 | + | |
| 441 | + | |
| 442 | + | |
| 443 | + | |
| 444 | + | |
| 445 | + | |
| 446 | + | |
| 447 | + | |
| 448 | + | |
| 449 | + | |
| 450 | + | |
| 451 | + | |
| 452 | + | |
| 453 | + | |
355 | 454 | | |
356 | 455 | | |
357 | 456 | | |
| |||
Lines changed: 41 additions & 3 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
171 | 171 | | |
172 | 172 | | |
173 | 173 | | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
174 | 200 | | |
175 | 201 | | |
176 | | - | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
177 | 207 | | |
178 | 208 | | |
179 | 209 | | |
| |||
184 | 214 | | |
185 | 215 | | |
186 | 216 | | |
187 | | - | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
188 | 222 | | |
189 | 223 | | |
190 | 224 | | |
| |||
195 | 229 | | |
196 | 230 | | |
197 | 231 | | |
198 | | - | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
199 | 237 | | |
200 | 238 | | |
201 | 239 | | |
| |||
Lines changed: 0 additions & 3 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
208 | 208 | | |
209 | 209 | | |
210 | 210 | | |
211 | | - | |
212 | | - | |
213 | | - | |
214 | 211 | | |
215 | 212 | | |
216 | 213 | | |
| |||
Lines changed: 9 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
158 | 158 | | |
159 | 159 | | |
160 | 160 | | |
161 | | - | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
162 | 167 | | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
163 | 171 | | |
164 | 172 | | |
165 | 173 | | |
| |||
Lines changed: 62 additions & 31 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
4 | 44 | | |
5 | | - | |
6 | | - | |
7 | | - | |
8 | | - | |
9 | | - | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
10 | 50 | | |
11 | 51 | | |
12 | 52 | | |
13 | 53 | | |
14 | 54 | | |
15 | 55 | | |
16 | 56 | | |
| 57 | + | |
| 58 | + | |
17 | 59 | | |
18 | 60 | | |
19 | 61 | | |
20 | 62 | | |
21 | 63 | | |
22 | 64 | | |
23 | | - | |
24 | | - | |
| 65 | + | |
25 | 66 | | |
26 | 67 | | |
27 | 68 | | |
28 | | - | |
29 | 69 | | |
| 70 | + | |
| 71 | + | |
30 | 72 | | |
31 | 73 | | |
32 | 74 | | |
33 | 75 | | |
34 | 76 | | |
35 | | - | |
36 | | - | |
37 | | - | |
| 77 | + | |
| 78 | + | |
38 | 79 | | |
39 | 80 | | |
40 | 81 | | |
41 | 82 | | |
42 | | - | |
43 | | - | |
44 | | - | |
45 | | - | |
46 | | - | |
47 | | - | |
48 | | - | |
49 | | - | |
50 | | - | |
51 | | - | |
52 | | - | |
53 | 83 | | |
54 | | - | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
55 | 87 | | |
56 | 88 | | |
57 | 89 | | |
58 | 90 | | |
59 | | - | |
60 | 91 | | |
61 | 92 | | |
62 | 93 | | |
63 | | - | |
64 | | - | |
65 | | - | |
66 | | - | |
67 | | - | |
68 | | - | |
69 | | - | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
70 | 101 | | |
71 | 102 | | |
72 | 103 | | |
| |||
0 commit comments