From d9872989faaa03c867186e2318b4e24b636ebcce Mon Sep 17 00:00:00 2001 From: iris <2+iris@noreply.localhost> Date: Sun, 6 Sep 2026 13:26:34 -0400 Subject: [PATCH] iris/android: the composer's launch position was the bench report pane, plus surface/insets lifecycle logging The empty benchmark-report TextEdit held .height(rest(1)) beside content.height(rest(2)), so it reserved a third of the window at every launch and pushed the composer two thirds down -- Iris's 11:39 phone report. It is sized to its content now, capped and scrollable, and sits above the transcript rather than under the composer. New log::info! lines for one insets change, one surface_changed, one renderer build and one surface_destroyed, each with the glyph/atlas counts, so a phone's adb logcat can answer the app-switch text loss the emulator cannot reproduce. --- docs/IRIS_TODO.md | 22 +++++++++++++++++- iris/android-app/src/bench_client.rs | 29 ++++++++++++++++++++++-- iris/src/android/view.rs | 34 ++++++++++++++++++++++++++++ 3 files changed, 82 insertions(+), 3 deletions(-) diff --git a/docs/IRIS_TODO.md b/docs/IRIS_TODO.md index 5b3039e..c510843 100644 --- a/docs/IRIS_TODO.md +++ b/docs/IRIS_TODO.md @@ -189,8 +189,28 @@ Iris's report on the build with the composing-text, tap-vs-swipe and atlas-reset fixes, with a screenshot, verbatim. Each is open until an agent ticks it here with the evidence. -- [ ] **"The app definitely does not start with keyboard spacing +- [x] **"The app definitely does not start with keyboard spacing correct. This is how it looks without me doing anything initially."** + **Not an inset bug at all -- fixed 2026-09-06.** The black third is the + bench shell's own empty *benchmark report* pane: `bench_client.rs`'s + root tree gave it `.height(rest(1))` beside `content.height(rest(2))`, + so an empty `TextEdit` reserved a third of the window at every launch + and pushed the composer up by exactly that. Measured on this checkout's + emulator at the phone's own size (1080x2424, density 420, gesture nav), + which reproduced Iris's screenshot exactly: new `iris insets:` log line + reported `bottom=63 ime_bottom=0` at launch (a nav bar, no keyboard -- + so the inset the composer was fed was never large), while `ui-trace + show -m Message --field box` put the field at `31,1488..1048,1540` on a + 2282px-tall surface, 789px clear of the bottom -- that pane's third. + **Unit mixing checked explicitly and cleared**: `set_bottom_inset` takes + physical px and stores `Len::abs`, `MainActivity.java`'s `1`/`0` + `ime_bottom` only ever reaches `insets.bottom.max(ime_bottom)` and + `> 0.0`, and every `dp` in the composer resolves at layout time. Fix: + the report pane is sized to its content (`.max_height(dp(260)) + .scrollable()`), and moved above the transcript so it cannot eat the + composer's nav-bar clearance. After: field box `31,2277..1048,2329`, + grey bar ending at device y2361 with the 63px nav strip below it + (`/tmp/fix1.png` this pass). The screenshot shows the composer bar (the grey band) sitting about two thirds of the way down a 704x1568 screen, with black below it to the bottom, and the transcript ending at "Claude / Results" just above diff --git a/iris/android-app/src/bench_client.rs b/iris/android-app/src/bench_client.rs index 091ae26..4b79e11 100644 --- a/iris/android-app/src/bench_client.rs +++ b/iris/android-app/src/bench_client.rs @@ -92,6 +92,12 @@ const ANIM_STEP_MS: u64 = 16; const FIXTURE_JSONL: &str = include_str!("../../../app/bench-fixture/assets/transcript.jsonl"); +/// How much of the screen a *filled* benchmark report may take before it +/// scrolls instead of growing -- roughly a third of a phone screen, the +/// share the pane used to reserve unconditionally. An empty report takes +/// nothing at all; see `new`'s comment at the tree it is used in. +const REPORT_MAX_HEIGHT_DP: f32 = 260.0; + pub struct BenchClient { ui_state: AndroidUiState, content: WeakWidget, @@ -255,10 +261,29 @@ impl AndroidAppState for BenchClient { let top_bar = WidgetPtr::new().add(rsc); let controls = bench_controls(rsc, 0.0); top_bar(rsc).set(controls); + // The report pane is sized to whatever report it is holding, not + // to a share of the window: `rest(1)` here reserved a third of + // the screen for an *empty* `TextEdit` at every launch, which is + // what Iris's 2026-09-06 11:39 phone report described as "the app + // does not start with keyboard spacing correct" -- the composer + // two thirds down with black below it, nothing to do with the IME + // inset (measured: `iris insets:` reports bottom=63 ime_bottom=0 + // at launch, while the `Message` field's own box sat 789px above + // the bottom of a 2282px surface -- exactly this pane's third). + // Capped and scrollable so a long report cannot take the screen + // back over, the same idiom `composer.rs` uses for the field. + // Above the transcript, not below it: the report is what the + // header's own "Run benchmark" button produces (UI_RULES.md -- + // results appear where the action was started), and a pane under + // the composer would eat the navigation-bar clearance + // `set_bottom_inset` gives it. let tree = ( top_bar, - content.height(rest(2)), - report_display.height(rest(1)).pad(dp(8)), + report_display + .pad(dp(8)) + .max_height(dp(REPORT_MAX_HEIGHT_DP)) + .scrollable(), + content.height(rest(1)), ) .span(Dir::DOWN) .add_strong(rsc) diff --git a/iris/src/android/view.rs b/iris/src/android/view.rs index 5a06e97..c743b9a 100644 --- a/iris/src/android/view.rs +++ b/iris/src/android/view.rs @@ -368,6 +368,21 @@ impl IrisViewPeer { let current_insets = ui_state.insets(); if current_insets != ui_state.last_insets { let physical = WindowInsets::from_physical(current_insets); + // One line per real insets change. Iris's phone is the only + // place several of these bugs reproduce and `adb logcat` is + // the only instrument there (this-machine-android: system + // tracing is broken on that device), so the numbers a layout + // is actually fed have to reach the log -- "the composer + // floats at launch" is unanswerable from a screenshot alone. + log::info!( + "iris insets: left={} top={} right={} bottom={} ime_bottom={} window={:?}", + physical.left, + physical.top, + physical.right, + physical.bottom, + physical.ime_bottom, + self.window_size(), + ); self.state.android_state_mut().last_insets = current_insets; self.state.on_insets_changed(&mut self.rsc, physical); } @@ -627,6 +642,12 @@ impl ViewPeer for IrisViewPeer { // backgrounding) still goes through `AndroidRenderer::new` below, // since `renderer` is `None` in that case. let already_live = self.state.android_state().renderer.is_some(); + log::info!( + "iris surface: surface_changed {width}x{height} already_live={already_live} \ + glyphs_cached={} atlas_pages={}", + self.rsc.ui.text.atlas.glyph_count(), + self.rsc.ui.text.atlas.page_count(), + ); if already_live { let ui_state = self.state.android_state_mut(); ui_state @@ -671,6 +692,13 @@ impl ViewPeer for IrisViewPeer { // builds a new renderer, exactly where invalidation is // needed, never on the reuse branch, where it would throw // away perfectly valid GPU state for nothing. + log::info!( + "iris surface: new renderer built ({:?}), clearing glyph atlas: \ + glyphs={} pages={}", + renderer.adapter_backend, + self.rsc.ui.text.atlas.glyph_count(), + self.rsc.ui.text.atlas.page_count(), + ); self.rsc.ui.text.atlas.clear(); self.rsc.ui.textures.reset(); self.state.android_state_mut().renderer = Some(renderer); @@ -707,6 +735,12 @@ impl ViewPeer for IrisViewPeer { _ctx: &mut CallbackCtx<'local>, _holder: &android_view::SurfaceHolder<'local>, ) { + log::info!( + "iris surface: surface_destroyed, tearing the renderer down \ + (glyphs_cached={} atlas_pages={})", + self.rsc.ui.text.atlas.glyph_count(), + self.rsc.ui.text.atlas.page_count(), + ); self.state.android_state_mut().renderer = None; }