From 32b76c8da4a0e9d269b841d54bb51d948bb22a9d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Blin?=
 <sebastien.blin@savoirfairelinux.com>
Date: Thu, 6 Jul 2023 11:31:20 -0400
Subject: [PATCH] messagelist: use history given from daemon (except SIP
 accounts)

With Jami-Daemon >= 14.0.0, the client doesn't need to construct
itself the history. This part is now handled by the daemon.
This patch uses the new API:
+ loadConversationMessages->loadConversation
+ SwarmMessageReceived/SwarmMessageUpdated/ReactionAdded/ReactionRemoved
+ remove MessageReceived
+ ConversationLoaded->SwarmLoaded

+ No need to use loadConversationUntil, the daemon will load whatever
the client needs.
+ No need to clear cache, just reset the body and emit data changes

Everything should work like before (even re-translation & changing
preview preference)

Change-Id: Iaf1fa3e84e8e157ae2d0bec210977f9a34415ebc
---
 daemon                                        |   2 +-
 src/app/appsettingsmanager.cpp                |   7 +
 src/app/appsettingsmanager.h                  |   2 +
 .../commoncomponents/EmojiReactionPopup.qml   |   8 +-
 src/app/commoncomponents/EmojiReactions.qml   |   4 +-
 src/app/commoncomponents/ReplyToRow.qml       |  10 -
 src/app/commoncomponents/ShowMoreMenu.qml     |   9 +-
 src/app/conversationlistmodelbase.cpp         |  23 +-
 src/app/lrcinstance.h                         |   2 +-
 src/app/mainview/ConversationView.qml         |  14 -
 src/app/mainview/components/ChatView.qml      |   3 +-
 .../mainview/components/MessageListView.qml   |  23 +-
 .../components/SmartListItemDelegate.qml      |   7 -
 src/app/messagesadapter.cpp                   |  54 +-
 src/app/messagesadapter.h                     |   2 -
 src/app/utilsadapter.cpp                      |  13 +-
 src/app/utilsadapter.h                        |   1 -
 src/libclient/accountmodel.cpp                |   8 +
 src/libclient/api/accountmodel.h              |   1 +
 src/libclient/api/conversation.h              |  18 +-
 src/libclient/api/conversationmodel.h         |  13 +-
 src/libclient/api/interaction.h               | 110 +++-
 src/libclient/authority/storagehelper.cpp     | 108 +---
 src/libclient/authority/storagehelper.h       |  25 -
 src/libclient/callbackshandler.cpp            |  60 +-
 src/libclient/callbackshandler.h              |  44 +-
 src/libclient/callmodel.cpp                   |   2 +-
 src/libclient/conversationmodel.cpp           | 571 +++++++-----------
 src/libclient/dbus/metatypes.h                |  35 ++
 src/libclient/messagelistmodel.cpp            | 296 +++------
 src/libclient/messagelistmodel.h              |  23 +-
 .../qtwrapper/configurationmanager_wrap.h     | 120 +++-
 src/libclient/typedefs.h                      |  11 +
 tests/qml/src/tst_MessageOptions.qml          |   4 +-
 34 files changed, 752 insertions(+), 881 deletions(-)

diff --git a/daemon b/daemon
index 317b7317d..8468f1592 160000
--- a/daemon
+++ b/daemon
@@ -1 +1 @@
-Subproject commit 317b7317dcda4afb733ddb9bd5b450d4635941ae
+Subproject commit 8468f15927ec7c83a5fca671bac1a4112883b8c9
diff --git a/src/app/appsettingsmanager.cpp b/src/app/appsettingsmanager.cpp
index be47216d8..9ad672c55 100644
--- a/src/app/appsettingsmanager.cpp
+++ b/src/app/appsettingsmanager.cpp
@@ -137,4 +137,11 @@ AppSettingsManager::loadTranslations()
     }
 
     Q_EMIT retranslate();
+    loadHistory();
+}
+
+void
+AppSettingsManager::loadHistory()
+{
+    Q_EMIT reloadHistory();
 }
diff --git a/src/app/appsettingsmanager.h b/src/app/appsettingsmanager.h
index a12709eac..eaada016d 100644
--- a/src/app/appsettingsmanager.h
+++ b/src/app/appsettingsmanager.h
@@ -140,9 +140,11 @@ public:
     QString getLanguage();
 
     void loadTranslations();
+    void loadHistory();
 
 Q_SIGNALS:
     void retranslate();
+    void reloadHistory();
 
 private:
     QSettings* settings_;
