diff --git a/docs/HANDOFF.md b/docs/HANDOFF.md index c670d50..7f4d5a8 100644 --- a/docs/HANDOFF.md +++ b/docs/HANDOFF.md @@ -26,6 +26,22 @@ 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 fraction 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 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. A +declared pixel length is unaffected -- 200 px of a box already 200 px wide is +the whole box -- so only `rel` and `rest` double. It shows in the `text` +example, whose "same words in half the width" panel is 474 px where +`upstream/main` draws 948, and that is 111,923 pixels of difference, so **the +claim above that the five reference renders are byte-identical is wrong for +`text`**; `tabs`, `view` and `minimal` are identical. The structural fix is the +built-in size already queued below -- a declared length resolved once, by the +parent -- rather than a second rule inside `SetSize`, which cannot tell whether +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. + **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 @@ -35,6 +51,12 @@ and x=1337 is withdrawn; it was taken before `02ff8c7` and `f61e893`, and a target directory shared between the two checkouts is the trap the render section already warns about. Byte-identical against `upstream/main` is met. +`4063635` adds the two hairline tests to `tests/layout.rs`, and `169db7f` +writes the shader's `scalar_within` the way `UiScalar::within` writes it -- +`from + (to - from) * t` rather than `mix` -- so the CPU and the GPU compose a +position with the same arithmetic and one multiplication fewer. All five +renders are unchanged by it. + Check for a review before starting anything, and read the newest `submitted_at` rather than the first result: @@ -105,14 +127,16 @@ Invariants, not history. Everything in `core/src/ui` rests on them. gap can therefore move by a pixel when a position falls the other side of the `floor`; it cannot be widened or deleted by one. Only a length expressed as a fraction can round away, and that was as true before the chain. Measured - 2026-09-15 with `examples/thin.rs` (untracked in the #18 worktree): sixteen - one-pixel lines and sixteen one-pixel gaps under four levels of span, padding - and `rest(3)/rest(7)/rest(5)`, rendered at six output sizes on - `upstream/main` and at `f61e893`. Every run is exactly one pixel on both, and - a line sits within one pixel of where the other revision put it. At 640x481 - the fixture is over-constrained -- the fixed pixels exceed the box -- and the - same three gaps vanish on both revisions, which is the span overflowing - rather than anything rounding. + 2026-09-15 by `tests/layout.rs`, which composes sixteen hairlines under four + levels of span and padding on `rest(3)/rest(7)/rest(5)` weights and applies + the shader's own snapping at four output sizes. **A span short of room takes + it from its shares and never from a fixed length**: the shares reach zero and + then go past it, so the fixed parts start overlapping each other -- at 400 + wide three marks sit at 132, 265 and 399, at 3 wide at 0, 1 and 2, and at 1 + wide two of them land on the same pixel. Overlapping is what hides a + separator in an over-constrained tree; collapsing one never happens. The same + line written as `rel(1.0 / 1920.0)` instead is zero pixels wide even at 1920, + which is the mutation that shows the tests have teeth. - **The walk stops where a length did not change.** A part of a box with no relative extent on an axis is a fixed length held as offsets from that box's start, and composing anything into it leaves no relative extent either -- so a