diff --git a/models/recruit/src/migration.ts b/models/recruit/src/migration.ts
index 1217608846..d680ddae1e 100644
--- a/models/recruit/src/migration.ts
+++ b/models/recruit/src/migration.ts
@@ -137,25 +137,9 @@ export const recruitOperation: MigrateOperation = {
 
     // Rename other
     const categories = await client.find(DOMAIN_TAGS, { _class: tags.class.TagCategory })
-    let prefix = 'tags:category:Category'
+    const prefix = 'tags:category:Category'
     for (const c of categories) {
       if (c._id.startsWith(prefix) || c._id === 'tags:category:Other') {
-        let newCID = c._id.replace(prefix, recruit.category.Category + '.') as Ref<TagCategory>
-        if (c._id === 'tags:category:Other') {
-          newCID = recruit.category.Other
-        }
-        await client.delete(DOMAIN_TAGS, c._id)
-        await client.create(DOMAIN_TAGS, { ...c, _id: newCID, targetClass: recruit.mixin.Candidate })
-        await client.update(DOMAIN_TAGS, { _class: tags.class.TagElement, category: c._id }, {
-          category: newCID,
-          targetClass: recruit.mixin.Candidate
-        })
-      }
-    }
-
-    prefix = 'recruit:category:Category'
-    for (const c of categories) {
-      if ((c._id.startsWith(prefix) && !c._id.startsWith(prefix + '.')) || c._id === 'tags:category:Other') {
         let newCID = c._id.replace(prefix, recruit.category.Category + '.') as Ref<TagCategory>
         if (c._id === 'tags:category:Other') {
           newCID = recruit.category.Other
@@ -164,7 +148,7 @@ export const recruitOperation: MigrateOperation = {
         try {
           await client.create(DOMAIN_TAGS, { ...c, _id: newCID, targetClass: recruit.mixin.Candidate })
         } catch (err: any) {
-          // Ignore
+          // ignore
         }
         await client.update(DOMAIN_TAGS, { _class: tags.class.TagElement, category: c._id }, {
           category: newCID,
diff --git a/plugins/activity-assets/lang/en.json b/plugins/activity-assets/lang/en.json
index 25b0ad23b2..b99a8ece81 100644
--- a/plugins/activity-assets/lang/en.json
+++ b/plugins/activity-assets/lang/en.json
@@ -9,6 +9,7 @@
     "Changed": "changed",
     "To": "to",
     "Unset": "unset",
-    "System": "System"
+    "System": "System",
+    "CollectionUpdated": "Update {collection}"
   }
 }
\ No newline at end of file
diff --git a/plugins/activity-assets/lang/ru.json b/plugins/activity-assets/lang/ru.json
index 0b83745d5d..04b7d82ae8 100644
--- a/plugins/activity-assets/lang/ru.json
+++ b/plugins/activity-assets/lang/ru.json
@@ -9,6 +9,7 @@
     "Changed": "изменил(а)",
     "To": "на",
     "Unset": "сбросил",
-    "System": "Система"
+    "System": "Система",
+    "CollectionUpdated": "Обновлена {collection}"
   }
 }
\ No newline at end of file
diff --git a/plugins/activity-resources/src/activity.ts b/plugins/activity-resources/src/activity.ts
index c5c3ea54f6..082dadb62d 100644
--- a/plugins/activity-resources/src/activity.ts
+++ b/plugins/activity-resources/src/activity.ts
@@ -6,7 +6,6 @@ import core, {
   Client,
   Collection,
   Doc,
-  DocumentUpdate,
   Hierarchy,
   Ref,
   SortingOrder,
@@ -32,7 +31,13 @@ export function activityKey (objectClass: Ref<Class<Doc>>, txClass: Ref<Class<Tx
   return objectClass + ':' + txClass
 }
 
-function isEqualOps (op1: DocumentUpdate<Doc>, op2: DocumentUpdate<Doc>): boolean {
+function isEqualOps (op1: any, op2: any): boolean {
+  if (typeof op1 === 'string' && typeof op2 === 'string') {
+    return op1 === op2
+  }
+  if (typeof op1 !== typeof op2) {
+    return false
+  }
   const o1 = Object.keys(op1).sort().join('-')
   const o2 = Object.keys(op2).sort().join('-')
   return o1 === o2
@@ -47,7 +52,7 @@ export interface DisplayTx {
   tx: TxCUD<Doc>
 
   // A set of collapsed transactions.
-  txes: Array<TxCUD<Doc>>
+  txes: DisplayTx[]
 
   // type check for createTx
   createTx?: TxCreateDoc<Doc>
@@ -81,7 +86,7 @@ const combineThreshold = 5 * 60 * 1000
  * Allow to recieve a list of transactions and notify client about it.
  */
 export interface Activity {
-  update: (object: Doc, listener: DisplayTxListener, sort: SortingOrder) => void
+  update: (object: Doc, listener: DisplayTxListener, sort: SortingOrder, editable: Map<Ref<Class<Doc>>, boolean>) => void
 }
 
 class ActivityImpl implements Activity {
@@ -99,8 +104,8 @@ class ActivityImpl implements Activity {
     this.txQuery2 = createQuery()
   }
 
-  private notify (object: Doc, listener: DisplayTxListener, sort: SortingOrder): void {
-    this.combineTransactions(object, this.txes1, this.txes2).then(
+  private notify (object: Doc, listener: DisplayTxListener, sort: SortingOrder, editable: Map<Ref<Class<Doc>>, boolean>): void {
+    this.combineTransactions(object, this.txes1, this.txes2, editable).then(
       (result) => {
         const sorted = result.sort((a, b) => (a.tx.modifiedOn - b.tx.modifiedOn) * sort)
         listener(sorted)
@@ -111,7 +116,7 @@ class ActivityImpl implements Activity {
     )
   }
 
-  update (object: Doc, listener: DisplayTxListener, sort: SortingOrder): void {
+  update (object: Doc, listener: DisplayTxListener, sort: SortingOrder, editable: Map<Ref<Class<Doc>>, boolean>): void {
     let isAttached = false
 
     isAttached = this.client.getHierarchy().isDerived(object._class, core.class.AttachedDoc)
@@ -128,7 +133,7 @@ class ActivityImpl implements Activity {
           },
       (result) => {
         this.txes1 = result
-        this.notify(object, listener, sort)
+        this.notify(object, listener, sort, editable)
       },
       { sort: { modifiedOn: SortingOrder.Descending } }
     )
@@ -141,13 +146,13 @@ class ActivityImpl implements Activity {
       },
       (result) => {
         this.txes2 = result
-        this.notify(object, listener, sort)
+        this.notify(object, listener, sort, editable)
       },
       { sort: { modifiedOn: SortingOrder.Descending } }
     )
   }
 
-  async combineTransactions (object: Doc, txes1: Array<TxCUD<Doc>>, txes2: Array<TxCUD<Doc>>): Promise<DisplayTx[]> {
+  async combineTransactions (object: Doc, txes1: Array<TxCUD<Doc>>, txes2: Array<TxCUD<Doc>>, editable: Map<Ref<Class<Doc>>, boolean>): Promise<DisplayTx[]> {
     const hierarchy = this.client.getHierarchy()
 
     // We need to sort with with natural order, to build a proper doc values.
@@ -163,7 +168,7 @@ class ActivityImpl implements Activity {
       // We do not need collection object updates, in main list of displayed transactions.
       if (this.isDisplayTxRequired(collectionCUD, updateCUD || mixinCUD, ntx, object)) {
         // Combine previous update transaction for same field and if same operation and time treshold is ok
-        results = this.integrateTxWithResults(results, result)
+        results = this.integrateTxWithResults(results, result, editable)
         this.updateRemovedState(result, results)
       }
     }
@@ -248,7 +253,7 @@ class ActivityImpl implements Activity {
           // Ignore
         }
       }
-      collectionCUD = true
+      collectionCUD = (cltx.tx._class === core.class.TxUpdateDoc) || (cltx.tx._class === core.class.TxMixin)
     }
     let firstTx = parents.get(tx.objectId)
     const result: DisplayTx = newDisplayTx(tx, hierarchy)
@@ -295,22 +300,21 @@ class ActivityImpl implements Activity {
     return false
   }
 
-  integrateTxWithResults (results: DisplayTx[], result: DisplayTx): DisplayTx[] {
-    const curUpdate: any =
-      result.tx._class === core.class.TxUpdateDoc
-        ? (result.tx as unknown as TxUpdateDoc<Doc>).operations
-        : (result.tx as unknown as TxMixin<Doc, Doc>).attributes
+  integrateTxWithResults (results: DisplayTx[], result: DisplayTx, editable: Map<Ref<Class<Doc>>, boolean>): DisplayTx[] {
+    const curUpdate: any = getCombineOpFromTx(result)
 
+    if (curUpdate === undefined || (result.doc !== undefined && editable.has(result.doc._class))) {
+      results.push(result)
+      return results
+    }
     const newResult = results.filter((prevTx) => {
-      if (this.isSameKindTx(prevTx, result, result.tx._class)) {
-        const prevUpdate: any =
-          prevTx.tx._class === core.class.TxUpdateDoc
-            ? (prevTx.tx as unknown as TxUpdateDoc<Doc>).operations
-            : (prevTx.tx as unknown as TxMixin<Doc, Doc>).attributes
+      const prevUpdate: any = getCombineOpFromTx(prevTx)
+      // If same tx or same collection
+      if (this.isSameKindTx(prevTx, result, result.tx._class) || (prevUpdate === curUpdate)) {
         if (result.tx.modifiedOn - prevTx.tx.modifiedOn < combineThreshold && isEqualOps(prevUpdate, curUpdate)) {
           // we have same keys,
           // Remember previous transactions
-          result.txes.push(...prevTx.txes, prevTx.tx)
+          result.txes.push(...prevTx.txes, prevTx)
           return false
         }
       }
@@ -331,6 +335,20 @@ class ActivityImpl implements Activity {
   }
 }
 
+function getCombineOpFromTx (result: DisplayTx): any {
+  let curUpdate: any
+  if (result.tx._class === core.class.TxUpdateDoc) {
+    curUpdate = (result.tx as unknown as TxUpdateDoc<Doc>).operations
+  }
+  if (result.tx._class === core.class.TxMixin) {
+    curUpdate = (result.tx as unknown as TxMixin<Doc, Doc>).attributes
+  }
+  if (result.collectionAttribute !== undefined) {
+    curUpdate = result.collectionAttribute.attributeOf + '.' + result.collectionAttribute.name
+  }
+  return curUpdate
+}
+
 export function newDisplayTx (tx: TxCUD<Doc>, hierarchy: Hierarchy): DisplayTx {
   const createTx = hierarchy.isDerived(tx._class, core.class.TxCreateDoc) ? (tx as TxCreateDoc<Doc>) : undefined
   return {
diff --git a/plugins/activity-resources/src/components/Activity.svelte b/plugins/activity-resources/src/components/Activity.svelte
index 58227a2210..ab7ac92ba3 100644
--- a/plugins/activity-resources/src/components/Activity.svelte
+++ b/plugins/activity-resources/src/components/Activity.svelte
@@ -16,7 +16,7 @@
 <script lang="ts">
   import activity, { TxViewlet } from '@anticrm/activity'
   import chunter from '@anticrm/chunter'
-  import { Doc, SortingOrder } from '@anticrm/core'
+  import { Class, Doc, Ref, SortingOrder } from '@anticrm/core'
   import { createQuery, getClient } from '@anticrm/presentation'
   import { Component, Grid, IconActivity, Label, Scroller } from '@anticrm/ui'
   import { ActivityKey, activityKey, DisplayTx, newActivity } from '../activity'
@@ -33,20 +33,25 @@
 
   const activityQuery = newActivity(client, attrs)
 
+  let viewlets: Map<ActivityKey, TxViewlet>
+  let editable: Map<Ref<Class<Doc>>, boolean> = new Map()
+
+  const descriptors = createQuery()
+  $: descriptors.query(activity.class.TxViewlet, {}, (result) => {
+    viewlets = new Map(result.map((r) => [activityKey(r.objectClass, r.txClass), r]))
+  
+    editable = new Map(result.map(it => [it.objectClass, it.editable ?? false]))
+  })
+
   $: activityQuery.update(
     object,
     (result) => {
       txes = result
     },
-    SortingOrder.Descending
+    SortingOrder.Descending,
+    editable
   )
 
-  let viewlets: Map<ActivityKey, TxViewlet>
-
-  const descriptors = createQuery()
-  $: descriptors.query(activity.class.TxViewlet, {}, (result) => {
-    viewlets = new Map(result.map((r) => [activityKey(r.objectClass, r.txClass), r]))
-  })
 </script>
 
 {#if fullSize || transparent}
diff --git a/plugins/activity-resources/src/components/TxView.svelte b/plugins/activity-resources/src/components/TxView.svelte
index 03a6aa850f..9ceff3a6b6 100644
--- a/plugins/activity-resources/src/components/TxView.svelte
+++ b/plugins/activity-resources/src/components/TxView.svelte
@@ -15,15 +15,25 @@
 -->
 <script lang="ts">
   import type { TxViewlet } from '@anticrm/activity'
-  import activity from '../plugin'
   import contact, { EmployeeAccount, formatName } from '@anticrm/contact'
   import core, { AnyAttribute, Doc, getCurrentAccount, Ref } from '@anticrm/core'
   import { Asset, getResource } from '@anticrm/platform'
   import { getClient } from '@anticrm/presentation'
-  import { Component, Icon, IconEdit, IconMoreH, Label, Menu, ShowMore, showPopup, TimeSince } from '@anticrm/ui'
+  import {
+    Component,
+    Icon, IconEdit,
+    IconMoreH,
+    Label,
+    Menu,
+    ShowMore,
+    showPopup,
+    TimeSince
+  } from '@anticrm/ui'
   import type { AttributeModel } from '@anticrm/view'
   import { getActions } from '@anticrm/view-resources'
   import { ActivityKey, DisplayTx } from '../activity'
+  import activity from '../plugin'
+  import TxViewTx from './TxViewTx.svelte'
   import { getValue, TxDisplayViewlet, updateViewlet } from './utils'
 
   export let tx: DisplayTx
@@ -50,12 +60,16 @@
 
   const client = getClient()
 
+  function getProps (props: any, edit: boolean): any {
+    return { ...props, edit }
+  }
+
   $: updateViewlet(client, viewlets, tx).then((result) => {
     if (result.id === tx.tx._id) {
       viewlet = result.viewlet
       model = result.model
       modelIcon = result.modelIcon
-      props = { ...result.props, edit }
+      props = getProps(result.props, edit)
     }
   })
 
@@ -76,7 +90,7 @@
             icon: IconEdit,
             action: () => {
               edit = true
-              props = { ...props, edit }
+              props = getProps(props, edit)
             }
           },
           ...actions.map((a) => ({
@@ -94,7 +108,7 @@
   }
   const onCancelEdit = () => {
     edit = false
-    props = { ...props, edit }
+    props = getProps(props, edit)
   }
   function isMessageType (attr?: AnyAttribute): boolean {
     return attr?.type._class === core.class.TypeMarkup
@@ -147,10 +161,7 @@
                 <Label label={viewlet.label} params={viewlet.labelParams ?? {}} />
               </span>
               {#if viewlet.labelComponent}
-                <Component
-                  is={viewlet.labelComponent}
-                  {props}
-                />
+                <Component is={viewlet.labelComponent} {props} />
               {/if}
             </div>
           {/if}
@@ -160,7 +171,11 @@
                 {#if value === null}
                   <span class="lower"><Label label={activity.string.Unset} /> <Label label={m.label} /></span>
                 {:else}
-                  <span class="lower" class:flex-grow={hasMessageType}><Label label={activity.string.Changed} /> <Label label={m.label} /> <Label label={activity.string.To} /></span>
+                  <span class="lower" class:flex-grow={hasMessageType}
+                    ><Label label={activity.string.Changed} />
+                    <Label label={m.label} />
+                    <Label label={activity.string.To} /></span
+                  >
                   {#if hasMessageType}
                     <div class="time"><TimeSince value={tx.tx.modifiedOn} /></div>
                   {/if}
@@ -180,9 +195,15 @@
             {#each model as m}
               {#await getValue(client, m, tx.mixinTx.attributes) then value}
                 {#if value === null}
-                  <span><Label label={activity.string.Unset} /> <span class="lower"><Label label={m.label} /></span></span>
+                  <span>
+                    <Label label={activity.string.Unset} /> <span class="lower"><Label label={m.label} /></span>
+                  </span>
                 {:else}
-                  <span><Label label={activity.string.Changed} /> <span class="lower"><Label label={m.label} /></span> <Label label={activity.string.To} /></span>
+                  <span>
+                    <Label label={activity.string.Changed} />
+                    <span class="lower"><Label label={m.label} /></span>
+                    <Label label={activity.string.To} />
+                  </span>
                   {#if isMessageType(m.attribute)}
                     <div class="strong message emphasized">
                       <svelte:component this={m.presenter} {value} />
@@ -196,10 +217,18 @@
               {/await}
             {/each}
           {:else if viewlet && viewlet.display === 'inline' && viewlet.component}
-            {#if typeof viewlet.component === 'string'}
-              <Component is={viewlet.component} {props} on:close={onCancelEdit} />
-            {:else}
-              <svelte:component this={viewlet.component} {...props} on:close={onCancelEdit} />
+            {#if tx.collectionAttribute !== undefined && tx.txes.length > 0}
+              <ShowMore ignore={edit}>
+                <div class="flex-row-center flex-grow flex-wrap">
+                  <TxViewTx {tx} {onCancelEdit} {edit} {viewlet}/>
+                </div>
+              </ShowMore>
+            {:else} 
+              {#if typeof viewlet.component === 'string'}
+                <Component is={viewlet.component} {props} on:close={onCancelEdit} />
+              {:else}
+                <svelte:component this={viewlet.component} {...props} on:close={onCancelEdit} />
+              {/if}
             {/if}
           {/if}
         </div>
@@ -211,10 +240,16 @@
       {#if viewlet && viewlet.component && viewlet.display !== 'inline'}
         <div class={viewlet.display}>
           <ShowMore ignore={viewlet.display !== 'content' || edit}>
-            {#if typeof viewlet.component === 'string'}
-              <Component is={viewlet.component} {props} on:close={onCancelEdit} />
-            {:else}
-              <svelte:component this={viewlet.component} {...props} on:close={onCancelEdit} />
+            {#if tx.collectionAttribute !== undefined && tx.txes.length > 0}
+              <div class="flex-row-center flex-grow flex-wrap">
+                <TxViewTx {tx} {onCancelEdit} {edit} {viewlet}/>
+              </div>
+            {:else} 
+              {#if typeof viewlet.component === 'string'}
+                <Component is={viewlet.component} {props} on:close={onCancelEdit} />
+              {:else}
+                <svelte:component this={viewlet.component} {...props} on:close={onCancelEdit} />
+              {/if}
             {/if}
           </ShowMore>
         </div>
diff --git a/plugins/activity-resources/src/components/TxViewTx.svelte b/plugins/activity-resources/src/components/TxViewTx.svelte
new file mode 100644
index 0000000000..ecf4cc5b47
--- /dev/null
+++ b/plugins/activity-resources/src/components/TxViewTx.svelte
@@ -0,0 +1,43 @@
+<script lang="ts">
+  import core, { Class, Doc, Ref } from '@anticrm/core'
+  import { Component, IconAdd, IconDelete } from '@anticrm/ui'
+  import { DisplayTx } from '../activity'
+  import { getDTxProps, TxDisplayViewlet } from './utils'
+
+  export let tx: DisplayTx
+  export let viewlet: TxDisplayViewlet
+  export let edit: boolean
+  export let onCancelEdit: () => void
+
+  function filterTx (dtx: DisplayTx[], _class: Ref<Class<Doc>>): DisplayTx[] {
+    return dtx.filter((it) => it.tx._class === _class)
+  }
+  function getProps (props: any, edit: boolean): any {
+    return { ...props, edit }
+  }
+</script>
+
+{#each filterTx([tx, ...tx.txes], core.class.TxCreateDoc) as ctx, i}
+  {#if i === 0}
+    <div class='mr-2'>
+      <IconAdd size={'small'} />
+    </div>
+  {/if}
+  {#if typeof viewlet?.component === 'string'}
+    <Component is={viewlet?.component} props={getProps(getDTxProps(ctx), edit)} on:close={onCancelEdit} />
+  {:else}
+    <svelte:component this={viewlet?.component} {...getProps(getDTxProps(ctx), edit)} on:close={onCancelEdit} />
+  {/if}
+{/each}
+{#each filterTx([tx, ...tx.txes], core.class.TxRemoveDoc) as ctx, i}
+  {#if i === 0}
+    <div class='mr-2'>
+      <IconDelete size={'small'} />
+    </div>
+  {/if}
+  {#if typeof viewlet?.component === 'string'}
+    <Component is={viewlet?.component} props={getProps(getDTxProps(ctx), edit)} on:close={onCancelEdit} />
+  {:else}
+    <svelte:component this={viewlet?.component} {...getProps(getDTxProps(ctx), edit)} on:close={onCancelEdit} />
+  {/if}
+{/each}
diff --git a/plugins/activity-resources/src/components/utils.ts b/plugins/activity-resources/src/components/utils.ts
index 14ffb9d816..4f00425a87 100644
--- a/plugins/activity-resources/src/components/utils.ts
+++ b/plugins/activity-resources/src/components/utils.ts
@@ -39,12 +39,16 @@ async function createPseudoViewlet (
       display: 'inline',
       icon: docClass.icon ?? activity.icon.Activity,
       label: label,
-      labelParams: { _class: trLabel },
+      labelParams: { _class: trLabel, collection: dtx.collectionAttribute?.label !== undefined ? await translate(dtx.collectionAttribute?.label, {}) : '' },
       component: presenter.presenter
     }
   }
 }
 
+export function getDTxProps (dtx: DisplayTx): any {
+  return { tx: dtx.tx, value: dtx.doc, dtx }
+}
+
 export async function updateViewlet (
   client: TxOperations,
   viewlets: Map<ActivityKey, TxViewlet>,
@@ -59,7 +63,7 @@ export async function updateViewlet (
   const key = activityKey(dtx.tx.objectClass, dtx.tx._class)
   let viewlet: TxDisplayViewlet = viewlets.get(key)
 
-  const props = { tx: dtx.tx, value: dtx.doc, dtx }
+  const props = getDTxProps(dtx)
   let model: AttributeModel[] = []
   let modelIcon: Asset | undefined
 
@@ -84,14 +88,15 @@ async function checkInlineViewlets (
   client: TxOperations,
   model: AttributeModel[]
 ): Promise<{ viewlet: TxDisplayViewlet, model: AttributeModel[] }> {
-  if (dtx.tx._class === core.class.TxCreateDoc) {
+  if (dtx.collectionAttribute !== undefined && dtx.txes.length > 0) {
+    // Check if we have a class presenter we could have a pseudo viewlet based on class presenter.
+    viewlet = await createPseudoViewlet(client, dtx, activity.string.CollectionUpdated)
+  } else if (dtx.tx._class === core.class.TxCreateDoc) {
     // Check if we have a class presenter we could have a pseudo viewlet based on class presenter.
     viewlet = await createPseudoViewlet(client, dtx, activity.string.DocCreated)
-  }
-  if (dtx.tx._class === core.class.TxRemoveDoc) {
+  } else if (dtx.tx._class === core.class.TxRemoveDoc) {
     viewlet = await createPseudoViewlet(client, dtx, activity.string.DocDeleted)
-  }
-  if (dtx.tx._class === core.class.TxUpdateDoc) {
+  } else if (dtx.tx._class === core.class.TxUpdateDoc) {
     model = await createUpdateModel(dtx, client, model)
   }
   return { viewlet, model }
diff --git a/plugins/activity-resources/src/plugin.ts b/plugins/activity-resources/src/plugin.ts
index 05d42ffcd5..fd4508585f 100644
--- a/plugins/activity-resources/src/plugin.ts
+++ b/plugins/activity-resources/src/plugin.ts
@@ -21,6 +21,7 @@ export default mergeIds(activityId, activity, {
   string: {
     DocCreated: '' as IntlString,
     DocDeleted: '' as IntlString,
+    CollectionUpdated: '' as IntlString,
     Changed: '' as IntlString,
     To: '' as IntlString,
     Unset: '' as IntlString,