diff --git a/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactList.kt b/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactList.kt
index 7fa969a5b36d8be169920ee6e3b99c961f6c1ee7..4e769de56af0ec0447e45ebf101fed93f3042e5d 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactList.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactList.kt
@@ -6,7 +6,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.lazy.itemsIndexed
 import androidx.compose.material.MaterialTheme
 import androidx.compose.material.Scaffold
 import androidx.compose.runtime.Composable
@@ -45,12 +45,8 @@ fun ContactList(
         },
         content = {
             LazyColumn {
-                items(viewModel.contactList) { contactItem ->
-                    ContactCard(
-                        contactItem,
-                        { viewModel.selectContact(contactItem.contact) },
-                        viewModel.isSelected(contactItem.contact)
-                    )
+                itemsIndexed(viewModel.contactList) { index, contactItem ->
+                    ContactCard(contactItem, { viewModel.selectContact(index) }, viewModel.isSelected(index))
                 }
             }
         },
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactListViewModel.kt b/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactListViewModel.kt
index ee4a7f8c82a3cb0c8a86c53c409169e8f54193bb..ef763a604c619e5549307ab1591509a9ad0e260f 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactListViewModel.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactListViewModel.kt
@@ -1,9 +1,10 @@
 package org.briarproject.briar.desktop.contact
 
 import androidx.compose.runtime.State
+import androidx.compose.runtime.mutableStateListOf
 import androidx.compose.runtime.mutableStateOf
 import org.briarproject.bramble.api.connection.ConnectionRegistry
-import org.briarproject.bramble.api.contact.Contact
+import org.briarproject.bramble.api.contact.ContactId
 import org.briarproject.bramble.api.contact.ContactManager
 import org.briarproject.bramble.api.contact.event.ContactAliasChangedEvent
 import org.briarproject.bramble.api.event.Event
@@ -31,32 +32,44 @@ constructor(
         eventBus.addListener(this)
     }
 
+    private val _filteredContactList = mutableStateListOf<ContactItem>()
     private val _filterBy = mutableStateOf("")
-    private val _selectedContact = mutableStateOf<Contact?>(null)
+    private var _selectedContactIndex = -1
+    private val _selectedContact = mutableStateOf<ContactItem?>(null)
 
+    override val contactList: List<ContactItem> = _filteredContactList
     val filterBy: State<String> = _filterBy
-    val selectedContact: State<Contact?> = _selectedContact
+    val selectedContact: State<ContactItem?> = _selectedContact
 
-    fun selectContact(contact: Contact) {
-        _selectedContact.value = contact
+    override fun loadContacts() {
+        super.loadContacts()
+        updateFilteredList()
     }
 
-    fun isSelected(contact: Contact) = _selectedContact.value?.id == contact.id
+    fun selectContact(index: Int) {
+        _selectedContactIndex = index
+        _selectedContact.value = _filteredContactList[index]
+    }
 
-    override fun filterContact(contact: Contact) =
-        // todo: also filter on alias
-        contact.author.name.contains(_filterBy.value, ignoreCase = true)
+    fun isSelected(index: Int) = _selectedContactIndex == index
+
+    private fun updateFilteredList() {
+        _filteredContactList.apply {
+            clear()
+            addAll(
+                _contactList.filter {
+                    // todo: also filter on alias?
+                    it.contact.author.name.lowercase().contains(_filterBy.value)
+                }
+            )
+        }
+    }
 
     fun setFilterBy(filter: String) {
         _filterBy.value = filter
         updateFilteredList()
     }
 
-    override fun updateFilteredList() {
-        super.updateFilteredList()
-        _selectedContact.value?.let { if (!filterContact(it)) _selectedContact.value = null }
-    }
-
     override fun eventOccurred(e: Event?) {
         super.eventOccurred(e)
         when (e) {
@@ -70,4 +83,14 @@ constructor(
             }
         }
     }
+
+    override fun updateItem(contactId: ContactId, update: (ContactItem) -> ContactItem) {
+        super.updateItem(contactId, update)
+        updateFilteredList()
+    }
+
+    override fun removeItem(contactId: ContactId) {
+        super.removeItem(contactId)
+        updateFilteredList()
+    }
 }
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactsViewModel.kt b/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactsViewModel.kt
index ccb10ccd338ccd31c237edff5b1088034771c7bb..ffd994358e38926a9d63a9a57c16dd182945774e 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactsViewModel.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactsViewModel.kt
@@ -1,6 +1,5 @@
 package org.briarproject.briar.desktop.contact
 
-import androidx.compose.runtime.mutableStateListOf
 import org.briarproject.bramble.api.connection.ConnectionRegistry
 import org.briarproject.bramble.api.contact.Contact
 import org.briarproject.bramble.api.contact.ContactId
@@ -26,18 +25,17 @@ abstract class ContactsViewModel(
         private val LOG = Logger.getLogger(ContactsViewModel::class.java.name)
     }
 
-    private val _fullContactList = mutableListOf<ContactItem>()
-    private val _filteredContactList = mutableStateListOf<ContactItem>()
+    protected val _contactList = mutableListOf<ContactItem>()
 
-    val contactList: List<ContactItem> = _filteredContactList
+    abstract val contactList: List<ContactItem>
 
     protected open fun filterContact(contact: Contact) = true
 
     open fun loadContacts() {
-        _fullContactList.apply {
+        _contactList.apply {
             clear()
             addAll(
-                contactManager.contacts.map { contact ->
+                contactManager.contacts.filter(::filterContact).map { contact ->
                     ContactItem(
                         contact,
                         connectionRegistry.isConnected(contact.id),
@@ -46,15 +44,6 @@ abstract class ContactsViewModel(
                 }
             )
         }
-        updateFilteredList()
-    }
-
-    // todo: when migrated to StateFlow, this could be done implicitly instead
-    protected open fun updateFilteredList() {
-        _filteredContactList.apply {
-            clear()
-            addAll(_fullContactList.filter { filterContact(it.contact) })
-        }
     }
 
     override fun eventOccurred(e: Event?) {
@@ -79,12 +68,10 @@ abstract class ContactsViewModel(
     }
 
     protected open fun updateItem(contactId: ContactId, update: (ContactItem) -> ContactItem) {
-        _fullContactList.replaceFirst({ it.contact.id == contactId }, update)
-        updateFilteredList()
+        _contactList.replaceFirst({ it.contact.id == contactId }, update)
     }
 
     protected open fun removeItem(contactId: ContactId) {
-        _fullContactList.removeFirst { it.contact.id == contactId }
-        updateFilteredList()
+        _contactList.removeFirst { it.contact.id == contactId }
     }
 }
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/conversation/PrivateMessageView.kt b/src/main/kotlin/org/briarproject/briar/desktop/conversation/PrivateMessageView.kt
index b9dc7644181c81c97321295ee10b97a739fd32d4..ae06e0135a6b6a8c462bd8a72497add5f8bfcefa 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/conversation/PrivateMessageView.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/conversation/PrivateMessageView.kt
@@ -25,7 +25,7 @@ fun PrivateMessageView(
         Column(modifier = Modifier.weight(1f).fillMaxHeight()) {
             contactListViewModel.selectedContact.value?.also { selectedContact ->
                 Conversation(
-                    selectedContact,
+                    selectedContact.contact,
                     introductionViewModel
                 )
             } ?: UiPlaceholder()
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/introduction/IntroductionViewModel.kt b/src/main/kotlin/org/briarproject/briar/desktop/introduction/IntroductionViewModel.kt
index fda0aa64dc456a881146eeb9992d716b7f2f5a6b..3b2e3f393c9348a78bff85c3b0b459ba68323951 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/introduction/IntroductionViewModel.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/introduction/IntroductionViewModel.kt
@@ -7,6 +7,7 @@ import org.briarproject.bramble.api.contact.Contact
 import org.briarproject.bramble.api.contact.ContactManager
 import org.briarproject.bramble.api.event.EventBus
 import org.briarproject.briar.api.conversation.ConversationManager
+import org.briarproject.briar.desktop.contact.ContactItem
 import org.briarproject.briar.desktop.contact.ContactsViewModel
 import java.util.logging.Logger
 import javax.inject.Inject
@@ -34,6 +35,7 @@ constructor(
     private val _secondScreen = mutableStateOf(false)
     private val _introductionMessage = mutableStateOf("")
 
+    override val contactList: List<ContactItem> = _contactList
     val firstContact: State<Contact?> = _firstContact
     val secondContact: State<Contact?> = _secondContact
     val secondScreen: State<Boolean> = _secondScreen
@@ -52,7 +54,6 @@ constructor(
 
     fun backToFirstScreen() {
         _secondScreen.value = false
-        _introductionMessage.value = ""
     }
 
     fun setIntroductionMessage(msg: String) {
@@ -60,6 +61,6 @@ constructor(
     }
 
     override fun filterContact(contact: Contact): Boolean {
-        return _firstContact.value?.id != contact.id
+        return _firstContact.value!!.id != contact.id
     }
 }
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/ui/BriarUi.kt b/src/main/kotlin/org/briarproject/briar/desktop/ui/BriarUi.kt
index f621dab9eb5a47577d6700d6010fb7fc320d56d2..afbda2623fdeeb8c14b7392255ea4193880b0acb 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/ui/BriarUi.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/ui/BriarUi.kt
@@ -104,6 +104,7 @@ constructor(
                     when (screenState) {
                         Screen.REGISTRATION ->
                             Registration(registrationViewModel) {
+                                contactListViewModel.loadContacts()
                                 screenState = Screen.MAIN
                             }
                         Screen.LOGIN ->