diff --git a/docs/HANDOFF.md b/docs/HANDOFF.md index 79dcc3e..898dbcb 100644 --- a/docs/HANDOFF.md +++ b/docs/HANDOFF.md @@ -8,7 +8,7 @@ log. Canonical Iris `main` is **`ca2b4b2`** (#17, the headless rig). **#18 `split/18-position-chain`** is open in `/home/bob/repos/iris-pr18`; its local -head is **`f437495`**, forty-six commits, pushed to the fork. No PR reviews +head is **`71c9c39`**, forty-seven commits, pushed to the fork. No PR reviews were present when checked on 2026-09-15; the verification summary is posted on the PR. @@ -30,8 +30,18 @@ add defensive work to recover optimizations from a false declaration. the largest fixed orthogonal length is reported, while any relative or `leftover` child makes the span report `leftover`. `Full` reports exactly `Len::rel(1.0)`. It does not read the children's orthogonal sizes, but their -`Holds` ranges still propagate through placement, so a resize repositions them -without redrawing when their own contracts permit it. +`Holds` ranges still propagate through final-box drawing, so a resize +repositions them without redrawing when their own contracts permit it. + +`71c9c39` replaces the public `Painter::place` distinction with an opt-in +widget property. `.region_node()` gives a widget one independently movable +retained region; `Widgets::set_region_node` can change that choice at runtime +and causes one structural redraw. Widgets without the property remain at the +default shallow chain depth: Iris recursively remaps their retained primitive +and mask regions when they move. `.scrollable()` enables a region node on its +content once as its convenient default; raw `Scroll::new` respects the +caller's choice, and the property can be disabled later without breaking +scrolling. `Span` and `Align` do not add nodes to their children. Do not change `Children` to select the pixel-longest arbitrary `Len` at the span's current width. A fixed child and a relative child can create multiple @@ -52,19 +62,23 @@ rewrite: - A widget retains the layer it was entered on, not the last child layer its painter visited. The old value drifted on local redraw and put a redrawn tab background above its retained text. +- `Span`'s leftover/no-leftover split is a strict layout decision, not a + rounding tolerance. Its `Holds` range must use the same exact divided + boundary as drawing; a tolerant endpoint retained zero-height children at + the boundary in generated seed 16. `core/src/ui/holds.rs`, the retained tests, and the generated cold-layout oracle pin those rules. Seeds 10 and 86 are now in the ordinary generated set. -## Verification at `f437495` +## Verification at `71c9c39` - `cargo fmt --all --check` - `cargo clippy --workspace --all-targets --all-features -- -D warnings` -- `cargo test --workspace`: 77 passed, 8 ignored +- `cargo test --workspace --all-features`: 84 passed, 10 ignored - `cargo test --release --test generated -- --ignored - a_long_run_of_seeds_agrees`: 100 seeds passed in 66.81 s -- The `tabs` reference at 900x1200 remained byte-identical to `ca2b4b2` after - retaining `Children` as the default. + a_long_run_of_seeds_agrees --exact`: 100 seeds passed in 64.77 s +- The `tabs` reference at 900x1200 remained byte-identical to `f437495`: zero + differing pixels out of 1,080,000 after the region-node rewrite. - At preceding head `29c7881`, reference renders against `/home/bob/repos/iris-main-cmp` at `ca2b4b2` covered: `tabs`, `view`, `minimal`, and `text` at 1920x1200; `tabs` cold at @@ -72,7 +86,10 @@ pin those rules. Seeds 10 and 86 are now in the ordinary generated set. before and after replay. Every comparison had zero differing pixels. The live-resize image is also byte-identical to the cold 900x1200 image. -The final pre-submit review was run in four passes. The earlier retained-layout +The final pre-submit review was run in four passes. The region-node review +caught an index-reuse hazard when removing a node; its move entry now remains +alive until every descendant has migrated. The generated oracle then exposed +the exact `Span` threshold described above. The earlier retained-layout review caught the layer defect above, corrected validity-range inversion for negative relative extents, and removed an impossible-state `unwrap` from `Scroll`. The orthogonal-sizing review caught both the circular longest-child @@ -98,19 +115,27 @@ tighter figures. ## Retained-layout invariants -- A slot holds a whole `UiRegion` in the coordinates of the slot it names. - `UiRegion::FULL` is the identity. Slots are opt-in: `Painter::place` gives a - child a slot; `widget` and `widget_within` share the nearest ancestor's. -- A widget's `ActiveData::region` is where it was placed in its parent's slot. - A placed widget draws in `FULL`; its box lives in its slot. This keeps chains - at positioning-container depth rather than full widget-tree depth. +- A region node holds a whole `UiRegion` in its parent node's coordinates. + `UiRegion::FULL` is the identity. Widgets opt in with `.region_node()` or + `Widgets::set_region_node`; ordinary widgets share the nearest ancestor + node. Region nodes therefore add chain depth only where moving a whole + subtree through one entry is useful. +- A widget's `ActiveData::region` is its box in its parent node. A region-node + widget draws in `FULL`; its box lives in its node. Moving an ordinary + retained subtree instead remaps its primitive, mask, and active regions. + Remapping stops at a descendant region node after rewriting that one entry. +- Changing `region_node` redraws the subtree once to rebuild the coordinate + boundary. The property belongs to widget identity, which is safe because a + widget has one parent. `.scrollable()` sets it once; raw `Scroll::new` does + not, and `Scroll` never reasserts it while drawing. - The first box a parent asks about is the offer. A later box chosen from the - child's answer is a placement, not another independent answer. Dirty widgets - are re-asked at the offer and only then placed again. + child's answer is the final box, not another independent answer. Dirty + widgets are re-asked at the offer and only then drawn in the final box. - A retained drawing can be reused only when its `Holds` interval contains the - new pixel box on both axes, its parent slot is unchanged, and the widget is - clean. A moved unslotted widget must draw again because it has no slot to - rewrite. + new pixel box on both axes, its parent node is unchanged, its region-node + choice matches the retained structure, and the widget is clean. A valid + ordinary subtree may move without redrawing because its regions are + recursively remapped. - `Painter` records size-dependency edges only when a parent reads a child's size or hint. An undrawn measured child remains recorded so a later change reaches the parent that decided whether to draw it. @@ -126,8 +151,10 @@ tighter figures. remains valid from its longest produced line through the width at which it was made, and `TextView` reports that interval through `Painter::holds`. - `Span`'s decision to distribute `leftover` is a pixel question. Its validity - interval is split at the length where fixed parts fill the box; pure - `leftover` children are undrawn on the no-space side. + interval is split exactly at the length where fixed parts fill the box; + pure `leftover` children are undrawn on the no-space side. Exact/open ranges + are for hard widget decisions; the ordinary tolerant ranges remain for + accumulated coordinate rounding. - `Scroll` reports its content's first measured size. Its drawing can survive container-length changes only over the interval in which clamping and its current offset do not change. @@ -186,9 +213,9 @@ instruction totals. ## Next -Let PR #18 review. The next small LAYOUT.md §2 item is `LazySpan`; -`set_child_offset` is no longer separate, because placing a child is -`Painter::place`. +Let PR #18 review. The next small LAYOUT.md §2 item is `LazySpan`. Region nodes +now cover the independently movable-subtree use case; do not restore a +separate child-placement API. After #18 lands, the next structural design is built-in alignment and size. Today a declared size and the `Size` returned by `draw` are two sources of