diff --git a/qml.qrc b/qml.qrc
index 42a243ac2425ff5d47cb56db02690fefd6f7e07c..e7ab3be111f6ad209f5213b9d062f3f81fc0ce84 100644
--- a/qml.qrc
+++ b/qml.qrc
@@ -168,7 +168,7 @@
         <file>src/app/commoncomponents/BubbleLabel.qml</file>
         <file>src/app/commoncomponents/BackButton.qml</file>
         <file>src/app/commoncomponents/JamiSwitch.qml</file>
-        <file>src/app/mainview/components/ReadOnlyFooter.qml</file>
+        <file>src/app/mainview/components/UpdateToSwarm.qml</file>
         <file>src/app/commoncomponents/TextMessageDelegate.qml</file>
         <file>src/app/mainview/components/MessageListView.qml</file>
         <file>src/app/commoncomponents/MessageBubble.qml</file>
diff --git a/src/app/constant/JamiStrings.qml b/src/app/constant/JamiStrings.qml
index ad830bc83e8c4731e2c7eee0266a4dda7ea8d82e..9169e96729a4b481f63483bd9e17fb008162d10b 100644
--- a/src/app/constant/JamiStrings.qml
+++ b/src/app/constant/JamiStrings.qml
@@ -41,8 +41,8 @@ Item {
     property string contactSearchInvitations: qsTr("Search your invitations")
     property string invitations: qsTr("Invitations")
     property string description: qsTr("Jami is a universal communication platform, with privacy as its foundation, that relies on a free distributed network for everyone.")
-    property string contactLeft: qsTr("You are viewing a conversation where all participants other than you have left. New interactions will not be possible.")
-    property string newConversation: qsTr("Start new conversation")
+    property string updateToSwarm: qsTr("Migrating to the Swarm technology will enable syncing this conversation across multiple devices and improve reliability. The legacy conversation history will be cleared in the process.")
+    property string migrateConversation: qsTr("Migrate conversation")
 
     // DaemonReconnectWindow
     property string reconnectWarn: qsTr("Could not re-connect to the Jami daemon (jamid).\nJami will now quit.")
diff --git a/src/app/conversationlistmodelbase.cpp b/src/app/conversationlistmodelbase.cpp
index 13d69e04f1996a4d892b3dda7dc0f8781274123f..827c84bd94e12f6143288bb2fec33fe480d65003 100644
--- a/src/app/conversationlistmodelbase.cpp
+++ b/src/app/conversationlistmodelbase.cpp
@@ -147,8 +147,6 @@ ConversationListModelBase::dataForItem(item_t item, int role) const
             }
         return ret;
     }
