diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/KaffeekasseScraper.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/KaffeekasseScraper.kt
index aa790757b56d0d8347f908aae51f7658565e5c4c..76b51fb1264977914fa6ea4245b18263d3721a5d 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/KaffeekasseScraper.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/KaffeekasseScraper.kt
@@ -17,6 +17,8 @@ import net.novagamestudios.kaffeekasse.api.portal.PortalClient
 import net.novagamestudios.kaffeekasse.model.kaffeekasse.Cart
 import net.novagamestudios.kaffeekasse.model.kaffeekasse.KnownItem
 import net.novagamestudios.kaffeekasse.model.kaffeekasse.ManualBillDetails
+import net.novagamestudios.kaffeekasse.model.kaffeekasse.Name.Companion.toNameOrNull
+import net.novagamestudios.kaffeekasse.model.kaffeekasse.ScraperPurchaseAccount
 import net.novagamestudios.kaffeekasse.model.kaffeekasse.Transaction
 import net.novagamestudios.kaffeekasse.model.kaffeekasse.isEmpty
 import java.time.LocalDateTime
@@ -45,7 +47,7 @@ class KaffeekasseScraper(
         }
     }
 
-    suspend fun submitCart(account: ManualBillDetails.PurchaseAccount, cart: Cart): Unit = with(client) {
+    suspend fun submitCart(account: ScraperPurchaseAccount, cart: Cart): Unit = with(client) {
         if (cart.isEmpty()) return
         post(
             manualBillUrl,
@@ -73,7 +75,9 @@ private fun Doc.scrapeManualBillDetails(): ManualBillDetails = form {
                         if (option.hasAttribute("disabled")) null
                         else if (option.attribute("value").isBlank()) null
                         else ManualBillDetails.PurchaseAccount(
-                            name = option.text,
+                            name = option.text.let {
+                                requireNotNull(it.toNameOrNull()) { "Invalid account name: $it" }
+                            },
                             id = option.attribute("value").let {
                                 requireNotNull(it.toIntOrNull()) { "Invalid account id: $it" }
                             },
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/model/BasicUserInfo.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/model/BasicUserInfo.kt
index e9f493409678e07f2658348672317aba57bebc75..3e2b94425645f533a870493706a34dd241d342b3 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/model/BasicUserInfo.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/model/BasicUserInfo.kt
@@ -2,6 +2,7 @@ package net.novagamestudios.kaffeekasse.api.kaffeekasse.model
 
 import kotlinx.serialization.SerialName
 import kotlinx.serialization.Serializable
+import net.novagamestudios.kaffeekasse.model.kaffeekasse.Name
 import net.novagamestudios.kaffeekasse.util.IntAsBooleanSerializer
 
 @Serializable
@@ -9,12 +10,9 @@ data class BasicUserInfo(
     @SerialName("id")
     override val id: Int,
     @SerialName("name")
-    override val name: String,
+    override val name: Name,
     @SerialName("empty_pin")
     val noPinSet: @Serializable(with = IntAsBooleanSerializer::class) Boolean? = null
 ) : APIPurchaseAccount {
-    val firstName by lazy { name.split(", ")[1] }
-    val lastName by lazy { name.split(", ")[0] }
-
     val mayHavePin get() = noPinSet == null || !noPinSet
 }
\ No newline at end of file
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/model/BlackWhiteListUserInfo.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/model/BlackWhiteListUserInfo.kt
index cb98eaee42a6b1b559a12d149ca18f25d7d1cf07..af64c210b240e8dbeae7abf46d10533a81ec949e 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/model/BlackWhiteListUserInfo.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/model/BlackWhiteListUserInfo.kt
@@ -2,11 +2,12 @@ package net.novagamestudios.kaffeekasse.api.kaffeekasse.model
 
 import kotlinx.serialization.SerialName
 import kotlinx.serialization.Serializable
+import net.novagamestudios.kaffeekasse.model.kaffeekasse.Name
 
 @Serializable
 data class BlackWhiteListUserInfo(
     @SerialName("target_id")
     override val id: Int,
     @SerialName("name")
-    override val name: String
+    override val name: Name
 ) : APIPurchaseAccount
\ No newline at end of file
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/model/UserInfoResponse.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/model/UserInfoResponse.kt
index 3ef3fbbe0e5ae2a9d83117045a47ebee1d6078ad..ac1e31fc7b302c0d34a1854d6e5962d63ee170c8 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/model/UserInfoResponse.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/api/kaffeekasse/model/UserInfoResponse.kt
@@ -3,13 +3,14 @@ package net.novagamestudios.kaffeekasse.api.kaffeekasse.model
 import kotlinx.serialization.SerialName
 import kotlinx.serialization.Serializable
 import kotlinx.serialization.json.JsonArray
+import net.novagamestudios.kaffeekasse.model.kaffeekasse.Name
 
 @Serializable
 data class UserInfoResponse(
     @SerialName("user_id")
     override val id: Int,
     @SerialName("name")
-    override val name: String,
+    override val name: Name,
     @SerialName("balance")
     override val balance: Double,
     @SerialName("favorites")
@@ -22,7 +23,7 @@ data class UserInfoResponse(
 
 sealed interface ExtendedUserInfo : APIPurchaseAccount {
     override val id: Int
-    override val name: String
+    override val name: Name
     val balance: Double
     val favorites: JsonArray
     val blacklist: List<BlackWhiteListUserInfo>?
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/model/kaffeekasse/ManualBillDetails.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/model/kaffeekasse/ManualBillDetails.kt
index 2753b2f3137190d0018555ab998ae8506dd7b5ad..c7bdbab2f5e96876a3e8d2306841586f30cb61d6 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/model/kaffeekasse/ManualBillDetails.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/model/kaffeekasse/ManualBillDetails.kt
@@ -16,10 +16,10 @@ data class ManualBillDetails(
 ) {
     @Serializable
     data class PurchaseAccount(
-        override val name: String,
+        override val name: Name,
         override val id: Int,
         val isDefault: Boolean = false
-    ) : net.novagamestudios.kaffeekasse.model.kaffeekasse.PurchaseAccount
+    ) : ScraperPurchaseAccount
     @Serializable
     data class ItemGroup(
         override val originalName: String,
@@ -47,4 +47,6 @@ data class ManualBillDetails(
         override val imageDrawable: Int? get() = knownItem?.drawableResource
         override val imageUrl: String? get() = null
     }
-}
\ No newline at end of file
+}
+
+sealed interface ScraperPurchaseAccount : PurchaseAccount
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/model/kaffeekasse/PurchaseAccount.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/model/kaffeekasse/PurchaseAccount.kt
index ffc199cc07122ffa66d126f21a5f4c9f1cd9c590..db8d3d222f61de5dffd5f061ee29ace81900e316 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/model/kaffeekasse/PurchaseAccount.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/model/kaffeekasse/PurchaseAccount.kt
@@ -1,6 +1,43 @@
 package net.novagamestudios.kaffeekasse.model.kaffeekasse
 
+import kotlinx.serialization.Serializable
+
 interface PurchaseAccount {
     val id: Int
-    val name: String
-}
\ No newline at end of file
+    val name: Name
+}
+
+@Serializable
+@JvmInline
+value class Name private constructor(private val raw: String) : Comparable<Name> {
+    private val components get() = raw.split(Separator)
+
+    operator fun component1() = components[0]
+    operator fun component2() = components[1]
+
+    val last get() = component1()
+    val first get() = component2()
+
+    val firstLast get() = "$first $last"
+
+    val char get() = last.first().uppercaseChar()
+
+    override fun toString() = raw
+
+    override fun compareTo(other: Name) = raw.uppercase().compareTo(other.raw.uppercase())
+
+    init {
+        require(components.size == 2) { "Name \"$raw\" does not have exactly two components" }
+    }
+
+    companion object {
+        const val Separator = ", "
+
+        operator fun invoke(firstName: String, lastName: String) = Name("$lastName$Separator$firstName")
+
+        fun String.toNameOrNull(): Name? = this
+            .split(Separator)
+            .takeIf { it.size == 2 }
+            ?.let { (lastName, firstName) -> Name(firstName, lastName) }
+    }
+}
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/i11/KaffeekasseRepository.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/i11/KaffeekasseRepository.kt
index 0cd93d672e1d32d4733cad0a534e863a14642728..534260f9efe840bb8114117f1e9d4a7db31f5f44 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/i11/KaffeekasseRepository.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/repositories/i11/KaffeekasseRepository.kt
@@ -22,6 +22,7 @@ import net.novagamestudios.kaffeekasse.model.kaffeekasse.ItemGroup
 import net.novagamestudios.kaffeekasse.model.kaffeekasse.KnownItem
 import net.novagamestudios.kaffeekasse.model.kaffeekasse.KnownItemGroup
 import net.novagamestudios.kaffeekasse.model.kaffeekasse.ManualBillDetails
+import net.novagamestudios.kaffeekasse.model.kaffeekasse.ScraperPurchaseAccount
 import net.novagamestudios.kaffeekasse.model.kaffeekasse.Stock
 import net.novagamestudios.kaffeekasse.model.kaffeekasse.Transaction
 import net.novagamestudios.kaffeekasse.model.kaffeekasse.isEmpty
@@ -122,31 +123,31 @@ class KaffeekasseRepository(
                     // Unknown item
                     itemGroupsById[item.itemTypeId]?.items?.add(
                         ItemImpl(
-                        id = item.id,
-                        originalName = item.originalName,
-                        category = ItemCategory.Other,
-                        cleanProductName = item.originalName,
-                        cleanVariantName = null,
-                        price = item.price,
-                        estimatedPrice = item.price,
-                        imageDrawable = null,
-                        imageUrl = item.imageUrl
-                    )
+                            id = item.id,
+                            originalName = item.originalName,
+                            category = ItemCategory.Other,
+                            cleanProductName = item.originalName,
+                            cleanVariantName = null,
+                            price = item.price,
+                            estimatedPrice = item.price,
+                            imageDrawable = null,
+                            imageUrl = item.imageUrl
+                        )
                     )
                 } else knownItems.forEach { known ->
                     // Combine with known values
                     itemGroupsById[item.itemTypeId]?.items?.add(
                         ItemImpl(
-                        id = item.id,
-                        originalName = item.originalName,
-                        category = known.category,
-                        cleanProductName = known.cleanProductName,
-                        cleanVariantName = known.cleanVariantName,
-                        price = item.price,
-                        estimatedPrice = item.price,
-                        imageDrawable = known.drawableResource,
-                        imageUrl = item.imageUrl
-                    )
+                            id = item.id,
+                            originalName = item.originalName,
+                            category = known.category,
+                            cleanProductName = known.cleanProductName,
+                            cleanVariantName = known.cleanVariantName,
+                            price = item.price,
+                            estimatedPrice = item.price,
+                            imageDrawable = known.drawableResource,
+                            imageUrl = item.imageUrl
+                        )
                     )
                 }
             }
@@ -173,7 +174,7 @@ class KaffeekasseRepository(
     }
 
 
-    suspend fun purchase(asUser: User, cart: Cart, account: ManualBillDetails.PurchaseAccount) {
+    suspend fun purchase(asUser: User, cart: Cart, account: ScraperPurchaseAccount) {
         if (cart.isEmpty()) return
         requireLoggedIn(asUser)
         scraper.submitCart(account, cart)
@@ -202,6 +203,7 @@ class KaffeekasseRepository(
     }
 
     override fun markUserDataDirty() {
+        stock.markDirty()
         account.clear()
         transactions.clear()
         extendedUserInfoById.values.forEach { it.markDirty() }
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/AccountSelection.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/AccountSelection.kt
index d358d2d027ce01f482f4aa6db59c9607e016e72e..30f34dc16e6d0d17341bbe1948ee60fa1dd7f63a 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/AccountSelection.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/AccountSelection.kt
@@ -18,17 +18,17 @@ import androidx.compose.ui.Modifier
 import net.novagamestudios.kaffeekasse.model.kaffeekasse.PurchaseAccount
 
 
-class AccountSelectionState(
-    val accounts: List<PurchaseAccount>
+class AccountSelectionState<out PA : PurchaseAccount>(
+    val accounts: List<PA>
 ) {
     var selectedIndex by mutableStateOf(0)
-    val selectedAccount: PurchaseAccount? get() = accounts.getOrNull(selectedIndex)
+    val selectedAccount: PA? get() = accounts.getOrNull(selectedIndex)
 }
 
 
 @Composable
 fun AccountSelection(
-    state: AccountSelectionState,
+    state: AccountSelectionState<*>,
     modifier: Modifier = Modifier
 ) {
     var expanded by remember { mutableStateOf(false) }
@@ -38,7 +38,7 @@ fun AccountSelection(
         modifier
     ) {
         OutlinedTextField(
-            state.selectedAccount?.name ?: "",
+            state.selectedAccount?.name?.let { "$it" } ?: "",
             onValueChange = { },
             Modifier
                 .menuAnchor(MenuAnchorType.PrimaryNotEditable),
@@ -53,7 +53,7 @@ fun AccountSelection(
         ) {
             state.accounts.forEachIndexed { index, account ->
                 DropdownMenuItem(
-                    text = { Text(account.name) },
+                    text = { Text("${account.name}") },
                     onClick = {
                         state.selectedIndex = index
                         expanded = false
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/Checkout.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/Checkout.kt
index b9af47722dc9b40a7aa31f99954f9a86b1d1700b..091ac1258045e3fe377d761ad87dd810460ff0bf 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/Checkout.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/Checkout.kt
@@ -57,9 +57,9 @@ import net.novagamestudios.common_utils.warn
 import net.novagamestudios.kaffeekasse.KaffeekasseModule.Companion.kaffeekasseCart
 import net.novagamestudios.kaffeekasse.api.kaffeekasse.model.APIPurchaseAccount
 import net.novagamestudios.kaffeekasse.model.kaffeekasse.Cart
-import net.novagamestudios.kaffeekasse.model.kaffeekasse.ManualBillDetails
 import net.novagamestudios.kaffeekasse.model.kaffeekasse.MutableCart
 import net.novagamestudios.kaffeekasse.model.kaffeekasse.PurchaseAccount
+import net.novagamestudios.kaffeekasse.model.kaffeekasse.ScraperPurchaseAccount
 import net.novagamestudios.kaffeekasse.model.kaffeekasse.isEmpty
 import net.novagamestudios.kaffeekasse.model.kaffeekasse.isNotEmpty
 import net.novagamestudios.kaffeekasse.model.session.Session
@@ -68,6 +68,7 @@ import net.novagamestudios.kaffeekasse.repositories.RepositoryProvider
 import net.novagamestudios.kaffeekasse.repositories.i11.KaffeekasseRepository
 import net.novagamestudios.kaffeekasse.ui.kaffeekasse.components.cards.CategoryIcon
 import net.novagamestudios.kaffeekasse.util.richdata.RichDataFlow
+import net.novagamestudios.kaffeekasse.util.richdata.RichDataSource
 import net.novagamestudios.kaffeekasse.util.richdata.RichDataState
 import net.novagamestudios.kaffeekasse.util.richdata.collectAsRichStateIn
 import net.novagamestudios.kaffeekasse.util.richdata.combineRich
@@ -78,7 +79,7 @@ import net.novagamestudios.kaffeekasse.util.richdata.stateIn
 import kotlin.time.Duration.Companion.seconds
 
 
-abstract class CheckoutViewModel(
+abstract class CheckoutViewModel<PA : PurchaseAccount>(
     private val coroutineScope: CoroutineScope,
     protected val kaffeekasse: KaffeekasseRepository,
     val cart: MutableCart,
@@ -94,7 +95,13 @@ abstract class CheckoutViewModel(
 
     var indicateSuccess by mutableStateOf(false)
 
-    abstract val accountSelectionState: RichDataState<AccountSelectionState>
+    protected abstract val purchaseAccounts: RichDataFlow<List<PA>>
+
+    val accountSelectionState: RichDataState<AccountSelectionState<PA>> by lazy {
+        purchaseAccounts
+            .mapRich { accounts -> AccountSelectionState(accounts) }
+            .collectAsRichStateIn(coroutineScope)
+    }
 
     protected abstract suspend fun refreshPurchaseAccountsIfNeeded()
     fun refreshPurchaseAccounts() {
@@ -104,7 +111,7 @@ abstract class CheckoutViewModel(
             }
         }
     }
-    protected abstract suspend fun performSubmitCart(purchaseAccount: PurchaseAccount)
+    protected abstract suspend fun performSubmitCart(purchaseAccount: PA)
 
     fun submitCart() {
         if (cart.isEmpty()) return
@@ -136,7 +143,7 @@ abstract class CheckoutViewModel(
             session: Session.WithRealUser,
             coroutineScope: CoroutineScope,
             onSubmitted: suspend () -> Unit
-        ): CheckoutViewModel = if (session is Session.WithDevice) {
+        ): CheckoutViewModel<*> = if (session is Session.WithDevice) {
             APICheckoutViewModel(
                 session.realUser,
                 coroutineScope,
@@ -162,27 +169,22 @@ private class ScraperCheckoutViewModel(
     kaffeekasse: KaffeekasseRepository,
     cart: MutableCart,
     onSubmitted: suspend () -> Unit
-) : CheckoutViewModel(
+) : CheckoutViewModel<ScraperPurchaseAccount>(
     coroutineScope,
     kaffeekasse,
     cart,
     onSubmitted
 ) {
-
-    private val purchaseAccounts = kaffeekasse.manualBillAccounts[user]
-
-    override val accountSelectionState = purchaseAccounts.mapRich { accounts ->
-        AccountSelectionState(accounts)
-    }.collectAsRichStateIn(coroutineScope)
+    override val purchaseAccounts: RichDataSource<List<ScraperPurchaseAccount>> = kaffeekasse.manualBillAccounts[user]
 
     override suspend fun refreshPurchaseAccountsIfNeeded() {
         purchaseAccounts.ensureCleanData()
     }
-    override suspend fun performSubmitCart(purchaseAccount: PurchaseAccount) {
+    override suspend fun performSubmitCart(purchaseAccount: ScraperPurchaseAccount) {
         kaffeekasse.purchase(
             asUser = user,
             cart = cart,
-            account = purchaseAccount as ManualBillDetails.PurchaseAccount
+            account = purchaseAccount
         )
     }
 }
@@ -193,49 +195,52 @@ private class APICheckoutViewModel(
     kaffeekasse: KaffeekasseRepository,
     cart: MutableCart,
     onSubmitted: suspend () -> Unit
-) : CheckoutViewModel(coroutineScope, kaffeekasse, cart, onSubmitted) {
+) : CheckoutViewModel<APIPurchaseAccount>(coroutineScope, kaffeekasse, cart, onSubmitted) {
 
     private val selfUserBasic = kaffeekasse.basicUserInfoList.mapRich { userList ->
         val selfDisplayName = user.displayName ?: return@mapRich null
-        userList.firstOrNull { "${it.firstName} ${it.lastName}" == selfDisplayName }
+        userList.firstOrNull { it.name.firstLast == selfDisplayName }
     }.stateIn(coroutineScope)
 
     private val selfUserExtended = selfUserBasic.flatMapLatestRich {
         kaffeekasse.getExtendedUserInfo(it.id)
     }
 
-    private val purchaseAccounts: RichDataFlow<List<PurchaseAccount>> = combineRich(
-        selfUserExtended,
+//    override val purchaseAccounts: RichDataFlow<List<PurchaseAccount>> = combineRich(
+//        selfUserExtended,
+//        kaffeekasse.basicUserInfoList
+//    ) { self, userList ->
+//        // FIXME wrong way around
+//
+//        val fromWhitelist = self.whitelist.orEmpty()
+//        val fromBlacklist = self.blacklist?.let { blacklist ->
+//            val blacklistIds = blacklist.mapTo(mutableSetOf()) { it.id }
+//            userList.filter { it.id !in blacklistIds }
+//        }.orEmpty()
+//
+//        listOf(self) + fromWhitelist + fromBlacklist
+//    }
+
+    override val purchaseAccounts = combineRich(
+        kaffeekasse.manualBillAccounts[user],
         kaffeekasse.basicUserInfoList
-    ) { self, userList ->
-        // FIXME wrong way around
-
-        val fromWhitelist = self.whitelist.orEmpty()
-        val fromBlacklist = self.blacklist?.let { blacklist ->
-            val blacklistIds = blacklist.mapTo(mutableSetOf()) { it.id }
-            userList.filter { it.id !in blacklistIds }
-        }.orEmpty()
-
-        listOf(self) + fromWhitelist + fromBlacklist
+    ) { scraperAccounts, apiAccounts ->
+        scraperAccounts.mapNotNull { account -> apiAccounts.firstOrNull { it.name == account.name } }
     }
 
-    override val accountSelectionState = purchaseAccounts.mapRich { accounts ->
-        AccountSelectionState(accounts)
-    }.collectAsRichStateIn(coroutineScope)
-
     override suspend fun refreshPurchaseAccountsIfNeeded() {
         kaffeekasse.basicUserInfoList.ensureCleanData()
         selfUserBasic.value.dataOrNull?.let {
             kaffeekasse.getExtendedUserInfo(it.id).ensureCleanData()
         }
     }
-    override suspend fun performSubmitCart(purchaseAccount: PurchaseAccount) {
+    override suspend fun performSubmitCart(purchaseAccount: APIPurchaseAccount) {
         kaffeekasse.purchase(
             asUser = user,
             cart = cart,
             targetAccount = purchaseAccount.takeUnless {
                 it.id == selfUserBasic.value.dataOrNull?.id
-            } as? APIPurchaseAccount
+            }
         )
     }
 }
@@ -243,7 +248,7 @@ private class APICheckoutViewModel(
 
 @Composable
 fun Checkout(
-    vm: CheckoutViewModel,
+    vm: CheckoutViewModel<*>,
     modifier: Modifier = Modifier
 ) {
     if (vm.cart.isNotEmpty()) CheckoutFAB(
@@ -293,7 +298,7 @@ private fun CheckoutFAB(
 
 @Composable
 private fun CheckoutModal(
-    accountSelectionState: AccountSelectionState,
+    accountSelectionState: AccountSelectionState<*>,
     cart: MutableCart,
     onSubmit: () -> Unit,
     onDismissRequest: () -> Unit,
@@ -327,7 +332,7 @@ private fun CheckoutModal(
 
 @Composable
 private fun CheckoutDetails(
-    accountSelectionState: AccountSelectionState,
+    accountSelectionState: AccountSelectionState<*>,
     cart: MutableCart,
     onSubmit: () -> Unit,
     modifier: Modifier = Modifier,
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/CustomItems.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/CustomItems.kt
index 2ffadf561e2583c31fee8a30077a479eb9213836..cb27a9c510f19b92197fef63bd59a2d67bf06d36 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/CustomItems.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/CustomItems.kt
@@ -127,7 +127,8 @@ class CustomItemsViewModel private constructor(
             allItemsByName: Map<String, Item>,
             transactions: List<Transaction>
         ): Sequence<Item> {
-            val statsByItem = transactions
+            val statsByItem: Map<Item, ItemStats> = transactions.asSequence()
+                .take(TransactionsLimit)
                 .mapNotNull {
                     val purpose = it.purpose as? Transaction.Purpose.Purchase ?: return@mapNotNull null
                     val item = allItemsByName[purpose.itemName] ?: return@mapNotNull null
@@ -169,6 +170,7 @@ class CustomItemsViewModel private constructor(
             val purchaseCount: Int
         )
 
+        private const val TransactionsLimit = 100
         private const val RecentItemsConsidered = 10
         private const val FrequentItemsConsidered = 10
         private const val RecentItemsWeight = 1.0
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/cards/Item.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/cards/Item.kt
index 4b93751b3e85a5f751efea3f738524d7c8779778..9c6fb2d808861665579ecae7ea3ed65d5364a85d 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/cards/Item.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/kaffeekasse/components/cards/Item.kt
@@ -148,7 +148,7 @@ private fun ItemInformation(
 
     // Very bad code
     val app = app()
-    val session = app.portalRepository.session.collectAsState()
+    val session = app.portalRepository.session.collectAsState().value
     if (session is Session.WithRealUser) {
         val transactionsState = app.kaffeekasseRepository.transactions[session.realUser].collectAsRichState()
         val lastUnitPrice by remember {
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/login/UserSelection.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/login/UserSelection.kt
index e1117c1b3e2bbe1ba573ce71ff468cd90a8d46aa..83f8330ca611d2caf9b6a2a81cce04ed2930c891 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/ui/login/UserSelection.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/ui/login/UserSelection.kt
@@ -118,9 +118,9 @@ class UserSelectionViewModel private constructor(
             users,
             loadDuringTransform = true
         ) { query, users ->
-            val sorted = users.asSequence().sortedBy { it.lastName.uppercase() }
+            val sorted = users.asSequence().sortedBy { it.name }
             if (query.isBlank()) return@combineRich sorted.toList()
-            sorted.filter { query in "${it.firstName} ${it.lastName}".lowercase() }.toList()
+            sorted.filter { query in it.name.firstLast.lowercase() }.toList()
         }
             .flowOn(Dispatchers.IO)
             .stateIn(screenModelScope)
@@ -257,7 +257,7 @@ fun UserSelection(
                 val gridState = rememberLazyGridState()
                 val coroutineScope = rememberCoroutineScope()
 
-                val currentStartChars by rememberDerivedStateOf {
+                val currentStartChars: Set<Char> by rememberDerivedStateOf {
                     val start = gridState.layoutInfo.viewportStartOffset + gridState.layoutInfo.beforeContentPadding
                     val end = gridState.layoutInfo.viewportEndOffset - gridState.layoutInfo.afterContentPadding
                     gridState.layoutInfo.visibleItemsInfo
@@ -267,13 +267,13 @@ fun UserSelection(
                             mid in start..<end
                         }
                         .mapNotNull { it.contentType as? Int }
-                        .mapNotNullTo(mutableSetOf()) { users.getOrNull(it)?.charOrNull }
+                        .mapNotNullTo(mutableSetOf()) { users.getOrNull(it)?.name?.char }
                 }
                 val userIndexAndCharIndexByChar: Map<Char, Pair<Int, Int>> by rememberDerivedStateOf {
                     users.asSequence()
                         .withIndex()
-                        .mapNotNull { (userIndex, user) ->
-                            user.charOrNull?.let { it to userIndex }
+                        .map { (userIndex, user) ->
+                            user.name.char to userIndex
                         }
                         .distinctBy { (char, _) -> char }
                         .withIndex()
@@ -463,7 +463,7 @@ private fun UserGrid(
 ) {
     var prevChar: Char? = null
     users.forEachIndexed { i, user ->
-        val char = user.charOrNull
+        val char = user.name.char
         if (prevChar != char) {
             item(
                 key = char,
@@ -518,9 +518,9 @@ private fun UserItem(
                 .padding(horizontal = 16.dp)
         ) {
             ProvideTextStyle(MaterialTheme.typography.titleMedium) {
-                Text("${user.lastName},")
+                Text("${user.name.last},")
                 Spacer(Modifier.width(8.dp))
-                Text(user.firstName)
+                Text(user.name.first)
             }
             Spacer(Modifier.weight(1f))
             if (user.noPinSet == false) {
@@ -551,7 +551,7 @@ private fun UserAuthDialog(
         TextButton(onClick = onDismiss) { Text("Abbrechen") }
     },
     icon = { Icon(Icons.Default.Password, "PIN") },
-    title = { Text("${state.user.firstName} ${state.user.lastName}") },
+    title = { Text(state.user.name.firstLast) },
     text = {
         val focusRequester = remember { FocusRequester() }
         OutlinedTextField(
@@ -577,5 +577,5 @@ private fun UserAuthDialog(
 )
 
 
-private val BasicUserInfo.charOrNull get() = lastName.firstOrNull()?.uppercaseChar()
+private val BasicUserInfo.charOrNull get() = name.last.firstOrNull()?.uppercaseChar()
 
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/util/richdata/RichDataFunctions.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/util/richdata/RichDataFunctions.kt
index c06161256fe40b7432deecbaf8bb61da9003e249..bf7fce133fe961cb3c39ef902708d68820166ab5 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/util/richdata/RichDataFunctions.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/util/richdata/RichDataFunctions.kt
@@ -3,7 +3,7 @@ package net.novagamestudios.kaffeekasse.util.richdata
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.supervisorScope
 
-interface RichDataFunctions<in T : Any> {
+interface RichDataFunctions<out T : Any> {
     fun markDirty()
     suspend fun ensureCleanData()
     suspend fun refresh()
diff --git a/app/src/main/java/net/novagamestudios/kaffeekasse/util/richdata/RichDataSource.kt b/app/src/main/java/net/novagamestudios/kaffeekasse/util/richdata/RichDataSource.kt
index 4374a937e6089b9cfa50b8c0463ffee3917ee8c5..c3db23acfab1e95da739fd69933dac4ecde7b1e6 100644
--- a/app/src/main/java/net/novagamestudios/kaffeekasse/util/richdata/RichDataSource.kt
+++ b/app/src/main/java/net/novagamestudios/kaffeekasse/util/richdata/RichDataSource.kt
@@ -2,7 +2,6 @@ package net.novagamestudios.kaffeekasse.util.richdata
 
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.Channel
-import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.FlowCollector
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
@@ -14,7 +13,7 @@ typealias RichDataCollector<T> = FlowCollector<RichData<T>>
 typealias RichDataFactory<T> = suspend RichDataCollector<T>.() -> RichData<T>
 
 
-interface RichDataSource<T : Any> : RichDataFunctions<T>, RichDataStateFlow<T> {
+interface RichDataSource<out T : Any> : RichDataFunctions<T>, RichDataStateFlow<T>, RichDataFlow<T> {
     companion object {
         context (CoroutineScope)
         fun <T : Any> RichDataSource(