fix(chat): stop crash when expanding reasoning card with Show full (#884)

* fix(chat): stop crash when expanding reasoning card with Show full

ReasoningCard applied heightIn(max = Dp.Unspecified) once "Show full"
was tapped. Inside a LazyColumn item the main axis is unbounded, so the
verticalScroll was measured with an infinite max height and Compose threw
IllegalStateException on the next layout pass.

When Show full is active the scroll wrapper is dropped entirely and the
trace renders at its natural height (legal inside a LazyColumn item).
Regression test reproduces the crash path: ReasoningCard in a LazyColumn
item, expand, Show full, collapse.

* test(chat): scroll to the reasoning toggle before asserting it

The card grows taller than the viewport after Show full, so the Show
less button sits below the fold. performScrollTo brings it on-screen
before the assert and the follow-up click.

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
This commit is contained in:
M57andM57 authored and GitHub committed 2026-08-12 13:10:00 +03:00
1 parent 1ea1596e4e
commit ff951eaf14
2 files changed
+92 -12

No files matched your search

@@ -73,7 +73,6 @@ import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.m57.hermescontrol.R
@@ -176,19 +175,26 @@ fun ReasoningCard(
}
AnimatedVisibility(visible = expanded) {
Column {
Column(
Text(
text = reasoningText,
style = MaterialTheme.typography.bodySmall.copy(fontFamily = FontFamily.Monospace),
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier =
Modifier
.heightIn(max = if (fullHeight) Dp.Unspecified else capHeight)
.verticalScroll(scrollState),
) {
Text(
text = reasoningText,
style = MaterialTheme.typography.bodySmall.copy(fontFamily = FontFamily.Monospace),
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(top = 4.dp),
)
}
.padding(top = 4.dp)
.then(
if (!fullHeight) {
// Capped at ~40% of the screen with internal scroll.
Modifier.heightIn(max = capHeight).verticalScroll(scrollState)
} else {
// "Show full": render the whole trace at natural height.
// A scrollable must NEVER be measured with an infinite
// max height — inside a LazyColumn item (unbounded main
// axis) that combination throws IllegalStateException.
Modifier
},
),
)
if (isStreaming) {
ReasoningPulsingDot(modifier = Modifier.padding(top = 6.dp))
}