-    case Role::ReadOnly:
-        return QVariant(item.readOnly);
     case Role::Presence: {
         // The conversation can show a green dot if at least one peer is present
         Q_FOREACH (const auto& peerUri, model_->peersForConversation(item.uid))
diff --git a/src/app/conversationlistmodelbase.h b/src/app/conversationlistmodelbase.h
index 37a2300e4aa0263b47526ad9015686da0a684678..f22e1c923ecc91e0d562c96aac93d70c91f65d5c 100644
--- a/src/app/conversationlistmodelbase.h
+++ b/src/app/conversationlistmodelbase.h
@@ -48,8 +48,7 @@
     X(IsRequest) \
     X(Mode) \
     X(Uris) \
-    X(Monikers) \
-    X(ReadOnly)
+    X(Monikers)
 
 namespace ConversationList {
 Q_NAMESPACE
diff --git a/src/app/conversationsadapter.cpp b/src/app/conversationsadapter.cpp
index de792930b7befeba39a3d85bc7138eeb19eec244..5a50608d13f122d971dfdfa20afbf28c1c836205 100644
--- a/src/app/conversationsadapter.cpp
+++ b/src/app/conversationsadapter.cpp
@@ -438,18 +438,15 @@ ConversationsAdapter::getConvInfoMap(const QString& convId)
             {"needsSyncing", convInfo.needsSyncing},
             {"isAudioOnly", isAudioOnly},
             {"callState", static_cast<int>(callState)},
-            {"callStackViewShouldShow", callStackViewShouldShow},
-            {"readOnly", convInfo.readOnly}};
+            {"callStackViewShouldShow", callStackViewShouldShow}};
 }
 
 void
 ConversationsAdapter::restartConversation(const QString& convId)
 {
-    // make sure this conversation meets the criteria of a "restartable" conv
-    // 'readOnly' implies 'isSwarm'
     auto& accInfo = lrcInstance_->getCurrentAccountInfo();
     const auto& convInfo = lrcInstance_->getConversationFromConvUid(convId);
-    if (convInfo.uid.isEmpty() || !convInfo.isCoreDialog() || !convInfo.readOnly) {
+    if (convInfo.uid.isEmpty() || !convInfo.isCoreDialog()) {
         return;
     }
 
@@ -474,7 +471,7 @@ ConversationsAdapter::restartConversation(const QString& convId)
                 [this, peerUri, &accInfo](const QString& convId) {
                     const auto& convInfo = lrcInstance_->getConversationFromConvUid(convId);
                     // 3. filter for the correct contact-conversation and select it
-                    if (!convInfo.uid.isEmpty() && convInfo.isCoreDialog() && !convInfo.readOnly
+                    if (!convInfo.uid.isEmpty() && convInfo.isCoreDialog()
                         && peerUri
                                == accInfo.conversationModel->peersForConversation(convId).at(0)) {
                         lrcInstance_->selectConversation(convId);
diff --git a/src/app/currentconversation.cpp b/src/app/currentconversation.cpp
index b27f9797054d8fddbb3286d9ff55ba2c6103cacd..1ccda9c03981339703f1db583f753c4e0b45c25c 100644
--- a/src/app/currentconversation.cpp
+++ b/src/app/currentconversation.cpp
@@ -57,7 +57,6 @@ CurrentConversation::updateData()
             set_isLegacy(convInfo.isLegacy());
             set_isCoreDialog(convInfo.isCoreDialog());
             set_isRequest(convInfo.isRequest);
-            set_readOnly(convInfo.readOnly);
             set_needsSyncing(convInfo.needsSyncing);
             set_color(Utils::getAvatarColor(convId).name());
             set_isSip(accInfo.profileInfo.type == profile::Type::SIP);
diff --git a/src/app/currentconversation.h b/src/app/currentconversation.h
index 997317959cab1dc3b718b95ed623df1ef36c9c83..72de66eda5c1c48b9d329d115b17df86862f737b 100644
--- a/src/app/currentconversation.h
+++ b/src/app/currentconversation.h
@@ -37,7 +37,6 @@ class CurrentConversation final : public QObject
     QML_PROPERTY(bool, isLegacy)
     QML_PROPERTY(bool, isCoreDialog)
     QML_PROPERTY(bool, isRequest)
-    QML_PROPERTY(bool, readOnly)
     QML_PROPERTY(bool, needsSyncing)
     QML_PROPERTY(bool, isSip)
     QML_PROPERTY(bool, isBanned)
diff --git a/src/app/mainview/components/ChatView.qml b/src/app/mainview/components/ChatView.qml
index 3159a74ce26a83679b57da47b85a59f74ad0c3f6..ce29207622255d1fbf17365089feb3b8ce1e7838 100644
--- a/src/app/mainview/components/ChatView.qml
+++ b/src/app/mainview/components/ChatView.qml
@@ -160,8 +160,8 @@ Rectangle {
                     }
                 }
 
-                ReadOnlyFooter {
-                    visible: CurrentConversation.readOnly
+                UpdateToSwarm {
+                    visible: !CurrentConversation.isSwarm && !CurrentConversation.isTemporary
                     Layout.fillWidth: true
                 }
 
@@ -171,11 +171,11 @@ Rectangle {
                     visible: {
                         if (CurrentConversation.isBlocked)
                             return false
-                        else if (CurrentConversation.needsSyncing || CurrentConversation.readOnly)
+                        else if (CurrentConversation.needsSyncing)
                             return false
                         else if (CurrentConversation.isSwarm && CurrentConversation.isRequest)
                             return false
-                        return true
+                        return CurrentConversation.isSwarm || CurrentConversation.isTemporary
                     }
 
                     Layout.alignment: Qt.AlignHCenter
diff --git a/src/app/mainview/components/ChatViewHeader.qml b/src/app/mainview/components/ChatViewHeader.qml
index c63d39bb26f8eeca0d6f83422f3ee961021486a0..9c0f9d9f3923e5e82cbde6a61e404f9485c2730f 100644
--- a/src/app/mainview/components/ChatViewHeader.qml
+++ b/src/app/mainview/components/ChatViewHeader.qml
@@ -46,12 +46,9 @@ Rectangle {
     property bool interactionButtonsVisibility: {
         if (CurrentConversation.inCall)
             return false
-        if (CurrentConversation.isSwarm &&
-                CurrentConversation.readOnly)
+        if (!CurrentConversation.isTemporary && !CurrentConversation.isSwarm)
             return false
-        if (CurrentConversation.isSwarm &&
-                (CurrentConversation.isRequest ||
-                 CurrentConversation.needsSyncing))
+        if (CurrentConversation.isRequest || CurrentConversation.needsSyncing)
             return false
         return true
     }
diff --git a/src/app/mainview/components/ConversationListView.qml b/src/app/mainview/components/ConversationListView.qml
index b2eeac5c9a16d3bfa3ca447b61b4481a64814ba9..6433a9bf975bb07fc886c427fdb3272ec540a75d 100644
--- a/src/app/mainview/components/ConversationListView.qml
+++ b/src/app/mainview/components/ConversationListView.qml
@@ -133,7 +133,7 @@ JamiListView {
                 "isSwarm": model.dataForRow(row, ConversationList.IsSwarm),
                 "isBanned": model.dataForRow(row, ConversationList.IsBanned),
                 "mode": model.dataForRow(row, ConversationList.Mode),
-                "readOnly": model.dataForRow(row, ConversationList.ReadOnly)
+                "isTemporary": model.dataForRow(row, ConversationList.ContactType) === Profile.Type.TEMPORARY,
             }
 
             responsibleAccountId = LRCInstance.currentAccountId
@@ -142,7 +142,7 @@ JamiListView {
             isBanned = item.isBanned
             mode = item.mode
             contactType = LRCInstance.currentAccountType
-            readOnly = item.readOnly
+            readOnly = mode === Conversation.Mode.NON_SWARM && !item.isTemporary
 
             if (model.dataForRow(row, ConversationList.IsCoreDialog)) {
                 userProfile.aliasText = item.title
diff --git a/src/app/mainview/components/SmartListItemDelegate.qml b/src/app/mainview/components/SmartListItemDelegate.qml
index 39b657317580f6442daba529555749bd550a0305..56361058788fb57016b1ea2218b0e075f563094d 100644
--- a/src/app/mainview/components/SmartListItemDelegate.qml
+++ b/src/app/mainview/components/SmartListItemDelegate.qml
@@ -168,13 +168,6 @@ ItemDelegate {
             }
         }
 
-        // read-only conversation indicator
-        ResponsiveImage {
-            visible: ReadOnly
-            source: JamiResources.lock_black_24dp_svg
-            color: JamiTheme.primaryForegroundColor
-        }
-
         // Draft indicator
         ResponsiveImage {
             visible: Draft && !root.highlighted
diff --git a/src/app/mainview/components/ReadOnlyFooter.qml b/src/app/mainview/components/UpdateToSwarm.qml
similarity index 93%
rename from src/app/mainview/components/ReadOnlyFooter.qml
rename to src/app/mainview/components/UpdateToSwarm.qml
index e639dfb5e0f3de0bd33f2fb037dda8a4e189e1b6..d43b0cc3825fccdb134a03aa5673d79ade4365e7 100644
--- a/src/app/mainview/components/ReadOnlyFooter.qml
+++ b/src/app/mainview/components/UpdateToSwarm.qml
@@ -46,7 +46,7 @@ Control {
             Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
             Layout.fillWidth: true
 
-            text: JamiStrings.contactLeft
+            text: JamiStrings.updateToSwarm
             font.pointSize: JamiTheme.textFontSize + 2
             color: JamiTheme.textColor
             wrapMode: Text.Wrap
@@ -60,6 +60,7 @@ Control {
 
             MaterialButton {
                 text: JamiStrings.removeContact
+                padding: 8
                 autoAccelerator: true
                 font.pointSize: JamiTheme.textFontSize + 2
                 onClicked: MessagesAdapter.removeContact(
@@ -67,7 +68,8 @@ Control {
             }
 
             MaterialButton {
-                text: JamiStrings.newConversation
+                text: JamiStrings.migrateConversation
+                padding: 8
                 autoAccelerator: true
                 font.pointSize: JamiTheme.textFontSize + 2
                 onClicked: ConversationsAdapter.restartConversation(
diff --git a/src/libclient/api/conversation.h b/src/libclient/api/conversation.h
index 4608b2017bcf27c911c1d91ea9666a7481269975..086fe5ff91102e92a90b128bac1e6908ca95bded 100644
--- a/src/libclient/api/conversation.h
+++ b/src/libclient/api/conversation.h
@@ -82,13 +82,25 @@ struct Info
 
     MapStringString infos {};
 
-    QString getCallId() const { return confId.isEmpty() ? callId : confId; }
+    QString getCallId() const
+    {
+        return confId.isEmpty() ? callId : confId;
+    }
 
-    inline bool isLegacy() const { return mode == Mode::NON_SWARM; }
-    inline bool isSwarm() const { return !isLegacy(); }
+    inline bool isLegacy() const
+    {
+        return mode == Mode::NON_SWARM;
+    }
+    inline bool isSwarm() const
+    {
+        return !isLegacy();
+    }
     // for each contact we must have one non-swarm conversation or one active one-to-one
     // conversation. Where active means peer did not leave the conversation.
-    inline bool isCoreDialog() const { return isLegacy() || mode == Mode::ONE_TO_ONE; };
+    inline bool isCoreDialog() const
+    {
+        return isLegacy() || mode == Mode::ONE_TO_ONE;
+    };
 
     inline QStringList participantsUris() const
     {
@@ -101,7 +113,6 @@ struct Info
     Mode mode = Mode::NON_SWARM;
     bool needsSyncing = false;
     bool isRequest = false;
-    bool readOnly = false;
 };
 
 } // namespace conversation
diff --git a/src/libclient/conversationmodel.cpp b/src/libclient/conversationmodel.cpp
index 1cacfaf7ec239d25c5dd82febc0d092d7d811cdb..89a0bc4c83302d60174fbd8345b5a3f98fdfa3ed 100644
--- a/src/libclient/conversationmodel.cpp
+++ b/src/libclient/conversationmodel.cpp
@@ -112,10 +112,9 @@ public:
      * return a vector of conversation indices for the given contact uri empty
      * if no index is found
      * @param uri of the contact to search
-     * @param writable whether or not to exclude read-only conversations(use for interactions)
      * @return an vector of indices
      */
-    std::vector<int> getIndicesForContact(const QString& uri, bool writable = false) const;
+    std::vector<int> getIndicesForContact(const QString& uri) const;
     /**
      * Initialize conversations_ and filteredConversations_
      */
@@ -2657,7 +2656,6 @@ ConversationModelPimpl::slotConversationMemberEvent(const QString& accountId,
             membersRemaining.append(member["uri"]);
     }
     conversation.participants = participants;
-    conversation.readOnly = membersRemaining == VectorString(1, linked.owner.profileInfo.uri);
     invalidateModel();
     Q_EMIT linked.modelChanged();
     Q_EMIT linked.conversationUpdated(conversationId);
@@ -2938,7 +2936,6 @@ ConversationModelPimpl::addSwarmConversation(const QString& convId)
         if (member["role"] == "left")
             membersLeft.append(member["uri"]);
     }
-    conversation.readOnly = mode == conversation::Mode::ONE_TO_ONE && membersLeft.size() == 1;
     conversation.participants = participants;
     conversation.mode = mode;
     conversation.unreadMessages = ConfigurationManager::instance().countInteractions(linked.owner.id,
@@ -3097,12 +3094,12 @@ ConversationModelPimpl::getConversationForPeerUri(const QString& uri,
 }
 
 std::vector<int>
-ConversationModelPimpl::getIndicesForContact(const QString& uri, bool writable) const
+ConversationModelPimpl::getIndicesForContact(const QString& uri) const
 {
     std::vector<int> ret;
     for (unsigned int i = 0; i < conversations.size(); ++i) {
         const auto& convInfo = conversations.at(i);
-        if (!convInfo.isCoreDialog() || (writable && convInfo.readOnly)) {
+        if (!convInfo.isCoreDialog()) {
             continue;
         }
         auto peers = peersForConversation(convInfo);