diff --git a/src/main/kotlin/org/briarproject/briar/desktop/login/AccountDeletedEvent.kt b/src/main/kotlin/org/briarproject/briar/desktop/login/AccountDeletedEvent.kt
deleted file mode 100644
index b1665174d84bf4b0aa0baa15ab7ee109957f95c2..0000000000000000000000000000000000000000
--- a/src/main/kotlin/org/briarproject/briar/desktop/login/AccountDeletedEvent.kt
+++ /dev/null
@@ -1,5 +0,0 @@
-package org.briarproject.briar.desktop.login
-
-import org.briarproject.bramble.api.event.Event
-
-class AccountDeletedEvent : Event()
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/login/ErrorScreen.kt b/src/main/kotlin/org/briarproject/briar/desktop/login/ErrorScreen.kt
index 47dc19d0552bfb72dced3fe629f32f2e0e66bb74..1c344a4b1b5626a0203b8acec6ae40149acd2b71 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/login/ErrorScreen.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/login/ErrorScreen.kt
@@ -9,10 +9,12 @@ import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.widthIn
 import androidx.compose.material.Button
 import androidx.compose.material.Icon
+import androidx.compose.material.IconButton
 import androidx.compose.material.MaterialTheme
 import androidx.compose.material.Surface
 import androidx.compose.material.Text
 import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.ArrowBack
 import androidx.compose.material.icons.filled.Error
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
@@ -45,11 +47,22 @@ fun main() = preview {
         }
     }
 
-    ErrorScreen(error)
+    ErrorScreen(error) {}
 }
 
 @Composable
