commit 31fcb41e07985754663f42e4e80fd7f18564f286
parent 3ada037acc2c6a2cd4b0ed0f3b573a5285632be9
Author: triesap <tyson@radroots.org>
Date: Sat, 1 Aug 2026 18:23:22 +0000
accounts: define the proof model vocabulary
- add explicit account identifiers and local login status
- constrain accounts to the approved proof fields
- model the complete typed action and problem surfaces
- establish an empty account state and add-account draft
Diffstat:
6 files changed, 125 insertions(+), 0 deletions(-)
diff --git a/app/desktop/src/main/kotlin/org/radroots/studio/accounts/model/Account.kt b/app/desktop/src/main/kotlin/org/radroots/studio/accounts/model/Account.kt
@@ -0,0 +1,13 @@
+package org.radroots.studio.accounts.model
+
+enum class LoginStatus {
+ LoggedOut,
+ LoggedIn,
+}
+
+data class Account(
+ val id: AccountId,
+ val displayName: String,
+ val serverUrl: String,
+ val loginStatus: LoginStatus,
+)
diff --git a/app/desktop/src/main/kotlin/org/radroots/studio/accounts/model/AccountId.kt b/app/desktop/src/main/kotlin/org/radroots/studio/accounts/model/AccountId.kt
@@ -0,0 +1,6 @@
+package org.radroots.studio.accounts.model
+
+@JvmInline
+value class AccountId(
+ val value: String,
+)
diff --git a/app/desktop/src/main/kotlin/org/radroots/studio/accounts/model/AccountsAction.kt b/app/desktop/src/main/kotlin/org/radroots/studio/accounts/model/AccountsAction.kt
@@ -0,0 +1,37 @@
+package org.radroots.studio.accounts.model
+
+sealed interface AccountsAction {
+ data class EditAddDisplayName(
+ val value: String,
+ ) : AccountsAction
+
+ data class EditAddServerUrl(
+ val value: String,
+ ) : AccountsAction
+
+ data object SubmitAddAccount : AccountsAction
+
+ data class SelectAccount(
+ val accountId: AccountId,
+ ) : AccountsAction
+
+ data class LogIn(
+ val accountId: AccountId,
+ ) : AccountsAction
+
+ data class LogOut(
+ val accountId: AccountId,
+ ) : AccountsAction
+
+ data class RequestRemoveAccount(
+ val accountId: AccountId,
+ ) : AccountsAction
+
+ data object CancelRemoveAccount : AccountsAction
+
+ data class ConfirmRemoveAccount(
+ val accountId: AccountId,
+ ) : AccountsAction
+
+ data object DismissProblem : AccountsAction
+}
diff --git a/app/desktop/src/main/kotlin/org/radroots/studio/accounts/model/AccountsProblem.kt b/app/desktop/src/main/kotlin/org/radroots/studio/accounts/model/AccountsProblem.kt
@@ -0,0 +1,22 @@
+package org.radroots.studio.accounts.model
+
+sealed interface AccountsProblem {
+ data object BlankDisplayName : AccountsProblem
+
+ data object InvalidServerUrl : AccountsProblem
+
+ data object InvalidGeneratedAccountId : AccountsProblem
+
+ data class DuplicateAccountId(
+ val accountId: AccountId,
+ ) : AccountsProblem
+
+ data class AccountNotFound(
+ val accountId: AccountId,
+ ) : AccountsProblem
+
+ data class RemovalTargetMismatch(
+ val expectedAccountId: AccountId?,
+ val actualAccountId: AccountId,
+ ) : AccountsProblem
+}
diff --git a/app/desktop/src/main/kotlin/org/radroots/studio/accounts/model/AccountsState.kt b/app/desktop/src/main/kotlin/org/radroots/studio/accounts/model/AccountsState.kt
@@ -0,0 +1,14 @@
+package org.radroots.studio.accounts.model
+
+data class AddAccountDraft(
+ val displayName: String = "",
+ val serverUrl: String = "",
+)
+
+data class AccountsState(
+ val accounts: List<Account> = emptyList(),
+ val selectedAccountId: AccountId? = null,
+ val addDraft: AddAccountDraft = AddAccountDraft(),
+ val pendingRemovalAccountId: AccountId? = null,
+ val problem: AccountsProblem? = null,
+)
diff --git a/app/desktop/src/test/kotlin/org/radroots/studio/accounts/model/AccountsStateTest.kt b/app/desktop/src/test/kotlin/org/radroots/studio/accounts/model/AccountsStateTest.kt
@@ -0,0 +1,33 @@
+package org.radroots.studio.accounts.model
+
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertNull
+
+class AccountsStateTest {
+ @Test
+ fun defaultStateIsEmpty() {
+ val state = AccountsState()
+
+ assertEquals(emptyList(), state.accounts)
+ assertNull(state.selectedAccountId)
+ assertEquals(AddAccountDraft(), state.addDraft)
+ assertNull(state.pendingRemovalAccountId)
+ assertNull(state.problem)
+ }
+
+ @Test
+ fun accountContainsOnlyTheApprovedProofFields() {
+ val account = Account(
+ id = AccountId("account-1"),
+ displayName = "Farm Account",
+ serverUrl = "https://farm.example.test",
+ loginStatus = LoginStatus.LoggedOut,
+ )
+
+ assertEquals("account-1", account.id.value)
+ assertEquals("Farm Account", account.displayName)
+ assertEquals("https://farm.example.test", account.serverUrl)
+ assertEquals(LoginStatus.LoggedOut, account.loginStatus)
+ }
+}