Auto-scroll nest chat to newest message when at bottom

Both NestFullScreen and NestLobbyScreen render the kind-1311
transcript with LazyColumn(reverseLayout = true). When a new message
is prepended, LazyColumn preserves visual position by shifting
firstVisibleItemIndex from 0 to 1, leaving the new message just below
the viewport — the user has to scroll down to see it.

If the user is at (or one item from) the bottom when a new message
arrives, scroll back to item 0 so the newest message is visible. If
they are reading older history, leave them where they are.
This commit is contained in:
Claude
2026-04-30 23:59:25 +00:00
parent bad3cfa8f6
commit f6f2559d31
2 changed files with 27 additions and 0 deletions
@@ -35,6 +35,7 @@ import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
@@ -100,6 +101,19 @@ internal fun ColumnScope.NestChatPanel(
val listState = rememberLazyListState()
// Auto-stick to the newest message. With reverseLayout=true, item 0
// is the bottom of the viewport (newest). When a new message is
// prepended, LazyColumn preserves visual position by shifting
// firstVisibleItemIndex from 0 → 1, leaving the new message just
// below the viewport. If the user was at (or near) the bottom,
// scroll back to 0 so the new message becomes visible. If they're
// reading older history, leave them where they are.
LaunchedEffect(messages.firstOrNull()?.idHex) {
if (listState.firstVisibleItemIndex <= 1) {
listState.animateScrollToItem(0)
}
}
Column(modifier = modifier.fillMaxWidth()) {
Box(
modifier =
@@ -164,6 +164,19 @@ private fun NestLobbyContent(
val listState = rememberLazyListState()
val scope = rememberCoroutineScope()
// Auto-stick to the newest message. With reverseLayout=true, item 0
// is the bottom of the viewport (newest). When a new message is
// prepended, LazyColumn preserves visual position by shifting
// firstVisibleItemIndex from 0 → 1, leaving the new message just
// below the viewport. If the user was at (or near) the bottom,
// scroll back to 0 so the new message becomes visible. If they're
// reading older history, leave them where they are.
LaunchedEffect(messages.firstOrNull()?.idHex) {
if (listState.firstVisibleItemIndex <= 1) {
listState.animateScrollToItem(0)
}
}
Scaffold(
contentWindowInsets = WindowInsets(0),
topBar = {