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:
1 parent
152bed7ec4
commit
4b976e240e
1 file changed
+120
-1
+120
-1
@@ -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
|
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.
|
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
|
### The plan as written, for reference
|
||||||
|
|
||||||
What follows is the plan as it was handed over on 2026-09-18, kept verbatim
|
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):
|
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
|
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
|
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`
|
turn on and what step 6 needs before the deferral can go. And what `Pad`
|
||||||
|
|||||||
Reference in new issue
Block a user