Record the ninth sweep, over the widget vocabulary

Eight findings in iris `c2b8bf8`, the largest a widget's own size hint
overriding a length rule that hint cannot express -- which no widget here but
`Image` can reach, so the dump is unchanged and the case builds its own widget.

The lesson for the next round is in the handoff beside the seventh and eighth
sweeps' own: where a round fixed a class of defect one instance at a time, the
check belongs in the shared helper, which closes the class and tests the
instances that round said were already fine.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
iris-aiandClaude Opus 5 committed 2026-09-20 03:57:11 -04:00
1 parent 0b60713806
commit 8adad924b9
2 files changed
+126 -3

No files matched your search

+14 -3
View File
@@ -6,8 +6,8 @@ found is in `docs/LAYOUT_LOG.md`.
## The Iris layout repair is submitted
**Iris PR #19** (`layout/one-ask`) replaces closed #18. The tip is `77ed7a2`,
and past the reviewed `cadfba0` it is ten rounds, each described in
**Iris PR #19** (`layout/one-ask`) replaces closed #18. The tip is `c2b8bf8`,
and past the reviewed `cadfba0` it is eleven rounds, each described in
`docs/LAYOUT_LOG.md`:
- **The repair**, `add6774` and `84dad21` -- collapsed-share placement,
@@ -44,6 +44,12 @@ and past the reviewed `cadfba0` it is ten rounds, each described in
sitting at the top of the same file, the GPU rigs' adapter probe written
twice, a mask resolved three times, a field nothing reads, a shrinker
claim its own assertion does not make, and three stale numbers.
- **A sweep over the widget vocabulary and the eighth sweep's own fix**,
`c2b8bf8` -- a widget's own size hint overriding a length rule the hint
cannot express, marking a widget for redraw having no name at twenty-one
call sites, the helper the eighth sweep shared being unable to see the
defect it had just fixed, two bare arrays where the framework names the
pair, forty-five lines nothing references, and one word for two things.
The settled design of the vocabulary rounds is in `docs/LAYOUT.md` under
"Three names, and the one argument that says them". Bryan settled the API
@@ -126,7 +132,10 @@ a compile error, which reads exactly like a fuzzer failure.
The eighth adds a second lesson, about the tests themselves: a fixture a
fuzzer shrank is a regression test, so a round that rewrites one has to
prove the tree is the same rather than that the case still passes, since
a fixture that quietly changed still passes and covers nothing.
a fixture that quietly changed still passes and covers nothing. The ninth
adds a third: where a round fixed a class of defect one instance at a
time, put the check in the shared helper, which both closes the class and
tests the instances that round said were already fine.
2. **A review of everything written before the review gate existed.**
`pre-submit-review` and the rule that nothing is submitted unreviewed
arrived on 2026-09-13, well after the Rust port and most of Iris were
@@ -140,6 +149,8 @@ a compile error, which reads exactly like a fuzzer failure.
`partial_align`s have no callers, `Vec2::align`/`partial_align` are
`UiVec2`'s with a conversion in front, and `impl_op!` carries four
grammars of which `core/src/util/vec2.rs` uses two, one line apart.
`Align` now has `Index<Axis>`, so the `if let Some` each
`partial_align` writes twice collapses when that sweep reaches them.
3. **Integrate the app's Iris capabilities before changing its pin.**
`32f6ad8` has 45 commits not reachable from the review branch; shared UI
ownership, richer masks, Android support, and app-side performance work
+112
View File
@@ -6,6 +6,118 @@ nothing here is rediscovered. Each entry says who found it and when.
it (settled design, the measurement method) belongs in `docs/LAYOUT.md`, and
the current plan is in `docs/HANDOFF.md`.
## Ninth sweep: the widget vocabulary, and the eighth sweep's fix (2026-09-20)
Over the part no earlier round named -- `src/widget/trait_fns.rs` and
`wrapper.rs`, `core/src/widget/widgets.rs`, the `util` additions, `examples/`
and the two manifests -- and once more over `77ed7a2`, which was the eighth
sweep's own commit. Eight findings, all in `c2b8bf8`. The cold dump over 400
depth-5 trees is byte-identical to `77ed7a2` across all **34,488** boxes, and
all three seed scans pass (400 at depth 5 in 63.27s, 1,000 at depth 6 in
160.45s, 2,000 at depth 4 in 302.52s).
**A hint overrode a rule.** `Widgets::declared_lens` asked
`rules[axis].declared()` and fell through to the widget's own `size_hint`
whenever that answered `None` -- which it does for a share, because a share is
not a declaration. So a widget carrying `width(leftover(1))` *and* hinting a
pixel length of its own was given a box of the hint, against the rule and
against the comment inside the function ("a hint still narrows the box where no
rule does"). `Painter::size_hint` spells the same rule-else-hint step three
hundred lines up and gets it right, with the reason written on it; both read
`Widgets::exact_len` now, and `declared_lens` is the part of its answer that
needs nobody to divide it.
`Image` is the only widget in the repository whose hint is a declared length
(every other hints `LEFTOVER`, whose `declared()` is `None` either way), and
neither the tests nor the generator builds one, so nothing here could reach the
difference -- which is why the dump is unchanged, and why
`a_share_rule_beats_the_widgets_own_pixel_size` builds a widget of its own. It
records the box it was asked in: 400 with the rule and 50 without, and 50
either way at `77ed7a2`. The alternative reading -- a hint narrows even under a
share rule -- would mean changing that comment instead, and is Bryan's to
prefer if he does.
**Marking a widget for redraw had no name.** Twenty-one sites under `tests/`
said it as `widgets_mut().get_dyn_mut(id);` with the widget thrown away: five
with a `let _ =` in front, one with a comment explaining what the line was for
("taking mutable access is the ordinary content-change signal"), and one inside
a local function already called `mark`. The framework's own word for it is in
its comments -- "marked for redraw" -- and `Widgets::mark_for_redraw` is now
the method. `revision_cost.rs` keeps the long spelling and says why in place,
since it is deliberately in the API subset an old worktree also has.
**`assert_same_regions` could not see the defect the eighth sweep had just
fixed.** It zips the warm and cold id lists, so a list naming one widget twice
compares fewer boxes than it lists and reports nothing. It now rejects a
repeated id and two lists of different lengths, which is the same check applied
to the whole class rather than to the four fixtures that had it wrong -- and it
verifies that round's claim about the other nine: all eighteen cases pass.
**Bare pairs where the framework has named ones.** `random.rs`'s `Lens` and
`Aligns` were `[Option<LayoutLen>; 2]` and `[Option<AxisAlign>; 2]`, read as
`[0]`/`[1]` and zipped against a hand-written `[Axis::X, Axis::Y]` in two rigs.
They are `SizeRules` and `Align`, which is what the framework calls those
pairs; `Align` took the `Index<Axis>` every other per-axis pair on this branch
has, and `RegionAlign::from` does the "an axis left out is centred" step both
rigs were spelling per axis. The three sites that wrote the axis pair out say
`Axis::BOTH`, which the layout core already says ten times. The plan's
`Debug` is why `Align` gained one.
**Forty-five lines nothing references.** `BothAxis<T>`, `AxisT`, `XAxis` and
`YAxis` -- a const trait, two marker types and three accessors -- have no user
anywhere in the workspace, and are the mechanism `impl_axis_index!` replaced,
in the very file this branch took `Vec2::axis`/`axis_mut` out of. Deleted as a
drive-by in a block the branch was already rewriting, the way `MASK_NONE` was;
drop it if the scope matters more. They pre-date the PR, so a plain "is this
name used" scan over the diff does not surface them.
**One word for two things.** `Wrapper` (the widget) arrived on this branch
beside core's `WidgetWrapper` (a dynamic borrow guard), both in the prelude.
The alias had two uses in one file and `DynBorrower<dyn Widget>` is what they
are, so it is gone rather than renamed. `Wrapper::new`, `Wrapper::empty` and
its hand-written `Default` were three names for one value, two unused.
**Smaller.** `Arena::get_mut` was the only `pub(crate)` among `pub` siblings on
a public type. `Selector` rounded the pointer onto the pixel grid in order to
add two values already on it, losing the precision the platform gave it for
nothing -- the step between the two regions is taken on the grid instead, which
also makes it agree with `Selectable`, the other caller of `select`. And the
two `debug` profile settings now carry their reason where the next reader
looks: `profile.dev`'s was added in a commit about renaming `rest` and
explained nowhere, and `profile.test`'s only in the message of the commit that
made the tests one target.
### Tripped a rule and left as it stands
- `examples/random.rs` is a viewer for the fuzz generator rather than a
demonstration of a feature, which is what an example is for here. Left:
`scripts/run-headless.sh` opens an example by name, so this is how a
generated tree is put on a screen at all.
- It also carries a fifth copy of the six-line `env` helper, on top of the four
under `tests/`. Same answer as the eighth sweep gave: sharing it means a new
file for six lines of `std`.
- `IRIS_SEED`/`IRIS_DEPTH`, `IRIS_GENERATED_SEED(S)`/`IRIS_GENERATED_DEPTH` and
`IRIS_DUMP_SEEDS`/`IRIS_DUMP_DEPTH` are three names for two knobs. Left: the
prefixes are what lets one shell set a rig's seed without changing another's,
and the two rigs that grow the same tree from the same number do share the
unprefixed pair.
- `set_size_rule` takes a `SizeRule` while `set_size_rules` takes
`impl Into<SizeRule>` per axis, so seven callers write `SizeRule::Exact(len)`
where two write `Some(len)`. Left: widening the single-axis one makes
`harness.rs`'s `len.into()` ambiguous between two conversions. The doc that
called it "for a caller holding a pair" -- which it is not, since it takes
two values -- now just says both axes at once.
- `Wrapper::set` is `replace` with the result dropped, and both have a caller
in `examples/tabs`. Left: it is `Option::insert` beside `Option::replace`,
and the tab bar wants each.
- `Masked`'s size comment credits `Scroll` with the same reasoning rather than
restating it. Left: the reason it gives is its own ("it clips what is inside
to that box"), and the cross-reference is to a design parallel, not an API.
- The nine `RegionAlign`/`CardinalAlign` constants include four nothing names
(`TOP_CENTER`, `CENTER_RIGHT`, `BOT_CENTER`, `V_CENTER`). Left: they are one
vocabulary of nine positions and six cardinals, and deleting the members
nobody has needed yet is the rule written on one member of a set.
## Eighth sweep: the tests, and the seventh sweep's fix (2026-09-20)
Over the part no earlier round named -- the 6,300 lines under `tests/`, which