Traced rather than guessed: the same box reached two ways, each rounding
where the other does not. Two of the three are fixed, the third is priced --
it costs the retained resize path, at a thousandth of a pixel.
`OrthoSize` is deleted rather than paused; the decision it was waiting on was
that a span asks whether a rule already answers for it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Four commits on #18 put layout on a grid: `Fixed<SHIFT>`, then positions,
lengths, and the last of the pixels with `Holds`. The invariants that were
about float rounding are rewritten rather than annotated -- the 0.05 px
comparison is equality now, a move is a translation rather than a
re-expression, and the span boundary needs no margin.
What is left open is written down where the next session will look for it:
the remaining step of imprecision is one rounding between two ways of asking
how long a box is, and `OrthoSize` is paused on a decision rather than
started.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The shrinker's `reorder` case is no longer red: its two seeds were a span
deciding whether anything was left over on a box length its own answer had
produced, which comes back a few bits off. `5ed9e87` in `/home/bob/repos/
iris-pr18` moves that boundary by `HOLDS_EPSILON_PX` of room instead of
softening the comparison, so the validity range stays exact where generated
seed 16 needs it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three attempts at "draw the widget, then move its drawing to where its
alignment says" failed, and the notes are worth more than the attempts: the
move is a change of coordinate frame, and no split of the stored state
carries it. Moving a widget's region with its drawing has a later local
redraw ask a differently rounded question, which crosses the one boundary
`Span` needs exact. Leaving the region alone lets the placed box accumulate,
because `try_reuse` returns a clean subtree's size without walking into it.
Both are measured.
Also recorded: that a container reporting a child's size while handing it a
bigger box places that content twice, which is what the align override is
for; that a widget must not report more than it occupies, which is why
`Scroll` now fills both axes; and Bryan's decision that alignment is one
fraction per axis rather than four directions, with the middle as the
default because the edges assume a direction.
The alignment work is in the working tree at `8220a78` and not committed.
Its one red case, and the four things queued behind it, are listed.
The measurement is the part worth keeping: adding an offset to a moved
subtree's stored regions drifts linearly and without bound, so the fraction
path is there for accuracy, not generality. `tests/drift.rs` on the Iris
branch pins it.
Built-in alignment and size moves onto #18 rather than waiting for it, with
ownership agreed -- properties on the `region_node` mechanism, a declared
length winning per axis, and a size rule that carries min/max/clamp so a
clamp is resolved where the box is decided instead of by a widget reading
its own pixels.
Rewrites the opening as state rather than a chronicle of the last few
commits, and gives the parked rule its own section: what the owner asked
for in her words, the half that is written and works with the code in
it, the four ways of asking that were measured and what each did, the
seed that shows it most clearly, and the design question underneath --
which is the same one holding OrthoSize. Also folds the declared-length
rule into the invariants, brings the vocabulary to leftover, says the
suite is eight seconds and must stay that way, and writes out the image
replay rather than pointing at a file in /tmp.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Only rel doubles: apply_rest turns rest into the whole box and px
coincides. Aligned and Scroll honour a hint the same way Span does, so
all three double it. Also corrects the note about a fractional hairline,
which fails because a fraction is a fraction of its own box rather than
through any rounding, and records that a span whose shares run out walks
its children backwards instead of overflowing forward.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The text reference render is not byte-identical after all: a declared
rel or rest length is applied by the span and then again inside SetSize,
so .width(rel(0.5)) draws its child at a quarter. Also replaces the
example that measured one-pixel lines with what the tests now say, since
a span short of room overlaps its fixed lengths rather than collapsing
them.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The tabs difference does not reproduce at f61e893 against upstream/main
built in its own worktree, so nothing is waiting on a decision. Records
the instruction counts, frame times and memory taken today rather than
mid-branch, and what a one-pixel line and a one-pixel gap do under the
chain, which is the question the pixel difference was really about.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A fresh session had no way to learn the handoff existed: AGENTS.md named
only docs/PLAN.md, and the filename tied the document to one piece of work.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Both gestures, since two widths in turn flatters a store and a drag does
not, and the memory beside the time: the question that decided the shape
of it was whether it is a fixed cost or one per text widget.
Bring LAYOUT.md's §1 and §2 to what shipped. A widget returns its size
rather than recording it, a slot carries a box rather than a
translation, and the per-axis answer is `OnResize` rather than
`is_size_independent` -- and the constraint the position chain replaced
was an agent's, which the text now says.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The random generator grew during the work, so the seed that names a
1061-widget tree in the pre-#16 rows names a 144-widget one now. Every
"still N times the old resize" here was measured across that change and
says nothing; a hand-written fixture that compiles on both revisions
says the old code is 2.2x cheaper and puts a paragraph outside the
output, for two reasons it records as deliberate.
Record what a resize frame is actually spent on -- 63% text, 0.9%
draw_inner -- and what removing the single-entry shaping and placement
caches measures at.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The random tree at depth 7 measured against the two revisions before it: the
GPU pass is a tenth of a millisecond everywhere, and every difference is the
CPU laying out. A repaint of one leaf costs 1313 draws and 6.5 ms where the
pre-#16 code cost one draw, because `redraw` escalates to the top size reader
before knowing whether the size changed at all. Drawing first and escalating
only on a different size takes that to one draw and takes a resize below the
pre-#16 number, but misplaces four widgets in the resize case, so it is the
owner's to call.
Also records the measure-by-drawing multiplier -- 1.3x the drawn widgets at
depth 4, 5.2x at depth 8 -- and that scrolling is now part of the random
trees.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
And the open question beside it: a slot that carries a box rather than a
translation, which would make a move and a stretch of a Scale subtree
one write each. To be measured with chain_cost before it is chosen.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>