diff --git a/src/app/commoncomponents/EmojiReactionPopup.qml b/src/app/commoncomponents/EmojiReactionPopup.qml
index 2358870a2..c29084686 100644
--- a/src/app/commoncomponents/EmojiReactionPopup.qml
+++ b/src/app/commoncomponents/EmojiReactionPopup.qml
@@ -131,7 +131,7 @@ Popup {
                         Repeater {
                             model: emojiArray.length < 15 ? emojiArray.length : 15
                             delegate: Text {
-                                text: emojiArray[index]
+                                text: emojiArray[index].body
                                 horizontalAlignment: Text.AlignRight
                                 font.pointSize: JamiTheme.emojiPopupFontsize
                             }
@@ -147,7 +147,7 @@ Popup {
                             delegate: Button {
                                 id: emojiButton
 
-                                text: emojiArray[index]
+                                text: emojiArray[index].body
                                 font.pointSize: JamiTheme.emojiPopupFontsize
                                 background.visible: false
                                 padding: 0
@@ -155,13 +155,13 @@ Popup {
                                 Text {
                                     visible: emojiButton.hovered
                                     anchors.centerIn: parent
-                                    text: emojiArray[index]
+                                    text: emojiArray[index].body
                                     font.pointSize: JamiTheme.emojiPopupFontsizeBig
                                     z: 1
                                 }
 
                                 onClicked: {
-                                    MessagesAdapter.removeEmojiReaction(CurrentConversation.id, emojiButton.text, msgId);
+                                    MessagesAdapter.removeEmojiReaction(CurrentConversation.id, emojiButton.text, emojiArray[index].commitId);
                                     if (emojiArray.length === 1)
                                         close();
                                 }
diff --git a/src/app/commoncomponents/EmojiReactions.qml b/src/app/commoncomponents/EmojiReactions.qml
index 64b61c676..36d87c51b 100644
--- a/src/app/commoncomponents/EmojiReactions.qml
+++ b/src/app/commoncomponents/EmojiReactions.qml
@@ -43,7 +43,7 @@ Item {
         for (const reaction of Object.entries(reactions)) {
             var authorEmojiList = reaction[1];
             for (var emojiIndex in authorEmojiList) {
-                var emoji = authorEmojiList[emojiIndex];
+                var emoji = authorEmojiList[emojiIndex].body;
                 if (emojiList.includes(emoji)) {
                     var findIndex = emojiList.indexOf(emoji);
                     if (findIndex != -1)
@@ -75,7 +75,7 @@ Item {
             var authorEmojiList = reaction[1];
             if (CurrentAccount.uri === authorUri) {
                 for (var emojiIndex in authorEmojiList) {
-                    list[index] = authorEmojiList[emojiIndex];
+                    list[index] = authorEmojiList[emojiIndex].body;
                     index++;
                 }
                 return list;
diff --git a/src/app/commoncomponents/ReplyToRow.qml b/src/app/commoncomponents/ReplyToRow.qml
index 0fcff7cfd..91527e610 100644
--- a/src/app/commoncomponents/ReplyToRow.qml
+++ b/src/app/commoncomponents/ReplyToRow.qml
@@ -31,16 +31,6 @@ Item {
     property int requestId: -1
     property var replyTransferName: MessagesAdapter.dataForInteraction(ReplyTo, MessageList.TransferName)
 
-    Component.onCompleted: {
-        // Make sure we show the original post
-        // In the future, we may just want to load the previous interaction of the thread
-        // and not show it, but for now we can simplify.
-        if (ReplyTo !== "") {
-            // Store the request Id for later filtering.
-            requestId = MessagesAdapter.loadConversationUntil(ReplyTo);
-        }
-    }
-
     Connections {
         target: MessagesAdapter
 
diff --git a/src/app/commoncomponents/ShowMoreMenu.qml b/src/app/commoncomponents/ShowMoreMenu.qml
index c3c2ec0d6..2f77d1857 100644
--- a/src/app/commoncomponents/ShowMoreMenu.qml
+++ b/src/app/commoncomponents/ShowMoreMenu.qml
@@ -110,19 +110,20 @@ BaseContextMenu {
     onClosed: if (emojiPicker)
         emojiPicker.closeEmojiPicker()
 
-    function getModel() {
+    function getQuickEmojiListModel() {
         const defaultModel = ["👍", "👎", "😂"];
         const reactedEmojis = Array.isArray(emojiReplied) ? emojiReplied.slice(0, defaultModel.length) : [];
         const uniqueEmojis = Array.from(new Set(reactedEmojis));
         const missingEmojis = defaultModel.filter(emoji => !uniqueEmojis.includes(emoji));
-        return uniqueEmojis.concat(missingEmojis);
+        const result = uniqueEmojis.concat(missingEmojis);
+        return result;
     }
 
     property list<MenuItem> menuItems: [
         GeneralMenuItemList {
-            id: audioMessage
+            id: emojiQuickReactions
 
-            modelList: getModel()
+            modelList: getQuickEmojiListModel()
             canTrigger: true
             iconSource: JamiResources.add_reaction_svg
             itemName: JamiStrings.copy
diff --git a/src/app/conversationlistmodelbase.cpp b/src/app/conversationlistmodelbase.cpp
index 3fc5ce470..250e72464 100644
--- a/src/app/conversationlistmodelbase.cpp
+++ b/src/app/conversationlistmodelbase.cpp
@@ -109,19 +109,32 @@ ConversationListModelBase::dataForItem(item_t item, int role) const
         return QVariant(item.unreadMessages);
     case Role::LastInteractionTimeStamp: {
         if (!item.interactions->empty()) {
-            auto ts = static_cast<qint32>(item.interactions->at(item.lastMessageUid).timestamp);
+            auto ts = static_cast<qint32>(item.interactions->rbegin()->second.timestamp);
             return QVariant(ts);
         }
         break;
     }
     case Role::LastInteraction: {
         if (!item.interactions->empty()) {
-            auto interaction = item.interactions->at(item.lastMessageUid);
-            auto body_ = interaction.body;
+            auto interaction = item.interactions->rbegin()->second;
+            auto& accInfo = lrcInstance_->getCurrentAccountInfo();
             if (interaction.type == interaction::Type::DATA_TRANSFER) {
-                body_ = interaction.commit.value("displayName");
+                return QVariant(interaction.commit.value("displayName"));
+            } else if (interaction.type == lrc::api::interaction::Type::CALL) {
+                return QVariant(interaction::getCallInteractionString(interaction.authorUri
+                                                                          == accInfo.profileInfo.uri,
+                                                                      interaction));
+            } else if (interaction.type == lrc::api::interaction::Type::CONTACT) {
+                auto bestName = interaction.authorUri == accInfo.profileInfo.uri
+                                    ? accInfo.accountModel->bestNameForAccount(accInfo.id)
+                                    : accInfo.contactModel->bestNameForContact(
+                                        interaction.authorUri);
+                return QVariant(
+                    interaction::getContactInteractionString(bestName,
+                                                             interaction::to_action(
+                                                                 interaction.commit["action"])));
             }
-            return QVariant(body_);
+            return QVariant(interaction.body);
         }
         break;
     }
diff --git a/src/app/lrcinstance.h b/src/app/lrcinstance.h
index 502165c3f..7656f970c 100644
--- a/src/app/lrcinstance.h
+++ b/src/app/lrcinstance.h
@@ -158,7 +158,7 @@ private:
     MapStringString contentDrafts_;
     MapStringString lastConferences_;
 
-    conversation::Info invalid {};
+    conversation::Info invalid {"", nullptr};
 
     bool debugMode_ {false};
     bool muteDaemon_ {true};
diff --git a/src/app/mainview/ConversationView.qml b/src/app/mainview/ConversationView.qml
index 3bc72cd0d..2ab9c41b9 100644
--- a/src/app/mainview/ConversationView.qml
+++ b/src/app/mainview/ConversationView.qml
@@ -35,14 +35,6 @@ ListSelectionView {
     visible: false
     onPresented: visible = true
 
-    Connections {
-        target: CurrentConversation
-        function onReloadInteractions() {
-            UtilsAdapter.clearInteractionsCache(CurrentAccount.id, CurrentConversation.id);
-            MessagesAdapter.loadMoreMessages();
-        }
-    }
-
     onDismissed: {
         callStackView.needToCloseInCallConversationAndPotentialWindow();
         LRCInstance.deselectConversation();
@@ -51,12 +43,6 @@ ListSelectionView {
     property string currentAccountId: CurrentAccount.id
     onCurrentAccountIdChanged: dismiss()
 
-    onVisibleChanged: {
-        if (visible)
-            return;
-        UtilsAdapter.clearInteractionsCache(CurrentAccount.id, CurrentConversation.id);
-    }
-
     color: JamiTheme.transparentColor
 
     leftPaneItem: viewCoordinator.getView("SidePanel")
diff --git a/src/app/mainview/components/ChatView.qml b/src/app/mainview/components/ChatView.qml
index 96ba2492b..dc582eb0e 100644
--- a/src/app/mainview/components/ChatView.qml
+++ b/src/app/mainview/components/ChatView.qml
@@ -328,9 +328,8 @@ Rectangle {
                     }
 
                     onHeightChanged: {
-                        if (loader.item != null) {
+                        if (loader.item)
                             Qt.callLater(loader.item.scrollToBottom);
-                        }
                     }
 
                     Layout.alignment: Qt.AlignHCenter
diff --git a/src/app/mainview/components/MessageListView.qml b/src/app/mainview/components/MessageListView.qml
index 6ab323677..3e6660fe8 100644
--- a/src/app/mainview/components/MessageListView.qml
+++ b/src/app/mainview/components/MessageListView.qml
@@ -36,8 +36,10 @@ JamiListView {
     }
 
     function loadMoreMsgsIfNeeded() {
-        if (atYBeginning && !CurrentConversation.allMessagesLoaded)
+        if (atYBeginning && !CurrentConversation.allMessagesLoaded) {
+            print("load more messages", atYBeginning, CurrentConversation.allMessagesLoaded)
             MessagesAdapter.loadMoreMessages()
+        }
     }
 
     function computeTimestampVisibility(item1, item1Index, item2, item2Index) {
@@ -252,6 +254,19 @@ JamiListView {
 
     onAtYBeginningChanged: loadMoreMsgsIfNeeded()
 
+    Timer {
+        id: chunkLoadDebounceTimer
+
+        interval: 100
+        repeat: false
+        running: false
+        onTriggered: {
+            if (root.contentHeight < root.height) {
+                root.loadMoreMsgsIfNeeded();
+            }
+        }
+    }
+
     Connections {
         target: MessagesAdapter
 
@@ -263,9 +278,9 @@ JamiListView {
         }
 
         function onMoreMessagesLoaded(loadingRequestId) {
-            if (root.contentHeight < root.height || root.atYBeginning) {
-                root.loadMoreMsgsIfNeeded()
-            }
+            // This needs to be throttled, otherwise we will continue to load more messages
+            // prior to the loaded chunk being rendered and changing the contentHeight.
+            chunkLoadDebounceTimer.restart();
         }
 
         function onFileCopied(dest) {
diff --git a/src/app/mainview/components/SmartListItemDelegate.qml b/src/app/mainview/components/SmartListItemDelegate.qml
index 6d21ceceb..edbf26c5b 100644
--- a/src/app/mainview/components/SmartListItemDelegate.qml
+++ b/src/app/mainview/components/SmartListItemDelegate.qml
@@ -46,13 +46,6 @@ ItemDelegate {
 
     property string lastInteractionFormattedDate: MessagesAdapter.getBestFormattedDate(lastInteractionDate)
 
-    Connections {
-        target: UtilsAdapter
-        function onChangeLanguage() {
-            UtilsAdapter.clearInteractionsCache(root.accountId, root.convId)
-        }
-    }
-
     property bool showSharePositionIndicator: PositionManager.isPositionSharedToConv(accountId, UID)
     property bool showSharedPositionIndicator: PositionManager.isConvSharingPosition(accountId, UID)
 
diff --git a/src/app/messagesadapter.cpp b/src/app/messagesadapter.cpp
index 4f2760e50..f39f0f127 100644
--- a/src/app/messagesadapter.cpp
+++ b/src/app/messagesadapter.cpp
@@ -51,13 +51,18 @@ MessagesAdapter::MessagesAdapter(AppSettingsManager* settingsManager,
     , settingsManager_(settingsManager)
     , messageParser_(new MessageParser(previewEngine, this))
     , filteredMsgListModel_(new FilteredMsgListModel(this))
-    , mediaInteractions_(std::make_unique<MessageListModel>())
+    , mediaInteractions_(std::make_unique<MessageListModel>(nullptr))
     , timestampTimer_(new QTimer(this))
 {
     setObjectName(typeid(*this).name());
 
     set_messageListModel(QVariant::fromValue(filteredMsgListModel_));
 
+    connect(settingsManager_,
+            &AppSettingsManager::reloadHistory,
+            &lrcInstance_->accountModel(),
+            &AccountModel::reloadHistory);
+
     connect(lrcInstance_, &LRCInstance::selectedConvUidChanged, this, [this]() {
         set_replyToId("");
         set_editId("");
@@ -68,7 +73,7 @@ MessagesAdapter::MessagesAdapter(AppSettingsManager* settingsManager,
         filteredMsgListModel_->setSourceModel(conversation.interactions.get());
 
         set_currentConvComposingList(conversationTypersUrlToName(conversation.typers));
-        mediaInteractions_.reset(new MessageListModel(this));
+        mediaInteractions_.reset(new MessageListModel(&lrcInstance_->getCurrentAccountInfo(), this));
         set_mediaMessageListModel(QVariant::fromValue(mediaInteractions_.get()));
     });
 
@@ -98,37 +103,14 @@ MessagesAdapter::loadMoreMessages()
     auto convId = lrcInstance_->get_selectedConvUid();
     try {
         const auto& convInfo = lrcInstance_->getConversationFromConvUid(convId, accountId);
-        if (convInfo.isSwarm()) {
-            auto* convModel = lrcInstance_->getCurrentConversationModel();
-            convModel->loadConversationMessages(convId, loadChunkSize_);
-        }
+        if (convInfo.isSwarm())
+            lrcInstance_->getCurrentConversationModel()->loadConversationMessages(convId,
+                                                                                  loadChunkSize_);
     } catch (const std::exception& e) {
         qWarning() << e.what();
     }
 }
 
-int
-MessagesAdapter::loadConversationUntil(const QString& to)
-{
-    try {
-        if (auto* model = getMsgListSourceModel()) {
-            auto idx = model->indexOfMessage(to);
-            if (idx == -1) {
-                auto accountId = lrcInstance_->get_currentAccountId();
-                auto convId = lrcInstance_->get_selectedConvUid();
-                const auto& convInfo = lrcInstance_->getConversationFromConvUid(convId, accountId);
-                if (convInfo.isSwarm()) {
-                    auto* convModel = lrcInstance_->getCurrentConversationModel();
-                    return convModel->loadConversationUntil(convId, to);
-                }
-            }
-        }
-    } catch (const std::exception& e) {
-        qWarning() << e.what();
-    }
-    return 0;
-}
-
 void
 MessagesAdapter::connectConversationModel()
 {
@@ -200,11 +182,8 @@ MessagesAdapter::removeEmojiReaction(const QString& convId,
                                      const QString& messageId)
 {
     try {
-        const auto authorUri = lrcInstance_->getCurrentAccountInfo().profileInfo.uri;
         // check if this emoji has already been added by this author
-        auto emojiId = lrcInstance_->getConversationFromConvUid(convId)
-                           .interactions->findEmojiReaction(emoji, authorUri, messageId);
-        editMessage(convId, "", emojiId);
+        editMessage(convId, "", messageId);
     } catch (...) {
         qDebug() << "Exception during removeEmojiReaction():" << messageId;
     }
@@ -267,13 +246,6 @@ MessagesAdapter::copyToDownloads(const QString& interactionId, const QString& di
     }
 }
 
-void
-MessagesAdapter::deleteInteraction(const QString& interactionId)
-{
-    lrcInstance_->getCurrentConversationModel()
-        ->clearInteractionFromConversation(lrcInstance_->get_selectedConvUid(), interactionId);
-}
-
 void
 MessagesAdapter::openUrl(const QString& url)
 {
@@ -754,13 +726,13 @@ MessagesAdapter::getFormattedDay(const quint64 timestamp)
 void
 MessagesAdapter::startSearch(const QString& text, bool isMedia)
 {
-    mediaInteractions_.reset(new MessageListModel(this));
+    auto accountId = lrcInstance_->get_currentAccountId();
+    mediaInteractions_.reset(new MessageListModel(&lrcInstance_->getCurrentAccountInfo(), this));
     set_mediaMessageListModel(QVariant::fromValue(mediaInteractions_.get()));
 
     if (text.isEmpty() && !isMedia)
         return;
 
-    auto accountId = lrcInstance_->get_currentAccountId();
     auto convId = lrcInstance_->get_selectedConvUid();
 
     try {
diff --git a/src/app/messagesadapter.h b/src/app/messagesadapter.h
index db3916ed1..0ae1bdd48 100644
--- a/src/app/messagesadapter.h
+++ b/src/app/messagesadapter.h
@@ -83,7 +83,6 @@ Q_SIGNALS:
 protected:
     Q_INVOKABLE bool isDocument(const interaction::Type& type);
     Q_INVOKABLE void loadMoreMessages();
-    Q_INVOKABLE qint32 loadConversationUntil(const QString& to);
     Q_INVOKABLE void connectConversationModel();
     Q_INVOKABLE void sendConversationRequest();
     Q_INVOKABLE void removeConversation(const QString& convUid);
@@ -112,7 +111,6 @@ protected:
     Q_INVOKABLE void openUrl(const QString& url);
     Q_INVOKABLE void openDirectory(const QString& arg);
     Q_INVOKABLE void removeFile(const QString& interactionId, const QString& path);
-    Q_INVOKABLE void deleteInteraction(const QString& interactionId);
     Q_INVOKABLE void joinCall(const QString& uri,
                               const QString& deviceId,
                               const QString& confId,
diff --git a/src/app/utilsadapter.cpp b/src/app/utilsadapter.cpp
index 3b5d4f4a6..241f425b5 100644
--- a/src/app/utilsadapter.cpp
+++ b/src/app/utilsadapter.cpp
@@ -86,6 +86,8 @@ UtilsAdapter::setAppValue(const Settings::Key key, const QVariant& value)
         set_isRTL(isRTL());
     } else if (key == Settings::Key::BaseZoom)
         Q_EMIT changeFontSize();
+    else if (key == Settings::Key::DisplayHyperlinkPreviews)
+        settingsManager_->loadHistory();
     else if (key == Settings::Key::EnableExperimentalSwarm)
         Q_EMIT showExperimentalCallSwarm();
     else if (key == Settings::Key::ShowChatviewHorizontally)
@@ -517,17 +519,6 @@ UtilsAdapter::monitor(const bool& continuous)
     lrcInstance_->monitor(continuous);
 }
 
-void
-UtilsAdapter::clearInteractionsCache(const QString& accountId, const QString& convId)
-{
-    try {
-        auto& accInfo = lrcInstance_->accountModel().getAccountInfo(accountId);
-        auto& convModel = accInfo.conversationModel;
-        convModel->clearInteractionsCache(convId);
-    } catch (...) {
-    }
-}
-
 QVariantMap
 UtilsAdapter::supportedLang()
 {
diff --git a/src/app/utilsadapter.h b/src/app/utilsadapter.h
index 7bafca689..14adcd7e5 100644
--- a/src/app/utilsadapter.h
+++ b/src/app/utilsadapter.h
@@ -128,7 +128,6 @@ public:
     Q_INVOKABLE void setDownloadPath(QString dir);
     Q_INVOKABLE void setScreenshotPath(QString dir);
     Q_INVOKABLE void monitor(const bool& continuous);
-    Q_INVOKABLE void clearInteractionsCache(const QString& accountId, const QString& convUid);
     Q_INVOKABLE QVariantMap supportedLang();
     Q_INVOKABLE QString tempCreationImage(const QString& imageId = "temp") const;
     Q_INVOKABLE void setTempCreationImageFromString(const QString& image = "",
diff --git a/src/libclient/accountmodel.cpp b/src/libclient/accountmodel.cpp
index d89aa6ee6..9d7c43699 100644
--- a/src/libclient/accountmodel.cpp
+++ b/src/libclient/accountmodel.cpp
@@ -1205,6 +1205,14 @@ AccountModel::notificationsCount() const
     return total;
 }
 
+void
+AccountModel::reloadHistory()
+{
+    for (const auto& [_id, account] : pimpl_->accounts) {
+        account.first.conversationModel->reloadHistory();
+    }
+}
+
 QString
 AccountModel::avatar(const QString& accountId) const
 {
diff --git a/src/libclient/api/accountmodel.h b/src/libclient/api/accountmodel.h
index 62f4c5453..b414bf64f 100644
--- a/src/libclient/api/accountmodel.h
+++ b/src/libclient/api/accountmodel.h
@@ -252,6 +252,7 @@ public:
      * Get notifications count across accounts
      */
     int notificationsCount() const;
+    void reloadHistory();
     /**
      * Retrieve account's avatar
      */
diff --git a/src/libclient/api/conversation.h b/src/libclient/api/conversation.h
index 7b856fd24..580cf9d54 100644
--- a/src/libclient/api/conversation.h
+++ b/src/libclient/api/conversation.h
@@ -59,17 +59,26 @@ to_mode(const int intMode)
 
 struct Info
 {
-    Info()
-        : interactions(std::make_unique<MessageListModel>(nullptr))
-    {}
+    explicit Info(const QString& uid, const account::Info* acc)
+        : uid(uid)
+        , interactions(std::make_unique<MessageListModel>(acc, nullptr))
+    {
+        account = acc;
+        if (acc) {
+            accountId = acc->id;
+            accountUri = acc->profileInfo.uri;
+        }
+    }
     Info(const Info& other) = delete;
     Info(Info&& other) = default;
     Info& operator=(const Info& other) = delete;
     Info& operator=(Info&& other) = default;
 
     bool allMessagesLoaded = false;
-    QString uid = "";
+    QString uid;
     QString accountId;
+    const account::Info* account {nullptr};
+    QString accountUri;
     QVector<member::Member> participants;
     VectorMapStringString activeCalls;
     VectorMapStringString ignoredActiveCalls;
@@ -77,7 +86,6 @@ struct Info
     QString callId;
     QString confId;
     std::unique_ptr<MessageListModel> interactions;
-    QString lastMessageUid;
     QString lastSelfMessageId;
     QHash<QString, QString> parentsId; // pair messageid/parentid for messages without parent loaded
     unsigned int unreadMessages = 0;
diff --git a/src/libclient/api/conversationmodel.h b/src/libclient/api/conversationmodel.h
index dcfeb31dd..7fc797e71 100644
--- a/src/libclient/api/conversationmodel.h
+++ b/src/libclient/api/conversationmodel.h
@@ -268,17 +268,6 @@ public:
      * clear all history
      */
     void clearAllHistory();
-    /**
-     * Clear one interaction from the history
-     * @param convId
-     * @param interactionId
-     */
-    void clearInteractionFromConversation(const QString& convId, const QString& interactionId);
-    /**
-     * Clear the cache for interactions in the conversation
-     * @param convId
-     */
-    void clearInteractionsCache(const QString& convId);
     /**
      * @param convId
      * @param interactionId
@@ -335,7 +324,6 @@ public:
      * @return id for loading request. -1 if not loaded
      */
     int loadConversationMessages(const QString& conversationId, const int size = 1);
-    int loadConversationUntil(const QString& conversationId, const QString& to);
     /**
      * accept request for conversation
      * @param conversationId conversation's id
@@ -413,6 +401,7 @@ public:
      * @return number of conversations requests + unread
      */
     int notificationsCount() const;
+    void reloadHistory() const;
     const VectorString peersForConversation(const QString& conversationId);
 
     // Presentation
diff --git a/src/libclient/api/interaction.h b/src/libclient/api/interaction.h
index be83d4780..61dc57d08 100644
--- a/src/libclient/api/interaction.h
+++ b/src/libclient/api/interaction.h
@@ -269,9 +269,61 @@ getContactInteractionString(const QString& authorUri, const ContactAction& actio
     case ContactAction::UNBANNED:
         return QObject::tr("%1 was re-added").arg(authorUri);
     case ContactAction::INVALID:
+        return QObject::tr("Contact added");
+    }
+    return QObject::tr("Contact added");
+}
+
+static inline QString
+getFormattedCallDuration(const std::time_t duration)
+{
+    if (duration == 0)
         return {};
+    std::string formattedString;
+    auto minutes = duration / 60;
+    auto seconds = duration % 60;
+    if (minutes > 0) {
+        formattedString += std::to_string(minutes) + ":";
+        if (formattedString.length() == 2) {
+            formattedString = "0" + formattedString;
+        }
+    } else {
+        formattedString += "00:";
+    }
+    if (seconds < 10)
+        formattedString += "0";
+    formattedString += std::to_string(seconds);
+    return QString::fromStdString(formattedString);
+}
+
+/**
+ * Get a formatted string for a call interaction's body
+ * @param isSelf
+ * @param info
+ * @return the formatted and translated call message string
+ */
+static inline QString
+getCallInteractionStringNonSwarm(bool isSelf, const std::time_t& duration)
+{
+    if (duration < 0) {
+        if (isSelf) {
+            return QObject::tr("Outgoing call");
+        } else {
+            return QObject::tr("Incoming call");
+        }
+    } else if (isSelf) {
+        if (duration) {
+            return QObject::tr("Outgoing call") + " - " + getFormattedCallDuration(duration);
+        } else {
+            return QObject::tr("Missed outgoing call");
+        }
+    } else {
+        if (duration) {
+            return QObject::tr("Incoming call") + " - " + getFormattedCallDuration(duration);
+        } else {
+            return QObject::tr("Missed incoming call");
+        }
     }
-    return {};
 }
 
 struct Body
@@ -287,6 +339,17 @@ public:
     std::time_t timestamp;
 };
 
+struct Emoji
+{
+    Q_GADGET
+
+    Q_PROPERTY(QString commitId MEMBER commitId)
+    Q_PROPERTY(QString body MEMBER body)
+public:
+    QString commitId;
+    QString body;
+};
+
 /**
  * @var authorUri
  * @var body
@@ -336,7 +399,7 @@ struct Info
         this->isRead = isRead;
     }
 
-    Info(const MapStringString& message, const QString& accountURI)
+    void init(const MapStringString& message, const QString& accountURI)
     {
         type = to_type(message["type"]);
         if (message.contains("react-to") && type == Type::TEXT) {
@@ -345,7 +408,7 @@ struct Info
         }
         authorUri = message["author"];
 
-        if (type == Type::TEXT || type == Type::EDITED || type == Type::REACTION) {
+        if (type == Type::TEXT) {
             body = message["body"];
         }
         timestamp = message["timestamp"].toInt();
@@ -367,6 +430,36 @@ struct Info
         }
         commit = message;
     }
+
+    Info(const MapStringString& message, const QString& accountURI)
+    {
+        init(message, accountURI);
+    }
+
+    Info(const SwarmMessage& msg, const QString& accountUri)
+    {
+        MapStringString msgBody;
+        for (const auto& key : msg.body.keys()) {
+            msgBody.insert(key, msg.body.value(key));
+        }
+        init(msgBody, accountUri);
+        parentId = msg.linearizedParent;
+        type = to_type(msg.type);
+        for (const auto& edition : msg.editions)
+            previousBodies.append(Body {edition.value("id"),
+                                        edition.value("body"),
+                                        QString(edition.value("timestamp")).toInt()});
+        QMap<QString, QVariantList> mapStringEmoji;
+        for (const auto& reaction : msg.reactions) {
+            auto author = reaction.value("author");
+            auto body = reaction.value("body");
+            auto emoji = Emoji {reaction.value("id"), body};
+            QVariant variant = QVariant::fromValue(emoji);
+            mapStringEmoji[author].append(variant);
+        }
+        for (auto i = mapStringEmoji.begin(); i != mapStringEmoji.end(); i++)
+            reactions.insert(i.key(), i.value());
+    }
 };
 
 static inline bool
@@ -375,6 +468,17 @@ isOutgoing(const Info& interaction)
     return interaction.authorUri.isEmpty();
 }
 
+static inline QString
+getCallInteractionString(bool isSelf, const Info& info)
+{
+    if (!info.confId.isEmpty()) {
+        if (info.duration <= 0) {
+            return QObject::tr("Join call");
+        }
+    }
+    return getCallInteractionStringNonSwarm(isSelf, info.duration);
+}
+
 } // namespace interaction
 } // namespace api
 } // namespace lrc
diff --git a/src/libclient/authority/storagehelper.cpp b/src/libclient/authority/storagehelper.cpp
index 8e505441e..866ed07c1 100644
--- a/src/libclient/authority/storagehelper.cpp
+++ b/src/libclient/authority/storagehelper.cpp
@@ -144,78 +144,6 @@ prepareUri(const QString& uri, api::profile::Type type)
     }
 }
 
-QString
-getFormattedCallDuration(const std::time_t duration)
-{
-    if (duration == 0)
-        return {};
-    std::string formattedString;
-    auto minutes = duration / 60;
-    auto seconds = duration % 60;
-    if (minutes > 0) {
-        formattedString += std::to_string(minutes) + ":";
-        if (formattedString.length() == 2) {
-            formattedString = "0" + formattedString;
-        }
-    } else {
-        formattedString += "00:";
-    }
-    if (seconds < 10)
-        formattedString += "0";
-    formattedString += std::to_string(seconds);
-    return QString::fromStdString(formattedString);
-}
-
-QString
-getCallInteractionStringNonSwarm(bool isSelf, const std::time_t& duration)
-{
-    if (duration < 0) {
-        if (isSelf) {
-            return QObject::tr("Outgoing call");
-        } else {
-            return QObject::tr("Incoming call");
-        }
-    } else if (isSelf) {
-        if (duration) {
-            return QObject::tr("Outgoing call") + " - " + getFormattedCallDuration(duration);
-        } else {
-            return QObject::tr("Missed outgoing call");
-        }
-    } else {
-        if (duration) {
-            return QObject::tr("Incoming call") + " - " + getFormattedCallDuration(duration);
-        } else {
-            return QObject::tr("Missed incoming call");
-        }
-    }
-}
-
-QString
-getCallInteractionString(bool isSelf, const api::interaction::Info& info)
-{
-    if (!info.confId.isEmpty()) {
-        if (info.duration <= 0) {
-            return QObject::tr("Join call");
-        }
-    }
-    return getCallInteractionStringNonSwarm(isSelf, info.duration);
-}
-
-QString
-getContactInteractionString(const QString& authorUri, const api::interaction::Status& status)
-{
-    if (authorUri.isEmpty()) {
-        return QObject::tr("Contact added");
-    } else {
-        if (status == api::interaction::Status::UNKNOWN) {
-            return QObject::tr("Invitation received");
-        } else if (status == api::interaction::Status::SUCCESS) {
-            return QObject::tr("Invitation accepted");
-        }
-    }
-    return {};
-}
-
 namespace vcard {
 QString
 compressedAvatar(const QString& image)
@@ -515,6 +443,21 @@ beginConversationWithPeer(Database& db,
     return newConversationsId;
 }
 
+QString
+getContactInteractionString(const QString& authorUri, const api::interaction::Status& status)
+{
+    if (authorUri.isEmpty()) {
+        return QObject::tr("Contact added");
+    } else {
+        if (status == api::interaction::Status::UNKNOWN) {
+            return QObject::tr("Invitation received");
+        } else if (status == api::interaction::Status::SUCCESS) {
+            return QObject::tr("Invitation accepted");
+        }
+    }
+    return {};
+}
+
 void
 getHistory(Database& db, api::conversation::Info& conversation, const QString& localUri)
 {
@@ -540,9 +483,11 @@ getHistory(Database& db, api::conversation::Info& conversation, const QString& l
                                        : std::stoi(durationString.toStdString());
             auto status = api::interaction::to_status(payloads[i + 5]);
             if (type == api::interaction::Type::CALL) {
-                body = getCallInteractionStringNonSwarm(payloads[i + 1] == localUri, duration);
+                body = api::interaction::getCallInteractionStringNonSwarm(payloads[i + 1]
+                                                                              == localUri,
+                                                                          duration);
             } else if (type == api::interaction::Type::CONTACT) {
-                body = getContactInteractionString(payloads[i + 1], status);
+                body = storage::getContactInteractionString(payloads[i + 1], status);
             }
             auto msg = api::interaction::Info({payloads[i + 1],
                                                body,
@@ -552,7 +497,6 @@ getHistory(Database& db, api::conversation::Info& conversation, const QString& l
                                                status,
                                                (payloads[i + 6] == "1" ? true : false)});
             conversation.interactions->emplace(payloads[i], std::move(msg));
-            conversation.lastMessageUid = payloads[i];
             if (status != api::interaction::Status::DISPLAYED || !payloads[i + 1].isEmpty()) {
                 continue;
             }
@@ -764,20 +708,6 @@ clearHistory(Database& db, const QString& conversationId)
     }
 }
 
-void
-clearInteractionFromConversation(Database& db,
-                                 const QString& conversationId,
-                                 const QString& interactionId)
-{
-    try {
-        db.deleteFrom("interactions",
-                      "conversation=:conversation AND id=:id",
-                      {{":conversation", conversationId}, {":id", interactionId}});
-    } catch (Database::QueryDeleteError& e) {
-        qWarning() << "deleteFrom error: " << e.details();
-    }
-}
-
 void
 clearAllHistory(Database& db)
 {
diff --git a/src/libclient/authority/storagehelper.h b/src/libclient/authority/storagehelper.h
index eaf66aa06..c7187936a 100644
--- a/src/libclient/authority/storagehelper.h
+++ b/src/libclient/authority/storagehelper.h
@@ -54,15 +54,6 @@ QString getPath();
  */
 QString prepareUri(const QString& uri, api::profile::Type type);
 
-/**
- * Get a formatted string for a call interaction's body
- * @param isSelf
- * @param info
- * @return the formatted and translated call message string
- */
-QString getCallInteractionString(bool isSelf, const api::interaction::Info& info);
-QString getCallInteractionStringNonSwarm(bool isSelf, const std::time_t& duration);
-
 /**
  * Get a formatted string for a contact interaction's body
  * @param author_uri
@@ -99,12 +90,6 @@ void setProfile(const QString& accountId,
 
 } // namespace vcard
 
-/**
- * @param  duration
- * @return a human readable call duration (M:ss)
- */
-QString getFormattedCallDuration(const std::time_t duration);
-
 /**
  * Get all conversations with a given participant's URI
  * @param db
@@ -311,16 +296,6 @@ void setInteractionRead(Database& db, const QString& id);
  */
 void clearHistory(Database& db, const QString& conversationId);
 
-/**
- * Clear interaction from history
- * @param  db
- * @param  conversationId
- * @param  interactionId
- */
-void clearInteractionFromConversation(Database& db,
-                                      const QString& conversationId,
-                                      const QString& interactionId);
-
 /**
  * Clear all history stored in the interactions table of the database
  * @param  db
diff --git a/src/libclient/callbackshandler.cpp b/src/libclient/callbackshandler.cpp
index b594ebddb..48e244342 100644
--- a/src/libclient/callbackshandler.cpp
+++ b/src/libclient/callbackshandler.cpp
@@ -301,9 +301,9 @@ CallbacksHandler::CallbacksHandler(const Lrc& parent)
             &CallbacksHandler::slotAudioMeterReceived,
             Qt::QueuedConnection);
     connect(&ConfigurationManager::instance(),
-            &ConfigurationManagerInterface::conversationLoaded,
+            &ConfigurationManagerInterface::swarmLoaded,
             this,
-            &CallbacksHandler::slotConversationLoaded,
+            &CallbacksHandler::slotSwarmLoaded,
             Qt::QueuedConnection);
     connect(&ConfigurationManager::instance(),
             &ConfigurationManagerInterface::messagesFound,
@@ -311,10 +311,25 @@ CallbacksHandler::CallbacksHandler(const Lrc& parent)
             &CallbacksHandler::slotMessagesFound,
             Qt::QueuedConnection);
     connect(&ConfigurationManager::instance(),
-            &ConfigurationManagerInterface::messageReceived,
+            &ConfigurationManagerInterface::swarmMessageReceived,
             this,
             &CallbacksHandler::slotMessageReceived,
             Qt::QueuedConnection);
+    connect(&ConfigurationManager::instance(),
+            &ConfigurationManagerInterface::swarmMessageUpdated,
+            this,
+            &CallbacksHandler::slotMessageUpdated,
+            Qt::QueuedConnection);
+    connect(&ConfigurationManager::instance(),
+            &ConfigurationManagerInterface::reactionAdded,
+            this,
+            &CallbacksHandler::slotReactionAdded,
+            Qt::QueuedConnection);
+    connect(&ConfigurationManager::instance(),
+            &ConfigurationManagerInterface::reactionRemoved,
+            this,
+            &CallbacksHandler::slotReactionRemoved,
+            Qt::QueuedConnection);
     connect(&ConfigurationManager::instance(),
             &ConfigurationManagerInterface::conversationProfileUpdated,
             this,
@@ -721,13 +736,14 @@ CallbacksHandler::slotRemoteRecordingChanged(const QString& callId,
 }
 
 void
-CallbacksHandler::slotConversationLoaded(uint32_t requestId,
-                                         const QString& accountId,
-                                         const QString& conversationId,
-                                         const VectorMapStringString& messages)
+CallbacksHandler::slotSwarmLoaded(uint32_t requestId,
+                                  const QString& accountId,
+                                  const QString& conversationId,
+                                  const VectorSwarmMessage& messages)
 {
-    Q_EMIT conversationLoaded(requestId, accountId, conversationId, messages);
+    Q_EMIT swarmLoaded(requestId, accountId, conversationId, messages);
 }
+
 void
 CallbacksHandler::slotMessagesFound(uint32_t requestId,
                                     const QString& accountId,
@@ -740,11 +756,37 @@ CallbacksHandler::slotMessagesFound(uint32_t requestId,
 void
 CallbacksHandler::slotMessageReceived(const QString& accountId,
                                       const QString& conversationId,
-                                      const MapStringString& message)
+                                      const SwarmMessage& message)
 {
     Q_EMIT messageReceived(accountId, conversationId, message);
 }
 
+void
+CallbacksHandler::slotMessageUpdated(const QString& accountId,
+                                     const QString& conversationId,
+                                     const SwarmMessage& message)
+{
+    Q_EMIT messageUpdated(accountId, conversationId, message);
+}
+
+void
+CallbacksHandler::slotReactionAdded(const QString& accountId,
+                                    const QString& conversationId,
+                                    const QString& messageId,
+                                    const MapStringString& reaction)
+{
+    Q_EMIT reactionAdded(accountId, conversationId, messageId, reaction);
+}
+
+void
+CallbacksHandler::slotReactionRemoved(const QString& accountId,
+                                      const QString& conversationId,
+                                      const QString& messageId,
+                                      const QString& reactionId)
+{
+    Q_EMIT reactionRemoved(accountId, conversationId, messageId, reactionId);
+}
+
 void
 CallbacksHandler::slotConversationProfileUpdated(const QString& accountId,
                                                  const QString& conversationId,
diff --git a/src/libclient/callbackshandler.h b/src/libclient/callbackshandler.h
index ea7033a47..e2f72b31c 100644
--- a/src/libclient/callbackshandler.h
+++ b/src/libclient/callbackshandler.h
@@ -22,6 +22,8 @@
 #include "api/datatransfer.h"
 #include "qtwrapper/conversions_wrap.hpp"
 
+#include <conversation_interface.h>
+
 #include <QObject>
 
 #include <memory>
@@ -335,17 +337,28 @@ Q_SIGNALS:
      * @param code
      */
     void remoteRecordingChanged(const QString& callId, const QString& peerNumber, bool state);
-    void conversationLoaded(uint32_t requestId,
-                            const QString& accountId,
-                            const QString& conversationId,
-                            const VectorMapStringString& messages);
+    void swarmLoaded(uint32_t requestId,
+                     const QString& accountId,
+                     const QString& conversationId,
+                     const VectorSwarmMessage& messages);
     void messagesFound(uint32_t requestId,
                        const QString& accountId,
                        const QString& conversationId,
                        const VectorMapStringString& messages);
     void messageReceived(const QString& accountId,
                          const QString& conversationId,
-                         const MapStringString& message);
+                         const SwarmMessage& message);
+    void messageUpdated(const QString& accountId,
+                        const QString& conversationId,
+                        const SwarmMessage& message);
+    void reactionAdded(const QString& accountId,
+                       const QString& conversationId,
+                       const QString& messageId,
+                       const MapStringString& reaction);
+    void reactionRemoved(const QString& accountId,
+                         const QString& conversationId,
+                         const QString& messageId,
+                         const QString& reactionId);
     void conversationProfileUpdated(const QString& accountId,
                                     const QString& conversationId,
                                     const MapStringString& profile);
@@ -643,17 +656,28 @@ private Q_SLOTS:
      * @param state, new state
      */
     void slotRemoteRecordingChanged(const QString& callId, const QString& contactId, bool state);
-    void slotConversationLoaded(uint32_t requestId,
-                                const QString& accountId,
-                                const QString& conversationId,
-                                const VectorMapStringString& messages);
+    void slotSwarmLoaded(uint32_t requestId,
+                         const QString& accountId,
+                         const QString& conversationId,
+                         const VectorSwarmMessage& messages);
     void slotMessagesFound(uint32_t requestId,
                            const QString& accountId,
                            const QString& conversationId,
                            const VectorMapStringString& messages);
     void slotMessageReceived(const QString& accountId,
                              const QString& conversationId,
-                             const MapStringString& message);
+                             const SwarmMessage& message);
+    void slotMessageUpdated(const QString& accountId,
+                            const QString& conversationId,
+                            const SwarmMessage& message);
+    void slotReactionAdded(const QString& accountId,
+                           const QString& conversationId,
+                           const QString& messageId,
+                           const MapStringString& reaction);
+    void slotReactionRemoved(const QString& accountId,
+                             const QString& conversationId,
+                             const QString& messageId,
+                             const QString& reactionId);
     void slotConversationProfileUpdated(const QString& accountId,
                                         const QString& conversationId,
                                         const MapStringString& message);
diff --git a/src/libclient/callmodel.cpp b/src/libclient/callmodel.cpp
index ee52a15e3..c1a4d693e 100644
--- a/src/libclient/callmodel.cpp
+++ b/src/libclient/callmodel.cpp
@@ -914,7 +914,7 @@ CallModel::getFormattedCallDuration(const QString& callId) const
     auto d = std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch()
                                                               - startTime.time_since_epoch())
                  .count();
-    return authority::storage::getFormattedCallDuration(d);
+    return interaction::getFormattedCallDuration(d);
 }
 
 bool
diff --git a/src/libclient/conversationmodel.cpp b/src/libclient/conversationmodel.cpp
index de2df8a41..87b4a602a 100644
--- a/src/libclient/conversationmodel.cpp
+++ b/src/libclient/conversationmodel.cpp
@@ -215,11 +215,6 @@ public:
 
     // filter out ourself from conversation participants.
     const VectorString peersForConversation(const conversation::Info& conversation) const;
-    // insert swarm interactions. Return false if interaction already exists.
-    bool insertSwarmInteraction(const QString& interactionId,
-                                interaction::Info& interaction,
-                                conversation::Info& conversation,
-                                bool insertAtBegin);
     void invalidateModel();
     void emplaceBackConversation(conversation::Info&& conversation);
     void eraseConversation(const QString& convId);
@@ -350,10 +345,10 @@ public Q_SLOTS:
                               datatransfer::Info info,
                               interaction::Status newStatus,
                               bool& updated);
-    void slotConversationLoaded(uint32_t requestId,
-                                const QString& accountId,
-                                const QString& conversationId,
-                                const VectorMapStringString& messages);
+    void slotSwarmLoaded(uint32_t requestId,
+                         const QString& accountId,
+                         const QString& conversationId,
+                         const VectorSwarmMessage& messages);
     /**
      * Listen messageFound signal.
      * Is the search response from MessagesAdapter::getConvMedias()
@@ -368,7 +363,18 @@ public Q_SLOTS:
                            const VectorMapStringString& messages);
     void slotMessageReceived(const QString& accountId,
                              const QString& conversationId,
-                             const MapStringString& message);
+                             const SwarmMessage& message);
+    void slotMessageUpdated(const QString& accountId,
+                            const QString& conversationId,
+                            const SwarmMessage& message);
+    void slotReactionAdded(const QString& accountId,
+                           const QString& conversationId,
+                           const QString& messageId,
+                           const MapStringString& reaction);
+    void slotReactionRemoved(const QString& accountId,
+                             const QString& conversationId,
+                             const QString& messageId,
+                             const QString& reactionId);
     void slotConversationProfileUpdated(const QString& accountId,
                                         const QString& conversationId,
                                         const MapStringString& profile);
@@ -1149,6 +1155,16 @@ ConversationModel::notificationsCount() const
     return notificationsCount;
 }
 
+void
+ConversationModel::reloadHistory() const
+{
+    std::for_each(pimpl_->conversations.begin(), pimpl_->conversations.end(), [&](const auto& c) {
+        c.interactions->reloadHistory();
+        Q_EMIT conversationUpdated(c.uid);
+        Q_EMIT dataChanged(pimpl_->indexOf(c.uid));
+    });
+}
+
 QString
 ConversationModel::title(const QString& conversationId) const
 {
@@ -1345,7 +1361,6 @@ ConversationModel::sendMessage(const QString& uid, const QString& body, const QS
                 return;
             }
 
-            newConv.lastMessageUid = msgId;
             newConv.lastSelfMessageId = msgId;
             // Emit this signal for chatview in the client
             Q_EMIT newInteraction(convId, msgId, msg);
@@ -1501,101 +1516,6 @@ ConversationModel::clearHistory(const QString& uid)
     Q_EMIT dataChanged(conversationIdx);
 }
 
-void
-ConversationModel::clearInteractionFromConversation(const QString& convId,
-                                                    const QString& interactionId)
-{
-    auto conversationIdx = pimpl_->indexOf(convId);
-    if (conversationIdx == -1)
-        return;
-
-    auto erased_keys = 0;
-    bool lastInteractionUpdated = false;
-    bool updateDisplayedUid = false;
-    QString newDisplayedUid = 0;
-    QString participantURI = "";
-    {
-        std::lock_guard<std::mutex> lk(pimpl_->interactionsLocks[convId]);
-        try {
-            auto& conversation = pimpl_->conversations.at(conversationIdx);
-            if (conversation.isSwarm()) {
-                // WARNING: clearInteractionFromConversation not implemented for swarm
-                return;
-            }
-            storage::clearInteractionFromConversation(pimpl_->db, convId, interactionId);
-            erased_keys = conversation.interactions->erase(interactionId);
-            participantURI = pimpl_->peersForConversation(conversation).front();
-            auto messageId = conversation.interactions->getRead(participantURI);
-
-            if (messageId != "" && messageId == interactionId) {
-                for (auto iter = conversation.interactions->find(interactionId);
-                     iter != conversation.interactions->end();
-                     --iter) {
-                    if (isOutgoing(iter->second) && iter->first != interactionId) {
-                        newDisplayedUid = iter->first;
-                        break;
-                    }
-                }
-                updateDisplayedUid = true;
-                conversation.interactions->setRead(participantURI, newDisplayedUid);
-            }
-
-            if (conversation.lastMessageUid == interactionId) {
-                // Update lastMessageUid
-                auto newLastId = QString::number(0);
-                if (!conversation.interactions->empty())
-                    newLastId = conversation.interactions->rbegin()->first;
-                conversation.lastMessageUid = newLastId;
-                lastInteractionUpdated = true;
-            }
-            if (conversation.lastSelfMessageId == interactionId) {
-                conversation.lastSelfMessageId = conversation.interactions->lastSelfMessageId(
-                    owner.profileInfo.uri);
-            }
-
-        } catch (const std::out_of_range& e) {
-            qDebug() << "can't clear interaction from conversation: " << e.what();
-        }
-    }
-    if (updateDisplayedUid) {
-        Q_EMIT displayedInteractionChanged(convId, participantURI, interactionId, newDisplayedUid);
-    }
-    if (erased_keys > 0) {
-        pimpl_->filteredConversations.invalidate();
-        Q_EMIT interactionRemoved(convId, interactionId);
-    }
-    if (lastInteractionUpdated) {
-        // last interaction as changed, so the order can change.
-        Q_EMIT modelChanged();
-        Q_EMIT dataChanged(conversationIdx);
-    }
-}
-
-void
-ConversationModel::clearInteractionsCache(const QString& convId)
-{
-    auto conversationIdx = pimpl_->indexOf(convId);
-    if (conversationIdx == -1)
-        return;
-
-    try {
-        auto& conversation = pimpl_->conversations.at(conversationIdx);
-        if (!conversation.isRequest && !conversation.needsSyncing && conversation.isSwarm()) {
-            {
-                std::lock_guard<std::mutex> lk(pimpl_->interactionsLocks[conversation.uid]);
-                conversation.interactions->clear();
-            }
-            conversation.allMessagesLoaded = false;
-            conversation.lastMessageUid = "";
-            conversation.lastSelfMessageId = "";
-            ConfigurationManager::instance().loadConversationMessages(owner.id, convId, "", 1);
-        }
-    } catch (const std::out_of_range& e) {
-        qDebug() << "can't find interaction from conversation: " << e.what();
-        return;
-    }
-}
-
 bool
 ConversationModel::isLastDisplayed(const QString& convId,
                                    const QString& interactionId,
@@ -1689,29 +1609,10 @@ ConversationModel::loadConversationMessages(const QString& conversationId, const
     }
     auto lastMsgId = conversation.interactions->empty() ? ""
                                                         : conversation.interactions->front().first;
-    return ConfigurationManager::instance().loadConversationMessages(owner.id,
-                                                                     conversationId,
-                                                                     lastMsgId,
-                                                                     size);
-}
-
-int
-ConversationModel::loadConversationUntil(const QString& conversationId, const QString& to)
-{
-    auto conversationOpt = getConversationForUid(conversationId);
-    if (!conversationOpt.has_value()) {
-        return -1;
-    }
-    auto& conversation = conversationOpt->get();
-    if (conversation.allMessagesLoaded) {
-        return -1;
-    }
-    auto lastMsgId = conversation.interactions->empty() ? ""
-                                                        : conversation.interactions->front().first;
-    return ConfigurationManager::instance().loadConversationUntil(owner.id,
-                                                                  conversationId,
-                                                                  lastMsgId,
-                                                                  to);
+    return ConfigurationManager::instance().loadConversation(owner.id,
+                                                             conversationId,
+                                                             lastMsgId,
+                                                             size);
 }
 
 void
@@ -1892,9 +1793,9 @@ ConversationModelPimpl::ConversationModelPimpl(const ConversationModel& linked,
             &ConversationModelPimpl::slotTransferStatusUnjoinable);
     // swarm conversations
     connect(&callbacksHandler,
-            &CallbacksHandler::conversationLoaded,
+            &CallbacksHandler::swarmLoaded,
             this,
-            &ConversationModelPimpl::slotConversationLoaded);
+            &ConversationModelPimpl::slotSwarmLoaded);
     connect(&callbacksHandler,
             &CallbacksHandler::messagesFound,
             this,
@@ -1903,6 +1804,18 @@ ConversationModelPimpl::ConversationModelPimpl(const ConversationModel& linked,
             &CallbacksHandler::messageReceived,
             this,
             &ConversationModelPimpl::slotMessageReceived);
+    connect(&callbacksHandler,
+            &CallbacksHandler::messageUpdated,
+            this,
+            &ConversationModelPimpl::slotMessageUpdated);
+    connect(&callbacksHandler,
+            &CallbacksHandler::reactionAdded,
+            this,
+            &ConversationModelPimpl::slotReactionAdded);
+    connect(&callbacksHandler,
+            &CallbacksHandler::reactionRemoved,
+            this,
+            &ConversationModelPimpl::slotReactionRemoved);
     connect(&callbacksHandler,
             &CallbacksHandler::conversationProfileUpdated,
             this,
@@ -2044,9 +1957,9 @@ ConversationModelPimpl::~ConversationModelPimpl()
                &ConversationModelPimpl::slotTransferStatusUnjoinable);
     // swarm conversations
     disconnect(&callbacksHandler,
-               &CallbacksHandler::conversationLoaded,
+               &CallbacksHandler::swarmLoaded,
                this,
-               &ConversationModelPimpl::slotConversationLoaded);
+               &ConversationModelPimpl::slotSwarmLoaded);
     disconnect(&callbacksHandler,
                &CallbacksHandler::messagesFound,
                this,
@@ -2055,6 +1968,18 @@ ConversationModelPimpl::~ConversationModelPimpl()
                &CallbacksHandler::messageReceived,
                this,
                &ConversationModelPimpl::slotMessageReceived);
+    disconnect(&callbacksHandler,
+               &CallbacksHandler::messageUpdated,
+               this,
+               &ConversationModelPimpl::slotMessageUpdated);
+    disconnect(&callbacksHandler,
+               &CallbacksHandler::reactionAdded,
+               this,
+               &ConversationModelPimpl::slotReactionAdded);
+    disconnect(&callbacksHandler,
+               &CallbacksHandler::reactionRemoved,
+               this,
+               &ConversationModelPimpl::slotReactionRemoved);
     disconnect(&callbacksHandler,
                &CallbacksHandler::conversationProfileUpdated,
                this,
@@ -2332,8 +2257,8 @@ ConversationModelPimpl::sort(const conversation::Info& convA, const conversation
         return true;
     // Sort by last Interaction
     try {
-        auto lastMessageA = historyA->at(convA.lastMessageUid);
-        auto lastMessageB = historyB->at(convB.lastMessageUid);
+        auto lastMessageA = historyA->rbegin()->second;
+        auto lastMessageB = historyB->rbegin()->second;
         return lastMessageA.timestamp > lastMessageB.timestamp;
     } catch (const std::exception& e) {
         qDebug() << "ConversationModel::sortConversations(), can't get lastMessage";
@@ -2353,38 +2278,26 @@ ConversationModelPimpl::sendContactRequest(const QString& contactUri)
     } catch (std::out_of_range& e) {
     }
 }
+
 void
-ConversationModelPimpl::slotConversationLoaded(uint32_t requestId,
-                                               const QString& accountId,
-                                               const QString& conversationId,
-                                               const VectorMapStringString& messages)
+ConversationModelPimpl::slotSwarmLoaded(uint32_t requestId,
+                                        const QString& accountId,
+                                        const QString& conversationId,
+                                        const VectorSwarmMessage& messages)
 {
-    if (accountId != linked.owner.id) {
+    if (accountId != linked.owner.id)
         return;
-    }
-
-    auto allLoaded = messages.size() == 0;
-
+    auto allLoaded = false;
     try {
         auto& conversation = getConversationForUid(conversationId).get();
-        QString oldLast, oldBegin; // Used to detect loading loops just in case.
-        if (conversation.interactions->size() != 0) {
-            oldBegin = conversation.interactions->begin()->first;
-            oldLast = conversation.interactions->rbegin()->first;
-        }
         for (const auto& message : messages) {
-            if (message["type"].isEmpty()) {
-                continue;
-            }
-            auto msgId = message["id"];
+            QString msgId = message.id;
             auto msg = interaction::Info(message, linked.owner.profileInfo.uri);
-            conversation.interactions->editMessage(msgId, msg);
-            conversation.interactions->reactToMessage(msgId, msg);
             auto downloadFile = false;
             if (msg.type == interaction::Type::INITIAL) {
                 allLoaded = true;
             } else if (msg.type == interaction::Type::DATA_TRANSFER) {
-                auto fileId = message["fileId"];
+                QString fileId = message.body.value("fileId");
                 QString path;
                 qlonglong bytesProgress, totalSize;
                 linked.owner.dataTransferModel->fileTransferInfo(accountId,
@@ -2404,68 +2317,43 @@ ConversationModelPimpl::slotConversationLoaded(uint32_t requestId,
                                                           : interaction::Status::TRANSFER_ONGOING;
                 linked.owner.dataTransferModel->registerTransferId(fileId, msgId);
                 downloadFile = (bytesProgress == 0);
-            } else if (msg.type == interaction::Type::CALL) {
-                msg.body = storage::getCallInteractionString(msg.authorUri
-                                                                 == linked.owner.profileInfo.uri,
-                                                             msg);
-            } else if (msg.type == interaction::Type::CONTACT) {
-                auto bestName = msg.authorUri == linked.owner.profileInfo.uri
-                                    ? linked.owner.accountModel->bestNameForAccount(linked.owner.id)
-                                    : linked.owner.contactModel->bestNameForContact(msg.authorUri);
-                msg.body = interaction::getContactInteractionString(bestName,
-                                                                    interaction::to_action(
-                                                                        message["action"]));
-            } else if (msg.type == interaction::Type::EDITED) {
-                conversation.interactions->addEdition(msgId, msg, false);
-            } else if (msg.type == interaction::Type::REACTION) {
-                conversation.interactions->addReaction(msg.react_to, msgId);
             }
-            insertSwarmInteraction(msgId, msg, conversation, true);
+
+            {
+                // If message is loaded, insert message at beginning
+                std::lock_guard<std::mutex> lk(interactionsLocks[conversation.uid]);
+                auto itExists = conversation.interactions->find(msgId);
+                // If found, nothing to do.
+                if (itExists != conversation.interactions->end())
+                    continue;
+
+                auto result = conversation.interactions->insert(std::make_pair(msgId, msg), true);
+                if (!result.second) {
+                    continue;
+                }
+            }
+
             if (downloadFile) {
-                // Note, we must do this after insertSwarmInteraction to find the interaction
-                handleIncomingFile(conversationId, msgId, message["totalSize"].toInt());
+                handleIncomingFile(conversationId,
+                                   msgId,
+                                   QString(message.body.value("totalSize")).toInt());
             }
         }
 
-        conversation.lastMessageUid = conversation.interactions->lastMessageUid();
         conversation.lastSelfMessageId = conversation.interactions->lastSelfMessageId(
             linked.owner.profileInfo.uri);
-        if (conversation.lastMessageUid.isEmpty() && !conversation.allMessagesLoaded
-            && messages.size() != 0) {
-            if (conversation.interactions->size() > 0) {
-                QString newLast, newBegin;
-                if (conversation.interactions->size() > 0) {
-                    newBegin = conversation.interactions->begin()->first;
-                    newLast = conversation.interactions->rbegin()->first;
-                }
-                if (newLast == oldLast && !newLast.isEmpty() && newBegin == oldBegin
-                    && !newBegin.isEmpty()) { // [[unlikely]] in c++20
-                    qCritical() << "Loading loop detected for " << conversationId << "(" << newBegin
-                                << " ; " << newLast << ")";
-                    return;
-                }
-            }
-            // In this case, we only have loaded merge commits. Load more messages
-            ConfigurationManager::instance().loadConversationMessages(linked.owner.id,
-                                                                      conversationId,
-                                                                      messages.rbegin()->value(
-                                                                          "id"),
-                                                                      2);
-            return;
-        }
         invalidateModel();
         Q_EMIT linked.modelChanged();
         Q_EMIT linked.newMessagesAvailable(linked.owner.id, conversationId);
         auto conversationIdx = indexOf(conversationId);
         Q_EMIT linked.dataChanged(conversationIdx);
         Q_EMIT linked.conversationMessagesLoaded(requestId, conversationId);
-
         if (allLoaded) {
             conversation.allMessagesLoaded = true;
             Q_EMIT linked.conversationUpdated(conversationId);
         }
     } catch (const std::exception& e) {
-        qDebug() << "messages loaded for not existing conversation";
+        qWarning() << e.what();
     }
 }
 
@@ -2508,35 +2396,27 @@ ConversationModelPimpl::slotMessagesFound(uint32_t requestId,
 void
 ConversationModelPimpl::slotMessageReceived(const QString& accountId,
                                             const QString& conversationId,
-                                            const MapStringString& message)
+                                            const SwarmMessage& message)
 {
-    if (accountId != linked.owner.id) {
+    if (accountId != linked.owner.id)
         return;
-    }
     try {
         auto& conversation = getConversationForUid(conversationId).get();
-        if (message["type"].isEmpty() || message["type"] == "application/update-profile") {
-            return;
-        }
-        if (message["type"] == "initial") {
+        if (message.type == "initial") {
             conversation.allMessagesLoaded = true;
             Q_EMIT linked.conversationUpdated(conversationId);
-            if (message.find("invited") == message.end()) {
+            if (message.body.find("invited") == message.body.end()) {
                 return;
             }
         }
-        auto msgId = message["id"];
+        QString msgId = message.id;
         auto msg = interaction::Info(message, linked.owner.profileInfo.uri);
-        conversation.interactions->editMessage(msgId, msg);
         api::datatransfer::Info info;
-        QString fileId;
-
-        auto updateUnread = false;
 
         if (msg.type == interaction::Type::DATA_TRANSFER) {
             // save data transfer interaction to db and assosiate daemon id with interaction id,
             // conversation id and db id
-            QString fileId = message["fileId"];
+            QString fileId = message.body.value("fileId");
             QString path;
             qlonglong bytesProgress, totalSize;
             linked.owner.dataTransferModel->fileTransferInfo(accountId,
@@ -2555,60 +2435,32 @@ ConversationModelPimpl::slotMessageReceived(const QString& accountId,
                          : bytesProgress == totalSize ? interaction::Status::TRANSFER_FINISHED
                                                       : interaction::Status::TRANSFER_ONGOING;
             linked.owner.dataTransferModel->registerTransferId(fileId, msgId);
-            if (msg.authorUri != linked.owner.profileInfo.uri) {
-                updateUnread = true;
-            }
-        } else if (msg.type == interaction::Type::CALL) {
-            // If we're a call in a swarm
-            if (msg.authorUri != linked.owner.profileInfo.uri)
-                updateUnread = true;
-            msg.body = storage::getCallInteractionString(msg.authorUri
-                                                             == linked.owner.profileInfo.uri,
-                                                         msg);
-        } else if (msg.type == interaction::Type::CONTACT) {
-            auto bestName = msg.authorUri == linked.owner.profileInfo.uri
-                                ? linked.owner.accountModel->bestNameForAccount(linked.owner.id)
-                                : linked.owner.contactModel->bestNameForContact(msg.authorUri);
-            msg.body = interaction::getContactInteractionString(bestName,
-                                                                interaction::to_action(
-                                                                    message["action"]));
-            if (msg.authorUri != linked.owner.profileInfo.uri) {
-                updateUnread = true;
-            }
-        } else if (msg.type == interaction::Type::TEXT) {
-            if (msg.authorUri != linked.owner.profileInfo.uri) {
-                updateUnread = true;
-            }
-        } else if (msg.type == interaction::Type::REACTION) {
-            conversation.interactions->addReaction(msg.react_to, msgId);
-        } else if (msg.type == interaction::Type::EDITED) {
-            conversation.interactions->addEdition(msgId, msg, true);
         }
 
-        if (!insertSwarmInteraction(msgId, msg, conversation, false)) {
-            // message already exists
-            return;
-        }
-        // once the reaction is added to interactions, we can update the reacted
-        // message
-        if (msg.type == interaction::Type::REACTION) {
-            auto reactInteraction = conversation.interactions->find(msg.react_to);
-            if (reactInteraction != conversation.interactions->end()) {
-                conversation.interactions->reactToMessage(msg.react_to, reactInteraction->second);
+        {
+            // If message is received, insert message after its parent.
+            std::lock_guard<std::mutex> lk(interactionsLocks[conversation.uid]);
+            auto itExists = conversation.interactions->find(msgId);
+            // If found, nothing to do.
+            if (itExists != conversation.interactions->end())
+                return;
+            int index = conversation.interactions->indexOfMessage(msg.parentId);
+            if (index >= 0) {
+                auto result = conversation.interactions->insert(index + 1, qMakePair(msgId, msg));
+                if (!result.second) {
+                    return;
+                }
+            } else {
+                return;
             }
         }
-        if (updateUnread) {
+        auto updateUnread = msg.authorUri != linked.owner.profileInfo.uri;
+        if (updateUnread)
             conversation.unreadMessages++;
-        }
-        if (msg.type == interaction::Type::MERGE) {
-            invalidateModel();
-            return;
-        }
-        conversation.lastMessageUid = conversation.interactions->lastMessageUid();
         conversation.lastSelfMessageId = conversation.interactions->lastSelfMessageId(
             linked.owner.profileInfo.uri);
         invalidateModel();
-        if (!interaction::isOutgoing(msg)) {
+        if (!interaction::isOutgoing(msg) && updateUnread) {
             Q_EMIT behaviorController.newUnreadInteraction(linked.owner.id,
                                                            conversationId,
                                                            msgId,
@@ -2616,15 +2468,93 @@ ConversationModelPimpl::slotMessageReceived(const QString& accountId,
         }
         Q_EMIT linked.newInteraction(conversationId, msgId, msg);
         Q_EMIT linked.modelChanged();
-        if (msg.status == interaction::Status::TRANSFER_AWAITING_HOST) {
-            handleIncomingFile(conversationId, msgId, message["totalSize"].toInt());
+        if (msg.status == interaction::Status::TRANSFER_AWAITING_HOST && updateUnread) {
+            handleIncomingFile(conversationId,
+                               msgId,
+                               QString(message.body.value("totalSize")).toInt());
+        }
+        Q_EMIT linked.dataChanged(indexOf(conversationId));
+    } catch (const std::exception& e) {
+        qDebug() << "messages received for not existing conversation";
+    }
+}
+
+void
+ConversationModelPimpl::slotMessageUpdated(const QString& accountId,
+                                           const QString& conversationId,
+                                           const SwarmMessage& message)
+{
+    if (accountId != linked.owner.id)
+        return;
+    try {
+        auto& conversation = getConversationForUid(conversationId).get();
+        QString msgId = message.id;
+        auto msg = interaction::Info(message, linked.owner.profileInfo.uri);
+
+        {
+            std::lock_guard<std::mutex> lk(interactionsLocks[conversation.uid]);
+            auto itExists = conversation.interactions->find(msgId);
+            // If not found, nothing to do.
+            if (itExists == conversation.interactions->end())
+                return;
+            // Now there is two cases:
+            // ParentId changed, in this case, remove previous message and re-insert at new place
+            // Else, just update body
+            conversation.interactions->erase(itExists);
+            int index = conversation.interactions->indexOfMessage(msg.parentId);
+            if (index >= 0) {
+                auto result = conversation.interactions->insert(index + 1, qMakePair(msgId, msg));
+                if (!result.second) {
+                    return;
+                }
+            } else {
+                return;
+            }
         }
+        invalidateModel();
+        Q_EMIT linked.modelChanged();
         Q_EMIT linked.dataChanged(indexOf(conversationId));
     } catch (const std::exception& e) {
         qDebug() << "messages received for not existing conversation";
     }
 }
 
+void
+ConversationModelPimpl::slotReactionAdded(const QString& accountId,
+                                          const QString& conversationId,
+                                          const QString& messageId,
+                                          const MapStringString& reaction)
+{
+    if (accountId != linked.owner.id) {
+        return;
+    }
+    try {
+        // qInfo() << "Add Reaction to " << messageId << " in " << conversationId;
+        auto& conversation = getConversationForUid(conversationId).get();
+        conversation.interactions->addReaction(messageId, reaction);
+    } catch (const std::exception& e) {
+        qWarning() << e.what();
+    }
+}
+
+void
+ConversationModelPimpl::slotReactionRemoved(const QString& accountId,
+                                            const QString& conversationId,
+                                            const QString& messageId,
+                                            const QString& reactionId)
+{
+    if (accountId != linked.owner.id) {
+        return;
+    }
+    try {
+        // qInfo() << "Remove Reaction from " << messageId << " in " << conversationId;
+        auto& conversation = getConversationForUid(conversationId).get();
+        conversation.interactions->rmReaction(messageId, reactionId);
+    } catch (const std::exception& e) {
+        qWarning() << e.what();
+    }
+}
+
 void
 ConversationModelPimpl::slotConversationProfileUpdated(const QString& accountId,
                                                        const QString& conversationId,
@@ -2641,51 +2571,6 @@ ConversationModelPimpl::slotConversationProfileUpdated(const QString& accountId,
     }
 }
 
-bool
-ConversationModelPimpl::insertSwarmInteraction(const QString& interactionId,
-                                               interaction::Info& interaction,
-                                               conversation::Info& conversation,
-                                               bool insertAtBegin)
-{
-    std::lock_guard<std::mutex> lk(interactionsLocks[conversation.uid]);
-    auto itExists = conversation.interactions->find(interactionId);
-    if (itExists != conversation.interactions->end()) {
-        // Erase interaction if exists, as it will be updated via a re-insertion
-        if (itExists->second.previousBodies.size() != 0) {
-            // If the message was edited, we should keep this state
-            interaction.body = itExists->second.body;
-            interaction.previousBodies = itExists->second.previousBodies;
-        }
-        itExists = conversation.interactions->erase(itExists);
-        if (itExists != conversation.interactions->end()) {
-            // next interaction doesn't have parent anymore.
-            conversation.parentsId[itExists->first] = interactionId;
-        }
-    }
-    int index = conversation.interactions->indexOfMessage(interaction.parentId);
-    if (index >= 0) {
-        auto result = conversation.interactions->insert(index + 1,
-                                                        qMakePair(interactionId, interaction));
-        if (!result.second)
-            return false;
-    } else {
-        auto result = conversation.interactions->insert(std::make_pair(interactionId, interaction),
-                                                        insertAtBegin);
-        if (!result.second)
-            return false;
-        if (!interaction.parentId.isEmpty())
-            conversation.parentsId[interactionId] = interaction.parentId;
-    }
-    if (!conversation.parentsId.values().contains(interactionId)) {
-        return true;
-    }
-    auto msgIds = conversation.parentsId.keys(interactionId);
-    conversation.interactions->moveMessages(msgIds, interactionId);
-    for (auto& msg : msgIds)
-        conversation.parentsId.remove(msg);
-    return true;
-}
-
 void
 ConversationModelPimpl::slotConversationRequestReceived(const QString& accountId,
                                                         const QString&,
@@ -2749,10 +2634,7 @@ ConversationModelPimpl::slotConversationReady(const QString& accountId,
         conversation.needsSyncing = false;
         Q_EMIT linked.conversationUpdated(conversationId);
         Q_EMIT linked.dataChanged(conversationIdx);
-        ConfigurationManager::instance().loadConversationMessages(linked.owner.id,
-                                                                  conversationId,
-                                                                  "",
-                                                                  0);
+        ConfigurationManager::instance().loadConversation(linked.owner.id, conversationId, "", 0);
         auto& peers = peersForConversation(conversation);
         if (peers.size() == 1)
             Q_EMIT linked.conversationReady(conversationId, peers.front());
@@ -2895,7 +2777,6 @@ ConversationModelPimpl::slotActiveCallsChanged(const QString& accountId,
 void
 ConversationModelPimpl::slotContactAdded(const QString& contactUri)
 {
-
     QString convId;
     try {
         convId = linked.owner.contactModel->getContact(contactUri).conversationId;
@@ -2904,14 +2785,15 @@ ConversationModelPimpl::slotContactAdded(const QString& contactUri)
     }
 
     auto isSwarm = !convId.isEmpty();
-    auto conv = !isSwarm? storage::getConversationsWithPeer(db, contactUri) : VectorString {convId};
+    auto conv = !isSwarm ? storage::getConversationsWithPeer(db, contactUri)
+                         : VectorString {convId};
     if (conv.isEmpty()) {
         if (linked.owner.profileInfo.type == profile::Type::SIP) {
             auto convId = storage::beginConversationWithPeer(db,
-                                                              contactUri,
-                                                              true,
-                                                              linked.owner.contactModel->getAddedTs(
-                                                                  contactUri));
+                                                             contactUri,
+                                                             true,
+                                                             linked.owner.contactModel->getAddedTs(
+                                                                 contactUri));
             addConversationWith(convId, contactUri, false);
             Q_EMIT linked.conversationReady(convId, contactUri);
             Q_EMIT linked.newConversation(convId);
@@ -2922,7 +2804,7 @@ ConversationModelPimpl::slotContactAdded(const QString& contactUri)
     try {
         auto& conversation = getConversationForUid(convId).get();
         MapStringString details = ConfigurationManager::instance()
-                                          .conversationInfos(linked.owner.id, conversation.uid);
+                                      .conversationInfos(linked.owner.id, conversation.uid);
         bool needsSyncing = details["syncing"] == "true";
         if (conversation.needsSyncing != needsSyncing) {
             conversation.isRequest = false;
@@ -2948,9 +2830,7 @@ ConversationModelPimpl::addContactRequest(const QString& contactUri)
         return;
     } catch (std::out_of_range&) {
         // no conversation exists. Add contact request
-        conversation::Info conversation;
-        conversation.uid = contactUri;
-        conversation.accountId = linked.owner.id;
+        conversation::Info conversation(contactUri, &linked.owner);
         conversation.participants = {{contactUri, member::Role::INVITED}};
         conversation.mode = conversation::Mode::NON_SWARM;
         conversation.isRequest = true;
@@ -2974,12 +2854,10 @@ ConversationModelPimpl::addConversationRequest(const MapStringString& convReques
     QString callId, confId;
     const MapStringString& details = ConfigurationManager::instance()
                                          .conversationInfos(linked.owner.id, convId);
-    conversation::Info conversation;
-    conversation.uid = convId;
+    conversation::Info conversation(convId, &linked.owner);
     conversation.infos = details;
     conversation.callId = callId;
     conversation.confId = confId;
-    conversation.accountId = linked.owner.id;
     conversation.participants = {{linked.owner.profileInfo.uri, member::Role::INVITED},
                                  {peerUri, member::Role::MEMBER}};
     conversation.mode = mode;
@@ -2993,8 +2871,10 @@ ConversationModelPimpl::addConversationRequest(const MapStringString& convReques
     };
     auto msg = interaction::Info(messageMap, linked.owner.profileInfo.uri);
 
-    insertSwarmInteraction(convId, msg, conversation, true);
-    conversation.lastMessageUid = convId;
+    {
+        std::lock_guard<std::mutex> lk(interactionsLocks[convId]);
+        conversation.interactions->insert(std::make_pair(convId, msg), true);
+    }
 
     // add the author to the contact model's contact list as a PENDING
     // if they aren't already a contact
@@ -3023,7 +2903,7 @@ ConversationModelPimpl::addConversationRequest(const MapStringString& convReques
     Q_EMIT linked.modelChanged();
     if (!callId.isEmpty()) {
         // If we replace a non swarm request by a swarm request while having a call.
-        Q_EMIT linked.selectConversation(convId);
+        linked.selectConversation(convId);
     }
     if (emitToClient)
         Q_EMIT behaviorController.newTrustRequest(linked.owner.id, convId, peerUri);
@@ -3032,7 +2912,7 @@ ConversationModelPimpl::addConversationRequest(const MapStringString& convReques
 void
 ConversationModelPimpl::slotPendingContactAccepted(const QString& uri)
 {
-    auto type = linked.owner.profileInfo.type;
+    profile::Type type;
     try {
         type = linked.owner.contactModel->getContact(uri).profileInfo.type;
     } catch (std::out_of_range& e) {
@@ -3110,16 +2990,14 @@ ConversationModelPimpl::slotContactModelUpdated(const QString& uri)
     searchResults.clear();
     auto users = linked.owner.contactModel->getSearchResults();
     for (auto& user : users) {
-        conversation::Info conversationInfo;
+        auto uid = linked.owner.profileInfo.type == profile::Type::SIP ? "SEARCHSIP"
+                                                                       : user.profileInfo.uri;
+        conversation::Info conversationInfo(uid, &linked.owner);
         // For SIP, we always got one search result, so "" is ok as there is no empty uri
         // For Jami accounts, the nameserver can return several results, so we use the uniqueness of
         // the id as id for a temporary conversation.
-        conversationInfo.uid = linked.owner.profileInfo.type == profile::Type::SIP
-                                   ? "SEARCHSIP"
-                                   : user.profileInfo.uri;
         conversationInfo.participants.append(
             member::Member {user.profileInfo.uri, member::Role::MEMBER});
-        conversationInfo.accountId = linked.owner.id;
         searchResults.emplace_front(std::move(conversationInfo));
     }
     Q_EMIT linked.searchResultUpdated();
@@ -3129,6 +3007,11 @@ ConversationModelPimpl::slotContactModelUpdated(const QString& uri)
 void
 ConversationModelPimpl::addSwarmConversation(const QString& convId)
 {
+    if (Lrc::dbusIsValid()) {
+        // Because the daemon may have already loaded interactions
+        // we clear them to receive all signals
+        ConfigurationManager::instance().clearCache(linked.owner.id, convId);
+    }
     QVector<member::Member> participants;
     const VectorMapStringString& members = ConfigurationManager::instance()
                                                .getConversationMembers(linked.owner.id, convId);
@@ -3137,10 +3020,8 @@ ConversationModelPimpl::addSwarmConversation(const QString& convId)
     const MapStringString& details = ConfigurationManager::instance()
                                          .conversationInfos(linked.owner.id, convId);
     auto mode = conversation::to_mode(details["mode"].toInt());
-    conversation::Info conversation;
+    conversation::Info conversation(convId, &linked.owner);
     conversation.infos = details;
-    conversation.uid = convId;
-    conversation.accountId = linked.owner.id;
     VectorMapStringString activeCalls = ConfigurationManager::instance()
                                             .getActiveCalls(linked.owner.id, convId);
     conversation.activeCalls = activeCalls;
@@ -3212,14 +3093,16 @@ ConversationModelPimpl::addSwarmConversation(const QString& convId)
         };
         auto msg = interaction::Info(messageMap, linked.owner.profileInfo.uri);
 
-        insertSwarmInteraction(convId, msg, conversation, true);
-        conversation.lastMessageUid = convId;
+        {
+            std::lock_guard<std::mutex> lk(interactionsLocks[convId]);
+            conversation.interactions->insert(std::make_pair(convId, msg), true);
+        }
         conversation.needsSyncing = true;
         Q_EMIT linked.conversationUpdated(conversation.uid);
         Q_EMIT linked.dataChanged(indexOf(conversation.uid));
     }
     emplaceBackConversation(std::move(conversation));
-    ConfigurationManager::instance().loadConversationMessages(linked.owner.id, convId, "", 1);
+    ConfigurationManager::instance().loadConversation(linked.owner.id, convId, "", 1);
 }
 
 void
@@ -3227,9 +3110,7 @@ ConversationModelPimpl::addConversationWith(const QString& convId,
                                             const QString& contactUri,
                                             bool isRequest)
 {
-    conversation::Info conversation;
-    conversation.uid = convId;
-    conversation.accountId = linked.owner.id;
+    conversation::Info conversation(convId, &linked.owner);
     conversation.participants = {{contactUri, member::Role::MEMBER}};
     conversation.mode = conversation::Mode::NON_SWARM;
     conversation.needsSyncing = false;
@@ -3510,14 +3391,14 @@ ConversationModelPimpl::addOrUpdateCallMessage(const QString& callId,
     // update the db
     auto msgId = storage::addOrUpdateMessage(db, conv_it->uid, msg, callId);
     // now set the formatted call message string in memory only
-    msg.body = storage::getCallInteractionString(msg.authorUri == linked.owner.profileInfo.uri, msg);
+    msg.body = interaction::getCallInteractionString(msg.authorUri == linked.owner.profileInfo.uri,
+                                                     msg);
     bool newInteraction = false;
     {
         std::lock_guard<std::mutex> lk(interactionsLocks[conv_it->uid]);
         auto interactionIt = conv_it->interactions->find(msgId);
         newInteraction = interactionIt == conv_it->interactions->end();
         if (newInteraction) {
-            conv_it->lastMessageUid = msgId;
             conv_it->interactions->emplace(msgId, msg);
         } else {
             interactionIt->second = msg;
@@ -3627,7 +3508,6 @@ ConversationModelPimpl::addIncomingMessage(const QString& peerId,
             std::lock_guard<std::mutex> lk(interactionsLocks[conversations[conversationIdx].uid]);
             conversations[conversationIdx].interactions->emplace(msgId, msg);
         }
-        conversations[conversationIdx].lastMessageUid = msgId;
         conversations[conversationIdx].unreadMessages = getNumberOfUnreadMessagesFor(convIds[0]);
     }
 
@@ -4115,7 +3995,6 @@ ConversationModelPimpl::slotTransferStatusCreated(const QString& fileId, datatra
             std::lock_guard<std::mutex> lk(interactionsLocks[conversations[conversationIdx].uid]);
             conversations[conversationIdx].interactions->emplace(interactionId, interaction);
         }
-        conversations[conversationIdx].lastMessageUid = interactionId;
         conversations[conversationIdx].unreadMessages = getNumberOfUnreadMessagesFor(convId);
     }
     Q_EMIT behaviorController.newUnreadInteraction(linked.owner.id,
diff --git a/src/libclient/dbus/metatypes.h b/src/libclient/dbus/metatypes.h
index c8347c040..375308152 100644
--- a/src/libclient/dbus/metatypes.h
+++ b/src/libclient/dbus/metatypes.h
@@ -42,6 +42,7 @@ Q_DECLARE_METATYPE(VectorString)
 Q_DECLARE_METATYPE(MapStringVectorString)
 Q_DECLARE_METATYPE(VectorVectorByte)
 Q_DECLARE_METATYPE(DataTransferInfo)
+Q_DECLARE_METATYPE(SwarmMessage)
 Q_DECLARE_METATYPE(uint64_t)
 Q_DECLARE_METATYPE(Message)
 
@@ -86,6 +87,36 @@ operator>>(const QDBusArgument& argument, DataTransferInfo& info)
     return argument;
 }
 
+static inline QDBusArgument&
+operator<<(QDBusArgument& argument, const SwarmMessage& m)
+{
+    argument.beginStructure();
+    argument << m.id;
+    argument << m.type;
+    argument << m.linearizedParent;
+    argument << m.body;
+    argument << m.reactions;
+    argument << m.editions;
+    argument.endStructure();
+
+    return argument;
+}
+
+static inline const QDBusArgument&
+operator>>(const QDBusArgument& argument, SwarmMessage& m)
+{
+    argument.beginStructure();
+    argument >> m.id;
+    argument >> m.type;
+    argument >> m.linearizedParent;
+    argument >> m.body;
+    argument >> m.reactions;
+    argument >> m.editions;
+    argument.endStructure();
+
+    return argument;
+}
+
 static inline QDBusArgument&
 operator<<(QDBusArgument& argument, const Message& m)
 {
@@ -140,6 +171,10 @@ registerCommTypes()
     qDBusRegisterMetaType<VectorVectorByte>();
     qRegisterMetaType<DataTransferInfo>("DataTransferInfo");
     qDBusRegisterMetaType<DataTransferInfo>();
+    qRegisterMetaType<SwarmMessage>("SwarmMessage");
+    qDBusRegisterMetaType<SwarmMessage>();
+    qRegisterMetaType<VectorSwarmMessage>("VectorSwarmMessage");
+    qDBusRegisterMetaType<VectorSwarmMessage>();
     qRegisterMetaType<Message>("Message");
     qDBusRegisterMetaType<Message>();
     qRegisterMetaType<QVector<Message>>("QVector<Message>");
diff --git a/src/libclient/messagelistmodel.cpp b/src/libclient/messagelistmodel.cpp
index 108165ca9..6e3022ca3 100644
--- a/src/libclient/messagelistmodel.cpp
+++ b/src/libclient/messagelistmodel.cpp
@@ -21,6 +21,9 @@
 
 #include "messagelistmodel.h"
 
+#include "authority/storagehelper.h"
+#include "api/accountmodel.h"
+#include "api/contactmodel.h"
 #include "api/conversationmodel.h"
 #include "api/interaction.h"
 #include "qtwrapper/conversions_wrap.hpp"
@@ -36,9 +39,9 @@ using constIterator = MessageListModel::constIterator;
 using iterator = MessageListModel::iterator;
 using reverseIterator = MessageListModel::reverseIterator;
 
-MessageListModel::MessageListModel(QObject* parent)
+MessageListModel::MessageListModel(const account::Info* account, QObject* parent)
     : QAbstractListModel(parent)
-
+    , account_(account)
 {}
 
 QPair<iterator, bool>
@@ -195,8 +198,16 @@ MessageListModel::clear()
     Q_EMIT beginResetModel();
     interactions_.clear();
     replyTo_.clear();
-    editedBodies_.clear();
-    reactedMessages_.clear();
+    Q_EMIT endResetModel();
+}
+
+void
+MessageListModel::reloadHistory()
+{
+    Q_EMIT beginResetModel();
+    for (auto& interaction : interactions_) {
+        interaction.second.linkPreviewInfo.clear();
+    }
     Q_EMIT endResetModel();
 }
 
@@ -266,54 +277,6 @@ MessageListModel::indexOfMessage(const QString& msgId, bool reverse) const
                    : getIndex(interactions_.begin(), interactions_.end());
 }
 
-void
-MessageListModel::moveMessages(QList<QString> msgIds, const QString& parentId)
-{
-    for (auto msgId : msgIds) {
-        moveMessage(msgId, parentId);
-    }
-}
-
-void
-MessageListModel::moveMessage(const QString& msgId, const QString& parentId)
-{
-    int currentIndex = indexOfMessage(msgId);
-    if (currentIndex == -1) {
-        qWarning() << "Incorrect index detected in MessageListModel::moveMessage";
-        return;
-    }
-
-    // if we have a next element check if it is a child interaction
-    QString childMessageIdToMove;
-    if (currentIndex < (interactions_.size() - 1)) {
-        const auto& next = interactions_.at(currentIndex + 1);
-        if (next.second.parentId == msgId) {
-            childMessageIdToMove = next.first;
-        }
-    }
-
-    auto endIdx = currentIndex;
-    auto pId = msgId;
-
-    // move a message
-    int newIndex = indexOfMessage(parentId) + 1;
-    if (newIndex >= interactions_.size()) {
-        newIndex = interactions_.size() - 1;
-        // If we can move all the messages after the current one, we can do it directly
-        childMessageIdToMove.clear();
-        endIdx = std::max(endIdx, newIndex - 1);
-    }
-
-    if (currentIndex == newIndex || newIndex == -1)
-        return;
-
-    // Pretty every messages is moved
-    moveMessages(currentIndex, endIdx, newIndex);
-    // move a child message
-    if (!childMessageIdToMove.isEmpty())
-        moveMessage(childMessageIdToMove, msgId);
-}
-
 void
 MessageListModel::updateReplies(item_t& message)
 {
@@ -359,19 +322,6 @@ MessageListModel::removeMessage(int index, iterator it)
     Q_EMIT endRemoveRows();
 }
 
-void
-MessageListModel::moveMessages(int from, int last, int to)
-{
-    if (last < from)
-        return;
-    QModelIndex sourceIndex = QAbstractListModel::index(from, 0);
-    QModelIndex destinationIndex = QAbstractListModel::index(to, 0);
-    Q_EMIT beginMoveRows(sourceIndex, from, last, destinationIndex, to);
-    for (int i = 0; i < (last - from); ++i)
-        interactions_.move(last, to);
-    Q_EMIT endMoveRows();
-}
-
 bool
 MessageListModel::contains(const QString& msgId)
 {
@@ -433,8 +383,26 @@ MessageListModel::dataForItem(item_t item, int, int role) const
         return QVariant(item.first);
     case Role::Author:
         return QVariant(item.second.authorUri);
-    case Role::Body:
+    case Role::Body: {
+        if (account_) {
+            if (item.second.type == lrc::api::interaction::Type::CALL) {
+                return QVariant(
+                    interaction::getCallInteractionString(item.second.authorUri
+                                                              == account_->profileInfo.uri,
+                                                          item.second));
+            } else if (item.second.type == lrc::api::interaction::Type::CONTACT) {
+                auto bestName = item.second.authorUri == account_->profileInfo.uri
+                                    ? account_->accountModel->bestNameForAccount(account_->id)
+                                    : account_->contactModel->bestNameForContact(
+                                        item.second.authorUri);
+                return QVariant(
+                    interaction::getContactInteractionString(bestName,
+                                                             interaction::to_action(
+                                                                 item.second.commit["action"])));
+            }
+        }
         return QVariant(item.second.body);
+    }
     case Role::Timestamp:
         return QVariant::fromValue(item.second.timestamp);
     case Role::Duration:
@@ -544,6 +512,45 @@ MessageListModel::addHyperlinkInfo(const QString& messageId, const QVariantMap&
     Q_EMIT dataChanged(modelIndex, modelIndex, {Role::LinkPreviewInfo});
 }
 
+void
+MessageListModel::addReaction(const QString& messageId, const MapStringString& reaction)
+{
+    int index = getIndexOfMessage(messageId);
+    if (index == -1)
+        return;
+    QModelIndex modelIndex = QAbstractListModel::index(index, 0);
+
+    auto emoji = api::interaction::Emoji {reaction["id"], reaction["body"]};
+    auto& pList = interactions_[index].second.reactions[reaction["author"]];
+    QList<QVariant> newList = pList.toList();
+    newList.emplace_back(QVariant::fromValue(emoji));
+    pList = QVariantList::fromVector(newList);
+    Q_EMIT dataChanged(modelIndex, modelIndex, {Role::Reactions});
+}
+
+void
+MessageListModel::rmReaction(const QString& messageId, const QString& reactionId)
+{
+    int index = getIndexOfMessage(messageId);
+    if (index == -1)
+        return;
+    QModelIndex modelIndex = QAbstractListModel::index(index, 0);
+
+    auto& reactions = interactions_[index].second.reactions;
+    for (const auto& key : reactions.keys()) {
+        QList<QVariant> emojis = reactions[key].toList();
+        for (auto it = emojis.begin(); it != emojis.end(); ++it) {
+            auto emoji = it->value<api::interaction::Emoji>();
+            if (emoji.commitId == reactionId) {
+                emojis.erase(it);
+                reactions[key] = emojis;
+                Q_EMIT dataChanged(modelIndex, modelIndex, {Role::Reactions});
+                return;
+            }
+        }
+    }
+}
+
 void
 MessageListModel::setParsedMessage(const QString& messageId, const QString& parsed)
 {
@@ -610,147 +617,6 @@ MessageListModel::emitDataChanged(const QString& msgId, VectorInt roles)
     Q_EMIT dataChanged(modelIndex, modelIndex, roles);
 }
 
-void
-MessageListModel::addEdition(const QString& msgId, const interaction::Info& info, bool end)
-{
-    auto editedId = info.commit["edit"];
-    if (editedId.isEmpty())
-        return;
-    auto& edited = editedBodies_[editedId];
-    auto editedMsgIt = std::find_if(edited.begin(), edited.end(), [&](const auto& v) {
-        return msgId == v.commitId;
-    });
-    if (editedMsgIt != edited.end())
-        return; // Already added
-    auto value = interaction::Body {msgId, info.body, info.timestamp};
-    if (end)
-        edited.push_back(value);
-    else
-        edited.push_front(value);
-    auto editedIt = find(editedId);
-    if (editedIt != interactions_.end()) {
-        // If already there, we can update the content
-        editMessage(editedId, editedIt->second);
-        if (!editedIt->second.react_to.isEmpty()) {
-            auto reactToIt = find(editedIt->second.react_to);
-            if (reactToIt != interactions_.end())
-                reactToMessage(editedIt->second.react_to, reactToIt->second);
-        }
-    }
-}
-
-void
-MessageListModel::addReaction(const QString& messageId, const QString& reactionId)
-{
-    auto itReacted = reactedMessages_.find(messageId);
-    if (itReacted != reactedMessages_.end()) {
-        itReacted->insert(reactionId);
-    } else {
-        QSet<QString> emojiList;
-        emojiList.insert(reactionId);
-        reactedMessages_.insert(messageId, emojiList);
-    }
-    auto interaction = find(reactionId);
-    if (interaction != interactions_.end()) {
-        // Edit reaction if needed
-        editMessage(reactionId, interaction->second);
-    }
-}
-
-QVariantMap
-MessageListModel::convertReactMessagetoQVariant(const QSet<QString>& emojiIdList)
-{
-    QVariantMap convertedMap;
-    QMap<QString, QStringList> mapStringEmoji;
-    for (auto emojiId = emojiIdList.begin(); emojiId != emojiIdList.end(); emojiId++) {
-        auto interaction = find(*emojiId);
-        if (interaction != interactions_.end()) {
-            auto author = interaction->second.authorUri;
-            auto body = interaction->second.body;
-            if (!body.isEmpty()) {
-                auto itAuthor = mapStringEmoji.find(author);
-                if (itAuthor != mapStringEmoji.end()) {
-                    mapStringEmoji[author].append(body);
-                } else {
-                    QStringList emojiList;
-                    emojiList.append(body);
-                    mapStringEmoji.insert(author, emojiList);
-                }
-            }
-        }
-    }
-    for (auto i = mapStringEmoji.begin(); i != mapStringEmoji.end(); i++) {
-        convertedMap.insert(i.key(), i.value());
-    }
-    return convertedMap;
-}
-
-void
-MessageListModel::editMessage(const QString& msgId, interaction::Info& info)
-{
-    auto it = editedBodies_.find(msgId);
-    if (it != editedBodies_.end()) {
-        if (info.previousBodies.isEmpty()) {
-            info.previousBodies.push_back(interaction::Body {msgId, info.body, info.timestamp});
-        }
-        // Find if already added (because MessageReceived can be triggered
-        // multiple times for same message)
-        for (const auto& editedBody : *it) {
-            auto itCommit = std::find_if(info.previousBodies.begin(),
-                                         info.previousBodies.end(),
-                                         [&](const auto& element) {
-                                             return element.commitId == editedBody.commitId;
-                                         });
-            if (itCommit == info.previousBodies.end()) {
-                info.previousBodies.push_back(editedBody);
-            }
-        }
-        info.body = it->rbegin()->body;
-        info.parsedBody.clear();
-        editedBodies_.erase(it);
-        emitDataChanged(msgId,
-                        {MessageList::Role::Body,
-                         MessageList::Role::ParsedBody,
-                         MessageList::Role::PreviousBodies,
-                         MessageList::Role::IsEmojiOnly});
-
-        // Body changed, replies should update
-        for (const auto& replyId : replyTo_[msgId]) {
-            int index = getIndexOfMessage(replyId);
-            if (index == -1)
-                continue;
-            QModelIndex modelIndex = QAbstractListModel::index(index, 0);
-            Q_EMIT dataChanged(modelIndex, modelIndex, {Role::ReplyToBody});
-        }
-    }
-}
-
-void
-MessageListModel::reactToMessage(const QString& msgId, interaction::Info& info)
-{
-    // If already there, we can update the content
-    auto itReact = reactedMessages_.find(msgId);
-
-    if (itReact != reactedMessages_.end()) {
-        auto convertedMap = convertReactMessagetoQVariant(reactedMessages_[msgId]);
-        info.reactions = convertedMap;
-        emitDataChanged(find(msgId), {Role::Reactions});
-    }
-}
-
-QString
-MessageListModel::lastMessageUid() const
-{
-    for (auto it = interactions_.rbegin(); it != interactions_.rend(); ++it) {
-        auto lastType = it->second.type;
-        if (lastType != interaction::Type::MERGE and lastType != interaction::Type::EDITED
-            and !it->second.body.isEmpty()) {
-            return it->first;
-        }
-    }
-    return {};
-}
-
 QString
 MessageListModel::lastSelfMessageId(const QString& id) const
 {
@@ -763,20 +629,4 @@ MessageListModel::lastSelfMessageId(const QString& id) const
     }
     return {};
 }
-
-QString
-MessageListModel::findEmojiReaction(const QString& emoji,
-                                    const QString& authorURI,
-                                    const QString& messageId)
-{
-    auto& messageReactions = reactedMessages_[messageId];
-    for (auto it = messageReactions.begin(); it != messageReactions.end(); it++) {
-        auto interaction = find(*it);
-        if (interaction != interactions_.end() && interaction->second.body == emoji
-            && interaction->second.authorUri == authorURI) {
-            return *it;
-        }
-    }
-    return {};
-}
 } // namespace lrc
diff --git a/src/libclient/messagelistmodel.h b/src/libclient/messagelistmodel.h
index 7345862fd..432ad1d0d 100644
--- a/src/libclient/messagelistmodel.h
+++ b/src/libclient/messagelistmodel.h
@@ -20,6 +20,7 @@
 #pragma once
 
 #include "api/interaction.h"
+#include "api/account.h"
 
 #include <QAbstractListModel>
 
@@ -79,7 +80,7 @@ public:
     typedef QList<QPair<QString, interaction::Info>>::Iterator iterator;
     typedef QList<QPair<QString, interaction::Info>>::reverse_iterator reverseIterator;
 
-    explicit MessageListModel(QObject* parent = nullptr);
+    explicit MessageListModel(const account::Info* account, QObject* parent = nullptr);
     ~MessageListModel() = default;
 
     // map functions
@@ -105,6 +106,7 @@ public:
     reverseIterator rbegin();
     Q_INVOKABLE int size() const;
     void clear();
+    void reloadHistory();
     bool empty() const;
     interaction::Info at(const QString& intId) const;
     QPair<QString, interaction::Info> front() const;
@@ -113,7 +115,6 @@ public:
 
     QPair<iterator, bool> insert(int index, QPair<QString, interaction::Info> message);
     int indexOfMessage(const QString& msgId, bool reverse = true) const;
-    void moveMessages(QList<QString> msgIds, const QString& parentId);
 
     int rowCount(const QModelIndex& parent = QModelIndex()) const override;
     Q_INVOKABLE virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
@@ -123,6 +124,8 @@ public:
     bool contains(const QString& msgId);
     int getIndexOfMessage(const QString& messageId) const;
     void addHyperlinkInfo(const QString& messageId, const QVariantMap& info);
+    void addReaction(const QString& messageId, const MapStringString& reaction);
+    void rmReaction(const QString& messageId, const QString& reactionId);
     void setParsedMessage(const QString& messageId, const QString& parsed);
 
     void setRead(const QString& peer, const QString& messageId);
@@ -136,18 +139,9 @@ public:
     void emitDataChanged(const QString& msgId, VectorInt roles = {});
     bool isOnlyEmoji(const QString& text) const;
 
-    void addEdition(const QString& msgId, const interaction::Info& info, bool end);
-    void addReaction(const QString& messageId, const QString& reactionId);
-    void editMessage(const QString& msgId, interaction::Info& info);
-    void reactToMessage(const QString& msgId, interaction::Info& info);
     QVariantMap convertReactMessagetoQVariant(const QSet<QString>&);
-    QString lastMessageUid() const;
     QString lastSelfMessageId(const QString& id) const;
 
-    QString findEmojiReaction(const QString& emoji,
-                              const QString& authorURI,
-                              const QString& messageId);
-
 protected:
     using Role = MessageList::Role;
 
@@ -161,17 +155,12 @@ private:
     QMap<QString, QString> lastDisplayedMessageUid_;
     QMap<QString, QStringList> messageToReaders_;
     QMap<QString, QSet<QString>> replyTo_;
+    const account::Info* account_;
     void updateReplies(item_t& message);
-    QMap<QString, QVector<interaction::Body>> editedBodies_;
-
-    // key = messageId and values = QSet of reactionIds
-    QMap<QString, QSet<QString>> reactedMessages_;
 
-    void moveMessage(const QString& msgId, const QString& parentId);
     void insertMessage(int index, item_t& message);
     iterator insertMessage(iterator it, item_t& message);
     void removeMessage(int index, iterator it);
-    void moveMessages(int from, int last, int to);
 };
 } // namespace api
 } // namespace lrc
diff --git a/src/libclient/qtwrapper/configurationmanager_wrap.h b/src/libclient/qtwrapper/configurationmanager_wrap.h
index 2ecafd2a2..448dda083 100644
--- a/src/libclient/qtwrapper/configurationmanager_wrap.h
+++ b/src/libclient/qtwrapper/configurationmanager_wrap.h
@@ -259,15 +259,25 @@ public:
                 }),
         };
         conversationsHandlers
-            = {exportable_callback<ConversationSignal::ConversationLoaded>(
+            = {exportable_callback<ConversationSignal::SwarmLoaded>(
                    [this](uint32_t id,
                           const std::string& accountId,
                           const std::string& conversationId,
-                          const std::vector<std::map<std::string, std::string>>& messages) {
-                       Q_EMIT conversationLoaded(id,
-                                                 QString(accountId.c_str()),
-                                                 QString(conversationId.c_str()),
-                                                 convertVecMap(messages));
+                          const std::vector<libjami::SwarmMessage>& messages) {
+                       VectorSwarmMessage vec;
+                       for (const auto& msg : messages) {
+                           vec.push_back({msg.id.c_str(),
+                                          msg.type.c_str(),
+                                          msg.linearizedParent.c_str(),
+                                          convertMap(msg.body),
+                                          convertVecMap(msg.reactions),
+                                          convertVecMap(msg.editions)});
+                       }
+
+                       Q_EMIT swarmLoaded(id,
+                                          QString(accountId.c_str()),
+                                          QString(conversationId.c_str()),
+                                          vec);
                    }),
                exportable_callback<ConversationSignal::MessagesFound>(
                    [this](uint32_t id,
@@ -279,13 +289,53 @@ public:
                                             QString(conversationId.c_str()),
                                             convertVecMap(messages));
                    }),
-               exportable_callback<ConversationSignal::MessageReceived>(
+               exportable_callback<ConversationSignal::SwarmMessageReceived>(
+                   [this](const std::string& accountId,
+                          const std::string& conversationId,
+                          const libjami::SwarmMessage& message) {
+                       ::SwarmMessage msg = {message.id.c_str(),
+                                             message.type.c_str(),
+                                             message.linearizedParent.c_str(),
+                                             convertMap(message.body),
+                                             convertVecMap(message.reactions),
+                                             convertVecMap(message.editions)};
+                       Q_EMIT swarmMessageReceived(QString(accountId.c_str()),
+                                                   QString(conversationId.c_str()),
+                                                   msg);
+                   }),
+               exportable_callback<ConversationSignal::SwarmMessageUpdated>(
                    [this](const std::string& accountId,
                           const std::string& conversationId,
-                          const std::map<std::string, std::string>& message) {
-                       Q_EMIT messageReceived(QString(accountId.c_str()),
+                          const libjami::SwarmMessage& message) {
+                       ::SwarmMessage msg = {message.id.c_str(),
+                                             message.type.c_str(),
+                                             message.linearizedParent.c_str(),
+                                             convertMap(message.body),
+                                             convertVecMap(message.reactions),
+                                             convertVecMap(message.editions)};
+                       Q_EMIT swarmMessageUpdated(QString(accountId.c_str()),
+                                                  QString(conversationId.c_str()),
+                                                  msg);
+                   }),
+               exportable_callback<ConversationSignal::ReactionAdded>(
+                   [this](const std::string& accountId,
+                          const std::string& conversationId,
+                          const std::string& messageId,
+                          const std::map<std::string, std::string>& reaction) {
+                       Q_EMIT reactionAdded(QString(accountId.c_str()),
+                                            QString(conversationId.c_str()),
+                                            QString(messageId.c_str()),
+                                            convertMap(reaction));
+                   }),
+               exportable_callback<ConversationSignal::ReactionRemoved>(
+                   [this](const std::string& accountId,
+                          const std::string& conversationId,
+                          const std::string& messageId,
+                          const std::string& reactionId) {
+                       Q_EMIT reactionRemoved(QString(accountId.c_str()),
                                               QString(conversationId.c_str()),
-                                              convertMap(message));
+                                              QString(messageId.c_str()),
+                                              QString(reactionId.c_str()));
                    }),
                exportable_callback<ConversationSignal::ConversationProfileUpdated>(
                    [this](const std::string& accountId,
@@ -970,25 +1020,15 @@ public Q_SLOTS: // METHODS
                              flags);
     }
 
-    uint32_t loadConversationMessages(const QString& accountId,
-                                      const QString& conversationId,
-                                      const QString& fromId,
-                                      const int size)
-    {
-        return libjami::loadConversationMessages(accountId.toStdString(),
-                                                 conversationId.toStdString(),
-                                                 fromId.toStdString(),
-                                                 size);
-    }
-    uint32_t loadConversationUntil(const QString& accountId,
-                                   const QString& conversationId,
-                                   const QString& fromId,
-                                   const QString& toId)
+    uint32_t loadConversation(const QString& accountId,
+                              const QString& conversationId,
+                              const QString& fromId,
+                              const int size)
     {
-        return libjami::loadConversationUntil(accountId.toStdString(),
-                                              conversationId.toStdString(),
-                                              fromId.toStdString(),
-                                              toId.toStdString());
+        return libjami::loadConversation(accountId.toStdString(),
+                                         conversationId.toStdString(),
+                                         fromId.toStdString(),
+                                         size);
     }
 
     void setDefaultModerator(const QString& accountID, const QString& peerURI, const bool& state)
@@ -1070,6 +1110,11 @@ public Q_SLOTS: // METHODS
                                             convertMap(prefs));
     }
 
+    void clearCache(const QString& accountId, const QString& conversationId)
+    {
+        return libjami::clearCache(accountId.toStdString(), conversationId.toStdString());
+    }
+
     uint32_t countInteractions(const QString& accountId,
                                const QString& conversationId,
                                const QString& toId,
@@ -1169,9 +1214,24 @@ Q_SIGNALS: // SIGNALS
                             const QString& accountId,
                             const QString& conversationId,
                             const VectorMapStringString& messages);
-    void messageReceived(const QString& accountId,
+    void swarmLoaded(uint32_t requestId,
+                     const QString& accountId,
+                     const QString& conversationId,
+                     const VectorSwarmMessage& messages);
+    void swarmMessageReceived(const QString& accountId,
+                              const QString& conversationId,
+                              const SwarmMessage& message);
+    void swarmMessageUpdated(const QString& accountId,
+                             const QString& conversationId,
+                             const SwarmMessage& message);
+    void reactionAdded(const QString& accountId,
+                       const QString& conversationId,
+                       const QString& messageId,
+                       const MapStringString& message);
+    void reactionRemoved(const QString& accountId,
                          const QString& conversationId,
-                         const MapStringString& message);
+                         const QString& messageId,
+                         const QString& reactionId);
     void messagesFound(uint32_t requestId,
                        const QString& accountId,
                        const QString& conversationId,
diff --git a/src/libclient/typedefs.h b/src/libclient/typedefs.h
index 2974f6a86..14a242c37 100644
--- a/src/libclient/typedefs.h
+++ b/src/libclient/typedefs.h
@@ -72,6 +72,17 @@ struct DataTransferInfo
     QString mimetype;
 };
 
+struct SwarmMessage
+{
+    QString id;
+    QString type;
+    QString linearizedParent;
+    MapStringString body;
+    VectorMapStringString reactions;
+    VectorMapStringString editions;
+};
+typedef QVector<SwarmMessage> VectorSwarmMessage;
+
 struct Message
 {
     QString from;
diff --git a/tests/qml/src/tst_MessageOptions.qml b/tests/qml/src/tst_MessageOptions.qml
index 8385c311c..27ac2ad25 100644
--- a/tests/qml/src/tst_MessageOptions.qml
+++ b/tests/qml/src/tst_MessageOptions.qml
@@ -86,8 +86,8 @@ Item {
 
             // Add some emoji reactions (one from current account uri, one from another uri)
             emojiReactions.reactions = {
-                "currentAccountUsername": ["🌭"],
-                "notCurrentAccountUri": ["🌮"]
+                "currentAccountUsername": [{"commitId":"hotdog", "body":"🌭"}],
+                "notCurrentAccountUri": [{"commitId":"tacos", "body":"🌮"}]
             };
 
             var optionsPopup = getOptionsPopup(true, getId(), "test", 0, "test");
-- 
GitLab