From 8adad924b91d201929ea9717e965f2e99aea49b4 Mon Sep 17 00:00:00 2001 From: iris-ai <4+iris-ai@noreply.localhost> Date: Sun, 20 Sep 2026 03:57:11 -0400 Subject: [PATCH] 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 --- docs/HANDOFF.md | 17 +++++-- docs/LAYOUT_LOG.md | 112 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 126 insertions(+), 3 deletions(-) diff --git a/docs/HANDOFF.md b/docs/HANDOFF.md index 334bd3a..9160c3b 100644 --- a/docs/HANDOFF.md +++ b/docs/HANDOFF.md @@ -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`, 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 diff --git a/docs/LAYOUT_LOG.md b/docs/LAYOUT_LOG.md index d921d99..f2b3988 100644 --- a/docs/LAYOUT_LOG.md +++ b/docs/LAYOUT_LOG.md @@ -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; 2]` and `[Option; 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` 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`, `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` 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` 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