Skip to content

Commit 12ba727

Browse files
authored
fix(comments): emit empty comment positions so undo clears orphan bubbles (#2235)
* fix(comments): emit empty comment positions so undo clears orphan bubbles PresentationEditor skipped emission when positions were empty, so undoing the last tracked-change mark never signaled the store to clear stale data. Also fix getFloatingComments to use isEditorBackedComment() so tracked-change comments (which lack selection.source) require live positions like editor comments. * fix(comments): use `in` operator for position key lookup Avoids rebuilding Object.keys() array per comment in getFloatingComments filter. * fix(tests): wait for dialog deactivation before re-activating in collapse test Firefox needs the active dialog to fully unmount before re-activation, otherwise the thread renders expanded instead of collapsed.
1 parent e925ef9 commit 12ba727

4 files changed

Lines changed: 32 additions & 9 deletions

File tree

packages/super-editor/src/core/presentation-editor/PresentationEditor.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3524,14 +3524,12 @@ export class PresentationEditor extends EventEmitter {
35243524
this.emit('paginationUpdate', payload);
35253525

35263526
// Emit fresh comment positions after layout completes.
3527-
// This ensures positions are always in sync with the current document and layout.
3527+
// Always emit — even when empty — so the store can clear stale positions
3528+
// (e.g. when undo removes the last tracked-change mark).
35283529
const allowViewingCommentPositions = this.#layoutOptions.emitCommentPositionsInViewing === true;
35293530
if (this.#documentMode !== 'viewing' || allowViewingCommentPositions) {
35303531
const commentPositions = this.#collectCommentPositions();
3531-
const positionKeys = Object.keys(commentPositions);
3532-
if (positionKeys.length > 0) {
3533-
this.emit('commentPositions', { positions: commentPositions });
3534-
}
3532+
this.emit('commentPositions', { positions: commentPositions });
35353533
}
35363534

35373535
this.#selectionSync.requestRender({ immediate: true });

packages/superdoc/src/stores/comments-store.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -876,11 +876,12 @@ export const useCommentsStore = defineStore('comments', () => {
876876
const comments = getGroupedComments.value?.parentComments
877877
.filter((c) => !c.resolvedTime)
878878
.filter((c) => {
879-
const keys = Object.keys(editorCommentPositions.value);
880-
const isPdfComment = c.selection?.source !== 'super-editor';
881-
if (isPdfComment) return true;
879+
// Non-editor comments (e.g. PDF) are always shown.
880+
// Editor-backed comments (including tracked changes, which have no
881+
// selection.source) must have a live position in the document.
882+
if (!isEditorBackedComment(c)) return true;
882883
const commentKey = c.commentId || c.importedId;
883-
return keys.includes(commentKey);
884+
return commentKey in editorCommentPositions.value;
884885
});
885886
return comments;
886887
});

packages/superdoc/src/stores/comments-store.test.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -946,5 +946,24 @@ describe('comments-store', () => {
946946
const floating = store.getFloatingComments;
947947
expect(floating).toEqual([]);
948948
});
949+
950+
it('excludes unresolved tracked change when positions are cleared (regression: SD-2071)', () => {
951+
store.commentsList = [
952+
{ commentId: 'tc-1', trackedChange: true, resolvedTime: null, createdTime: 1, selection: {} },
953+
];
954+
// Undo removed the mark — positions are now empty
955+
store.editorCommentPositions = {};
956+
957+
const floating = store.getFloatingComments;
958+
expect(floating).toEqual([]);
959+
});
960+
961+
it('keeps PDF comments visible when editor positions are empty (SD-2071)', () => {
962+
store.commentsList = [{ commentId: 'pdf-1', createdTime: 1, selection: { source: 'pdf', selectionBounds: {} } }];
963+
store.editorCommentPositions = {};
964+
965+
const floating = store.getFloatingComments;
966+
expect(floating.map((c) => c.commentId)).toEqual(['pdf-1']);
967+
});
949968
});
950969
});

tests/behavior/tests/comments/comment-thread-collapse.spec.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ test('thread with 2+ replies collapses and expands on click', async ({ superdoc
4242
});
4343
await superdoc.waitForStable();
4444

45+
// Wait for dialog to lose active state before re-activating — Firefox needs
46+
// this gap so the component fully unmounts its expanded state.
47+
const activeDialog = superdoc.page.locator('.comment-placeholder .comments-dialog.is-active');
48+
await expect(activeDialog).toHaveCount(0, { timeout: 5_000 });
49+
4550
// Activate the comment dialog
4651
const dialog = await activateCommentDialog(superdoc, 'collapse');
4752

0 commit comments

Comments
 (0)