Record the root unification, and where bounds stand

`0d03267` in iris unifies the root's layout path with every other widget's:
`Placing::WINDOW` is the box nobody drew and `Placing::ask` the one place a
box is decided. The leftover-as-a-minimum gap the log recorded for
`root_layout` is closed by it rather than left.

`SizeRule::{Min, Max, Clamp}` is on `layout/bounds` and not in #19. Every
hand-written test passes and the 400-seed scan does not, for a reason that is
a design question: a bound is the first rule whose effect depends on the box
its parent gives it, and the retained machinery hands a widget a box by paths
that never ask it again. The log records the four readings measured and what
each one's seeds were, so none of them is tried twice.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
iris-aiandClaude Opus 5 committed 2026-09-20 14:35:28 -04:00
1 parent 6a92857079
commit 149a6838af
2 files changed
+91 -15

No files matched your search

+64
View File
@@ -6,6 +6,70 @@ 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`.
## One ask, the root's included (2026-09-20)
`0d03267`. `root_layout` read the root's declared lengths against the window
while every other widget's box came of `Painter::widget_at`, where a rule of
the widget's own can take the box past what its parent offered. Bryan asked
for the two paths unified, and ruled out doing it by putting a widget above
the root: a root sized in pixels would make every fraction under it a fraction
of a px length, which pins the whole tree to those pixels and re-asks it on
every resize.
`Placing::WINDOW` is the unification -- the full output, fractions of the full
output, no move entry and no mask -- and `Placing::ask` is the one place a box
is decided, called by the painter, by a local redraw, and by the root's first
draw. What is left of the root's own path is the bookkeeping a widget with no
parent keeps.
Two things fell out. `DrawInfo::asked` is now the place the parent offered
rather than the place the ask came to, so a local redraw re-decides a rule
instead of re-reading its decision; the two were the same until a rule could
move the box. And the root's `is_region_node` is read, where the old path
passed `false`.
The comparison a rule makes is kept on the widget asked about rather than on
the asker. A window range means the same thing at either end of an ask and
`in_parent` passes one up unchanged, so the asker still ends up holding it
through the child's drawing -- and the root, which has no asker, needs nothing
of its own, since `resize` already checks its record.
## Bounds are a rule that reads its box, and the oracle refuses one (2026-09-20)
On the branch `layout/bounds` (`76aaf06`), not in #19. `SizeRule::{Min, Max,
Clamp}` is the capability `MaxSize` had on the app's pin and nothing on this
branch has. Every hand-written test passes, including a capped scroll taking
its viewport from the cap; the 400-seed depth-5 scan does not.
The finding is general and worth keeping whatever is decided. A bound is the
first rule whose effect depends on the box its parent gives it, and the
retained machinery hands a widget a box by paths that never ask it again
(`place_in` from a re-placing parent, `reposition` after a parent's box moved).
A decision made when the box was one length survives into a box of another, so
warm and cold disagree about a tree they agree on structurally. Four readings
measured over 400 seeds at depth 5:
- decided at every ask and kept: seeds 291, 1, 120, 178, 64 differ.
- re-decided at `place_in` as well: seeds 1, 362, 188, 254, 156, because that
path's box is the one the answer chose, not the one the widget was asked in.
- skipping a place the parent decided outright, which is the rule the share
follows: worse, since the same widget then gets two decisions by two paths.
- the bound as an answer rule only, leaving the box alone: seeds 4 and 196 --
much the closest.
The share is the one existing rule of this kind, and it is stable for two
reasons that do not generalise: `place_at` re-asks a child whose rel base it
narrows, and a share that binds is baked into the retained place as a `Sized`
length. A bound that binds is a length of the rel base, and `Sized` cannot say
"this slot, narrowed" for a `Within` place.
Neutering the bounds in the generator while leaving its random draws in place
puts the same shapes back to green, so the divergence is the bounds and not
the new trees. Two findings from the branch are worth having whatever happens
to it: a rule that is a fraction must pin its rel base, which `Exact` was
already missing for bounds-shaped reasons; and `widget_trait!` dropped
attributes, so no method it defines could carry a doc comment.
## Images in the generated trees (2026-09-20)
`Image` is the only widget here whose size hint is a length in pixels, so it is