Read the keyboard's inset in the layout phase again, not in composition

Reported: opening the keyboard lags more than it used to, and the scroll
area lags behind the rest of the UI vertically until the keyboard is fully
up.

Both come from the shape of the previous commit's fix rather than from what
it was fixing. Coercing the stuck-open animated inset to zero is right, but
it was written as a bottom padding computed in `SessionScreen`'s body --
`padding(bottom = ... + imeInsets.getBottom(this).toDp())` -- and reading
the inset there subscribes the whole composable to a value the platform
rewrites every frame of the keyboard's animation. That is exactly what the
comment above the box says the arrangement exists to avoid: the transcript
box was meant to be the whole of what a keyboard frame re-measures, with
nothing recomposed at all.

Measured on the emulator with the debug button's counters, over one
keyboard open on an idle session: `session screen recomposed` 16 before,
1 after -- the one being `isImeVisible` flipping, which is the recomposition
the guard actually needs. The per-frame layout work either side is
unchanged (17 measures of the transcript, ~0.6ms each), because that is the
work the keyboard is supposed to cost.

The second symptom is the same cause seen from the other end. The composer
is moved by a `graphicsLayer` block, which re-reads the inset in the draw
phase of the frame it changed; the transcript's padding was reading it in
composition, so the two only stayed together while that recomposition kept
landing inside the frame. `imePadding` reads it in the layout phase of the
same frame, which is where it was before and where the composer can be
followed from by construction.

`isImeVisible` still does the correcting -- the modifier is dropped rather
than the inset zeroed, which is the same coercion by a different route, so
a callback starved of its `onEnd` still cannot leave the composer floating.

Verified by tracking the two against each other frame by frame, from a
screen recording rather than from uiautomator, whose bounds do not update
per frame for a layer translation: the purple outline of the message field
and the last message bubble both move -820px over the ~150ms the keyboard
takes, and are within the 2px measurement floor of each other on every one
of the ten frames in between. Format, compile and lint are clean.
This commit is contained in:
iris committed 2026-09-01 00:51:15 -04:00
1 parent aaf475fb1f
commit 28902ac834
2 files changed
+30 -13

No files matched your search

+15
View File
@@ -616,6 +616,21 @@ machine belongs in `~/.claude/TOOLCHAIN.md` (toolchain versions) or
toggle and used to force both places back to zero the moment the
platform says the keyboard is gone, whatever the animated value still
claims.
**The guard is a boolean; the inset itself must never be read in the
composable body.** That correction first shipped as a `padding(bottom =
... imeInsets.getBottom(this) ...)` computed in `SessionScreen`, which
subscribes the whole screen to a value that changes every frame of the
animation: measured on the emulator at **16 full recompositions of
`SessionScreen` per keyboard open, against 1**, and it put the
transcript's position behind a recomposition while the composer's stayed
a draw-phase read of the same frame, so the two were only together while
that recomposition kept landing inside the frame. It is `.then(if (imeVisible) Modifier.imePadding() else
Modifier)` instead -- `imePadding` reads the inset in the layout phase,
which is what the comment above the transcript box means by "the whole of
what the keyboard re-measures", and dropping the modifier is the same
coercion to zero that the boolean was added for. The counter to check is
`session screen recomposed` in the debug button's report, which should
move by one across a keyboard open, not by the number of frames it took.
- **The keyboard pans the window unless the activity opts into resize.**
Without `android:windowSoftInputMode="adjustResize"`, opening the IME
slides the whole window up (top bar off screen) instead of resizing —