Skip to content

Commit 75c94b4

Browse files
authored
SD-3357 - fix: dedupe tracked changes from merged activity feed (#3609)
* fix(custom-ui): dedupe tracked changes from merged activity feed (SD-3357) SuperDoc surfaces tracked changes in both ui.comments.items (as trackedChange:true comments) and ui.trackChanges.items with the same id. The ActivitySidebar merged both slices without filtering, so each suggestion rendered twice (one comment card + one change card). Skip comment.trackedChange items when building the feed, and drop a leftover debug log. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(custom-ui): dedupe activity feed by id, not trackedChange flag (SD-3357) A real comment thread anchored over text that overlaps a suggestion also gets trackedChange:true (comments.list links it via assignTrackedChangeLink), so filtering on the flag wrongly hid those discussions. Dedupe by id instead — synthetic tracked-change comment rows reuse the tracked-change id, while real comments keep their own. Also exclude pairedWithChangeId so replacement pairs collapsed by trackChanges.list (replacements: paired) don't leak one half back as a comment. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix: clarify deduplication logic in activity feed comments
1 parent 5dd00c8 commit 75c94b4

1 file changed

Lines changed: 24 additions & 1 deletion

File tree

demos/editor/custom-ui/src/components/ActivitySidebar.tsx

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,32 @@ export function ActivitySidebar({ composeOpen, onCloseComposer, decided }: Props
7070
// (separate ticket), we'll be able to interleave by document
7171
// position; until then this stable two-bucket ordering matches what
7272
// the controller used to do internally.
73+
//
74+
// SuperDoc models tracked changes as comment-linked entities, so
75+
// `ui.comments.items` mirrors each tracked change as a synthetic
76+
// comment whose id is the tracked-change id. Without de-duping, every
77+
// suggestion shows twice (one comment card + one change card).
78+
//
79+
// We dedupe by id, NOT by the `trackedChange` flag: a real comment
80+
// thread also gets `trackedChange: true` when its anchor overlaps a
81+
// suggestion (`comments.list()` links it via `assignTrackedChangeLink`),
82+
// so a flag filter would wrongly hide those discussions. Only the
83+
// synthetic rows reuse a tracked-change id; real comments have their
84+
// own. Note: this demo runs `replacements: 'independent'`, so both
85+
// sides of a replacement surface as separate change rows and both
86+
// synthetic comment ids land in the dedupe set. Under the default
87+
// `'paired'` mode the collapsed row drops the delete-side id, which
88+
// `trackChanges.list()` does not currently expose — the delete-side
89+
// synthetic comment would leak as a duplicate. Engine follow-up needed
90+
// before this pattern is safe in paired mode.
7391
const feed = useMemo<ActivityItem[]>(() => {
92+
const changeIds = new Set<string>();
93+
for (const tc of trackChanges.items) changeIds.add(tc.id);
7494
const items: ActivityItem[] = [];
75-
for (const c of comments.items) items.push({ kind: 'comment', id: c.id, comment: c });
95+
for (const c of comments.items) {
96+
if (changeIds.has(c.id)) continue;
97+
items.push({ kind: 'comment', id: c.id, comment: c });
98+
}
7699
for (const tc of trackChanges.items) items.push({ kind: 'change', id: tc.id, change: tc.change });
77100
return items;
78101
}, [comments.items, trackChanges.items]);

0 commit comments

Comments
 (0)