Files
ai-app/docs
irisandClaude Opus 5 8e5928cc6a iris: List becomes LazySpan, and takes a Dir
First of three steps agreed with Iris for getting scrolling out of the
list and into `Scroll`, so that `.scrollable()` is the one way anything
in iris scrolls. docs/IRIS_TODO.md's "In progress" block carries the
whole plan and the decisions behind it; this step is the rename and the
direction.

`List` -> `LazySpan`, and it moves in beside `Span` under
`widget/position/`. It is what `Span` is -- a sequence of children along
an axis -- laid out lazily from an anchor instead of eagerly from the
start, and the name says the one thing that matters about it. It also
stops colliding with `BlockKind::List` in the markdown code.
`ListRow` -> `LazyItem`; `RowKey` keeps its name, since rows are the
vocabulary in transcript-ui.

`Axis` -> `Dir`, with the sign meaning what it means in `Span`: which
end of the box item 0 sits at. **That is a different question from which
end the view is pinned to**, and conflating them would stand a
transcript on its head -- its oldest message is item 0 and sits at the
top (`Dir::DOWN`) while the view clings to the bottom. So the pin is its
own constructor argument, `LazySpan::new(dir, at_end)`, spelled the same
way as `Scroll::new`'s.

Making `Dir::UP` real rather than nominal is most of the diff. The walk
now works entirely in direction-relative pixels from the leading edge --
`Edge::Top`/`Bottom` are `Leading`/`Trailing`, `Placement` likewise, and
`RowExtent`'s fields and every local are `lead`/`trail` -- with two
places converting: `abs_region`, which flips the box for `Sign::Neg`,
and `flip_pos`, which converts the screen-space positions the public
helpers speak in (`note_tap`, `key_at`, `extent`, all fed by pointer
events) into the walk's space. Without the second, a reversed span would
hit-test at the mirror of where it drew.

`a_dir_up_span_grows_upward_from_item_zero` asserts on where each row was
**actually drawn** (`UiRenderState::active`), not on `extents`: the first
version of it read `extent()` and passed with `abs_region`'s flip deleted
-- checking the bookkeeping against itself while every row painted at the
mirror of where it belonged. It now fails with the flip removed (row 2 at
80..100 instead of 0..20), which is the check that matters.

Verified: cargo fmt --check, clippy --workspace --all-targets clean,
cargo test --workspace green (21 suites), including the phone-shaped
fixture tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 20:17:57 -04:00
..