From 59965d314f7656bcc1ce78834b6ffead40c98d99 Mon Sep 17 00:00:00 2001 From: iris <2+iris@noreply.localhost> Date: Thu, 10 Sep 2026 18:51:29 -0400 Subject: [PATCH] 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). --- AGENTS.md | 10 ++++++++++ .../src/main/kotlin/com/example/aiapp/SessionScreen.kt | 5 ++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index 8432cf9..20c5c03 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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 for a round trip. It is `HISTORY_SCREENS` viewports now, counted from what 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.** `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, diff --git a/app/androidApp/src/main/kotlin/com/example/aiapp/SessionScreen.kt b/app/androidApp/src/main/kotlin/com/example/aiapp/SessionScreen.kt index d144d0e..13bfc91 100644 --- a/app/androidApp/src/main/kotlin/com/example/aiapp/SessionScreen.kt +++ b/app/androidApp/src/main/kotlin/com/example/aiapp/SessionScreen.kt @@ -1041,7 +1041,10 @@ fun SessionScreen( // 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. val unitSizes = remember(address) { HashMap() } - 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 } .collect { info -> val visible = info.visibleItemsInfo