| 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
LazyListScope items 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.isLoadingOnly or equivalent source-specific loading state.
- Keep recommendation loading in a separate
isDiscoverLoadingOnly.
- Compute
sourceColumns from list mode and metrics.
- Use a small collapsed count such as
columns * 5.
- Build
visibleSourceGroups with a max source count.
- Use
itemsIndexed rows:
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
item if the page itself is a LazyColumn.
- 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.