commit f300c50132a008822ace4b45aae2871619030f4c parent 7275bd259a8bddfe04ef4aff4b706fe310081a1d Author: triesap <tyson@radroots.org> Date: Mon, 3 Aug 2026 23:59:20 +0000 accounts: distinguish selected and active identity - project selected and active account state independently - disable redundant activation for the current session - preserve explicit account switching and sign-out actions - cover active markers and chooser behavior in Compose tests Diffstat:
4 files changed, 30 insertions(+), 7 deletions(-)
diff --git a/app/desktop/src/main/kotlin/org/radroots/studio/accounts/ui/AccountsUiModel.kt b/app/desktop/src/main/kotlin/org/radroots/studio/accounts/ui/AccountsUiModel.kt @@ -18,6 +18,7 @@ data class AccountUiModel( val label: String, val keyAvailability: String, val selected: Boolean, + val active: Boolean, ) data class ProfileUiModel( @@ -61,7 +62,13 @@ data class StudioUiModel( fun StudioStoreState.toUiModel(): StudioUiModel { val selectedPublicKeyHex = snapshot.selectedPublicKeyHex - val accounts = snapshot.accounts.map { it.toUiModel(it.publicKeyHex == selectedPublicKeyHex) } + val activePublicKeyHex = snapshot.activeAccount?.account?.publicKeyHex + val accounts = snapshot.accounts.map { + it.toUiModel( + selected = it.publicKeyHex == selectedPublicKeyHex, + active = it.publicKeyHex == activePublicKeyHex, + ) + } return StudioUiModel( route = route, accounts = accounts, @@ -98,17 +105,21 @@ private fun importGuidance( fun shortenNpub(npub: String): String = if (npub.length <= 24) npub else "${npub.take(14)}…${npub.takeLast(8)}" -private fun AccountDto.toUiModel(selected: Boolean) = AccountUiModel( +private fun AccountDto.toUiModel(selected: Boolean, active: Boolean = false) = AccountUiModel( publicKeyHex = publicKeyHex, npub = npub, shortNpub = shortenNpub(npub), label = displayLabel.ifBlank { shortenNpub(npub) }, keyAvailability = keyAvailability.name.lowercase().replace('_', ' '), selected = selected, + active = active, ) private fun ActiveAccountDto.toUiModel(selectedPublicKeyHex: String?) = ActiveAccountUiModel( - account = account.toUiModel(account.publicKeyHex == selectedPublicKeyHex), + account = account.toUiModel( + selected = account.publicKeyHex == selectedPublicKeyHex, + active = true, + ), heading = profile?.displayName?.takeIf(String::isNotBlank) ?: profile?.name?.takeIf(String::isNotBlank) ?: account.displayLabel.ifBlank { shortenNpub(account.npub) }, diff --git a/app/desktop/src/main/kotlin/org/radroots/studio/accounts/ui/StudioScreen.kt b/app/desktop/src/main/kotlin/org/radroots/studio/accounts/ui/StudioScreen.kt @@ -335,11 +335,12 @@ private fun ColumnScope.SavedAccountList( BasicText(account.npub) BasicText("Key: ${account.keyAvailability}") if (account.selected) BasicText("Selected") + if (account.active) BasicText("Active") TextAction( - text = "Activate", + text = if (account.active) "Active account" else "Activate", testTag = "activate-account:${account.publicKeyHex}", contentDescription = "Activate ${account.label}", - enabled = !model.busy, + enabled = !model.busy && !account.active, onClick = { actions.activateAccount(account.publicKeyHex) }, ) TextAction( diff --git a/app/desktop/src/test/kotlin/org/radroots/studio/accounts/ui/AccountsUiModelTest.kt b/app/desktop/src/test/kotlin/org/radroots/studio/accounts/ui/AccountsUiModelTest.kt @@ -3,6 +3,7 @@ package org.radroots.studio.accounts.ui import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse +import kotlin.test.assertTrue import kotlin.test.assertNull import org.radroots.studio.application.GeneratedKeyBackup import org.radroots.studio.application.StudioStoreState @@ -42,6 +43,8 @@ class AccountsUiModelTest { assertEquals(listOf("ws://localhost:8080"), model.configuredRelays) assertFalse(model.accountChooserVisible) assertFalse(model.accounts.single().label.contains("server", ignoreCase = true)) + assertTrue(model.accounts.single().selected) + assertTrue(model.accounts.single().active) } @Test diff --git a/app/desktop/src/test/kotlin/org/radroots/studio/accounts/ui/StudioScreenTest.kt b/app/desktop/src/test/kotlin/org/radroots/studio/accounts/ui/StudioScreenTest.kt @@ -6,6 +6,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.assertIsSelected +import androidx.compose.ui.test.assertIsNotEnabled import androidx.compose.ui.test.assertCountEquals import androidx.compose.ui.test.onAllNodesWithTag import androidx.compose.ui.test.onNodeWithTag @@ -241,7 +242,7 @@ class StudioScreenTest { @Test fun activeAccountCanOpenChooserWithoutDroppingCurrentSession() = runComposeUiTest { - val first = accountUi("44".repeat(32), selected = true) + val first = accountUi("44".repeat(32), selected = true, active = true) val second = accountUi("55".repeat(32), selected = false) val active = ActiveAccountUiModel( account = first, @@ -271,6 +272,8 @@ class StudioScreenTest { onNodeWithTag("switch-account").performClick() onNodeWithTag("accounts-screen").assertIsDisplayed() + onNodeWithTag("activate-account:${first.publicKeyHex}", useUnmergedTree = true).assertIsNotEnabled() + onNodeWithText("Active").assertIsDisplayed() onNodeWithTag("activate-account:${second.publicKeyHex}", useUnmergedTree = true).performClick() assertEquals(second.publicKeyHex, activated) assertEquals(SessionStateDto.ACTIVE, emptyUiModel().copy( @@ -302,11 +305,16 @@ private fun emptyUiModel( importGuidance = importGuidance, ) -private fun accountUi(publicKeyHex: String, selected: Boolean) = AccountUiModel( +private fun accountUi( + publicKeyHex: String, + selected: Boolean, + active: Boolean = false, +) = AccountUiModel( publicKeyHex = publicKeyHex, npub = "npub1${publicKeyHex.take(12)}", shortNpub = "npub1${publicKeyHex.take(12)}", label = "Account ${publicKeyHex.take(2)}", keyAvailability = "available", selected = selected, + active = active, )