From a128648bd2db305ecb38025cc2b31c95b30f50e0 Mon Sep 17 00:00:00 2001 From: iris-ai <4+iris-ai@noreply.localhost> Date: Tue, 15 Sep 2026 13:52:04 -0400 Subject: [PATCH] Record the declared-length fix, and park the rest-with-no-leftover rule Co-Authored-By: Claude Opus 5 --- docs/HANDOFF.md | 46 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/docs/HANDOFF.md b/docs/HANDOFF.md index 2348f01..80a1409 100644 --- a/docs/HANDOFF.md +++ b/docs/HANDOFF.md @@ -26,8 +26,32 @@ reference renders and the resize render are byte-identical across all of it, and the hundred-seed sweep passes. `/home/bob/repos/ai-app-2` is on `rustify`, worktree clean. -**A declared `rel` length is applied twice, and it is the one thing to fix -before #18 lands.** `.width(rel(0.5))` in a 400-wide span puts the `SetSize` at +**Fixed, 2026-09-15, by `de9ddc0`: a declared length is resolved where the +widget is drawn.** `Painter` takes a child's `size_hint` in its own box before +drawing it there -- which is what a fraction of a length means, and is the +identity for a caller that already reserved the space -- and `SetSize::draw` +keeps none of it. `rest` is deliberately not resolved there: a part of what is +left over is only a length to the widget dividing one, so it passes up in the +size, the way it already passes up out of a span. A declared length is then +part of the box its parent decided, so a change to one redraws the parent; the +lengths resolved into a box are kept beside it in `ActiveData::declared` and +compared. Assuming instead that any dirty widget which declares a length needs +its parent costs 17% of a frame that dirties 130 of 260 widgets and buys +nothing. All five reference renders, the resize render and the image replay are +byte-identical to `upstream/main`, and the 100-seed sweep passes. + +`9644971` is the other half of the cost. Whether the inliner took +`DrawLayers::write` into `Painter::glyphs` turned out to depend on unrelated +code in `iris-core`: the resolution above pushed it out, and a call per glyph +cost 12% of a resize frame with every counter -- widget draws, primitive +writes, text renders -- unchanged. `#[inline]` says it directly. The +random-tree rig is 12.59B instructions per 500 resize frames where it was +13.25B before either change, and the `revision_cost` fixture is 1.286 ms +against 1.289. + +The defect it fixed, for the record: + +**A declared `rel` length used to be applied twice.** `.width(rel(0.5))` in a 400-wide span puts the `SetSize` at 0..200 and its child at 0..100: the span sizes the box from the hint, and `c596bf1` then measures the child in the declared length again *inside* that box. `px` is unaffected, since 200 px of a box already 200 px wide is the whole @@ -45,6 +69,24 @@ its box is its own declared length or the whole of what its parent had. Minimal repro: `(inner.width(rel(0.5)), rect()).span(Dir::RIGHT)` at 400 wide, asking where `inner` landed. +**Parked, and wanted: a `rest` with nothing left over should not draw.** The +owner asked for it on 2026-09-15 -- "if it's 0 wide for rest then it shouldn't +draw in the first place", with `rel` and `px` continuing to overflow forward as +they do now, and only a length that is *nothing but* `rest` skipped. Written +and reverted: `Span` needs `painter.px_len(axis)` to know whether anything is +left over, since `rel(0.5)` beside 300 px is full at 600 and overfull at 400 +and the difference is not expressible in fractions, and that makes a span's +drawing depend on its own box. Inside a `Scroll`, whose box comes from the +content, seed 13 of `adding_and_removing_span_children` then lands warm and +cold on different answers: the same span holds the same region in both, and +`ActiveData::px` says it drew against 63 px warm and 900 cold, so the leftover +question is answered in two different boxes. `Painter::undraw` -- drop a child +drawn only to measure it, so it is not one of this widget's children this +frame -- is the other half and worked. `Branch` in `iris::random` already makes +a pixel-dependent choice about which child to draw and the fuzzer is happy with +it, so the shape is supported; what is not settled is which box a span should +ask in. + **The `tabs` divergence does not reproduce, so nothing waits on the owner.** Re-measured 2026-09-15 at `f61e893` against `upstream/main` built in its own worktree: `tabs` at 1920x1200 is byte-identical, and so are a cold 900x1200 and