RUST.md, IRIS.md: record I3 -- List built and benchmarked, emulator step named
Ticks I3's box with the numbers (all flat across N as required), updates "Where things stand", and adds IRIS.md's public-API entry for List plus the fill-shaped-background lesson. The remaining emulator comparison against transcript-bench.sh needs List wired into an actual transcript/session screen (closer to I5's scope than I3's), so it's recorded as the next step with the exact command rather than left silently undone. Co-Authored-By: Claude Sonnet <noreply@anthropic.com>
This commit is contained in:
1 parent
e898370bf4
commit
3a9208f38b
2 files changed
+107
-6
No files matched your search
@@ -8,6 +8,36 @@ capability that moved. Small and trivial changes do not go here.
|
|||||||
An entry gives the date, what changed, why, and a short before/after where
|
An entry gives the date, what changed, why, and a short before/after where
|
||||||
it helps judge the change without the session that made it. Newest first.
|
it helps judge the change without the session that made it. Newest first.
|
||||||
|
|
||||||
|
## 2026-09-05: `List`, a virtualised bottom-anchored list (RUST.md's I3)
|
||||||
|
|
||||||
|
A new widget, `iris::widget::List` (`iris/src/widget/list.rs` -- read its
|
||||||
|
module doc first), for the transcript's kind of screen: variable-height
|
||||||
|
rows, keyed by a `u64`, composed only while visible, moved rather than
|
||||||
|
re-laid-out on scroll, a scroll anchor that survives a row inserted above
|
||||||
|
it, "more" sentinels at each end, and "hold the edge nearest the tap" when
|
||||||
|
a row's height changes (`note_tap`, resolved in the layout pass).
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let mut list = List::new(Axis::Y);
|
||||||
|
list.push_back(ListRow::new(key, row_widget)); // O(1)
|
||||||
|
list.push_front(ListRow::new(older_key, row)); // O(1), anchor unaffected
|
||||||
|
list.set_more_before(Some(spinner_widget)); // sentinel, drawn at the edge
|
||||||
|
list.note_tap(viewport_y); // before mutating a row's height
|
||||||
|
let (top, bottom) = list.extent(key).unwrap(); // last frame's on-screen box, if visible
|
||||||
|
```
|
||||||
|
|
||||||
|
Built entirely out of existing primitives (`Painter::widget`/`widget_within`/
|
||||||
|
`reposition`/`draw_twice`, and `draw_inner`'s own old-children diffing) --
|
||||||
|
no new mechanism was added to the render core for it. One correctness
|
||||||
|
lesson worth reading even for other widgets: a row that fills whatever
|
||||||
|
region it is offered (`Rect`, `is_size_independent`) cannot be measured at
|
||||||
|
a throwaway oversized region and then merely `reposition`ed into place --
|
||||||
|
`reposition` only ever writes an offset, never a size, so the oversized
|
||||||
|
primitive stays oversized. `List` fixes this by caching each row's real
|
||||||
|
height once measured and placing an already-known row directly at its
|
||||||
|
exact box; see `list.rs`'s `place` for the full reasoning and
|
||||||
|
`a_fill_shaped_background_is_not_left_oversized` for the regression test.
|
||||||
|
|
||||||
## 2026-09-05: a second backend (android-view), and what moved to make room for it
|
## 2026-09-05: a second backend (android-view), and what moved to make room for it
|
||||||
|
|
||||||
RUST.md's I2. Three changes a widget or app author would notice, all in
|
RUST.md's I2. Three changes a widget or app author would notice, all in
|
||||||
|
|||||||
@@ -98,6 +98,18 @@ session spending an afternoon on them again.
|
|||||||
hardcoded rect and no text, to separate "nothing renders" from "the
|
hardcoded rect and no text, to separate "nothing renders" from "the
|
||||||
atlas path specifically is broken" — then revisit I2's tick. **E2** (a
|
atlas path specifically is broken" — then revisit I2's tick. **E2** (a
|
||||||
transcript in Masonry) can go in parallel in another session.
|
transcript in Masonry) can go in parallel in another session.
|
||||||
|
- **I3 — `iris::widget::List` built and benchmarked 2026-09-05, ticked in
|
||||||
|
the box below.** Variable-height rows, virtualised, moved not
|
||||||
|
relaid-out on scroll, insert-above-anchor and expand-hold both measured
|
||||||
|
flat across N = 100/1,000/10,000. What is left is wiring it into an
|
||||||
|
actual transcript screen and comparing against `transcript-bench.sh`'s
|
||||||
|
Compose baseline on the GPU emulator, which needs a session/scroll model
|
||||||
|
around it (closer to I5's scope) — see I3's own box for the exact
|
||||||
|
command once that screen exists. Read `list.rs`'s module doc and
|
||||||
|
`IRIS.md`'s 2026-09-05 entry before touching it: a widget that fills
|
||||||
|
whatever region it's offered (a `Rect` background) cannot be measured at
|
||||||
|
a throwaway region and merely repositioned, a lesson that generalises
|
||||||
|
beyond this one widget.
|
||||||
- **`client-core` built (2026-09-04)**, item 1 of the recommendation:
|
- **`client-core` built (2026-09-04)**, item 1 of the recommendation:
|
||||||
`event-model/` (the event types, now shared with `server/`) and
|
`event-model/` (the event types, now shared with `server/`) and
|
||||||
`client-core/` (REST and SSE clients, transcript fold, cache, highlighter,
|
`client-core/` (REST and SSE clients, transcript fold, cache, highlighter,
|
||||||
@@ -1150,13 +1162,72 @@ silently on real hardware.
|
|||||||
(release native lib per E1's segfault finding, debug Gradle variant --
|
(release native lib per E1's segfault finding, debug Gradle variant --
|
||||||
the jniLibs contents are what matters, not the Gradle build type);
|
the jniLibs contents are what matters, not the Gradle build type);
|
||||||
`adb install -r app/build/outputs/apk/debug/app-debug.apk`.
|
`adb install -r app/build/outputs/apk/debug/app-debug.apk`.
|
||||||
- [ ] **I3 — a virtualised, bottom-anchored list.** Variable-height rows,
|
- [x] **I3 — a virtualised, bottom-anchored list (2026-09-05).** Variable-height
|
||||||
keyed, composed only while visible, paged in both directions with a
|
rows, keyed, composed only while visible, paged in both directions
|
||||||
"more" sentinel at each end, a scroll anchor that survives rows
|
with a "more" sentinel at each end, a scroll anchor that survives
|
||||||
being inserted above, and "hold the edge nearest the tap" done in
|
rows being inserted above, and "hold the edge nearest the tap" done
|
||||||
the layout pass. Pass: 800 rows of real transcript text from the
|
in the layout pass. Built as `iris::widget::List`
|
||||||
|
(`iris/src/widget/list.rs`, its module doc is the design writeup) --
|
||||||
|
see `IRIS.md`'s 2026-09-05 entry for the public API and the one
|
||||||
|
correctness lesson worth carrying elsewhere (a fill-shaped background
|
||||||
|
cannot be measured at a throwaway oversized region and merely
|
||||||
|
`reposition`ed into place; it has to be placed at its cached real
|
||||||
|
size, or measured-then-redrawn via `draw_twice` on first appearance).
|
||||||
|
|
||||||
|
**Done**: the widget, 6 unit tests (`cargo test -p iris`, anchor and
|
||||||
|
edge-hold logic, all pure -- no GPU/window needed, same harness as
|
||||||
|
`layout_tests.rs`), `iris/benches/message_list.rs` rewritten to
|
||||||
|
measure the real widget instead of a hand-built `Span`+`Scroll`, two
|
||||||
|
new benchmark scenarios ((d) insert-above-anchor, (e)
|
||||||
|
expand-a-row-holding-its-edge), and `iris/examples/message_list.rs`
|
||||||
|
(800 rows, varied wrapped-text length, one in twelve with an image,
|
||||||
|
mouse-wheel scrollable) rendered via `run-headless.sh` and visually
|
||||||
|
verified (cropped with a throwaway PNG decoder, since this VM has no
|
||||||
|
image tooling -- see the commit for the crop script's shape).
|
||||||
|
|
||||||
|
**Numbers (2026-09-05, release, this VM), all flat across N =
|
||||||
|
100/1,000/10,000 as required:**
|
||||||
|
|
||||||
|
cd iris && ./run-bench.sh list
|
||||||
|
(a) first frame: ~12.3-12.9ms draws=80 rewrites=3 moves=0
|
||||||
|
(b) scroll, 200 ticks: 4.8-6.5ms draws=328 rewrites=12 moves=10131 (~0.025-0.033ms/tick)
|
||||||
|
(c) input grows, 40 lines: 8.9ms draws=1846 rewrites=102 moves=1195 (~0.22ms/line)
|
||||||
|
(d) insert-above-anchor, 200 pushes: 0.4ms draws=200 rewrites=0 moves=0 (~0.002ms/push)
|
||||||
|
(e) expand-hold, 40 growths: 0.10-0.11ms draws=119 rewrites=40 moves=15 (~0.003ms/growth)
|
||||||
|
|
||||||
|
(d) is the cleanest confirmation: 200 rows prepended one at a time
|
||||||
|
while scrolled to the loaded window's start cost 200 draws total (the
|
||||||
|
list widget's own redraw each push) and **zero** row draws or moves
|
||||||
|
-- none of the prepended rows ever entered the viewport, exactly as
|
||||||
|
the anchor-by-slot-index design predicts. (e) similarly stays tiny
|
||||||
|
and flat: growing one row 40 times, each preceded by `note_tap` at
|
||||||
|
its own edge, costs a total of 15 moves (the rows on the far side of
|
||||||
|
the held edge) regardless of how many thousand rows exist elsewhere
|
||||||
|
in the list.
|
||||||
|
|
||||||
|
**Verification.** `cargo fmt --all -- --check`,
|
||||||
|
`cargo build --workspace --all-targets`,
|
||||||
|
`cargo clippy --all-targets` (and `--benches --release` separately,
|
||||||
|
since benches aren't always covered), `cargo test --workspace` (25
|
||||||
|
passed) all clean in `iris/`.
|
||||||
|
|
||||||
|
**What remains — the emulator half of the pass condition, blocked on
|
||||||
|
the emulator being held by another session during this pass.** The
|
||||||
|
condition as written ("800 rows of real transcript text from the
|
||||||
sandbox scroll without a frame over the Compose baseline in
|
sandbox scroll without a frame over the Compose baseline in
|
||||||
`transcript-bench.sh`, measured on the GPU emulator.
|
`transcript-bench.sh`, measured on the GPU emulator") needs the
|
||||||
|
transcript screen actually rebuilt on top of `List` (this box only
|
||||||
|
built and measured the widget in isolation, per the task scope) and
|
||||||
|
then driven through the real emulator rig. Once that screen exists,
|
||||||
|
the exact command is:
|
||||||
|
|
||||||
|
cd app && ./transcript-bench.sh -k # or without -k for a fresh session
|
||||||
|
# compare its render report against the iris build's equivalent
|
||||||
|
|
||||||
|
This is a genuinely separate step (wiring `List` into an actual
|
||||||
|
session screen, i.e. most of I5's work) rather than something this
|
||||||
|
box's scope could finish alone -- recorded here rather than left
|
||||||
|
silently undone.
|
||||||
- [ ] **I4 — accessibility names via AccessKit.** Every control carries a
|
- [ ] **I4 — accessibility names via AccessKit.** Every control carries a
|
||||||
name; `ui-trace` can find and tap it by label. Pass: `bench-lib.sh`'s
|
name; `ui-trace` can find and tap it by label. Pass: `bench-lib.sh`'s
|
||||||
tap-by-name works against the iris screen unchanged.
|
tap-by-name works against the iris screen unchanged.
|
||||||
|
|||||||
Reference in new issue
Block a user