name: kototoro-browse-source-scroll description: Diagnose and fix Kototoro Browse/Explore page content source scrolling jank when many sources exist. Use for ExploreHostScreen, ExploreViewModel, source quick access, LazyColumn source rows, source grouping, browse recommendation loading, and tab switching performance.
Kototoro Browse Source Scroll
Use this skill when the Browse/Explore page stalls, freezes, or shows a blank frame while switching to the sources area, especially with hundreds or thousands of installed/enabled sources.
Root Cause Pattern
Do not treat this as only an image/icon loading problem. In Kototoro the expensive path can be:
- Rendering all sources inside one large composable block.
- Grouping/chunking sources during every recomposition.
- Showing source quick access fully expanded while tracking recommendation hero content is also present.
- Mixing discover recommendation loading state with source loading state.
- Recreating source rows on every tab/filter change because keys/content types are too coarse.
Preferred Fix Pattern
- Split source quick access into
LazyListScopeitems instead of a single detached block. - Render source cards as lazy rows with stable row keys and
contentType. - Precompute source metrics with
remember(gridScale). - Precompute groups with
remember(sources, isGroupedByLanguage, context). - Default to a collapsed source section when recommendations are enabled; expand only on user request.
- Force expanded only when recommendations are disabled, so source-only mode preserves the old direct-access behavior.
- Track source loading separately from discover/recommendation loading.
Implementation Checklist
- Add a
BrowseSourceItems.isLoadingOnlyor equivalent source-specific loading state. - Keep recommendation loading in a separate
isDiscoverLoadingOnly. - Compute
sourceColumnsfrom list mode and metrics. - Use a small collapsed count such as
columns * 5. - Build
visibleSourceGroupswith a max source count. - Use
itemsIndexedrows:
itemsIndexed(
items = rows,
key = { rowIndex, rowSources ->
val firstId = rowSources.firstOrNull()?.id ?: rowIndex.toLong()
"source_row_${groupIndex}_${rowIndex}_$firstId"
},
contentType = { _, _ -> "source_row" },
) { _, rowSources ->
SourceQuickAccessRow(...)
}
Pitfalls
- Do not put a large nested source grid inside one
itemif the page itself is aLazyColumn. - Do not use only index keys for source rows if groups can change.
- Do not let disabled recommendation features still trigger recommendation load or refresh.
- Do not remove source long-click selection behavior while refactoring rows.
Verification
Run:
JAVA_HOME="/usr/lib/jvm/java-17-openjdk-amd64" ./gradlew :app:compileDebugKotlin --no-daemon
Manual acceptance:
- Switching to Browse with many sources does not pause before first paint.
- Scrolling remains responsive with hundreds or thousands of sources.
- "Show more/less" works and preserves source click/long-click behavior.
- Source-only mode still shows all sources without hiding access behind recommendations.