A rule holds what a widget answers and a widget holds the box (Bryan,
2026-09-20). `layout/bounds` is green on all three scans; the hole left is a
retained answer re-placed under a different rel base, which predates bounds.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`0d03267` in iris unifies the root's layout path with every other widget's:
`Placing::WINDOW` is the box nobody drew and `Placing::ask` the one place a
box is decided. The leftover-as-a-minimum gap the log recorded for
`root_layout` is closed by it rather than left.
`SizeRule::{Min, Max, Clamp}` is on `layout/bounds` and not in #19. Every
hand-written test passes and the 400-seed scan does not, for a reason that is
a design question: 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. The log records the four readings measured and what
each one's seeds were, so none of them is tried twice.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two rounds past the ninth sweep: `b295c8b` reads a share under a parent that
divides nothing as a minimum, which is what Bryan said it means and what a span
already did, sharing one comparison with it; `2dba90b` grows images in the
generated trees, which is the only widget here whose hint is a length in pixels
and so the only one that reaches the case.
The dump baseline is 34,571 boxes now that the trees carry images, and a seed no
longer names the tree it used to -- the log says what that costs and where those
defects live instead.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Bryan's rule, generalising the max he gave for Scroll's content length on
2026-09-18: a leftover under a parent that does not divide is still a leftover
and acts as a minimum, so the length is max(box, px + rel*box) -- it fills the
rest where the fixed parts are shorter and overflows where they are longer.
Measured against a span, which implements it, and against the non-dividing path,
which drops the overflow in the one row where the fixed part is longer than the
box. Recorded with the cause and the window contract a fix needs; not fixed,
because what a length means is Bryan's to settle and nothing here mixes the two
outside the span's own test.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Eight findings in iris `c2b8bf8`, the largest a widget's own size hint
overriding a length rule that hint cannot express -- which no widget here but
`Image` can reach, so the dump is unchanged and the case builds its own widget.
The lesson for the next round is in the handoff beside the seventh and eighth
sweeps' own: where a round fixed a class of defect one instance at a time, the
check belongs in the shared helper, which closes the class and tests the
instances that round said were already fine.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The sweep over the 6,300 lines of tests #19 adds, and over the seventh
sweep's own commit. Seven findings, in iris `77ed7a2`.
Also corrects a number four earlier rounds carried: the cold dump over 400
depth-5 trees is 34,488 boxes, not 34,492. The run prints eight lines that
are not a box, so `wc -l` gives 34,496 and a partial filter gives whatever it
gives. Nothing any round concluded moves, because each compared two dumps
rather than their line counts -- but the handoff now says how to count, so
the next round does not have to guess which figure was right.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Six findings in iris f8aa0c5, and the seven things that tripped a rule and
were left. Three of those are named as work waiting for the pre-review-gate
pass, since they pre-date #19 and are outside its diff.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The local fix left `origin/main` at the app's line, so a fresh clone of the
fork -- which has no `upstream` remote -- still resolved `main` to the wrong
base. Force-pushed to `ca2b4b2` with `origin/HEAD` following it; the app's
45 commits stay reachable through `app-pin`, which was pushed first.
Verified by cloning the fork: `git merge-base main <branch>` gives ca2b4b2
with only `origin` configured, the parley migration no longer appears in the
diff, and the pinned `32f6ad8` is still present.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The `iris` submodule's `main` was the fork's own line -- the app's 45
commits -- and not an ancestor of upstream's main, so it sat four merged
pull requests behind PR #19's actual base. Every obvious command was
therefore wrong in a way that looked plausible: `git diff main...` showed
#10, #12, #16 and #17 as the review branch's work, which is how the parley
text migration's undo path kept being reported as #19's.
The app's line is now `app-pin`, pushed to origin, and `main` tracks
`upstream/main`. `.gitmodules` follows `app-pin`, so `submodule update
--remote` cannot drag the pin onto upstream. The recorded commit is
unchanged at 32f6ad8; only the name it is reachable by has changed.
`origin/main` on the fork is left alone: moving it is a non-fast-forward on
a shared branch.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
PR #19's base is upstream/main at ca2b4b2, not the local `main`, which
tracks the fork's divergent line and sits four merged pull requests behind
it. Diffing against `main` shows #10, #12, #16 and #17 as this branch's
work; that is how the parley text migration's undo path kept surfacing in
sweeps. Git cannot record a pull request's base and `main` cannot be
renamed, so the handoff carries the note and the checkout carries
`git iris-base`/`git iris-diff`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`pre-submit-review` arrived after the Rust port and most of Iris were
written, so all of that went in unreviewed and no sweep has covered it. The
surface-texture defect is the argument for a pass of its own.
Nothing in Iris is kept for the app's sake either: the app is to be rewritten
against the new API rather than ported call by call.
The handoff said "three rounds" over four bullets and carried the detail of
each, which belongs in the findings log. It now names the four and their
commit ranges and points at `LAYOUT_LOG.md`, which gains the naming and
sweep round in full.
The check section split in two: the cheap gate runs on every change
including a rename, because the cold dump is the only thing that catches two
same-typed values being swapped; the seed scans run only when the change can
alter what layout computes, and never with the tree still moving under them.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Iris PR #19's tip is 2807a92. LAYOUT.md's settled-design section named the
retired `PlaceDescAxis::within`/`shifted`/`sized` constructors and the
`axis`/`axis_mut` accessors; it now says the `_desc` chaining rule, the
`axis` lift, and that every pair is a struct of two per-axis values read
with `[axis]`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The handoff had grown a chronicle of what each commit renamed. What is
durable about it -- the three names, and the one argument a container uses
to say them -- is design, so it goes to `docs/LAYOUT.md` as its own section
and the handoff keeps a pointer plus where the branch stands.
`docs/PLAN.md`'s "frame/extent experiment" entry described a prototype in
retired words; it becomes a pointer, and the one finding it held that was
not recorded elsewhere -- why the extent-relative child-frame trial was
removed -- joins the failed hypotheses in `docs/LAYOUT.md`.
`frame` is gone from the layout vocabulary in `docs/LAYOUT.md` too, since
leaving the design document in the old words defeats the rename; the
rendered-frame sense of the word is untouched. The `local == UiRegion::FULL`
item is dropped from the handoff: that comment was #18's and is not on the
review branch.
The `extent` rename landed as iris `5642f20`; the handoff had not listed it
as pending at all, and its item 2 named renames that are already upstream.
Say which vocabulary the app has still to absorb, drop the retired-clone
and archive chronicle, and strike the three clarity findings now done.
Two live defects at cadfba0, each with the repro that showed it and the
fix that answered it: a span places the slot after a collapsed leftover
child a gap early and a gap too long, and a child reused under a parent
that redrew keeps the mask index that parent replaced, so its drawing is
clipped where the subtree used to be. Neither is covered by a test, which
is why the suite is green over both.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`docs/LAYOUT_LOG.md` existed until the one-ask protocol landed. It has, as
Iris PR #19 replacing #18, so what has to outlive the log moves into
`docs/LAYOUT.md`: the hint rule and the one ask a span makes, the reported
shares that are left and the two-answers design that would remove them, what
the fuzzers reach and what they cannot, the hypotheses not to repeat, and the
counter and dump commands with the numbers they print now. The handoff becomes
what it is for -- where the work stands, which is waiting on a review.
Measured at a888717 and at the new f6242aa on wip/hint-first, with the
inset experiment recorded as tested and rejected. The plan is now review,
render and land.
Re-measured the work counters at a888717 (unchanged from adbedaf), and
tested the handoff's claim about a positive span pinning `far`: without the
pin, seed 1's resize goes from 44 draws of 13 widgets to 20 of 9. The pin is
worth removing, and what stops a sound removal is that no `Part` can say
"from the cursor to the end of the box" without naming the box's length.
The pre-submit review of the six one-ask commits found a scroll placing
content that fits into a window-length box rather than its viewport, and
three comments still calling window lengths frame lengths.
Every check in the table was run over the branch head with nothing else
running and no edit in flight: fmt, both clippy feature sets, the suite,
and all three fuzzers over sixteen cases.
The record's boxes are part, asked and placed, LayoutHolds carries window
ranges beside its two pins, and what is left of the layout plan is the
retained cost and the review of the branch as one diff.
Step 3 is done: against #18 the render set is byte-identical but for two
pixels of antialiasing in random and one padded block in text moving a
pixel, and a live resize of random matches a cold render at that size
exactly. The cost table is re-measured at adbedaf, where the fuzzer's own
Branch was found pinning the window and hiding what the change costs.
Step 2 of the layout plan is done in the experiment checkout, at 1512d84
and 23523ee. The handoff carries the commits and what is left; LAYOUT.md
carries the settled rule, which is now one coordinate unit with the frame
a length of it and both pins beside each other; the log carries what
implementing the plan corrected in it, including the four that would have
shipped as wrong layout and the two rig gaps that hid them.
The handoff now describes wip/one-ask at 3091fb8: a widget draws once in
the box it is asked in, its answer is placed by re-expressing the drawing,
and nothing is drawn again in a box an answer chose. The log records what
the step 3 plan got wrong -- it kept the second draw whose measurement
bit could not be defined, demanded a contract of the answer box, and
narrowed frames by region -- and what fuzzing the new protocol found: the
Part::Of composition dropping a pinned length, and a local redraw that
must be put back in the parent's answer box. The Pad frame rule is left
as a decision for Bryan with its cost stated.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>