Split a stood-down run across spacers Constraints can hold

`Can't represent a width of 0 and height of 273238 in Constraints`, from
`Modifier.height` during measure, which took the app down on opening one
particular session. Compose packs a Constraints into a single Long, and at
a width of zero that leaves eighteen bits for the height -- so 262,143px is
the ceiling and a taller spacer throws.

Collapsing every stood-down row into one spacer is what made that
reachable. It is the change that stopped the per-frame cost growing with
the conversation, and it also turned "the history above the window" into a
single fixed height -- which for a long conversation read at the newest end
is the whole transcript. The one that crashed was 273,238px.

Split rather than clamped: the height is load-bearing. It is what keeps the
transcript's total the same as the rows it stands in for, so shortening it
would move everything under the reader -- a silent wrong answer in place of
a loud one. The gaps the arrangement inserts between the pieces come out of
the total for the same reason.

The cap is 100,000px rather than the 262,143 that would just fit, because
the limit depends on how many bits the width took: a spacer sized against
today's screen width is a crash waiting for a wider one.

Verified by forcing the split -- built with the cap at 5,000px, a run came
out as ten spacers with the transcript intact and nothing reporting an
unbuilt row -- then restored. Testing it only at the real cap would have
tested the branch that cannot fail on any conversation reachable here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
irisandClaude Opus 5 committed 2026-08-31 13:46:14 -04:00
1 parent c2ceaf01d6
commit c9e9ddb62f
1 file changed
+47 -4
@@ -418,6 +418,37 @@ class TranscriptScroll(internal val scroll: ScrollState) {
* Getting that wrong does not look like a spacing bug; it shortens the content, which moves
* everything the reader is looking at.
*/
/**
* The spacers that stand in for rows [run], because one of them will not always do.
*
* `Modifier.height` turns into a fixed `Constraints`, and Compose packs a Constraints into a
* single Long -- with a width of zero it has eighteen bits for the height, so anything over
* 262,143px throws `Can't represent a width of 0 and height of N in Constraints` and takes the
* app down during measure. Collapsing every stood-down row into one spacer is what made that
* reachable: a long conversation scrolled to the newest end has its whole history above the
* window, and one seen here was 273,238px of it.
*
* Split rather than clamped, because the height is load-bearing -- it is what holds the
* transcript's total the same as the rows it replaces, and shortening it would move everything
* under the reader. The gaps the arrangement inserts between the pieces come out of the total
* for the same reason.
*/
fun spacerHeights(run: IntRange): List<Int> {
val total = runHeight(run)
if (total <= 0) return emptyList()
if (total <= SPACER_MAX) return listOf(total)
var pieces = 2
while (true) {
val body = total - (pieces - 1) * spacing
if (body <= 0) return listOf(total.coerceAtMost(SPACER_MAX))
if ((body + pieces - 1) / pieces <= SPACER_MAX) {
val each = body / pieces
return List(pieces) { each + if (it == 0) body - each * pieces else 0 }
}
pieces++
}
}
fun runHeight(run: IntRange): Int {
if (run.isEmpty() || order.isEmpty()) return 0
var total = 0
@@ -854,10 +885,13 @@ val TRANSCRIPT_PADDING: PaddingValues = PaddingValues(16.dp)
@Composable
private fun StoodDownRun(state: TranscriptScroll, run: IntRange) {
if (run.isEmpty()) return
DebugStats.count("rows stood down as one spacer")
Spacer(
Modifier.fillMaxWidth().height(with(LocalDensity.current) { state.runHeight(run).toDp() })
)
val heights = state.spacerHeights(run)
DebugStats.count("rows stood down as spacers")
DebugStats.atLeast("spacers standing in for a run", heights.size.toLong())
val density = LocalDensity.current
heights.forEachIndexed { index, height ->
key(index) { Spacer(Modifier.fillMaxWidth().height(with(density) { height.toDp() })) }
}
}
@Composable
@@ -919,5 +953,14 @@ private const val STAND_UP_PER_FRAME = 2
*/
private const val SEED_SCREENS = 2
/**
* The tallest a single spacer may be; see [TranscriptScroll.spacerHeights].
*
* Comfortably under the 262,143px that a fixed-width `Constraints` can hold, rather than at it: the
* limit depends on how many bits the width took, and a spacer that is nearly the maximum is a crash
* waiting for a wider screen.
*/
private const val SPACER_MAX = 100_000
/** What a row is assumed to be worth before any of them have been measured. */
private const val ROW_GUESS = 800