Keep compact Codex history loading

Restart the history observer after every successful page so a collapsed tool page cannot consume the only layout invalidation that would request the next one.\n\nVerified with a cold 365-event tool-heavy sandbox transcript: without scrolling or expanding a group, cache coverage advanced continuously to sequence 1. Android format, compile, lint, and JVM tests pass. Transcript bench: 26 rows/26 units loaded, transcript draw 0.72 ms per frame (debug emulator).
This commit is contained in:
iris committed 2026-09-10 18:51:29 -04:00
1 parent e3cca97cdd
commit 59965d314f
2 files changed
+14 -1

No files matched your search

+10
View File
@@ -408,6 +408,16 @@ belongs in `~/.claude/TOOLCHAIN.md` or `~/.claude/MACHINE.md` instead.
the reader hit the end of what was loaded on every swipe and stood there the reader hit the end of what was loaded on every swipe and stood there
for a round trip. It is `HISTORY_SCREENS` viewports now, counted from what for a round trip. It is `HISTORY_SCREENS` viewports now, counted from what
is actually on screen. is actually on screen.
- **A page landing while the history observer was fetching it could spend the
layout change meant to ask for the next one.** The observer collected only
`LazyListState.layoutInfo`; while its collector was suspended in
`loadOlderPage`, a compact page could be composed and laid out without
leaving another change to observe afterward. Codex exposed it because a
page full of calls collapses into one tool group: loading stopped until
expanding that group forced a layout. The observer is also keyed on
`oldestSeq` now, so every successful page restarts it against the settled
layout. A failed page does not advance that cursor and still waits for the
next scroll instead of retrying in a loop.
- **Only `fetchTranscript` was off the main thread; the fold was not.** - **Only `fetchTranscript` was off the main thread; the fold was not.**
`foldEvent` returns a new list per event, so a page is that many copies of `foldEvent` returns a new list per event, so a page is that many copies of
a growing list — fine at 80 events and about 300,000 element copies at 800, a growing list — fine at 80 events and about 300,000 element copies at 800,
@@ -1041,7 +1041,10 @@ fun SessionScreen(
// There is no correction beside this one. Following the newest message is not an effect: the // There is no correction beside this one. Following the newest message is not an effect: the
// list is reversed, so an arriving message extends the end the viewport is pinned to. // list is reversed, so an arriving message extends the end the viewport is pinned to.
val unitSizes = remember(address) { HashMap<Any, Int>() } val unitSizes = remember(address) { HashMap<Any, Int>() }
LaunchedEffect(listState, moreHistory) { // A successful page advances `oldestSeq` even when its collapsed rows add too little height to
// produce another layout after this collector returns. Restarting on that cursor makes the
// promised re-check happen; a failed page leaves it unchanged and still waits for a scroll.
LaunchedEffect(listState, moreHistory, oldestSeq) {
snapshotFlow { listState.layoutInfo } snapshotFlow { listState.layoutInfo }
.collect { info -> .collect { info ->
val visible = info.visibleItemsInfo val visible = info.visibleItemsInfo