Merge branch 'main' of git.arirex.me:iris/ai-app

This commit is contained in:
iris committed 2026-09-01 01:20:46 -04:00
commit 6d12388063
2 files changed
+30 -13

No files matched your search

+15
View File
@@ -620,6 +620,21 @@ machine belongs in `~/.claude/TOOLCHAIN.md` (toolchain versions) or
toggle and used to force both places back to zero the moment the toggle and used to force both places back to zero the moment the
platform says the keyboard is gone, whatever the animated value still platform says the keyboard is gone, whatever the animated value still
claims. 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.** - **The keyboard pans the window unless the activity opts into resize.**
Without `android:windowSoftInputMode="adjustResize"`, opening the IME Without `android:windowSoftInputMode="adjustResize"`, opening the IME
slides the whole window up (top bar off screen) instead of resizing — slides the whole window up (top bar off screen) instead of resizing —
@@ -21,6 +21,7 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.ime import androidx.compose.foundation.layout.ime
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.isImeVisible import androidx.compose.foundation.layout.isImeVisible
import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
@@ -1220,19 +1221,20 @@ fun SessionScreen(settings: ServerSettings, summary: SessionSummary, onBack: ()
// message -- and then the keyboard's, per frame of its animation. This modifier // message -- and then the keyboard's, per frame of its animation. This modifier
// is the whole of what the keyboard re-measures: the box's own size never // is the whole of what the keyboard re-measures: the box's own size never
// changes, so nothing above it is touched. // changes, so nothing above it is touched.
.padding( .padding(bottom = with(LocalDensity.current) { composerHeight.toDp() })
bottom = // The keyboard's room, and only while the platform says there is a keyboard --
with(LocalDensity.current) { composerHeight.toDp() } + // dropping the modifier is what coerces the stuck-open animated value to zero,
// Not the `imePadding()` modifier: it trusts the same animated // the same guard the composer's translation applies below. It has to stay a
// value `imeVisible` exists to correct, so it is exactly as prone // *modifier* rather than a padding computed here: `imePadding` reads the inset
// to sticking open. Coerced to zero the moment the platform says // in the layout phase, so a keyboard frame re-measures this box and nothing
// the keyboard is gone, whatever the animation still claims. // else, while reading `imeInsets` in this composable body subscribes the whole
if (imeVisible) { // of `SessionScreen` to a value that changes every frame of the animation.
with(LocalDensity.current) { imeInsets.getBottom(this).toDp() } // That cost 16 full recompositions of this screen per keyboard open, against
} else { // one, and it made the transcript's position depend on a recomposition landing
0.dp // inside the frame that the inset changed -- which the composer's does not,
} // since its translation is re-read in that frame's draw phase. When the
) // recomposition misses, the transcript trails the composer up the screen.
.then(if (imeVisible) Modifier.imePadding() else Modifier)
) { ) {
Box(Modifier.fillMaxSize()) { Box(Modifier.fillMaxSize()) {
TranscriptList( TranscriptList(