Stop the transcript being able to go blank
Scrolling up emptied the screen and only reopening the session brought it back. The guess for an unmeasured row's height was re-derived from a running average each time it was wanted, and that average moves as rows are measured -- so the height a spacer had been *built* at stopped matching the height the running totals were adding up, and the two drifted apart. Once they differed by more than the retain window every row failed the distance test at once, and a transcript of nothing but spacers has nothing left to measure and so nothing to correct itself with. Two changes, and the second is the one that matters. A guess is now made once per row and kept, so it cannot drift from what was built with it. And which rows stay built is decided as a *range* rather than by each row testing itself: the row nearest the viewport is in that range by construction, whatever the arithmetic says, so the worst a mistake here can do is build too few rows or too many. A blank transcript is no longer a state this can reach. That is the shape worth keeping from the bug. Every row answering independently meant one wrong number could stand all of them down together, and the failure was silent, self-sustaining, and looked exactly like the screen having nothing to show. Checked by scrolling sixty swipes to the oldest loaded end and back -- the content holds throughout. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
53735f8df4
commit
718fb5320c
1 file changed
+50
-7
@@ -145,6 +145,7 @@ class TranscriptScroll(internal val scroll: ScrollState) {
|
||||
}
|
||||
tops = out
|
||||
topsStale = false
|
||||
topsVersion++
|
||||
}
|
||||
|
||||
/** Where the last touch went down, in the content's own coordinates. */
|
||||
@@ -256,17 +257,59 @@ class TranscriptScroll(internal val scroll: ScrollState) {
|
||||
* made a page of history land as one hundred-millisecond frame -- seventeen screens of markdown
|
||||
* shaped at once, because "not measured yet" described the whole page.
|
||||
*/
|
||||
fun retains(seq: Long): Boolean {
|
||||
val index = rowIndex[seq] ?: return true
|
||||
val height = assumed(seq)
|
||||
fun retains(seq: Long): Boolean = rowIndex[seq]?.let { it in retainedRange() } ?: true
|
||||
|
||||
/**
|
||||
* Which rows stay built, as a range of indices rather than a test each row makes for itself.
|
||||
*
|
||||
* A range can be guaranteed non-empty and a per-row test cannot, which is the point. Every row
|
||||
* answering independently means a fault in the arithmetic stands *all* of them down at once and
|
||||
* leaves a blank transcript with nothing measured, so nothing to correct it -- which is exactly
|
||||
* what happened. Here the nearest row to the viewport is in the range by construction, whatever
|
||||
* the numbers say, so the worst a mistake can do is build too few rows or too many.
|
||||
*/
|
||||
private fun retainedRange(): IntRange {
|
||||
refreshTops()
|
||||
val top = tops.getOrNull(index) ?: return true
|
||||
if (order.isEmpty()) return IntRange.EMPTY
|
||||
val viewportTop = scroll.maxValue - scroll.value
|
||||
val margin = scroll.viewportSize * RETAIN_SCREENS
|
||||
return top + height >= viewportTop - margin &&
|
||||
top <= viewportTop + scroll.viewportSize + margin
|
||||
if (viewportTop == rangeAt && topsVersion == rangeVersion) return rangeFrom..rangeTo
|
||||
val margin = (scroll.viewportSize * RETAIN_SCREENS).coerceAtLeast(1)
|
||||
val from = viewportTop - margin
|
||||
val to = viewportTop + scroll.viewportSize + margin
|
||||
var first = -1
|
||||
var last = -1
|
||||
var nearest = 0
|
||||
var nearestGap = Int.MAX_VALUE
|
||||
order.forEachIndexed { index, seq ->
|
||||
val top = tops[index]
|
||||
val bottom = top + assumed(seq)
|
||||
if (bottom >= from && top <= to) {
|
||||
if (first < 0) first = index
|
||||
last = index
|
||||
}
|
||||
val gap = if (bottom < viewportTop) viewportTop - bottom else top - viewportTop
|
||||
if (gap in 0..<nearestGap) {
|
||||
nearestGap = gap
|
||||
nearest = index
|
||||
}
|
||||
}
|
||||
if (first < 0) {
|
||||
first = nearest
|
||||
last = nearest
|
||||
}
|
||||
rangeFrom = first
|
||||
rangeTo = last
|
||||
rangeAt = viewportTop
|
||||
rangeVersion = topsVersion
|
||||
return first..last
|
||||
}
|
||||
|
||||
private var rangeFrom = 0
|
||||
private var rangeTo = -1
|
||||
private var rangeAt = Int.MIN_VALUE
|
||||
private var rangeVersion = -1
|
||||
private var topsVersion = 0
|
||||
|
||||
/** The height a row was last measured at, for the spacer that stands in for it. */
|
||||
fun heightOf(seq: Long): Int = assumed(seq)
|
||||
|
||||
|
||||
Reference in new issue
Block a user