`.width()` built a `SetSize` whose whole job was to answer `size_hint`, so
every declared length cost a widget, an `ActiveData` and a link of chain to
say one number. It is now a `SizeRule` per axis on `WidgetData`, beside
`region_node`, resolved by `Painter` where the widget is drawn. `SetSize`
and `MaxSize` are gone; `MaxSize` had no caller but its own builders.
That settles which of two answers is the size. A rule wins on the axis it
names and the `Size` returned by `draw` answers the rest, applied once in
`draw_inner` rather than by each widget that could carry one -- so the
widget under a rule never learns of it. `Painter::size_hint` reads the rule
first for the same reason: a rule that beats what a widget would draw has
to beat what it says about itself.
`declared_lens` still falls back to a non-leftover `size_hint`, which is
how an image or a gap gets its own pixel size rather than the whole offer.
That is the offer's business rather than a declaration's, and it falls away
when a widget occupies its reported size inside the box it was offered.
`known` and `declared` are separate because a share is a length to whoever
divides one and not to whoever composes a box: `.width(leftover(3))` is
known without drawing but cannot narrow anything.
Checked: fmt, clippy, 85 tests, and 100 generated seeds agreeing warm
against cold in 67.6 s. `minimal`, `text` and `view` render byte-identical
at 1920x1200; `tabs` differs only in the widget count it prints about
itself, which is two wrapper types smaller.
A move rewrites a retained subtree's stored regions, and those stores are
the only record of where it is. So a move that works from the last answer
integrates its own rounding with nothing to correct it, while one that
re-expresses each part as the same fraction of the new box is anchored to
that box and cannot.
Nothing was checking which of those `try_reuse` does. Replacing the fraction
with an offset added to both endpoints -- which is cheaper, and looks like it
should be exact for a translation -- shortens this fixture's row by 0.071
over 20,000 moves and by 0.712 over 200,000, growing with the count rather
than settling. That is five minutes of scrolling at 60Hz to pass the 0.05
physical pixels layout treats as the same place, and it keeps going. Placing
the far end from the near one instead of offsetting both leaves 0.069, since
the length is re-derived from the endpoints either way.
The existing warm-against-cold checks did not reach it: the generated oracle
compares within 0.05, and `unsettled.rs` compares exactly but only over a
handful of frames, where the drift is still 6e-5.