Merge remote-tracking branch 'origin/rustify' into worktree-agent-ace98b0bdaf33ffff

# Conflicts:
#	docs/IRIS.md
#	docs/RUST.md
This commit is contained in:
iris committed 2026-09-07 15:33:25 -04:00
commit 4274b8b8d0
27 files changed
+1571 -149

No files matched your search

+103 -32
View File
@@ -43,6 +43,29 @@ gated on her verdict**, so this pass works the P0 defects and the pure
prerequisites in this order. Each item is ticked here by the agent that
closes it.
### Queue, 2026-09-07 (orchestrator)
In order; two builders at a time. Each is ticked here by the agent that
closes it.
- [x] Test rig, layers 1 and 2 ("Three test layers" below), landed 2026-09-07.
- [ ] Fling parity with Compose, and the phone's keyboard push-up, with
insets shown in the diagnostics overlay. Running, in a worktree.
- [ ] Rows at the transcript's top edge: culled too early in one state,
drawn through the header in the other (docs/IRIS_TODO.md, 2026-09-07).
First after the rig lands, using its layer-1 harness.
- [ ] Phone logging through Dev Updater (Iris has no logcat; see
docs/TODO.md and the memory note): research how Dev Updater shows an
app's runtime log, design the smallest route (the app keeps its own
recent log; a debug button copies it; Dev Updater reads it), write
the decision in docs/DECISIONS.md, build it.
- [ ] Masks with a shape -- docs/LAYOUT.md "Masks with a shape (decided
2026-09-07)". A mask references a primitive already drawn
(rect SDF, texture or glyph alpha), chained and multiplied; `.masked()`
points at the widget's own primitives; hit-testing applies the shape.
- [ ] Compose app: the `Reversed range` crash in `ToolInput.highlighted`
(docs/TODO.md). Main branch, not rustify.
### Desktop and phone share the code (Iris, 2026-09-07)
Iris plans to develop a desktop app as well, and asked that most code be
@@ -70,7 +93,7 @@ The bench client (`android-app/src/bench_client.rs`, ~1000 lines) is
the first thing to look at moving, since a desktop bench on the same
fixture is layer 2 of the test rig below.
### Three test layers, cheapest first (decided 2026-09-07, rig not yet built)
### Three test layers, cheapest first (decided 2026-09-07; layers 1 and 2 built the same day)
Iris's suggestion, adopted and layered: test at the cheapest layer that
can answer the question, and go up only when it cannot. The emulator
@@ -78,43 +101,91 @@ costs minutes a cycle; the desktop window seconds; the headless harness
runs inside `cargo test`.
1. **Headless, in-process, no compositor and no GPU -- the default.**
`iris/src/layout_tests.rs` already builds a tree over `UiRenderState`
with no window, `sense.rs`'s gesture tests feed fabricated
`CursorState`s with their own times, and `List::tick_fling` is
driven by hand. Extend that into one harness that opens
`transcript-ui`'s screen on `app/bench-fixture` (as `bench_client.rs`
does on Android, no server), at the phone's logical size and
`content_scale` from `docs/bench/iris-phone-v2-2026-09-06.md` (2.55,
120Hz -- read, never typed from memory), ticks frames, and feeds a
**replayed touch stream** from a trivial file of `(t_ms, action, x,
y)` lines with `cursor.time` taken from the file. That is what the
emulator cannot do at all: the batched 120Hz flick from the phone
report becomes a deterministic test asserting on scroll offset and on
the `iris drag release:` velocity. Anything about layout, scroll
position, selection, focus or fold state is answered here, with
assertions rather than eyes. Nothing renders; a widget's placed
rectangle is the evidence.
`iris::harness` (`iris/src/harness.rs`), plus the fixture crate it
opens. `Harness::new(size, density)` builds an `Rsc`, a
`UiRenderState` and a state whose `FocusHost`/`OpenUrl` *record* what
the platform was asked for; `frame(t_ms)`/`frames_until(..)` run
frames on a clock the test owns, and `replay(&TouchScript)` feeds a
recorded gesture one sample at a time exactly as
`IrisViewPeer::on_touch_event` replays Android's historical samples.
The recordings are plain `t_ms action x y` files under
`iris/transcript-fixture/touch/`, and `flick-120hz.touch` is the
phone's own shape: DOWN, four samples 4ms apart, UP, 20ms in total.
cd iris && cargo test -p transcript-fixture
runs in about a second and asserts (a) the flick releases with a real
velocity (`List::fling_velocity`, which only `Released(Some(v))`
fills), (b) the list travels and settles inside the AOSP spline's own
`FlingCalculator::duration`, (c) a tap moves nothing and opens no
link, (d) a long-press-then-drag leaves selected text and does not
pan, and (e) the composer clears a simulated 1000px IME inset
(`Composer::set_bottom_inset`). Each was confirmed to fail without
its subject rather than assumed: dropping `animate(id)` from
`Selection::drag` -- the phone's own "fling does nothing" defect --
and starting the fling curve at the wall clock each fail only the
flick test; flinging on `Tapped` fails only the tap test; a 5s
`LONG_PRESS` fails only the selection test; a `set_bottom_inset` that
ignores its argument fails only the composer test.
**What still cannot be answered below layer 3**: nothing renders
here, so anything about pixels -- glyph rasterisation, the atlas,
stale or duplicated primitives, colour, the surface lifecycle, the
renderer rebuild -- is invisible to layer 1 and only *looked at* in
layer 2. Frame *times* are not measurable at either: layer 1 does no
GPU work at all and layer 2 runs a debug build on this VM's virtio
GPU, so a number from either is not the phone's. Anything JNI (the
IME, real insets, the clipboard, battery) is layer 3 by construction:
layer 1 records that the platform was asked and layer 2 has no
Android platform to ask.
2. **A phone-shaped desktop window under headless sway -- for looking.**
`iris/run-headless.sh` already runs a winit binary under a private
sway and screenshots it with `grim`. Add a `--phone` mode (output and
window at the phone's size and scale, with the scale reaching iris
the way Android's does so dp layout runs at that density) and
touch-shaped mouse input: a left-button drag pans and flings through
`DragArbiter`, long-press selects, no hover. One input path, not a
parallel one (`CODE_RULES`). Colour, spacing, text and anything a
person has to see is answered here. `swaymsg seat - cursor
move/press/release` drives it when a gesture is needed on screen.
cd iris && ./run-headless.sh phone --phone --shot /tmp/p.png -- -p transcript-fixture
About 15 seconds warm. `--phone` sets the private sway output to
1080x2424@120Hz and exports `IRIS_SCALE=2.55`, which reaches iris the
way `DisplayMetrics.density` does on Android
(`iris::default::content_scale`) -- the desktop backend now lays out
in physical pixels with a density instead of dividing into a separate
logical space, so both platforms run one path. `transcript-fixture`'s
`phone` example opens the same screen from the same bytes as layer 1
and the Android bench.
A gesture on screen uses the *same recordings*:
./run-headless.sh phone --phone --replay transcript-fixture/touch/flick-120hz.touch \
--shot /tmp/p.png -- -p transcript-fixture
writes `/tmp/p-before.png` and `/tmp/p.png` either side of the flick;
looked at 2026-09-07, the list moved back about seven turns of the
fixture and settled.
**`swaymsg seat - cursor` cannot drive it, and that cost an hour.**
This compositor runs the headless backend with no input devices
(`WLR_LIBINPUT_NO_DEVICES=1`, `LIBSEAT_BACKEND=noop`): the cursor
commands all report `success` and nothing whatever reaches the
client, with `swaymsg -t get_seats` showing `capabilities: 0` as the
only sign. wlroots 0.19 dropped `WLR_HEADLESS_INPUTS`, and ydotool's
uinput device would be ignored by a compositor that is not reading
libinput. `iris/rig-input`'s `replay-touch` uses the
**virtual-pointer protocol** instead, which is a client protocol and
needs neither devices nor root, and it parses `iris::harness`'s own
`TouchScript`. Two traps inside it, both found by printing winit's
events: a button sent in the same frame as the motion that first puts
the pointer over the window is dropped (the client sees the enter,
the moves and the *release*, never the press), so the pointer is
positioned and left to settle 200ms first; and a leftover window from
an earlier manual run **tiles beside the new one**, halving the width
and producing a screenshot that looks exactly like a duplicated-
primitive rendering bug -- `swaymsg -t get_tree` and `pgrep -af
examples/phone` are the check.
3. **The Android emulator -- platform plumbing and the final pass.**
JNI, IME, insets, surface lifecycle, the renderer rebuild, and one
verification run before a build goes to the phone. Not for iterating
on layout.
Pass condition for the rig: `cargo test` runs a fixture-backed headless
transcript screen with a replayed flick and asserts a nonzero release
velocity and a moved scroll offset; one command opens the same screen in
a phone-shaped window and screenshots it. Record the commands here when
it lands.
### The 2026-09-07 phone report on `ed04d4c`: the fling was linear, and the keyboard is a targetSdk
Iris's three lines on the `ed04d4c` build (Pixel 9 Pro XL, GrapheneOS