diff --git a/docs/HANDOFF.md b/docs/HANDOFF.md index bc81120..d60a2ac 100644 --- a/docs/HANDOFF.md +++ b/docs/HANDOFF.md @@ -55,6 +55,12 @@ and past the reviewed `cadfba0` it is twelve rounds, each described in overflow its pixels asked for, said as the place the parent gives and sharing one comparison with the span; and `Image` grown in the generated trees, which is the only widget here whose hint is a length in pixels. +- **One ask, the root's included**, `0d03267` -- the root had a layout path + of its own, so a rule that reads the box it is offered reached every widget + but that one. `Placing::WINDOW` is the box nobody drew and `Placing::ask` + the one place a box is decided; the root's own path is now the bookkeeping + a widget with no parent keeps. Bryan asked for this rather than a widget + above the root, which would pin the tree to pixels. 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 @@ -110,14 +116,15 @@ the name it is reachable by. **Always**, because they cost nothing: format, workspace clippy under `-D warnings` with and without `layout-diagnostics`, the workspace tests, and -the **cold dump**. `layout_dump` over 400 depth-5 trees is **34,488** boxes --- **34,571 since `2dba90b` grew images in the trees**; counted again on -2026-09-20, the fourth through eighth sweeps having all repeated 34,492, which -is a `wc -l` of the whole run rather than of its box lines -- and it is the only thing that catches two same-typed values being -swapped, which is the failure mode of a rename or a move. Count the lines -that are a box (`grep -cE '^[0-9]+ [0-9]+ '`), not the output. The repair moved 650 of those -boxes, all from the collapsed-share correction; every commit since has been -byte-identical to `84dad21`. +the **cold dump**. `layout_dump` over 400 depth-5 trees is **34,571** boxes +since `2dba90b` grew images in the trees, and `0d03267` left every one of them +byte-identical. It was 34,488 before the images; the fourth through eighth +sweeps all repeated 34,492, which is a `wc -l` of the whole run rather than of +its box lines, so count the lines that are a box +(`grep -cE '^[0-9]+ [0-9]+ '`). It is the only thing that catches two +same-typed values being swapped, which is the failure mode of a rename or a +move. The repair moved 650 of those boxes, all from the collapsed-share +correction; every commit since has been byte-identical to `84dad21`. **Only when the change can alter what layout computes**: the three seed scans -- 400 at depth 5, 1,000 at depth 6, 2,000 at depth 4. They cost about a @@ -185,19 +192,24 @@ glyphs do not follow a shortened entry. - A `leftover` under a parent that does not divide is a minimum size -- `max(box, px + rel*box)` (Bryan, 2026-09-20), which is a `SizeRule::Min` of `rel(1.0)` and shares `Painter::longer_than` with the span. Done in - `b295c8b` for a widget its parent asks; `root_layout` still reads the old - way, for which see `docs/LAYOUT_LOG.md`. + `b295c8b`, and in `0d03267` for the root as well, which used to read it the + old way. - `Fixed::div` by zero answers `MIN`/`MAX` while `ratio` answers `ZERO`; both 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`. -- `LazySpan`, then `SizeRule::{Min, Max, Clamp}`. A cap may not contain - `leftover`; whether `Max` narrows the child's drawing box is a product - decision. `Min` is `Painter::longer_than` again -- a `leftover` where - nothing divides it is already a `Min` of `rel(1.0)` -- so it belongs on - that path rather than beside it. +- `SizeRule::{Min, Max, Clamp}` is written and pushed as `layout/bounds` + (`76aaf06`), and **is not ready to land**: every hand-written test passes, + including a capped scroll taking its viewport from the cap, but the + 400-seed scan does not, and the reason is a design question Bryan has to + settle -- see `docs/LAYOUT_LOG.md` under "Bounds are a rule that reads its + box". Bryan settled two things already: `Max` narrows the child's drawing + box (2026-09-20), and a rule gives a length or bounds one but never both, + since a capped share is said with two widgets. A cap may not contain + `leftover`. +- `LazySpan`. - `Scroll` taking a direction rather than one axis. Other product work is in `docs/PLAN.md` and the focused documents it links. diff --git a/docs/LAYOUT_LOG.md b/docs/LAYOUT_LOG.md index ab57be0..cac5bf5 100644 --- a/docs/LAYOUT_LOG.md +++ b/docs/LAYOUT_LOG.md @@ -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