Document deferred layout requests and verification on Iris PR 19

This commit is contained in:
iris-ai committed 2026-09-20 16:50:50 -04:00
1 parent ab8b05b762
commit 4c7072e62f
3 files changed
+104 -16

No files matched your search

+38 -14
View File
@@ -4,10 +4,33 @@ Where the work in flight stands. The settled layout design, the vocabulary
and the measurement method are in `docs/LAYOUT.md`; what the review of #19
found is in `docs/LAYOUT_LOG.md`.
## Deferred comparisons are submitted on PR #19
The `layout/one-ask` tip is `8780b40`, in `/home/bob/repos/iris-deferred`.
`/home/bob/repos/iris` remains on `layout/bounds`; that branch's `de1eb7e` was
fast-forwarded into #19 before this work. The app's Iris pin is untouched.
The new request path composes min/max/clamp before assigning leftover slots,
while ordinary widgets keep returning `Size`. Known requests skip provisional
painting; measured leaves can complete a nested expression. The implementation
uses a reusable expression arena and retained draw buffers. The allocation rig
checks zero allocations after warm-up for unchanged plain and clamped trees.
Generalized cross-axis maxima are deferred because hidden main-axis shares
must not contribute to them. The current design is in `docs/LAYOUT.md`.
Format, workspace Clippy with and without layout diagnostics, and all 197
workspace tests pass. The broad scans passed: 400 depth-5 trees through the
shrinker, 1,000 depth-6 and 2,000 depth-4 generated trees, plus 400 depth-5
expression trees and 400 relative-bound trees. All 34,986 unbounded cold boxes
match `de1eb7e`. The forced-redraw allocation fixtures report zero allocations
across 100 resize frames after warm-up. The text resize benchmark uses 5.89%
more retired instructions than `de1eb7e`; this is not a universal speedup.
See `docs/LAYOUT.md` for the measurements and remaining measured paths.
## The Iris layout repair is submitted
**Iris PR #19** (`layout/one-ask`) replaces closed #18. The tip is `2dba90b`,
and past the reviewed `cadfba0` it is twelve rounds, each described in
**Iris PR #19** (`layout/one-ask`) replaces closed #18. Before the deferred
comparison work above, its review rounds past `cadfba0` are described in
`docs/LAYOUT_LOG.md`:
- **The repair**, `add6774` and `84dad21` -- collapsed-share placement,
@@ -68,9 +91,8 @@ over 2026-09-17 to 19; it is current, not frozen.
The core design remains sound. Round-to-nearest is still unchanged.
Two checkouts share one Git storage: `/home/bob/repos/iris` is the active
`layout/one-ask` worktree, and `ai-app-2/iris` stays on `app-pin` at the
app's `32f6ad8` pin until the integration below is ready.
The Iris worktrees share Git storage. `ai-app-2/iris` stays on `app-pin` at
the app's `32f6ad8` pin until the integration below is ready.
### The branch layout, and the trap that used to be here
@@ -198,17 +220,19 @@ glyphs do not follow a shortened entry.
are caller bugs under `debug_assert`, but the fallbacks differ.
- `docs/LAYOUT.md` §4, §5 and the density section name `Painter::place`,
`Painter::region()`, `SetSize`, `desired_width`, `apply_rest`, `Len::dp`,
`Aligned` and `MaxSize`, none of which exist. Do not restore
`OnResize::Translate` or `OrthoSize`.
- **`layout/bounds` (`de1eb7e`) is green and ready to review**, kept off #19
so that branch stays one subject. A rule holds what a widget answers
`Aligned`, which no longer exist; `MaxSize` now exists with the bounds API.
Do not restore `OnResize::Translate` or `OrthoSize`.
- **`layout/bounds` (`de1eb7e`) is included in #19** for the deferred
comparison implementation. A rule holds what a widget answers
(`SizeRule::{Min, Max, Clamp}`); a widget holds the box (`MaxSize`, which
`.max_width`/`.max_height` build). Bryan settled the split on 2026-09-20
after four readings were measured; the reasoning and the one remaining hole
-- a retained answer re-placed under a different rel base, which is older
than bounds -- are in `docs/LAYOUT_LOG.md`. A bound may not contain
`leftover`, and the generated trees grow bounds in pixels until that hole is
closed.
after four readings were measured; the reasoning is in `docs/LAYOUT_LOG.md`.
The deferred-request work fixes stale relative-bound reuse by tracking the
input base and comparing resolved bounds, with a reduced natural-size-hint
regression and 400 depth-5 relative-bound trees. Intrinsic bounds remain
`Len`s; comparisons involving leftover use `SizeRequest`. Ordinary generated
trees retain pixel bounds; `deferred_generated` supplies relative bounds and
expressions in separate corpora.
- `LazySpan`.
- `Scroll` taking a direction rather than one axis.
+61
View File
@@ -12,6 +12,67 @@ in flight stands. The sections from "Three names, and the one argument that
says them" onwards are the settled design, the findings that outlived the
working log, and the measurement method.
## Deferred size requests on PR #19
`Widget::draw` still returns the small, copyable `Size` of two `LayoutLen`s.
An optional `size_request` method describes an axis without drawing it. Its
context can query children and compose sums, minima and maxima; `None` means
that drawing in a concrete offer is necessary. Existing leaf widgets need no
new method: their `size_hint` supplies a plain request when available.
Declarations can use expressions, for example
`rect(color).width(leftover(1).clamp(40, 120))`. In a 300-pixel row beside an
uncapped equal share, that child takes 120 and its sibling takes 180. At 100
pixels they take 50 each. Comparisons can have shares on both sides, such as
`(px(30) + leftover(1)).min(leftover(2))`. Weights must be nonnegative.
A span discovers requests, measures any unknown content, solves the shared
allocation, then places children. Known requests avoid provisional painting.
After measuring a container, discovery can use its children's measured answers
to finish an expression that was previously incomplete. Thus a measured leaf
inside a nested capped row does not force the row's request to become a pixel
constant. A custom container must implement request composition to propagate
such expressions through itself.
Plain sums retain the existing symbolic allocation path. Comparisons with a
known ordering (pixels with equal fractional/share coefficients, or shares
with equal fixed terms) fold to ordinary lengths. Remaining expressions are
monotone piecewise linear functions of one share unit. The allocator advances
through exact rational crossings until the sum fills the offered room. Caps
return room to other shares; floors may overflow; all caps being reached may
leave unused room. Prefix rounding gives adjacent slots the same edge.
Persistent declarations share immutable expression nodes. Temporary composition
uses a reusable arena, and each retained drawing keeps its vector capacities.
Temporary request handles are valid only within that layout pass. An explicit
allocated placement distinguishes a solved slot from a box that merely fills
its parent's region, so declarations are not evaluated a second time against
the slot they already chose. Request dependencies invalidate the allocator when
a descendant's rules or contents change. A solved declaration retains its
pixel length when moved, including by scrolling; it must not become a fresh
fraction of the destination box. Relative intrinsic bounds record their input
base separately from the widget's resulting box, and retained answers compare
resolved bounds before reuse.
Two measured cases remain intentional. Intrinsic fixed content is drawn in the
remaining offer and then moved; drawing it in its reported size would change
wrapping and overflow. When no leftover room exists, discovered intrinsic
shares use that same measured path. Cross-axis span maxima also remain measured:
a main-axis share can be hidden, so the maximum of every child's request is
not necessarily the maximum of the children that actually draw. Generalizing
that would require carrying visibility through the expression system too.
The allocation rig forces every widget to redraw across 100 resize frames:
both the plain and clamped eight-row fixtures allocate zero times after warm-up.
This is a measured property of those stable visible trees, not a guarantee for
arbitrary widgets or visibility changes. An eight-level known nested span paints
its leaf once. The 40-row, 1,000-frame text resize rig costs 9,891,085,554 retired
instructions versus 9,341,015,256 at `de1eb7e` (5.89% more), with the same printed
geometry. Request dependency tracking has a cost even when text still needs
measurement. The 400-tree unbounded cold dump preserves all 34,986 boxes.
The app's submodule pin is unchanged. This design lives on Iris PR #19.
## Design
### UI ownership and frame access
+5 -2
View File
@@ -870,8 +870,11 @@ span rather than silently discarding its `rest` component.
the box a parent asks a widget in is its *region*, where the drawing ends up
is its *placement*, and the length a fraction resolves against is its *rel
base*. A container says all three in one `PlaceDesc` argument. `Widget::draw`
remains the only layout body. The invariants, the measured costs, the fuzzing
method and the failed hypotheses are all in `docs/LAYOUT.md`; the app's pin at
remains the only layout body. PR #19 also has optional size-request discovery
for deferred min/max/clamp expressions (2026-09-20); ordinary widgets still
return `Size`, and plain requests retain symbolic allocation. The invariants,
the measured costs, the fuzzing method and the failed hypotheses are all in
`docs/LAYOUT.md`; the app's pin at
`32f6ad8` predates every part of it, so nothing here depends on it until the
integration described in `docs/HANDOFF.md` lands.