@@ -993,6 +993,81 @@ describe('comments-store', () => {
993993 expect ( store . commentsList . filter ( ( comment ) => comment . commentId === 'tc-history-replay-reply' ) ) . toHaveLength ( 1 ) ;
994994 } ) ;
995995
996+ it ( 'keeps already-resolved tracked-change comments during empty replay sync' , ( ) => {
997+ const editorDispatch = vi . fn ( ) ;
998+ const tr = { setMeta : vi . fn ( ) } ;
999+ const superdoc = { emit : vi . fn ( ) } ;
1000+ const editor = {
1001+ state : { } ,
1002+ view : { state : { tr } , dispatch : editorDispatch } ,
1003+ options : { documentId : 'doc-1' } ,
1004+ } ;
1005+
1006+ trackChangesHelpersMock . getTrackChanges . mockReturnValue ( [ ] ) ;
1007+ groupChangesMock . mockReturnValue ( [ ] ) ;
1008+
1009+ const resolvedComment = {
1010+ commentId : 'tc-already-resolved' ,
1011+ trackedChange : true ,
1012+ trackedChangeText : 'Accepted text' ,
1013+ resolvedTime : 999 ,
1014+ resolvedByEmail : 'reviewer@example.com' ,
1015+ resolvedByName : 'Reviewer' ,
1016+ fileId : 'doc-1' ,
1017+ getValues : vi . fn ( ( ) => ( { commentId : 'tc-already-resolved' , fileId : 'doc-1' } ) ) ,
1018+ } ;
1019+ store . commentsList = [ resolvedComment ] ;
1020+
1021+ store . syncTrackedChangeComments ( { superdoc, editor } ) ;
1022+
1023+ expect ( store . commentsList ) . toHaveLength ( 1 ) ;
1024+ expect ( resolvedComment . resolvedTime ) . toBe ( 999 ) ;
1025+ expect ( syncCommentsToClientsMock ) . not . toHaveBeenCalledWith (
1026+ superdoc ,
1027+ expect . objectContaining ( { type : comments_module_events . DELETED } ) ,
1028+ ) ;
1029+ } ) ;
1030+
1031+ it ( 'restores resolution snapshot instead of deleting when pruning a previously-reopened thread' , ( ) => {
1032+ const editorDispatch = vi . fn ( ) ;
1033+ const tr = { setMeta : vi . fn ( ) } ;
1034+ const superdoc = { emit : vi . fn ( ) } ;
1035+ const editor = {
1036+ state : { } ,
1037+ view : { state : { tr } , dispatch : editorDispatch } ,
1038+ options : { documentId : 'doc-1' } ,
1039+ } ;
1040+
1041+ const existingComment = {
1042+ commentId : 'tc-snapshot-restore' ,
1043+ trackedChange : true ,
1044+ trackedChangeText : 'Existing' ,
1045+ resolvedTime : 555 ,
1046+ resolvedByEmail : 'reviewer@example.com' ,
1047+ resolvedByName : 'Reviewer' ,
1048+ fileId : 'doc-1' ,
1049+ getValues : vi . fn ( ( ) => ( { commentId : 'tc-snapshot-restore' } ) ) ,
1050+ } ;
1051+ store . commentsList = [ existingComment ] ;
1052+
1053+ // Step 1: undo — mark reappears, thread reopens (snapshot saved, resolvedTime cleared)
1054+ trackChangesHelpersMock . getTrackChanges . mockReturnValueOnce ( [ { mark : { attrs : { id : 'tc-snapshot-restore' } } } ] ) ;
1055+ groupChangesMock . mockReturnValueOnce ( [ { insertedMark : { mark : { attrs : { id : 'tc-snapshot-restore' } } } } ] ) ;
1056+ store . syncTrackedChangeComments ( { superdoc, editor } ) ;
1057+
1058+ expect ( existingComment . resolvedTime ) . toBeNull ( ) ;
1059+
1060+ // Step 2: redo — mark gone, snapshot should restore resolvedTime instead of deleting
1061+ trackChangesHelpersMock . getTrackChanges . mockReturnValueOnce ( [ ] ) ;
1062+ groupChangesMock . mockReturnValueOnce ( [ ] ) ;
1063+ store . syncTrackedChangeComments ( { superdoc, editor } ) ;
1064+
1065+ expect ( store . commentsList ) . toHaveLength ( 1 ) ;
1066+ expect ( existingComment . resolvedTime ) . toBe ( 555 ) ;
1067+ expect ( existingComment . resolvedByEmail ) . toBe ( 'reviewer@example.com' ) ;
1068+ expect ( existingComment . resolvedByName ) . toBe ( 'Reviewer' ) ;
1069+ } ) ;
1070+
9961071 it ( 'keeps tracked-change comments when importedId is live even if commentId differs' , ( ) => {
9971072 const editorDispatch = vi . fn ( ) ;
9981073 const tr = { setMeta : vi . fn ( ) } ;
0 commit comments