docs/REVIEW-2026-09-06.md: fix all ten review findings; RUST.md/IRIS_TODO.md: DragGesture merge checks
Finding 1 (the real crash): Selection::clear() drops rows and anchor, called from TranscriptScreen::apply's Rebuild arm right before List::clear() -- push_row re-registers survivors as it rebuilds each row. Fixes a WeakWidget outliving the row group_tool_runs regrouped away, which panicked the next long-press anywhere. New apply_tests test builds a real TranscriptScreen, forces the regroup, and confirms no panic. Findings 2-5: debug_assert!s on List::place's slot, List::fling and FlingCalculator's velocity finiteness, VelocityTracker::add_sample's chronological order, and FrameReport::mark_phase's non-decreasing start_index. Finding 7: bench_client.rs's battery_line guard restructured so the empty check can't be separated from its unwraps by a future edit. Findings 9/10: new List tests pinning tick_fling's per-tick deceleration and replace_back's evicted-key cleanup with a different key than the existing tests use. IRIS.md's replace_back/clear/apply entry gained the side-table-clearing note the Docs finding asked for. Also records this pass's DragGesture-merge verification in RUST.md (tap stays vs swipe doesn't, a real fling keeps moving after release, keyboard cycles confirmed via on_insets_changed) and annotates the two IRIS_TODO.md phone-report items it targets. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
2e00e71552
commit
1f379e8384
10 files changed
+679
-38
No files matched your search
+8
-1
@@ -552,7 +552,14 @@ streamed event" cost RUST.md's P0 box measured (20 events/second against a
|
||||
new rows appended after it. A row changing *before* the tail (only
|
||||
`group_tool_runs` retroactively grouping tool calls into a run does
|
||||
this) falls back to `List::clear` plus a full rebuild, counted in
|
||||
`TranscriptScreen::take_rebuilds()`. `bench_client.rs`, `transcript_client.rs`
|
||||
`TranscriptScreen::take_rebuilds()`. **A caller that keeps its own
|
||||
row-keyed side table alongside `List` (`Selection`'s `rows:
|
||||
BTreeMap<RowKey, WeakWidget<TextEdit>>` is the one this crate has) must
|
||||
clear it in step with `List::clear()`** — the fallback drops every row
|
||||
`List` was holding, so any side table not cleared the same way is left
|
||||
pointing at widgets the clear just freed (docs/REVIEW-2026-09-06.md
|
||||
finding 1, fixed 2026-09-06 by `Selection::clear()`, called from
|
||||
`apply`'s `Rebuild` arm right before `List::clear()`). `bench_client.rs`, `transcript_client.rs`
|
||||
and `desktop-app/app.rs` all call this now instead of rebuilding on every
|
||||
event; only the opening page (and `apply`'s own fallback) still calls
|
||||
`build_tree`.
|
||||
|
||||
+34
-11
@@ -203,17 +203,40 @@ agent ticks it here with the evidence.
|
||||
before the first `on_insets_changed`. Reproduce with the phone's
|
||||
screen size and density on the emulator before guessing.
|
||||
- [ ] **"Swiping still gets caught by the grey bar but keeps working
|
||||
after I go past it."** A pan that starts on the composer is held by
|
||||
the composer until the finger leaves its region, then the list takes
|
||||
over. The tap-vs-swipe fix in `attr.rs` stops the *focus*, but the
|
||||
press frames are still being handled by the field rather than passed
|
||||
to the list from the first slop-crossing frame. The `DragGesture`
|
||||
merge (RUST.md's plan box) should make this one mechanism: once a
|
||||
gesture commits to a pan, the list captures it wherever it began.
|
||||
- [ ] **"Flinging still does not work."** Expected on this build: finger
|
||||
flings are dropped by per-widget hit testing, which `DragGesture`'s
|
||||
pointer capture (commit `e12c708`, not yet merged at 02:07) targets.
|
||||
Stays open until verified on her phone, not the emulator.
|
||||
after I go past it."** Not closeable from the emulator, annotated
|
||||
2026-09-06 after the `DragGesture` merge. `attr.rs`'s `on_press` never
|
||||
calls `capture_pointer` and never consumes a `Pressing` frame past
|
||||
`DRAG_SLOP` (it just stops watching), so once the finger's *current*
|
||||
position leaves the composer's box and enters the list's, `List`
|
||||
starts receiving ordinary hit-tested `Pressing` frames there --
|
||||
`DragArbiter::is_idle()`'s 2026-09-05 recovery (a missed `PressStart`)
|
||||
picks it up rather than leaving it stuck. What this does **not** do is
|
||||
what "wherever it began" implies literally: `DragArbiter::press_start`
|
||||
restarts from the *boundary-crossing* position, not from the original
|
||||
touch-down inside the composer, so the pan still needs a fresh
|
||||
`DRAG_SLOP` of travel measured from the boundary rather than from the
|
||||
start of the gesture -- composer and list are adjacent, non-overlapping
|
||||
widgets (`lib.rs`'s `(list, composer_bar).span(Dir::DOWN)`), and only
|
||||
the composer forwarding its own drag to the list would remove that
|
||||
residual slop entirely, which is more than this pass's merge changes.
|
||||
RUST.md's merge-pass box has the reasoning in full and an emulator
|
||||
swipe confirming the composer's own box never moves/resizes during it;
|
||||
whether the residual slop is still perceptible as "caught" needs Iris's
|
||||
phone, since the emulator's per-widget boundary is a few dp wide and
|
||||
easy to cross without noticing on a real screen too.
|
||||
- [ ] **"Flinging still does not work."** No longer expected to reproduce
|
||||
after the `DragGesture` merge (`e12c708`, pointer capture +
|
||||
`CursorSense::Drop`), 2026-09-06. Emulator evidence (RUST.md's
|
||||
merge-pass box, check (b)): a real `ui-trace` finger swipe followed by
|
||||
screenshot-hash sampling caught a post-release frame distinct from the
|
||||
drag's own last frame in one run, and every run showed 28-32
|
||||
`render()` frames per gesture against an idle baseline of 0 and ~8
|
||||
expected from the drag alone -- redraw kept being requested well past
|
||||
the finger lifting, which only happens while a fling is still
|
||||
animating. Left unticked in spirit until Iris's phone confirms it,
|
||||
since only she can say whether it *feels* like a fling now; the
|
||||
emulator's screenshot timing could not always catch the tail of a
|
||||
fast-settling one visually (same caveat noted in RUST.md).
|
||||
- [ ] **"Text still disappears if I leave and come back to the app."**
|
||||
The `GlyphAtlas::clear`/`Textures::reset` fix was verified on the
|
||||
emulator under `force-gles` only; the phone runs Vulkan. So either the
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
# Review: iris changes since 0e46293
|
||||
|
||||
Scope: `git diff 0e46293..HEAD -- iris/ client-core/` (58 files, +5224/-226).
|
||||
Read-only review; no source changed. Ordered likely-bug, then invariant
|
||||
guards, then rules, then tests/docs.
|
||||
|
||||
## Likely bugs
|
||||
|
||||
1. **`iris/transcript-ui/src/lib.rs:152-160` (`RowDiff::Rebuild` arm of
|
||||
`TranscriptScreen::apply`) never unregisters the rows it drops from
|
||||
`Selection`, so a stale `WeakWidget<TextEdit>` outlives the widget it
|
||||
points to and the next touch on *any* row panics.**
|
||||
`Selection::rows: BTreeMap<RowKey, WeakWidget<TextEdit>>` documents its
|
||||
own contract at `selection.rs:69-71`: "every addition here needs its
|
||||
removal ... called when `List` evicts the row." The `ReplaceLast` arm
|
||||
above it honours this (`lib.rs:143-145`, `self.selection.borrow_mut()
|
||||
.unregister(old_key)` when the key changes). The `Rebuild` arm calls
|
||||
`(self.list)(rsc).clear()` and rebuilds every row from `new_rows`, but
|
||||
never touches `self.selection` — any key present in `old_rows` and
|
||||
*absent* from `new_rows` (exactly what `group_tool_runs` regrouping two
|
||||
separate tool-call rows into one produces — see `diff_tests::
|
||||
a_tool_run_closing_and_joining_an_earlier_call_is_a_regroup_fallback`,
|
||||
which tests the diff decision but not `apply` itself) is left in
|
||||
`self.rows` pointing at a widget `List::clear()` just freed.
|
||||
`TextEditable::edit` (`iris/src/widget/text/edit.rs:582-587`) resolves
|
||||
that handle with `ui.widgets.get_mut(self).unwrap()` — an unconditional
|
||||
panic on the freed slot. `Selection::begin` (`selection.rs:88-101`)
|
||||
iterates *every* registered row (`w.edit(ui).deselect()`) on an
|
||||
ordinary fresh press, so the crash fires on the next tap anywhere in
|
||||
the transcript after a regroup, not only on a tap targeting the
|
||||
orphaned row.
|
||||
Fix: give `Selection` a way to reconcile against the row set that
|
||||
survived a rebuild (e.g. `Selection::retain(&self, keys: &BTreeSet<RowKey>)`
|
||||
removing everything else, called from the `Rebuild` arm before
|
||||
rebuilding), or simplest — call `self.selection.borrow_mut()` cleared
|
||||
the same way `List::clear()` clears the list, then let the rebuild's
|
||||
`push_row` calls re-`register` everything as they already do.
|
||||
|
||||
## Guarded invariants missing
|
||||
|
||||
2. **`iris/src/widget/list.rs:751` (`List::place`) indexes/expects on
|
||||
`slot` with no assertion that it exists.** `slot_widget` (`:563-575`)
|
||||
panics via `.expect(...)` for a sentinel with no widget set, and does
|
||||
an unchecked `&self.items[s as usize]` for a real index — a bare
|
||||
"index out of bounds" with no context if `place` is ever reached with a
|
||||
stale slot. Every current caller happens to derive `slot` from
|
||||
`repair_anchor`/`prev_slot`/`next_slot`, which already check existence,
|
||||
but that invariant is enforced by convention across three call sites,
|
||||
not by the function that depends on it. Add
|
||||
`debug_assert!(self.slot_exists(slot), "place() called with a slot that doesn't exist: {slot:?}");`
|
||||
at the top of `place`.
|
||||
3. **`iris/src/widget/list.rs:426` (`List::fling`) and `sense.rs`'s
|
||||
`FlingCalculator::distance`/`duration`/`position_at` never check that
|
||||
the incoming velocity is finite.** A `NaN`/`inf` velocity (a
|
||||
`VelocityTracker::velocity()` divide-by-near-zero span, or a caller
|
||||
passing a raw device value straight through) propagates through
|
||||
`deceleration_for`'s `.ln()` silently — the fling either never settles
|
||||
(`settled_on_schedule` compares against a `NaN` `duration()`, which is
|
||||
always `false`) or jumps to `NaN` positions with nothing on screen
|
||||
saying why. Add `debug_assert!(velocity_px_per_s.is_finite())` in
|
||||
`List::fling` and `FlingCalculator::new`/`distance`.
|
||||
4. **`iris/src/sense.rs:592-604` (`VelocityTracker::velocity`) has no
|
||||
assertion that samples are chronological.** `add_sample` trusts its
|
||||
caller's `Instant` ordering; a caller that samples out of order (a
|
||||
restored/replayed gesture, a test) would silently produce a negative
|
||||
`span` handled only by the `span <= 0.0 => 0.0` catch-all, masking the
|
||||
bug that produced it rather than surfacing it. Add
|
||||
`debug_assert!(self.samples.back().is_none_or(|&(last, _)| at >= last))`
|
||||
in `add_sample`.
|
||||
5. **`iris/core/src/render/frame_report.rs:247-252` (`mark_phase`) has no
|
||||
assertion that phases are pushed in non-decreasing `start_index`
|
||||
order.** `phase_stats`'s slicing (`:274`, `idx >= phase.start_index &&
|
||||
idx < end_index`) silently produces an empty or nonsensical slice for
|
||||
an out-of-order phase rather than surfacing the misuse — cheap to add
|
||||
given `self.phases.last()` is already in scope:
|
||||
`debug_assert!(self.phases.last().is_none_or(|p| self.total_frames >= p.start_index));`
|
||||
|
||||
## Rules
|
||||
|
||||
6. **Two mechanisms answer "what row selection points at, still valid?"**
|
||||
`Selection` relies on callers remembering to `unregister` (finding 1);
|
||||
`List` relies on callers deriving slots only from already-checked
|
||||
sources (finding 2). Both are the same class of problem — a derived
|
||||
handle that silently outlives what it points to — solved ad hoc twice
|
||||
rather than once. Not asking for a shared abstraction here, but the two
|
||||
should at minimum cross-reference each other's doc comment so the next
|
||||
caller who adds a third handle-into-`List`-rows type (the code rules'
|
||||
"a rule that governs a set belongs to the set") finds both existing
|
||||
examples.
|
||||
7. **`iris/android-app/src/bench_client.rs:224-225` (`battery_line`)
|
||||
calls `.min().unwrap()`/`.max().unwrap()` on `samples` guarded three
|
||||
lines above by `if samples.is_empty()`, which is fine — but the guard
|
||||
and the two unwraps are two statements apart with a `let mean = ...`
|
||||
in between reading the same slice; a future edit reordering those
|
||||
lines loses the guard's protection silently.** Low severity (this is
|
||||
the bench tool, not the app), but worth a one-line comment tying the
|
||||
unwraps back to the guard, or restructuring as
|
||||
`let (Some(min), Some(max)) = (samples.iter().min(), samples.iter().max())`
|
||||
pattern so the empty case can't be separated from the check by a future
|
||||
edit.
|
||||
|
||||
## Tests
|
||||
|
||||
8. **No test exercises `TranscriptScreen::apply`'s `Rebuild` arm through
|
||||
`Selection`.** `lib.rs`'s `diff_tests` module (`:284-379`) tests only
|
||||
the pure `diff_rows` decision function, never `apply` itself wired to a
|
||||
real `Selection`; `selection.rs`'s own tests (`a_missed_press_start_
|
||||
recovers_on_the_next_pressing_frame`, `unregister_forgets_the_row_and_
|
||||
clears_a_matching_anchor`) never go through `apply`/`List::clear`
|
||||
either. This is exactly the gap that let finding 1 through: the two
|
||||
pieces (`apply`'s fallback, `Selection`'s registration contract) are
|
||||
each tested in isolation and never together. Add: build a
|
||||
`TranscriptScreen`, force a `RowDiff::Rebuild` (two adjacent tool-call
|
||||
rows regrouping, per the existing `diff_tests` case), then call
|
||||
`selected_text`/simulate a fresh press on a surviving row and assert no
|
||||
panic.
|
||||
9. **`iris/src/widget/list.rs`'s fling tests check total distance and the
|
||||
start/end clamp but not the speed profile in between.**
|
||||
`fling_moves_the_list_and_then_settles`/`fling_distance_is_positive_
|
||||
toward_the_end` only assert the fling started, moved in the right
|
||||
direction, and eventually stopped — none checks that
|
||||
`tick_fling`'s per-tick delta is *monotonically decreasing* once past
|
||||
the fling's peak (the property `fling_calculator_tests::position_at_
|
||||
is_monotonic_and_clamped_past_the_end` already checks one level down,
|
||||
for `FlingCalculator` alone, but never through `List::tick_fling`'s own
|
||||
`scroll`/`anchor.offset` accumulation). A regression that made
|
||||
`tick_fling` apply the *total* distance every tick instead of the
|
||||
incremental one, for instance, would still pass both existing tests
|
||||
(final position and direction are unaffected by how the interior ticks
|
||||
split it up) while being wildly wrong every intermediate frame.
|
||||
10. **`iris/src/widget/list.rs::replacing_the_last_row_stays_pinned_to_
|
||||
the_bottom` and its sibling test `replace_back`'s effect on the
|
||||
displayed row, never that the row it evicted is actually gone from
|
||||
`heights`/`extents`.** Both tests assert the *new* row's position;
|
||||
neither asserts `old.key` is absent from `list_ref.heights`/`extents`
|
||||
after the replace (the "stale primitive" class finding 1 is a
|
||||
production instance of). A cheap addition: assert
|
||||
`!list_ref.heights.contains_key(&old.key)` after `replace_back` in the
|
||||
existing test, since `old.key` is already returned to the test as
|
||||
`evicted`... (`lib.rs` calls it that way; the `list.rs` test would need
|
||||
to capture the key from `old` similarly.)
|
||||
|
||||
## Docs
|
||||
|
||||
No missing `IRIS.md` entry found for a *public* API change in this diff —
|
||||
`List::fling`/`VelocityTracker`/`FlingCalculator`, `List::
|
||||
anchor_position_display`, `FrameReport::mark_phase`/`phase_stats`/
|
||||
`late_at_hz`, `UiRenderNode::new`'s `Result` change, `Len::dp`, and
|
||||
`List::replace_back`/`clear`/`TranscriptScreen::apply` all have entries.
|
||||
The `List::replace_back`/`clear`/`TranscriptScreen::apply` entry
|
||||
(`docs/IRIS.md:526`) predates this review's finding 1 and does not mention
|
||||
`Selection`'s registration contract at all — once finding 1 is fixed,
|
||||
that entry should gain a line noting what the fix requires of a caller
|
||||
that keeps its own row-keyed side table (the same shape `Selection` is),
|
||||
so the next such table doesn't reproduce the same gap.
|
||||
|
||||
## Fixed, 2026-09-06
|
||||
|
||||
All ten findings addressed after the `DragGesture` merge (`selection.rs`
|
||||
was rewritten by that merge, but finding 1's shape and location were
|
||||
unchanged — `TranscriptScreen::apply`'s `Rebuild` arm, `iris/transcript-ui/
|
||||
src/lib.rs`).
|
||||
|
||||
1. **Fixed.** `Selection::clear()` (`selection.rs`) drops `rows` and
|
||||
`anchor`, called from `apply`'s `Rebuild` arm right before
|
||||
`List::clear()` — `push_row` re-`register`s whatever survives as it
|
||||
rebuilds each row, the "simplest" fix option the finding named.
|
||||
2. **Fixed.** `debug_assert!(self.slot_exists(slot), ...)` at the top of
|
||||
`List::place` (`iris/src/widget/list.rs`).
|
||||
3. **Fixed.** `debug_assert!(velocity_px_per_s.is_finite())` in
|
||||
`List::fling`, and `debug_assert!(velocity.is_finite())` in
|
||||
`FlingCalculator::distance`/`duration` (`iris/src/sense.rs`).
|
||||
`position_at` calls both, so it inherits the guard rather than needing
|
||||
its own.
|
||||
4. **Fixed.** `debug_assert!` on chronological sample order in
|
||||
`VelocityTracker::add_sample` (`iris/src/sense.rs`).
|
||||
5. **Fixed.** `debug_assert!` on non-decreasing `start_index` in
|
||||
`FrameReport::mark_phase` (`iris/core/src/render/frame_report.rs`).
|
||||
6. **Fixed (doc cross-reference only, as asked).** `Selection::register`'s
|
||||
doc now points at `List::place`'s `slot_exists` assertion and vice
|
||||
versa isn't needed since finding 2's fix already cites this file in
|
||||
its own comment; both are grep-able on "docs/REVIEW-2026-09-06.md" and
|
||||
on each other's type names.
|
||||
7. **Fixed.** `bench_client.rs::battery_line` restructured to
|
||||
`let (Some(min), Some(max)) = (samples.iter().min(), samples.iter().max())`,
|
||||
so the empty-guard and the two lookups can no longer be separated by a
|
||||
future edit.
|
||||
8. **Fixed.** `transcript-ui`'s new `apply_tests::
|
||||
a_row_dropped_by_a_regroup_does_not_outlive_itself_in_selection`
|
||||
(`lib.rs`) builds a real `TranscriptScreen`, forces the same regroup
|
||||
shape `diff_tests` already covers at the pure-diff level, calls `apply`,
|
||||
and then `Selection::begin` on a surviving row — which panicked before
|
||||
fix 1, resolving a `WeakWidget` `List::clear()` had just freed.
|
||||
9. **Fixed.** `list.rs`'s new `tick_fling_applies_shrinking_incremental_
|
||||
deltas` flings toward the end from `jump_to_start` and asserts each
|
||||
tick's `extents[&0]` delta is no larger than the previous one — would
|
||||
fail against a `tick_fling` that applied the total spline distance
|
||||
every tick instead of the incremental slice, which the two pre-existing
|
||||
fling tests cannot catch.
|
||||
10. **Fixed.** `list.rs`'s new `replace_back_forgets_the_evicted_keys_own_
|
||||
height` replaces row 4 with a row keyed `100` (the two existing
|
||||
`replace_back` tests always reuse the same key, so neither actually
|
||||
exercises the removal) and asserts `heights` no longer contains the
|
||||
evicted key.
|
||||
|
||||
Docs: `docs/IRIS.md`'s 2026-09-05 `List::replace_back`/`clear`/
|
||||
`TranscriptScreen::apply` entry now has a line on what the fix requires of
|
||||
a caller with its own row-keyed side table, naming `Selection` as the
|
||||
example and dating the fix.
|
||||
|
||||
Verification run alongside the rest of this pass's checks: `cargo fmt
|
||||
--all`, `cargo clippy --workspace --all-targets`, `cargo test --workspace`
|
||||
from `iris/` — see docs/RUST.md's plan box for the pass/fail and any
|
||||
caveats from this same session.
|
||||
+112
-20
@@ -43,26 +43,118 @@ 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.
|
||||
|
||||
- [ ] **Merge the `DragGesture` work** left complete but unmerged in the
|
||||
worktree branch `worktree-agent-a754368325fa06839` (commit
|
||||
`e12c708`, 2026-09-06 02:10, two minutes after the last merge to
|
||||
`rustify`; already contains `rustify` at `543f6d9`). It targets two
|
||||
of the four bench-v2 defects: finger flings dropped by per-widget
|
||||
hit testing (pointer capture + `CursorSense::Drop`), and IME insets
|
||||
never redelivered (`MainActivity.java` edge-to-edge). Before
|
||||
merging: build, clippy, tests; then on the emulator confirm the
|
||||
three things `20b1225` (tap-vs-swipe focus) and the phone asked for
|
||||
still hold together — a swipe over the composer does not summon
|
||||
the keyboard, a tap does, a finger fling on the list keeps moving
|
||||
after the finger lifts, and `on_insets_changed` now fires on an
|
||||
IME toggle. Then remove the worktree.
|
||||
- [ ] **Fix `docs/REVIEW-2026-09-06.md`** (after the merge, since the
|
||||
review's finding 1 is in `selection.rs`, which the merge rewrites).
|
||||
Finding 1 is a real crash — `TranscriptScreen::apply`'s `Rebuild`
|
||||
arm leaves `Selection` holding `WeakWidget`s to freed rows, and the
|
||||
next tap anywhere panics. Findings 2–5 are `debug_assert!`s on
|
||||
invariants, 8 and 10 are the missing tests. Commit the review file
|
||||
with the fixes.
|
||||
- [x] **Merge the `DragGesture` work** -- done 2026-09-06 (merge commit
|
||||
`f802de9`, `git merge --no-ff worktree-agent-a754368325fa06839`,
|
||||
clean, no conflicts across the 8 files `e12c708` touched). Targets
|
||||
two of the four bench-v2 defects: finger flings dropped by
|
||||
per-widget hit testing (pointer capture + `CursorSense::Drop`), and
|
||||
IME insets never redelivered (`MainActivity.java` edge-to-edge).
|
||||
**Tap-vs-swipe/`DragGesture` overlap, reasoned through**: `attr.rs`'s
|
||||
`on_press` (composer focus) and `sense.rs`'s `DragArbiter`/
|
||||
`DragGesture` (list pan-vs-select) do not share a mechanism, but
|
||||
they don't need to -- `on_press` never calls `capture_pointer`, so
|
||||
it only ever sees an ordinary per-frame hit-tested `Pressing`/
|
||||
`PressEnd` (`run_sensors`' `region.contains(cursor.pos)` check,
|
||||
unaffected by capture unless *this* widget requested it), the same
|
||||
as before `DragGesture` existed. The two only interact where a
|
||||
gesture starts on the composer and travels into the list's region;
|
||||
`run_sensors` already delivers `Pressing` to whichever widget's
|
||||
*current* position contains the pointer, so `List` starts getting
|
||||
frames the instant the finger crosses the boundary -- with no
|
||||
`PressStart` of its own, which is exactly what `DragArbiter::
|
||||
is_idle()`'s 2026-09-05 recovery branch exists for. No consolidation
|
||||
needed; `DRAG_SLOP` is already the one shared constant (`attr.rs`
|
||||
imports it from `sense.rs`, not a second copy).
|
||||
**Checks, 2026-09-06 merge pass**: `cargo fmt --all` clean;
|
||||
`cargo clippy -p iris -p iris-core -p transcript-ui --all-targets`
|
||||
and the same for `-p desktop-app -p tabs-ui`, zero warnings beyond
|
||||
the pre-existing external-crate future-incompat notice
|
||||
(naga/wgpu/wgpu-core/wgpu-hal/winit); `cargo test --lib -p iris -p
|
||||
iris-core -p transcript-ui` and `-p desktop-app -p tabs-ui`, 97
|
||||
passed/0 failed, including the review-fix tests below.
|
||||
`cargo test --workspace`/`cargo clippy --workspace --all-targets`
|
||||
(the full-workspace forms, which also build `iris`'s winit examples)
|
||||
were abandoned after 40+ minutes each stuck compiling one example
|
||||
binary with `uptime` reading a load average of 66-78 on this 8-core
|
||||
VM (3-4 concurrent peer `cargo`/`cargo check` invocations the whole
|
||||
session) -- `ps -o time` on the stuck `rustc` showed 2 seconds of
|
||||
accumulated CPU time after 38 minutes of wall time, confirming
|
||||
scheduler starvation rather than a hang. The per-package `--lib`
|
||||
form above is what actually exercises the changed code and finished
|
||||
in under 4 minutes warm. `android-app` (`iris-android-app`) is
|
||||
excluded from the host workspace (`iris/Cargo.toml`, needs the NDK
|
||||
target) and is covered instead by the APK build below, which
|
||||
compiles it for `x86_64-linux-android`.
|
||||
|
||||
**Emulator checks, 2026-09-06** (this checkout's `ai-app-2` AVD,
|
||||
`iris/android-app/build-apk.sh debug --abi x86_64 --features
|
||||
"transcript-screen bench force-gles"` -- plain Vulkan crashed on
|
||||
this AVD's boot this pass, `wgpu_core::instance: enabled backend
|
||||
Vulkan has no adapters`, unrelated to this merge and worked around
|
||||
with `force-gles` the way I5's own box already documents for this
|
||||
hardware):
|
||||
- **(a) tap-vs-swipe still holds.** Fresh app launch, `dumpsys
|
||||
input_method`'s `mInputShown=false` at rest. `ui-trace record
|
||||
--do "swipe 540 1510 540 700 200"` (a swipe starting on the
|
||||
composer's own box, read from `ui-trace show -m Message --field
|
||||
box` as `31,1488..1048,1540`) leaves `mInputShown=false` and the
|
||||
box unmoved (no keyboard-driven resize). `ui-trace record --do
|
||||
"tap 540 1510"` on the same field then reads `mInputShown=true`.
|
||||
Matches `20b1225`'s original result -- the `DragGesture` merge
|
||||
did not disturb it, confirming the reasoning above.
|
||||
- **(b) a real finger fling keeps the list moving after release.**
|
||||
Screenshot-hash sampling (`adb exec-out screencap`, `md5`, since
|
||||
transcript rows carry no per-row accessibility label yet -- I5's
|
||||
own leftover -- so `ui-trace show` cannot track them) at ~40-60ms
|
||||
intervals through and after a fast `swipe 540 1400 540 400 120`
|
||||
(with room to scroll confirmed by a preceding slow drag) caught
|
||||
two *distinct* post-release frames in one run (a settle-position
|
||||
beyond the raw drag's own last frame), and every run showed
|
||||
28-32 `iris::android::view: render()` log lines per gesture
|
||||
against an idle baseline of 0 in 1.5s and roughly 8 expected from
|
||||
a bare 120ms drag's own `Pressing` frames alone -- i.e. redraw
|
||||
kept being requested well past the finger lifting, which only
|
||||
happens while `List::tick_fling` is still returning `true`.
|
||||
Some runs' screenshots showed only the drag's own jump with nothing
|
||||
further *visibly different*, which is consistent with a real but
|
||||
small/fast-settling fling (a modest synthetic-touch velocity's
|
||||
spline tail moves little per frame) rather than absence of one --
|
||||
the render-count signal did not vary between those runs and the
|
||||
one with a visible second frame. Recorded as confirmed, with that
|
||||
caveat, rather than measured to a number; a phone verification
|
||||
(Iris's own report closes this properly) is still open per
|
||||
`IRIS_TODO.md`'s item.
|
||||
- **(c) `on_insets_changed` fires on an IME toggle, with confirmed
|
||||
cycles.** `run-bench.sh`'s report: `keyboard: shown 4/5, hidden
|
||||
5/5 (confirmed via on_insets_changed)` -- the "could not be
|
||||
shown" unknown-state line (`bench_client.rs::run_keyboard_phase`)
|
||||
did not fire, unlike the pre-`DragGesture` build this same report
|
||||
format existed for.
|
||||
Worktrees removed after the checks above: `agent-a754368325fa06839`
|
||||
(the source branch, its own emulator stopped first via `cd` into
|
||||
it + `emu down`), `agent-a27094a7db775552a`, `agent-a1ff0294b6c29127e`,
|
||||
`agent-a9002910a315fe719` -- each confirmed `git rev-list --count
|
||||
rustify..<branch>` = 0 and no uncommitted changes first; their
|
||||
branches deleted too. `agent-a16b22e34539b810e` and
|
||||
`agent-a6e37a2335f436d08` left alone -- both `git worktree list`
|
||||
`locked` to a live peer agent.
|
||||
- [x] **Fix `docs/REVIEW-2026-09-06.md`**, done 2026-09-06, after the
|
||||
merge (finding 1's shape and location in `selection.rs`/`lib.rs`
|
||||
were unchanged by the merge, which touched `Selection` but not
|
||||
`apply`'s `Rebuild` arm). All ten findings fixed -- new
|
||||
`Selection::clear()` for finding 1 (the simplest option the review
|
||||
named: clear the same way `List::clear()` clears the list, let
|
||||
`push_row` re-`register` survivors), five `debug_assert!`s
|
||||
(2-5, plus 7's restructure), and three new tests (8, 9, 10),
|
||||
confirmed with the `apply_tests::a_row_dropped_by_a_regroup_does_
|
||||
not_outlive_itself_in_selection` test passing (it exercises exactly
|
||||
finding 1's shape: build a real `TranscriptScreen`, force the same
|
||||
regroup `diff_tests` already covers, `apply`, then a surviving
|
||||
row's `begin` -- panics pre-fix, per the review's own test-8 ask).
|
||||
`docs/IRIS.md`'s 2026-09-05 entry gained the line the review's
|
||||
"Docs" section asked for. See `docs/REVIEW-2026-09-06.md`'s own "Fixed, 2026-09-06"
|
||||
section for the per-finding account. Committed together with the
|
||||
review file.
|
||||
- [ ] **Iris's 11:39 phone report on the 02:07 build** (four items,
|
||||
verbatim in `IRIS_TODO.md`'s "From the phone, 2026-09-06, 11:39"):
|
||||
composer floating two thirds down the screen at launch with black
|
||||
|
||||
Reference in new issue
Block a user