diff --git a/server/collaborator/src/extensions/storage.ts b/server/collaborator/src/extensions/storage.ts
index d4da1cb9be..3a8cf2ba43 100644
--- a/server/collaborator/src/extensions/storage.ts
+++ b/server/collaborator/src/extensions/storage.ts
@@ -14,7 +14,7 @@
 //
 
 import { DocumentId } from '@hcengineering/collaborator-client'
-import { MeasureContext } from '@hcengineering/core'
+import { type Markup, MeasureContext } from '@hcengineering/core'
 import {
   Document,
   Extension,
@@ -37,19 +37,35 @@ export interface StorageConfiguration {
   transformer: Transformer
 }
 
+type DocumentName = string
+
+type ConnectionId = string
+
+interface DocumentUpdates {
+  context: Context
+  collaborators: Set<ConnectionId>
+}
+
 export class StorageExtension implements Extension {
   private readonly configuration: StorageConfiguration
-  private readonly collaborators = new Map<string, Set<string>>()
-  private readonly markups = new Map<string, Record<string, string>>()
+  private readonly updates = new Map<DocumentName, DocumentUpdates>()
+  private readonly markups = new Map<DocumentName, Record<Markup, Markup>>()
 
   constructor (configuration: StorageConfiguration) {
     this.configuration = configuration
   }
 
   async onChange ({ context, documentName }: withContext<onChangePayload>): Promise<any> {
-    const collaborators = this.collaborators.get(documentName) ?? new Set()
-    collaborators.add(context.connectionId)
-    this.collaborators.set(documentName, collaborators)
+    const { connectionId } = context
+
+    const updates = this.updates.get(documentName)
+    if (updates === undefined) {
+      const collaborators = new Set([connectionId])
+      this.updates.set(documentName, { context, collaborators })
+    } else {
+      updates.context = context
+      updates.collaborators.add(connectionId)
+    }
   }
 
   async onLoadDocument ({ context, documentName }: withContext<onLoadDocumentPayload>): Promise<any> {
@@ -59,7 +75,7 @@ export class StorageExtension implements Extension {
     return await this.loadDocument(documentName, context)
   }
 
-  async afterLoadDocument ({ context, documentName, document }: withContext<afterLoadDocumentPayload>): Promise<any> {
+  async afterLoadDocument ({ documentName, document }: withContext<afterLoadDocumentPayload>): Promise<any> {
     // remember the markup for the document
     this.markups.set(documentName, this.configuration.transformer.fromYdoc(document))
   }
@@ -70,14 +86,14 @@ export class StorageExtension implements Extension {
 
     ctx.info('store document', { documentName, connectionId })
 
-    const collaborators = this.collaborators.get(documentName)
-    if (collaborators === undefined || collaborators.size === 0) {
+    const updates = this.updates.get(documentName)
+    if (updates === undefined || updates.collaborators.size === 0) {
       ctx.info('no changes for document', { documentName, connectionId })
       return
     }
 
-    this.collaborators.delete(documentName)
-    await this.storeDocument(documentName, document, context)
+    this.updates.delete(documentName)
+    await this.storeDocument(documentName, document, updates.context)
   }
 
   async onConnect ({ context, documentName, instance }: withContext<onConnectPayload>): Promise<any> {
@@ -93,19 +109,19 @@ export class StorageExtension implements Extension {
     const params = { documentName, connectionId, connections: document.getConnectionsCount() }
     ctx.info('disconnect from document', params)
 
-    const collaborators = this.collaborators.get(documentName)
-    if (collaborators === undefined || !collaborators.has(connectionId)) {
+    const updates = this.updates.get(documentName)
+    if (updates === undefined || updates.collaborators.size === 0) {
       ctx.info('no changes for document', { documentName, connectionId })
       return
     }
 
-    this.collaborators.delete(documentName)
+    this.updates.delete(documentName)
     await this.storeDocument(documentName, document, context)
   }
 
   async afterUnloadDocument ({ documentName }: afterUnloadDocumentPayload): Promise<any> {
     this.configuration.ctx.info('unload document', { documentName })
-    this.collaborators.delete(documentName)
+    this.updates.delete(documentName)
     this.markups.delete(documentName)
   }
 
diff --git a/server/collaborator/src/rpc/methods/updateContent.ts b/server/collaborator/src/rpc/methods/updateContent.ts
index ba4d7fe189..6544a22474 100644
--- a/server/collaborator/src/rpc/methods/updateContent.ts
+++ b/server/collaborator/src/rpc/methods/updateContent.ts
@@ -53,7 +53,7 @@ export async function updateContent (
             fragment.delete(0, fragment.length)
             applyUpdate(document, update)
           })
-        })
+        }, connection)
       })
     })
   } finally {
diff --git a/server/collaborator/src/storage/platform.ts b/server/collaborator/src/storage/platform.ts
index 49ddea1973..bdc417cca5 100644
--- a/server/collaborator/src/storage/platform.ts
+++ b/server/collaborator/src/storage/platform.ts
@@ -34,6 +34,7 @@ import { Doc as YDoc } from 'yjs'
 import { Context } from '../context'
 
 import { CollabStorageAdapter } from './adapter'
+import { areEqualMarkups } from '@hcengineering/text'
 
 export class PlatformStorageAdapter implements CollabStorageAdapter {
   constructor (private readonly storage: StorageAdapter) {}
@@ -156,6 +157,14 @@ export class PlatformStorageAdapter implements CollabStorageAdapter {
   ): Promise<void> {
     const { objectClass, objectId, objectAttr } = parsePlatformDocumentId(platformDocumentId)
 
+    const currMarkup = markup.curr[objectAttr]
+    const prevMarkup = markup.prev[objectAttr]
+
+    if (areEqualMarkups(currMarkup, prevMarkup)) {
+      ctx.info('markup not changed, skip platform update', { documentName })
+      return
+    }
+
     const attribute = client.getHierarchy().findAttribute(objectClass, objectAttr)
     if (attribute === undefined) {
       ctx.warn('attribute not found', { documentName, objectClass, objectAttr })
@@ -192,8 +201,8 @@ export class PlatformStorageAdapter implements CollabStorageAdapter {
         attributeUpdates: {
           attrKey: objectAttr,
           attrClass: core.class.TypeMarkup,
-          prevValue: markup.prev[objectAttr],
-          set: [markup.curr[objectAttr]],
+          prevValue: prevMarkup,
+          set: [currMarkup],
           added: [],
           removed: [],
           isMixin: hierarchy.isMixin(objectClass)