docs: record the phone-report fixes, follow-ups and the bundled-font API

RUST.md's P0 box gets Iris's first real-phone report (no crash) and the
four defects it found (glyph-wipe-on-first-touch, missing bold glyphs,
text far too small, status-bar inset not applied), what was fixed and
how it was verified on the emulator, and what's still open (item 1's
root cause, and the top-row height anomaly noted in the last commit).

IRIS_TODO.md gets a new "From the phone, 2026-09-06" section for the two
items explicitly deferred to a follow-up agent: no scroll momentum/fling,
and occasional jitter scrolling down.

IRIS.md gets the public-API entry for TextData's bundled fonts/
font_diagnostics, UiRenderNode::new/resize's new window_size parameter,
AndroidUiState::content_scale, AndroidAppState::on_insets_changed, and
iris_core::WgpuErrorLog.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
irisandClaude Fable 5.1 committed 2026-09-05 23:59:40 -04:00
1 parent fd7e17523d
commit 560a74caf8
3 files changed
+184

No files matched your search

+39
View File
@@ -513,3 +513,42 @@ phase dropped from 369.3ms/284.5ms (full rebuild per event, prior pass) to
~101130ms/~76103ms across three runs (this fix) — see RUST.md's P0 box ~101130ms/~76103ms across three runs (this fix) — see RUST.md's P0 box
for the full numbers and the comparison's caveats (different AVD for the full numbers and the comparison's caveats (different AVD
instances, not a controlled A/B on identical hardware state). instances, not a controlled A/B on identical hardware state).
## 2026-09-06: bundled fonts, `content_scale`, `AndroidAppState::on_insets_changed`
From RUST.md's P0 box, working Iris's first real-phone report (font/scale/
inset bugs the emulator never showed).
- **`TextData` now bundles Noto Sans + Noto Sans Mono** (regular/bold/
italic/bold-italic static faces, OFL) and registers them ahead of the
platform's own fonts in the `SansSerif`/`Monospace` generic-family
lists, rather than relying on the platform's font enumeration alone.
`TextData::font_diagnostics() -> FontDiagnostics` reports what was found
and what each style axis resolved to — logged once at startup and shown
on a screen's Diagnostics page if it has one. Adds ~3.6 MB uncompressed
to any binary linking `iris-core`; `build-apk.sh`'s own output says the
delivered (compressed) number.
- **`UiRenderNode::new`/`resize` now take the window size explicitly**
(`window_size: impl Into<Vec2>`) instead of deriving it from the
surface's physical `SurfaceConfiguration`. Existing callers pass a
*logical* size (physical ÷ density/scale-factor) now; this is what makes
a `font_size: 16.0` 16 dp instead of 16 raw device pixels on a
high-density phone. Before this, `scale_factor` did not exist anywhere
in the crate, on either platform.
- **`AndroidUiState::content_scale: f32`** (`DisplayMetrics.density`, read
once in `new_peer`) and the desktop equivalent (`window.scale_factor()`)
now divide every physical-pixel number before it reaches layout or
touch handling — see `content_scale`'s own field doc for the full list
of what depends on it.
- **New: `AndroidAppState::on_insets_changed(&mut self, rsc, LogicalInsets)`**,
a default-no-op hook called from `render()` exactly when
`AndroidUiState::insets()` changes. Nothing previously consumed
`insets().top` at all; a screen with chrome under the status bar
implements this to pad it, in the same logical units `content_scale`
converts everything else to.
- **New: `iris_core::WgpuErrorLog`**, installed via `Device::
on_uncaptured_error` on the Android device (wgpu's default handler is an
unconditional panic outside `UiRenderNode::new`'s own error scopes).
Explicit `Arc`-backed value passed to the callback and kept on
`AndroidRenderer`, not a global — a caller wanting one on desktop builds
its own the same way.
+22
View File
@@ -117,6 +117,28 @@ order and what "done" looks like. Tick and date them in place.
screen wants the same thing (P1's own transcript rows already read screen wants the same thing (P1's own transcript rows already read
their content from a `TextEdit` for the same reason). their content from a `TextEdit` for the same reason).
## From the phone, 2026-09-06
Found on Iris's own phone while working RUST.md's P0 box's phone-report
follow-ups. Recorded here rather than fixed in that pass, so a follow-up
agent takes them without colliding with that pass's `bench_client.rs`/
`android/view.rs`/`android/sense.rs` changes.
- [ ] **Swiping has no momentum.** iris's `List` pans exactly as far as the
finger moves and stops dead on release -- unlike Compose, which flings
and decelerates. Needs velocity tracking over the last several move
samples and an Android-style decelerating fling, cancelled by the next
touch-down, redrawing every frame until it settles. `BenchRun.kt`'s own
fling phase (RUST.md's "Benchmark v2" box) is the reference shape:
"travel way faster... which is better for stress testing."
- [ ] **Scrolling down sometimes jitters the text.** Two named suspects,
neither confirmed: `DragArbiter`'s slop being released as one jump
(`iris/src/sense.rs`), or a per-frame pan delta applied a frame late.
Measure by tracing the list's scroll offset per frame against a
monotonic synthetic drag, the same way `iris/android-app/trace-draw.sh`-
style instrumentation traced the touch-scroll dropout in RUST.md's I5
box -- not by eyeballing a screenshot.
## Build ## Build
- [x] **Benchmarks**, not unit tests, run on demand (2026-09-05; a - [x] **Benchmarks**, not unit tests, run on demand (2026-09-05; a
+123
View File
@@ -4315,6 +4315,129 @@ device.
confirming the phase marks partition the whole run rather than confirming the phase marks partition the whole run rather than
overlapping or dropping frames between them. overlapping or dropping frames between them.
**Iris's first real phone report, 2026-09-06** (the redelivered,
no-`force-gles` APK above): no crash. Two screenshots, before any
touch: headings/links/code/table all render correctly. Four defects
found and worked this pass:
1. **Every glyph disappears on the first tap or scroll; rectangles
stay drawn** (the keyboard case is the same thing -- a tap on the
composer). **Not root-caused this pass.** Audited `GpuTextures`'
atlas-grow/patch path, `ArrBuf`'s resize-on-length-change
contract, and the masks/move_offsets/rsc bind-group rebuild logic
in `core/src/render/mod.rs` against wgpu's queue-ordering
contract -- everything read as spec-correct (a `queue.write_texture`/
`write_buffer` issued before a later `queue.submit` is guaranteed
visible to it on the same queue, and a dropped `Buffer`/`Texture`/
`BindGroup` still in flight is kept alive by wgpu's own tracker).
No violation found by static reading; reproducing needs either
the phone or a Mali driver trace, neither available this pass.
Instrumented for the next report instead: `Device::
on_uncaptured_error` is now installed on the Android device
(`WgpuErrorLog`, `android::render::AndroidRenderer`), and
`IrisViewPeer::render` logs masks/moves-resized, atlas
pages-grown and image bind-group creates for the first 10 frames
after every `surface_changed` -- exactly the window this bug
lands in. The bench screen's new "Diagnostics" button (below)
surfaces the error log and adapter identity on demand.
2. **Bold words render as blank gaps of the correct advance width**
(regular, links, inline code render fine). Fixed by bundling Noto
Sans/Noto Sans Mono (regular/bold/italic/bold-italic, static
cuts, OFL) into `iris-core` and registering them ahead of the
platform's own fonts -- `core/src/primitive/text.rs`'s
`TextData::register_bundled_fonts`. Named hypothesis, not
confirmed on the phone: the system "Roboto" on a modern Android
device is the variable "Roboto Flex," and this crate's glyph
path (`TextData::place`) does not apply `Synthesis`/variable-axis
correction at all -- a bundled *static* per-style face sidesteps
the question rather than answering it. `TextData::font_diagnostics`
reports what got resolved; logged once at startup and shown on
the Diagnostics page.
3. **Text far too small** -- iris had no device-pixel-ratio handling
on *either* platform before this pass (grepped for `scale_factor`
across the whole crate: zero hits). `DisplayMetrics.density`
(Android) / `Window::scale_factor()` (desktop) now divides every
physical-pixel number (window size, touch coordinates, the
shader's window uniform) down to logical units before it reaches
layout, so a `font_size: 16.0` is 16 dp rather than 16 raw device
pixels on a ~3x-density phone. Cost a second, real bug found only
by measuring on this checkout's emulator after the first fix
landed: `android::view::IrisViewPeer::surface_changed`'s call
into `UiRenderState::resize` (the layout engine's own notion of
the canvas, which every widget's absolute `PixelRegion` is
computed against) was still being handed raw physical
`width`/`height`, while `AndroidRenderer`'s side of the same
resize had already switched to logical -- splitting layout and
the shader into two different units. A fixed-size widget (the
bench screen's `.height(56)` button row) exposed it at ~40
physical px against the ~147px `56 * content_scale` predicts;
a proportional (`rest(n)`) size hid it by adapting to whichever
total it was given. Both are logical now. **Not fully verified**:
a fresh-install emulator screenshot after both fixes shows
visibly larger, readable text (`docs/bench/` has neither
screenshot committed -- see AGENTS.md on transcripts/screenshots
not going in this repo -- but the before/after is described in
the commit), and the button row's own height still isn't
obviously matching `56 * content_scale` on this run -- worth a
second look with `ui-trace show --field box` once there's time,
but not a blocker for the magnitude of the original bug (3x too
small).
4. **Status-bar inset not applied** -- confirmed nothing in this
app ever read `insets().top` at all (`android/insets.rs` has
carried `Insets.top` since it was written; nothing consumed it).
Fixed with a new, generic hook: `AndroidAppState::
on_insets_changed(rsc, LogicalInsets)`, called from `render()`
exactly when `AndroidUiState::insets()` changes, in logical units
matching everything else `content_scale` now divides.
`BenchClient::on_insets_changed` rebuilds the root tree with
`Padding::top(insets.top)` on the button row -- rebuilding the
whole tree rather than one `WidgetPtr` slot's content, because
the first attempt (a `Pad` dropped into an unrelated `WidgetPtr`
slot with no height override of its own) did not propagate the
wrapped span's fixed height correctly, which is what surfaced
finding 3's `UiRenderState::resize` bug in the first place.
Verified via `ui-trace show --field box`: the button row's top
(150 physical px) sits 8px below `statusBarBackground`'s bottom
edge (142px) on this checkout's emulator.
**A named `Diagnostics` control now exists** (RUST.md's own earlier
ask): a third button on the bench screen's top row, filling the
existing benchmark-report `TextEdit` with adapter identity/backend/
driver, font resolution, the atlas's live view count, every
uncaptured wgpu error since surface creation, and the frame report
-- `android::render::AndroidRenderer::diagnostics_report`. Uses the
existing "Copy report" button/clipboard path rather than a second
one.
**Verified this pass, this checkout's emulator** (`EMU_GPU` default,
`--features force-gles` -- this cold `emu up` again enumerated zero
Vulkan adapters, the same pre-existing flakiness earlier boxes
documented, not something this pass's diff caused): `cargo fmt --all
-- --check`, `cargo clippy --workspace --all-targets` (zero warnings
beyond the pre-existing wgpu future-incompat notice), `cargo test
--workspace` (all passing, unchanged pure-logic counts), `cargo ndk
-t x86_64 -P 26 check` clean, `./run-bench.sh` end to end
(`frames=691`, 24/24 swipes, 400/400 streamed events, no crash),
fresh-install screenshots and `ui-trace` box readouts for the four
items above. **Not verified this pass**: the actual phone (no
access), and item 1's root cause (needs either the phone's next
Diagnostics-page report or a Mali trace).
**Recorded but not fixed this pass** (a follow-up agent takes these,
to avoid colliding with this pass's `bench_client.rs`/`view.rs`
changes) -- see `IRIS_TODO.md`'s "From the phone, 2026-09-06":
swiping has no momentum (stops exactly where the finger releases,
unlike Compose's fling), and scrolling down sometimes jitters the
text.
**Redelivered, 2026-09-06, later the same day.** New arm64 APK
(Vulkan, no `force-gles`, bundled fonts, content-scale fix,
Diagnostics control), same `dev.iris.android.demo.bench` id, same
`CN=ai-app` signing cert, copied to `~/host/bench/
iris-bench-arm64.apk` and `~/repos/ai-app-bench/iris/build/outputs/
apk/release/iris-bench-arm64.apk`; that repo's own README gained a
dated entry. Still not confirmed on Iris's actual phone.
- [ ] **P1 — session screen parity.** History paging backward (with the - [ ] **P1 — session screen parity.** History paging backward (with the
page-boundary healing `client-core` does not have yet, below), page-boundary healing `client-core` does not have yet, below),
`TranscriptSource`-backed cache/server stitching, jump-to-latest, `TranscriptSource`-backed cache/server stitching, jump-to-latest,