Say what the two open transparent-frames seeds are and what they turn on

A second planning pass over 49cec82: both seeds are the rule that picks
which answer places a box, shown by shrunk trees, a draw trace and two
counter-experiments, with the recommendation they point at.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
iris-aiandClaude Fable 5.1 committed 2026-09-18 13:03:14 -04:00
1 parent 152bed7ec4
commit 4b976e240e
1 file changed
+120 -1
+120 -1
View File
@@ -351,6 +351,123 @@ narrow (`Part::Of`, what `Pad` does), or a third thing Bryan decides.
row), or "outset pixels, inset `rel` and `leftover`", which needs a way
to say a box in a narrowed frame that the grid cannot express today.
### What the two open seeds are (2026-09-18, second planning pass)
Read by the planning session from the code and from traces at `49cec82`,
after Bryan asked whether the two questions above need answering or whether
there is a larger flaw. Both, and the flaw is one level below the frame: it
is the rule that decides **which answer places a widget's box**.
```rust
// draw_inner
let measured = match info.offer() {
true => answer.0,
false => self.active[&id].measured().unwrap_or(answer.0),
};
// DrawInfo
fn offer(&self) -> bool { self.place == self.offer_place }
// draw_at: the placing re-draw reuses the parent's `info`, so it runs with
// the same flag as the measuring one
let at_offer = info.offer();
```
The `offer` bit is meant to say "this ask is a measurement, keep its
answer". It has no consistent value once a container's body is evaluated in
more than one box, and the code evaluates it in three kinds: the room its
parent measured it in, a box the parent decided (a span slot, a fill), and a
box derived from its own answer (`place()`, and a scroll's content box).
- **Seed 2, `repaint`.** Shrinks to `Stack{sized by child 0}[x: leftover]
> [OneLine, Pad{0} > Span{DOWN}[Rect, OneLine], Branch]`. The stack's box
is one text line tall. The span measured in the stack's *room* has leftover
room, draws the rect and reports `leftover` across; measured in the
stack's *box* it has none, undraws the rect and reports the one-line
text's width. Cold kept the first because the stack's placing evaluation
reused the pad's retained answer: the span's `extent_len` pin is dropped
by `in_parent`'s `Part::Of` arm (only `All` composes a pin), so the pad's
answer looked valid for a box the span had never been measured in. Warm
re-asked the span in the box and got the second. Composing the pin through
`Of` where the part is the whole box less pixels (`Len::from_parts(len.rel,
len.px - part_len.px)`) makes seed 2 agree, and breaks seed 220 (`reorder`)
and `unsettled::a_widget_under_a_region_node_is_asked_in_the_box_that_node_was_offered`.
- **Seed 108, `reorder`.** Shrinks to `Span{LEFT} > [Span{LEFT} > [Span{DOWN}
> [Branch{probe Rect, wide Rect, narrow Wrapped, 483}, Wrapped], Rect],
Rect]`. The trace shows the `DOWN` span evaluated at 900, 450, 600 and
300 px across in one cold layout, the wrapped text re-shaped at each. In
the last, the narrow text is drawn in its real 300 px box and answers 286
px, and `draw_inner` discards that for the retained 438.9 px answer from
the 450 px evaluation, because the branch's `below` place embeds
`extent_len(Y)`, which moved when the sibling text's height changed, so
`place != offer_place`. It then redraws the text 438.9 px wide inside a
300 px box. Warm does the same with a different stale answer (286 px from
the first frame). Making `measured = answer.0` unconditionally and
recording every ask's answer fixes seed 108 and fails seeds 184, 246, 292,
372 and two suite tests, among them
`unsettled::a_span_given_the_box_its_answer_decided_matches_a_cold_layout`;
the new failures are all under a `Scroll`.
So the gate is load-bearing in one case and wrong in the other two. A
container re-drawn in a box its own answer derived (a scroll's content box
holding a row with a `leftover` child and a wrapping text) must place its
children at the answers the layout was computed from, or the text is offered
the room the leftover child gave back and the layout chases its own tail. A
container drawn in a box its parent decided (a 300 px slot after being
measured in 600) must re-measure its children there. A same-tree
re-evaluation whose place expression happens to differ must keep the fresh
answer. `place == offer_place` cannot tell these apart; nor could the four
rules tried before it; nor can the deferral, which is about local redraws
while these failures are inside full parent redraws. **The answers to
question 1 as posed do not resolve this**: (a) leaves it, (c) is the same bit
with more state.
**Where the plan was wrong.** It claimed the retained model rests on the
frame's length being the same on every ask. Answers depend on the *part*,
not the frame, for every widget that reads its box, so transparent frames
fixed fraction resolution (a real win) and moved the second geometry from the
frame to the extent rather than removing it. Step 6's premise followed from
that claim and was false for the same reason. And the plan carried the
`offer_place` concept forward from the old protocol without noticing it is
undefinable here. The worker executed the plan as written, found exactly
this, and stopped; `Part::Of` was a sound addition apart from the dropped
pin.
**Recommendation for Bryan to decide.** Make a container's body run only in
boxes its parent offered or decided, never in one derived from its own
answer: the placing step becomes reuse-or-translate and never `draw_at`,
and the offer machinery (`answer` gating, `offer_place`, `offer_part`,
`at_offer`, `measured()`) goes, leaving one answer per widget with the holds
that say which parts it is valid for, re-asked by `redraw` in the part of
the last ask. That needs every drawing to hold for its own answer box, which
today fails in three widgets and is fixable locally in each:
- `Span` reads `extent_len` unconditionally. The slots depend on `far` only
with `leftover` children (when it fills, so the box is the part) or with
`Sign::Neg` (compute slots from `total` instead). Pin only in those cases.
- `Stack` draws non-sizing children in `All` of its measuring box, a box it
will never have when the sizing child reports `px`/`rel`. Draw them in
`From(0..size)` per axis where `size` is that child's answer, `All` where
it is `leftover`.
- `Branch` reads `extent_len(Y)` for "the rest of my box"; that is
`Of(40px..FULL)`.
`Scroll` is the one legitimate own-answer box left. With the gate gone its
content box lays the row out afresh, the text takes the room the leftover
child gave back, and `content_len` from the viewport measurement is stale by
that difference; it is deterministic on both paths, so warm equals cold, but
the scroll range is off in the circular case. That case (a wrapping text
beside a `leftover` child in a horizontally scrolling row) needs its own
decision; Compose gives the text an unbounded width there. None of this was
run; it is what the traces and the two experiments point at.
**Question 2, `Pad`.** The contradiction is not specific to `Pad`. A
`rel(1.0)` child directly in a half-row `Fill` slot overflows its slot
because `rel` is of the row, and Bryan accepted that. A `rel(1.0)` inside a
pad in that slot overflows the pad's box for the same reason and by the same
amount less the padding. "Fill my padded box" is `leftover` inside the pad,
which already works. So: keep `Pad` as the outset, drop the "outset pixels,
inset `rel` and `leftover`" variant as inexpressible and unnecessary, and
change `examples/text.rs` from `.width(rel(1.0))` to `leftover` or no rule.
### The plan as written, for reference
What follows is the plan as it was handed over on 2026-09-18, kept verbatim
@@ -1443,7 +1560,9 @@ The replay used for the reference check:
In order, from the review above and Bryan's steer (2026-09-17):
1. **Two questions for Bryan, both from "What is not done, and why".** What
1. **Two questions for Bryan, both from "What is not done, and why"**, now
sharpened by **What the two open seeds are**, which says why answering
them as posed does not settle the seeds and what would. What
a widget's answer is the answer *to*, when a container is drawn in two of
its own boxes in one frame -- which is what the two open fuzzer seeds
turn on and what step 6 needs before the deferral can go. And what `Pad`