From a8feb88c2e71a272efced50b42f1f35633b61bac Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 20 Jun 2026 19:30:33 +0000 Subject: [PATCH] refactor: derive app list from observeNotes, drop tick + rescan The screen previously used observeNewEvents purely as an invalidation tick to re-scan LocalCache.addressables on every app-definition insertion. Since observeNotes already seeds with the cached kind-31990 notes and re-emits as new ones arrive, the candidate list now derives straight from those notes: the appDefinitionsTick counter and the repeated full-cache rescan are gone. Initial value is seeded from the current cache snapshot to preserve the first-frame content. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01J8oLe5cMzSY8AzTiULJZiW --- .../ProfileAppRecommendationsScreen.kt | 45 ++++++++++--------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/apps/recommendations/ProfileAppRecommendationsScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/apps/recommendations/ProfileAppRecommendationsScreen.kt index 5101c013c6..1d675f2502 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/apps/recommendations/ProfileAppRecommendationsScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/apps/recommendations/ProfileAppRecommendationsScreen.kt @@ -45,7 +45,6 @@ import androidx.compose.material3.TextFieldDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @@ -83,31 +82,36 @@ import com.vitorpamplona.amethyst.ui.theme.placeholderText import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter import com.vitorpamplona.quartz.nip89AppHandlers.definition.AppDefinitionEvent import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch +import kotlinx.coroutines.flow.flowOn @Composable fun ProfileAppRecommendationsScreen( accountViewModel: AccountViewModel, nav: INav, ) { - val myPubkey = accountViewModel.userProfile().pubkeyHex - // Pull my kind 31989 events plus recent kind 31990 app definitions from // relays so the list below has candidates while this screen is open. ProfileAppRecommendationsFilterAssemblerSubscription(accountViewModel) - // Ticks whenever a new app definition (kind 31990) is inserted into the - // cache, so the candidate snapshot below recomputes. observeNewEvents lets - // the cache index do the kind narrowing instead of scanning the global - // firehose of every new event. - var appDefinitionsTick by remember { mutableIntStateOf(0) } - LaunchedEffect(myPubkey) { - launch(Dispatchers.IO) { - LocalCache - .observeNewEvents(Filter(kinds = listOf(AppDefinitionEvent.KIND))) - .collect { appDefinitionsTick++ } - } - } + // Kind 31990 app definitions, kept live. observeNotes seeds with what is + // already cached and re-emits as new definitions are inserted, so the + // candidate list below derives straight from these notes — no manual tick or + // full-cache rescan. (Per-row metadata changes are watched by AppRow itself, + // which is why an in-place addressable replacement not re-emitting here is + // fine.) The initial value is the current cache snapshot so the first frame + // matches the seeded emission instead of flashing empty. + val appDefinitionNotes by remember { + LocalCache + .observeNotes(Filter(kinds = listOf(AppDefinitionEvent.KIND))) + .flowOn(Dispatchers.IO) + }.collectAsStateWithLifecycle( + initialValue = + remember { + LocalCache.addressables + .filterIntoSet(AppDefinitionEvent.KIND) { _, _ -> true } + .toList() + }, + ) val myRecommendationEvents by accountViewModel.account.appRecommendations.flow .collectAsStateWithLifecycle() @@ -140,10 +144,11 @@ fun ProfileAppRecommendationsScreen( } val apps = - remember(appDefinitionsTick, pinnedRecommended, pinnedFollows) { - LocalCache.addressables - .filterIntoSet(AppDefinitionEvent.KIND) { _, note -> - val event = note.event as? AppDefinitionEvent ?: return@filterIntoSet false + remember(appDefinitionNotes, pinnedRecommended, pinnedFollows) { + appDefinitionNotes + .filterIsInstance() + .filter { note -> + val event = note.event as? AppDefinitionEvent ?: return@filter false // Unnamed apps are poor recommendation candidates; keep them // only when already recommended, so they can be turned off. note.address in pinnedRecommended ||