-fun ErrorScreen(error: StartResult) = Surface {
+fun ErrorScreen(viewHolder: ErrorViewHolder) =
+    ErrorScreen(viewHolder.error, viewHolder.onBackButton)
+
+@Composable
+fun ErrorScreen(
+    error: StartResult,
+    onBackButton: () -> Unit,
+) = Surface {
+    IconButton(onClick = onBackButton) {
+        Icon(Icons.Filled.ArrowBack, i18n("back"))
+    }
+
     Column(
         modifier = Modifier.fillMaxSize().padding(32.dp),
         horizontalAlignment = CenterHorizontally,
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/login/ErrorViewHolder.kt b/src/main/kotlin/org/briarproject/briar/desktop/login/ErrorViewHolder.kt
new file mode 100644
index 0000000000000000000000000000000000000000..1f43078d385e5d492199e671749d02632d601150
--- /dev/null
+++ b/src/main/kotlin/org/briarproject/briar/desktop/login/ErrorViewHolder.kt
@@ -0,0 +1,9 @@
+package org.briarproject.briar.desktop.login
+
+import org.briarproject.bramble.api.lifecycle.LifecycleManager
+
+class ErrorViewHolder(
+    private val viewModel: StartupViewModel,
+    val error: LifecycleManager.StartResult,
+    val onBackButton: () -> Unit,
+) : StartupViewModel.ViewHolder
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/login/LoginScreen.kt b/src/main/kotlin/org/briarproject/briar/desktop/login/LoginScreen.kt
index 576026e5b15d7bf4da2dad084fcbdfaf5d8046c6..008ffbe444b68e1cd895ee48ee2e32b778706388 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/login/LoginScreen.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/login/LoginScreen.kt
@@ -21,33 +21,32 @@ import androidx.compose.ui.text.input.ImeAction
 import androidx.compose.ui.text.input.KeyboardType
 import androidx.compose.ui.text.input.PasswordVisualTransformation
 import androidx.compose.ui.unit.dp
-import org.briarproject.briar.desktop.login.LoginViewModel.State.COMPACTING
-import org.briarproject.briar.desktop.login.LoginViewModel.State.MIGRATING
-import org.briarproject.briar.desktop.login.LoginViewModel.State.SIGNED_OUT
-import org.briarproject.briar.desktop.login.LoginViewModel.State.STARTED
-import org.briarproject.briar.desktop.login.LoginViewModel.State.STARTING
+import org.briarproject.briar.desktop.login.LoginViewHolder.State.COMPACTING
+import org.briarproject.briar.desktop.login.LoginViewHolder.State.MIGRATING
+import org.briarproject.briar.desktop.login.LoginViewHolder.State.SIGNED_OUT
+import org.briarproject.briar.desktop.login.LoginViewHolder.State.STARTED
+import org.briarproject.briar.desktop.login.LoginViewHolder.State.STARTING
 import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18n
-import org.briarproject.briar.desktop.viewmodel.viewModel
 
 @OptIn(ExperimentalMaterialApi::class)
 @Composable
 fun LoginScreen(
-    viewModel: LoginViewModel = viewModel(),
-) = StartupScreen(i18n("startup.title.login")) {
-    when (viewModel.state.value) {
+    viewHolder: LoginViewHolder,
+) = StartupScreenScaffold(i18n("startup.title.login")) {
+    when (viewHolder.state.value) {
         SIGNED_OUT ->
             FormScaffold(
                 explanationText = null,
                 buttonText = i18n("startup.button.login"),
-                buttonClick = viewModel::signIn,
-                buttonEnabled = viewModel.buttonEnabled.value
+                buttonClick = viewHolder::signIn,
+                buttonEnabled = viewHolder.buttonEnabled.value
             ) {
                 LoginForm(
-                    viewModel.password.value,
-                    viewModel::setPassword,
-                    viewModel.passwordInvalidError.value,
-                    viewModel::deleteAccount,
-                    viewModel::signIn
+                    viewHolder.password.value,
+                    viewHolder::setPassword,
+                    viewHolder.passwordInvalidError.value,
+                    viewHolder::deleteAccount,
+                    viewHolder::signIn
                 )
             }
         STARTING -> LoadingView(i18n("startup.database.opening"))
@@ -56,15 +55,15 @@ fun LoginScreen(
         STARTED -> {} // case handled by BriarUi
     }
 
-    if (viewModel.decryptionFailedError.value) {
+    if (viewHolder.decryptionFailedError.value) {
         // todo: is this actually needed on Desktop?
-        // todo: use ErrorScreen to display this instead
+        // todo: use ErrorScreen to display this instead?
         AlertDialog(
-            onDismissRequest = viewModel::closeDecryptionFailedDialog,
+            onDismissRequest = viewHolder::closeDecryptionFailedDialog,
             title = { Text(i18n("startup.error.decryption.title")) },
             text = { Text(i18n("startup.error.decryption.text")) },
             confirmButton = {
-                TextButton(onClick = viewModel::closeDecryptionFailedDialog) {
+                TextButton(onClick = viewHolder::closeDecryptionFailedDialog) {
                     Text(i18n("ok"))
                 }
             },
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/login/LoginViewModel.kt b/src/main/kotlin/org/briarproject/briar/desktop/login/LoginViewHolder.kt
similarity index 65%
rename from src/main/kotlin/org/briarproject/briar/desktop/login/LoginViewModel.kt
rename to src/main/kotlin/org/briarproject/briar/desktop/login/LoginViewHolder.kt
index 418e0b5ba10a2e594d619989eda8d83ab0202a69..8ad23db1f64aa5b65bad1bc2da5b8919d13ffe72 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/login/LoginViewModel.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/login/LoginViewHolder.kt
@@ -6,32 +6,21 @@ import org.briarproject.bramble.api.account.AccountManager
 import org.briarproject.bramble.api.crypto.DecryptionException
 import org.briarproject.bramble.api.crypto.DecryptionResult.INVALID_PASSWORD
 import org.briarproject.bramble.api.crypto.DecryptionResult.KEY_STRENGTHENER_ERROR
-import org.briarproject.bramble.api.db.TransactionManager
-import org.briarproject.bramble.api.event.Event
-import org.briarproject.bramble.api.event.EventBus
-import org.briarproject.bramble.api.lifecycle.LifecycleManager
 import org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState
-import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent
-import org.briarproject.briar.desktop.login.LoginViewModel.State.COMPACTING
-import org.briarproject.briar.desktop.login.LoginViewModel.State.MIGRATING
-import org.briarproject.briar.desktop.login.LoginViewModel.State.SIGNED_OUT
-import org.briarproject.briar.desktop.login.LoginViewModel.State.STARTED
-import org.briarproject.briar.desktop.login.LoginViewModel.State.STARTING
-import org.briarproject.briar.desktop.login.StartupUtils.startBriarCore
+import org.briarproject.briar.desktop.login.LoginViewHolder.State.COMPACTING
+import org.briarproject.briar.desktop.login.LoginViewHolder.State.MIGRATING
+import org.briarproject.briar.desktop.login.LoginViewHolder.State.SIGNED_OUT
+import org.briarproject.briar.desktop.login.LoginViewHolder.State.STARTED
+import org.briarproject.briar.desktop.login.LoginViewHolder.State.STARTING
 import org.briarproject.briar.desktop.threading.BriarExecutors
-import org.briarproject.briar.desktop.viewmodel.EventListenerDbViewModel
 import org.briarproject.briar.desktop.viewmodel.asState
-import javax.inject.Inject
 
-class LoginViewModel
-@Inject
-constructor(
+class LoginViewHolder(
+    private val viewModel: StartupViewModel,
     private val accountManager: AccountManager,
     private val briarExecutors: BriarExecutors,
-    private val lifecycleManager: LifecycleManager,
-    private val eventBus: EventBus,
-    db: TransactionManager,
-) : EventListenerDbViewModel(briarExecutors, lifecycleManager, db, eventBus) {
+    initialLifecycleState: LifecycleState,
+) : StartupViewModel.ViewHolder {
 
     enum class State {
         SIGNED_OUT, STARTING, MIGRATING, COMPACTING, STARTED
@@ -60,18 +49,11 @@ constructor(
         _decryptionFailedError.value = false
     }
 
-    override fun onInit() {
-        super.onInit()
-        updateState(lifecycleManager.lifecycleState)
+    init {
+        lifecycleStateChanged(initialLifecycleState)
     }
 
-    override fun eventOccurred(e: Event?) {
-        if (e is LifecycleEvent) {
-            updateState(e.lifecycleState)
-        }
-    }
-
-    private fun updateState(s: LifecycleState) {
+    override fun lifecycleStateChanged(s: LifecycleState) {
         _state.value =
             if (accountManager.hasDatabaseKey()) {
                 when {
@@ -87,7 +69,7 @@ constructor(
 
     fun deleteAccount() = briarExecutors.onIoThread {
         accountManager.deleteAccount()
-        eventBus.broadcast(AccountDeletedEvent())
+        viewModel.showRegistration()
     }
 
     fun signIn() {
@@ -97,7 +79,7 @@ constructor(
         briarExecutors.onIoThread {
             try {
                 accountManager.signIn(password.value)
-                startBriarCore(accountManager, lifecycleManager, eventBus)
+                viewModel.startBriarCore()
             } catch (e: DecryptionException) {
                 // failure, try again
                 briarExecutors.onUiThread {
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/login/RegistrationScreen.kt b/src/main/kotlin/org/briarproject/briar/desktop/login/RegistrationScreen.kt
index 50b275f76f3ab085c5b5643a0eace842578f3f95..b981d77398344374e33b77033433d8ddd5d0fb1d 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/login/RegistrationScreen.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/login/RegistrationScreen.kt
@@ -23,52 +23,51 @@ import androidx.compose.ui.text.input.ImeAction
 import androidx.compose.ui.text.input.KeyboardType
 import androidx.compose.ui.text.input.PasswordVisualTransformation
 import androidx.compose.ui.unit.dp
-import org.briarproject.briar.desktop.login.RegistrationViewModel.State.CREATED
-import org.briarproject.briar.desktop.login.RegistrationViewModel.State.CREATING
-import org.briarproject.briar.desktop.login.RegistrationViewModel.State.INSERT_NICKNAME
-import org.briarproject.briar.desktop.login.RegistrationViewModel.State.INSERT_PASSWORD
+import org.briarproject.briar.desktop.login.RegistrationViewHolder.State.CREATED
+import org.briarproject.briar.desktop.login.RegistrationViewHolder.State.CREATING
+import org.briarproject.briar.desktop.login.RegistrationViewHolder.State.INSERT_NICKNAME
+import org.briarproject.briar.desktop.login.RegistrationViewHolder.State.INSERT_PASSWORD
 import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18n
-import org.briarproject.briar.desktop.viewmodel.viewModel
 
 @Composable
 fun RegistrationScreen(
-    viewModel: RegistrationViewModel = viewModel(),
-) = StartupScreen(
+    viewHolder: RegistrationViewHolder,
+) = StartupScreenScaffold(
     title = i18n("startup.title.registration"),
-    showBackButton = viewModel.showBackButton.value,
-    onBackButton = viewModel::goBack
+    showBackButton = viewHolder.showBackButton.value,
+    onBackButton = viewHolder::goBack
 ) {
-    when (viewModel.state.value) {
+    when (viewHolder.state.value) {
         INSERT_NICKNAME ->
             FormScaffold(
                 explanationText = i18n("startup.field.nickname.explanation"),
                 buttonText = i18n("next"),
-                buttonClick = viewModel::goToPassword,
-                buttonEnabled = viewModel.buttonEnabled.value
+                buttonClick = viewHolder::goToPassword,
+                buttonEnabled = viewHolder.buttonEnabled.value
             ) {
                 NicknameForm(
-                    viewModel.nickname.value,
-                    viewModel::setNickname,
-                    viewModel.nicknameTooLongError.value,
-                    viewModel::goToPassword
+                    viewHolder.nickname.value,
+                    viewHolder::setNickname,
+                    viewHolder.nicknameTooLongError.value,
+                    viewHolder::goToPassword
                 )
             }
         INSERT_PASSWORD ->
             FormScaffold(
                 explanationText = i18n("startup.field.password.explanation"),
                 buttonText = i18n("startup.button.register"),
-                buttonClick = viewModel::signUp,
-                buttonEnabled = viewModel.buttonEnabled.value
+                buttonClick = viewHolder::signUp,
+                buttonEnabled = viewHolder.buttonEnabled.value
             ) {
                 PasswordForm(
-                    viewModel.password.value,
-                    viewModel::setPassword,
-                    viewModel.passwordConfirmation.value,
-                    viewModel::setPasswordConfirmation,
-                    viewModel.passwordStrength.value,
-                    viewModel.passwordTooWeakError.value,
-                    viewModel.passwordMatchError.value,
-                    viewModel::signUp
+                    viewHolder.password.value,
+                    viewHolder::setPassword,
+                    viewHolder.passwordConfirmation.value,
+                    viewHolder::setPasswordConfirmation,
+                    viewHolder.passwordStrength.value,
+                    viewHolder.passwordTooWeakError.value,
+                    viewHolder.passwordMatchError.value,
+                    viewHolder::signUp
                 )
             }
         CREATING -> LoadingView(i18n("startup.database.creating"))
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/login/RegistrationViewModel.kt b/src/main/kotlin/org/briarproject/briar/desktop/login/RegistrationViewHolder.kt
similarity index 78%
rename from src/main/kotlin/org/briarproject/briar/desktop/login/RegistrationViewModel.kt
rename to src/main/kotlin/org/briarproject/briar/desktop/login/RegistrationViewHolder.kt
index a72d5a9e85072789659072f61dab1346cb59453d..e83d4e6b3df82e273bad38eeaf5b216ea0f55b38 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/login/RegistrationViewModel.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/login/RegistrationViewHolder.kt
@@ -6,28 +6,19 @@ import mu.KotlinLogging
 import org.briarproject.bramble.api.account.AccountManager
 import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator
 import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.QUITE_WEAK
-import org.briarproject.bramble.api.event.EventBus
 import org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH
-import org.briarproject.bramble.api.lifecycle.LifecycleManager
-import org.briarproject.briar.desktop.login.RegistrationViewModel.State.CREATED
-import org.briarproject.briar.desktop.login.RegistrationViewModel.State.CREATING
-import org.briarproject.briar.desktop.login.RegistrationViewModel.State.INSERT_NICKNAME
-import org.briarproject.briar.desktop.login.RegistrationViewModel.State.INSERT_PASSWORD
-import org.briarproject.briar.desktop.login.StartupUtils.startBriarCore
+import org.briarproject.briar.desktop.login.RegistrationViewHolder.State.CREATING
+import org.briarproject.briar.desktop.login.RegistrationViewHolder.State.INSERT_NICKNAME
+import org.briarproject.briar.desktop.login.RegistrationViewHolder.State.INSERT_PASSWORD
 import org.briarproject.briar.desktop.threading.BriarExecutors
-import org.briarproject.briar.desktop.viewmodel.ViewModel
 import org.briarproject.briar.desktop.viewmodel.asState
-import javax.inject.Inject
 
-class RegistrationViewModel
-@Inject
-constructor(
+class RegistrationViewHolder(
+    private val viewModel: StartupViewModel,
     private val accountManager: AccountManager,
     private val briarExecutors: BriarExecutors,
-    private val lifecycleManager: LifecycleManager,
     private val passwordStrengthEstimator: PasswordStrengthEstimator,
-    private val eventBus: EventBus,
-) : ViewModel {
+) : StartupViewModel.ViewHolder {
 
     companion object {
         private val LOG = KotlinLogging.logger {}
@@ -106,8 +97,7 @@ constructor(
         briarExecutors.onIoThread {
             if (accountManager.createAccount(_nickname.value, _password.value)) {
                 LOG.info { "Created account" }
-                startBriarCore(accountManager, lifecycleManager, eventBus)
-                _state.value = CREATED
+                viewModel.startBriarCore()
             } else {
                 LOG.warn { "Failed to create account" }
                 _state.value = INSERT_NICKNAME
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/login/StartupFailedEvent.kt b/src/main/kotlin/org/briarproject/briar/desktop/login/StartupFailedEvent.kt
deleted file mode 100644
index 1976843ebced37832cfdc103cbb40cd376c4af87..0000000000000000000000000000000000000000
--- a/src/main/kotlin/org/briarproject/briar/desktop/login/StartupFailedEvent.kt
+++ /dev/null
@@ -1,6 +0,0 @@
-package org.briarproject.briar.desktop.login
-
-import org.briarproject.bramble.api.event.Event
-import org.briarproject.bramble.api.lifecycle.LifecycleManager
-
-class StartupFailedEvent(val result: LifecycleManager.StartResult) : Event()
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/login/StartupScreen.kt b/src/main/kotlin/org/briarproject/briar/desktop/login/StartupScreen.kt
index 38fa1f96470e22dfed6e4a1ad5f930126a1d75d3..8dead60266c91b2dd547667427ad86df602523a5 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/login/StartupScreen.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/login/StartupScreen.kt
@@ -28,10 +28,22 @@ import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.unit.dp
 import org.briarproject.briar.desktop.ui.BriarLogo
 import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18n
+import org.briarproject.briar.desktop.viewmodel.viewModel
 import java.util.Locale
 
 @Composable
 fun StartupScreen(
+    viewModel: StartupViewModel = viewModel(),
+) {
+    when (val holder = viewModel.mode.value) {
+        is LoginViewHolder -> LoginScreen(holder)
+        is RegistrationViewHolder -> RegistrationScreen(holder)
+        is ErrorViewHolder -> ErrorScreen(holder)
+    }
+}
+
+@Composable
+fun StartupScreenScaffold(
     title: String,
     showBackButton: Boolean = false,
     onBackButton: () -> Unit = {},
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/login/StartupUtils.kt b/src/main/kotlin/org/briarproject/briar/desktop/login/StartupUtils.kt
deleted file mode 100644
index cbb1dc1460db44c3121e095980dc07452a4f14a2..0000000000000000000000000000000000000000
--- a/src/main/kotlin/org/briarproject/briar/desktop/login/StartupUtils.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-package org.briarproject.briar.desktop.login
-
-import mu.KotlinLogging
-import org.briarproject.bramble.api.account.AccountManager
-import org.briarproject.bramble.api.event.EventBus
-import org.briarproject.bramble.api.lifecycle.IoExecutor
-import org.briarproject.bramble.api.lifecycle.LifecycleManager
-import org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.ALREADY_RUNNING
-import org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SUCCESS
-
-object StartupUtils {
-
-    private val LOG = KotlinLogging.logger {}
-
-    @IoExecutor
-    fun startBriarCore(accountManager: AccountManager, lifecycleManager: LifecycleManager, eventBus: EventBus) {
-        val dbKey = accountManager.databaseKey ?: throw AssertionError()
-        val result = lifecycleManager.startServices(dbKey)
-        when (result) {
-            SUCCESS -> lifecycleManager.waitForStartup()
-            ALREADY_RUNNING -> LOG.info { "Already running" }
-            else -> {
-                LOG.warn { "Startup failed: $result" }
-                eventBus.broadcast(StartupFailedEvent(result))
-            }
-        }
-    }
-}
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/login/StartupViewModel.kt b/src/main/kotlin/org/briarproject/briar/desktop/login/StartupViewModel.kt
new file mode 100644
index 0000000000000000000000000000000000000000..9f8121894051de27fbb3d7371e910d6daddd48ec
--- /dev/null
+++ b/src/main/kotlin/org/briarproject/briar/desktop/login/StartupViewModel.kt
@@ -0,0 +1,86 @@
+package org.briarproject.briar.desktop.login
+
+import androidx.compose.runtime.mutableStateOf
+import mu.KotlinLogging
+import org.briarproject.bramble.api.account.AccountManager
+import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator
+import org.briarproject.bramble.api.db.TransactionManager
+import org.briarproject.bramble.api.event.Event
+import org.briarproject.bramble.api.event.EventBus
+import org.briarproject.bramble.api.lifecycle.IoExecutor
+import org.briarproject.bramble.api.lifecycle.LifecycleManager
+import org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.ALREADY_RUNNING
+import org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SUCCESS
+import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent
+import org.briarproject.briar.desktop.threading.BriarExecutors
+import org.briarproject.briar.desktop.viewmodel.EventListenerDbViewModel
+import org.briarproject.briar.desktop.viewmodel.asState
+import javax.inject.Inject
+
+class StartupViewModel
+@Inject
+constructor(
+    private val accountManager: AccountManager,
+    private val briarExecutors: BriarExecutors,
+    private val lifecycleManager: LifecycleManager,
+    private val passwordStrengthEstimator: PasswordStrengthEstimator,
+    db: TransactionManager,
+    eventBus: EventBus,
+) : EventListenerDbViewModel(briarExecutors, lifecycleManager, db, eventBus) {
+
+    companion object {
+        private val LOG = KotlinLogging.logger {}
+    }
+
+    sealed interface ViewHolder {
+        fun lifecycleStateChanged(s: LifecycleManager.LifecycleState) {}
+    }
+
+    private val _mode = mutableStateOf(decideMode())
+    val mode = _mode.asState()
+
+    private fun decideMode() =
+        if (accountManager.accountExists()) makeLogin()
+        else makeRegistration()
+
+    private fun makeLogin() = LoginViewHolder(
+        this, accountManager, briarExecutors, lifecycleManager.lifecycleState
+    )
+
+    fun showLogin() {
+        _mode.value = makeLogin()
+    }
+
+    private fun makeRegistration() = RegistrationViewHolder(
+        this, accountManager, briarExecutors, passwordStrengthEstimator
+    )
+
+    fun showRegistration() {
+        _mode.value = makeRegistration()
+    }
+
+    private fun makeError(error: LifecycleManager.StartResult) = ErrorViewHolder(
+        this, error, onBackButton = { _mode.value = decideMode() }
+    )
+    fun showError(error: LifecycleManager.StartResult) {
+        _mode.value = makeError(error)
+    }
+
+    override fun eventOccurred(e: Event) {
+        if (e is LifecycleEvent) _mode.value.lifecycleStateChanged(e.lifecycleState)
+    }
+
+    @IoExecutor
+    fun startBriarCore() {
+        val dbKey = accountManager.databaseKey ?: throw AssertionError()
+        val result = lifecycleManager.startServices(dbKey)
+        when (result) {
+            SUCCESS -> lifecycleManager.waitForStartup()
+            ALREADY_RUNNING -> LOG.info { "Already running" }
+            else -> {
+                LOG.warn { "Startup failed: $result" }
+                showError(result)
+            }
+        }
+    }
+}
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 2ec6a6949cf298ecc32b6f4ada4250bcfb9cf364..8bfae9ecc8eb26b4b9986162093ae8a43059890b 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/ui/BriarUi.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/ui/BriarUi.kt
@@ -9,26 +9,18 @@ import androidx.compose.runtime.staticCompositionLocalOf
 import androidx.compose.ui.res.painterResource
 import androidx.compose.ui.window.Window
 import org.briarproject.bramble.api.FeatureFlags
-import org.briarproject.bramble.api.account.AccountManager
 import org.briarproject.bramble.api.event.Event
 import org.briarproject.bramble.api.event.EventBus
 import org.briarproject.bramble.api.event.EventListener
 import org.briarproject.bramble.api.lifecycle.LifecycleManager
 import org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING
-import org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult
 import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent
 import org.briarproject.briar.desktop.DesktopFeatureFlags
-import org.briarproject.briar.desktop.login.AccountDeletedEvent
-import org.briarproject.briar.desktop.login.ErrorScreen
-import org.briarproject.briar.desktop.login.LoginScreen
-import org.briarproject.briar.desktop.login.RegistrationScreen
-import org.briarproject.briar.desktop.login.StartupFailedEvent
+import org.briarproject.briar.desktop.login.StartupScreen
 import org.briarproject.briar.desktop.settings.SettingsViewModel
 import org.briarproject.briar.desktop.theme.BriarTheme
-import org.briarproject.briar.desktop.ui.Screen.LOGIN
 import org.briarproject.briar.desktop.ui.Screen.MAIN
-import org.briarproject.briar.desktop.ui.Screen.REGISTRATION
-import org.briarproject.briar.desktop.ui.Screen.STARTUP_ERROR
+import org.briarproject.briar.desktop.ui.Screen.STARTUP
 import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18n
 import org.briarproject.briar.desktop.viewmodel.ViewModelProvider
 import org.briarproject.briar.desktop.viewmodel.viewModel
@@ -37,11 +29,9 @@ import javax.annotation.concurrent.Immutable
 import javax.inject.Inject
 import javax.inject.Singleton
 
-sealed interface Screen {
-    object REGISTRATION : Screen
-    object LOGIN : Screen
-    object MAIN : Screen
-    class STARTUP_ERROR(val error: StartResult) : Screen
+enum class Screen {
+    STARTUP,
+    MAIN
 }
 
 interface BriarUi {
@@ -61,7 +51,6 @@ val LocalDesktopFeatureFlags = staticCompositionLocalOf<DesktopFeatureFlags?> {
 internal class BriarUiImpl
 @Inject
 constructor(
-    private val accountManager: AccountManager,
     private val lifecycleManager: LifecycleManager,
     private val eventBus: EventBus,
     private val viewModelProvider: ViewModelProvider,
@@ -71,19 +60,12 @@ constructor(
 
     private var screenState by mutableStateOf(
         if (lifecycleManager.lifecycleState == RUNNING) MAIN
-        else if (accountManager.accountExists()) LOGIN
-        else REGISTRATION
+        else STARTUP
     )
 
     override fun eventOccurred(e: Event?) {
-        when {
-            e is LifecycleEvent && e.lifecycleState == RUNNING ->
-                screenState = MAIN
-            e is AccountDeletedEvent ->
-                screenState = REGISTRATION
-            e is StartupFailedEvent ->
-                screenState = STARTUP_ERROR(e.result)
-        }
+        if (e is LifecycleEvent && e.lifecycleState == RUNNING)
+            screenState = MAIN
     }
 
     override fun stop() {
@@ -112,11 +94,9 @@ constructor(
             ) {
                 val settingsViewModel: SettingsViewModel = viewModel()
                 BriarTheme(isDarkTheme = settingsViewModel.isDarkMode.value) {
-                    when (val state = screenState) {
-                        is REGISTRATION -> RegistrationScreen()
-                        is LOGIN -> LoginScreen()
-                        is STARTUP_ERROR -> ErrorScreen(state.error)
-                        is MAIN -> MainScreen(settingsViewModel)
+                    when (screenState) {
+                        STARTUP -> StartupScreen()
+                        MAIN -> MainScreen(settingsViewModel)
                     }
                 }
             }
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/viewmodel/ViewModelModule.kt b/src/main/kotlin/org/briarproject/briar/desktop/viewmodel/ViewModelModule.kt
index 65e0f3277195d5e599551a5cb4c4b4095466d9c8..55ec3a26838057ba23b076e39624813daeaec434 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/viewmodel/ViewModelModule.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/viewmodel/ViewModelModule.kt
@@ -8,8 +8,7 @@ import org.briarproject.briar.desktop.contact.ContactListViewModel
 import org.briarproject.briar.desktop.contact.add.remote.AddContactViewModel
 import org.briarproject.briar.desktop.conversation.ConversationViewModel
 import org.briarproject.briar.desktop.introduction.IntroductionViewModel
-import org.briarproject.briar.desktop.login.LoginViewModel
-import org.briarproject.briar.desktop.login.RegistrationViewModel
+import org.briarproject.briar.desktop.login.StartupViewModel
 import org.briarproject.briar.desktop.navigation.SidebarViewModel
 import org.briarproject.briar.desktop.privategroups.PrivateGroupListViewModel
 import org.briarproject.briar.desktop.privategroups.ThreadedConversationViewModel
@@ -23,13 +22,8 @@ abstract class ViewModelModule {
 
     @Binds
     @IntoMap
-    @ViewModelKey(LoginViewModel::class)
-    abstract fun bindLoginViewModel(loginViewModel: LoginViewModel): ViewModel
-
-    @Binds
-    @IntoMap
-    @ViewModelKey(RegistrationViewModel::class)
-    abstract fun bindRegistrationViewModel(registrationViewModel: RegistrationViewModel): ViewModel
+    @ViewModelKey(StartupViewModel::class)
+    abstract fun bindStartupViewModel(startupViewModel: StartupViewModel): ViewModel
 
     @Binds
     @IntoMap