Commit Graph
93 Commits
Author SHA1 Message Date
iris-ai 264dc4f0c2 Record frame-extent performance gains and alignment verification 2026-09-17 15:54:23 -04:00
iris-ai 8f1a5e0a46 Record exact layout equality and measured reuse improvements 2026-09-17 15:11:23 -04:00
iris-ai 0080bcbcba Document tested frame and extent prototype and remaining costs 2026-09-17 14:50:22 -04:00
iris-ai 3084491b9b Record the resize walk and the re-parenting defects, and what verified them
The first item on the plan landed as two commits on #18. The re-parenting
half turned out to be two defects rather than the predicted one -- the old
parent's child list erasing a subtree it no longer draws, and the settling
walk ordering by a stale depth -- and the `depth()` assertion the note
expected to catch it catches only the second.

`dirty_size_under` is deleted, so the section that argued for it is marked
superseded rather than removed: it says what the bottom-up walk is buying.
2026-09-17 13:12:55 -04:00
iris-aiandClaude Fable 5.1 8c16f9d0fd Record the frame-and-extent finding and the resize walk, and drop the measure/draw split
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-17 12:34:41 -04:00
iris-ai 0cdda1713f Record the span boundary landing and what the guard experiment showed 2026-09-17 05:13:30 -04:00
iris-ai be895375ec Record the bottom-up settle, and park two real defects on branches
Bryan's ordering replaces the stale-answer check with something the shape
of the frame makes impossible: nothing shallower is drawn while anything
deeper is dirty, so a parent never reads an answer that is about to move.

The other two are written up rather than landed. A stack takes its sizing
child's fraction twice, which no oracle can see because warm and cold are
wrong alike; the fix is three grid steps short of the tolerance. And
padding is to go outside what it pads with an `Inset` beside it, which one
unaccounted halving is holding up. Both are pushed as `wip/` branches.
2026-09-17 04:50:50 -04:00
iris-ai faa4e98311 Record the text-width defect, and that the depth-4 seeds are fixed
The two seeds a wider scan found were one defect, older than the branch: a
text reported a width rounded to the step below the line it measured, so
the box that came back to it broke differently. Two tolerances were holding
that together and both are gone.
2026-09-17 03:21:35 -04:00
iris-ai f64d6a8d4d Record the report rule, the stale-answer guard, and two open seeds
The `rel` reading Bryan decided landed, and with it a defect the retained
machinery had all along: a span that can overflow itself makes the two asks'
boxes differ far enough that a reused answer is read before the subtree
under it has settled.

Widening the fuzzers to 2000 seeds at depth 4 then found two warm/cold
divergences that predate both commits, so the branch is not correct and the
routine verification does not say otherwise. They lead "Next".
2026-09-17 02:58:25 -04:00
iris-aiandClaude Fable 5.1 4763e1a70d Record the snap as approved and CPU rounding as recommended
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-17 01:49:20 -04:00
iris-aiandClaude Fable 5.1 c0b0921973 Record the 2026-09-17 layout review in the handoff and prune its history
The review's verdict, Bryan's decisions on rel and on the measure/draw
split, the four findings with their measurements, the residual and the
shader snap, and the reordered queue. The chronicle of closed defects,
superseded verification lists and the cross-fixture tables the document
said not to compare are gone; the rules, lessons and current measurements
stay.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-17 01:43:17 -04:00
iris-aiandClaude Opus 5 4e75019b9a Record the pixel path closing, and the mask a redraw inherits
A box in pixels is threaded down the draw now rather than composed back up
the move chain, which closes the offer chain breaking at a region node,
makes `Holds::through` an exact preimage, and retires `redraw`'s third ask.
The section that diagnosed the defect becomes the one that states the rule,
with what the plan got wrong recorded beside it: `px` cannot be stored on
`ActiveData`, because a resize that every `Holds` admits redraws nothing and
would leave it stale everywhere.

`AGREE_STEPS` stays 2, measured rather than assumed: one step passes the
100-seed oracle and fails the 400-seed shrinker on `resize-size` by
0.002 px, so the second step belongs to the resize path re-expressing a part
as a fraction of a box that changed length.

The review of that change found an older defect of its own -- a `Masked`
widget settled on its own panicked, since `redraw` handed it its own mask as
the one it inherited -- and the invariant that closes it is here too.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 00:21:29 -04:00
iris-aiandClaude Fable 5.1 9a26862bce Record the frame rule, the decided axes, and the root entry's removal
Iris 5b78002 on #18: a child's answer comes back in the asker's frame, the
near-edge override is per-axis decided flags, and the window is no longer
a move entry. Also records the threading plan that closes the region-node
offer defect, and why widening the grid would not.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 23:02:39 -04:00
iris-aiandClaude Opus 5 82646e8408 Record the narrow step left in the pixel path
It was in 5f16617's message and not here, which is the wrong place for it:
the handoff is what a session picking this up cold reads.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 22:09:34 -04:00
iris-aiandClaude Opus 5 9f825ed6fb Diagnose seed 220: the offer chain breaks at a region node
`5f16617` carries the composed box down the draw instead of walking back up
for it, which made exact composition a 3.6%/3.3% speedup rather than a cost.
The numbers are in "Performance" and the i64 item.

And seed 220 is diagnosed rather than left as a seed. `offered_region`
composes a widget's offer through its ancestors' offers except where the
parent is a region node, where it falls back to the node's **placed** box.
`Scroll` is where placed and offered differ -- it offers its content the
viewport and places it in a box as long as the content -- so the offer
becomes a function of the answer being re-derived and the old answer
confirms itself. It is the fourth defect this session to come down to the
offer against the placed box.

The write-up has the trace both ways, the fact that each child order settles
to its own fixed point so the tree is not unsettled, that marking everything
dirty converges warm onto cold, and the coordinate-frame problem that makes
the two plausible fixes awkward -- `offer` is in the parent widget's frame
and `region` in the parent node's, so neither answers "was this placed where
it was offered" alone.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 21:50:22 -04:00
iris-aiandClaude Opus 5 da2f93254e Record the exact composition, and the seed it stops one step short of
`45a7176` composes a box down its move chain on a grid fine enough to round
once instead of once a level. `Holds::through`'s two-route allowance is two
half steps where it was three, and it cost nothing: 1,880M instructions and
755M cycles against 1,908M and 760M, with all twenty-five work counters
identical. What paid for the widening is that a length composes on its own in
half the multiplies both ends of a box need, and that only the fraction needs
the extra room.

The i64 item in "Next" keeps the measurement that redirected it, since
aiming the work at the screen invariant would have been wasted.

One half step further is arithmetically available and not taken: shrinker
seed 220 on `reorder` then diverges warm against cold, by re-breaking a
wrapping text whose reported width moves a `Branch` onto its other subtree.
That is written up under the `Holds::through` invariant with its reduction
command and its ten-widget tree, and it is the next thing to chase.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 21:31:37 -04:00
iris-aiandClaude Opus 5 b0c13b85a9 Record that the shader, not the grid, decides screen pixels
`d21a215` answers the API question: one widget keeps one length per axis, and
`Wrapper` -- what `WidgetPtr` already was -- is the second widget the second
length needs.

The `i64` composition item is rewritten around a measurement that changes
what it is for. `prelude.wgsl` decodes the raw counts into `f32`, walks the
move chain in floats, and lands every edge on a whole pixel with
`snap_floor`. Checked in the render rather than read off the source: there is
no partially covered column anywhere along `tabs`'s band of rounded rects, so
every box edge is hard, and the two `pad(10)` gaps are exactly ten pixels on
both sides of the truncating multiply. The screen invariant is therefore
already as good as integers allow, and exact composition cannot improve it.

What it can still buy is layout's own decisions -- the `Span` leftover
boundary, the queued clamp crossover, `Holds` validity, warm-against-cold
agreement -- with `Holds::through`'s two-route allowance as the test of
whether it worked.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 20:50:50 -04:00
iris-aiandClaude Opus 5 82c006cd59 Record four fixes that came out of one look at the tabs render
Iris #18 is at `e166e00`, and warm layout now agrees with cold on every rig
the repository has: all fifteen shrinker cases at 400 seeds of depth 5, and
the oracle at 1000 seeds of depth 6. Both pass for the first time. The
handoff's two standing leads, shrinker seed 288 and oracle seed 326, were one
defect and are gone.

What found them was asking why the `tabs` render differed, rather than
counting the pixels that differed:

- `d8ae9c3`: `redraw`'s second ask handed the final box over as if it were an
  offer, so a locally redrawn widget with its own alignment was placed twice.
- `2bc6bdf`: `Stack` and `Pad` overrode every child's alignment with the near
  edge, which is owed only where the box really is the child's own answer.
  `tabs`'s counters and `text`'s narrow panel match canonical `main` again.
- `38eba54`: `Holds::through`'s allowance was four half steps where three is
  the floor, and tightening it moved none of the rig's work counters. Why it
  cannot go to zero is recorded with it.
- `e166e00`: pins what the gap question was actually about -- a length in
  pixels is that many pixels at any nesting depth, and a share is one or two
  steps off, which is a trade for the row filling exactly.

Two corrections to this document. The bisect it reported for the
partial-repaint defect named a commit that cannot have caused it, and the
reasoning built on that was wrong. And its claim that `tabs` "differs only in
the widget count it prints about itself" was checked at `8220a78` and left to
cover `d3b0ebf`, which changed the render twice more.

One API question is open: `.width()` now overwrites what `.sized()` set,
since both are properties of one widget, so `tabs`'s centred square is a bar.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 18:18:26 -04:00
iris-aiandClaude Opus 5 a271795408 Record the partial-repaint fix, the truncating multiply, and what is left
Three commits on iris #18, now at `08c9d5a`.

`aea878d` fixes the ignored `redrawing_one_widget_does_not_move_what_scrolls_
around_it`, and the bisect this document reported for it was a red herring:
`95fb4f9` cannot be the cause, since the failing tree has no `Masked` in it.
The cause was `redraw` skipping a dirty widget's second ask whenever the
offer and the final box were the same *length*. It also closed three of the
four unreduced shrinker leads and two of the three depth-6 oracle seeds; the
two that are left are written up with their reductions.

`60367d8` stops `Holds::through` allowing for a rounding that did not happen,
which compounded down a chain of widgets each taking the whole of its parent.

`08c9d5a` makes `Fixed::mul` drop to the step below (Bryan: truncation is
preferable at this point), with the two short-circuits priced against the old
multiply. Measured against the float head in `iris-float-cmp`, which is the
comparison that was asked for: 1,800M instructions and 715M cycles against
1,761M and 688M, from 1,915M and 777M -- three quarters of the instruction
gap and two thirds of the cycle gap closed. The float pair's work counters
differ and the fixed-point pair's twenty-five are identical, so only the
second number is same-work-at-a-different-speed; both are in the table.

Also recorded: `cycles:u` returns garbage readings in this VM the way
`instructions:u` does, and a whole set of three can be garbage at once;
`tabs` is no longer a byte-identical render and why; and Bryan's idea of
composing in `i64` and narrowing only when storing, with what is already
protected, what is not, and the test that would say it worked.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 17:09:45 -04:00
iris-aiandClaude Opus 5 06f9ae7799 Measure what truncating costs instead of asserting it
This section said truncating "breaks the one rounding the grid is for" and
left it at that, which is the same fault it withdraws the fused multiply-add
for: a claim nobody ran.

Run, it fails one exactness test by a step -- a share reaching 199.99902 of a
200px row -- and does not fail the warm-against-cold oracle any harder than
the head, which gives the same three failures on the same seeds within
0.002px. So the 57M cycles are not obviously bought with layout drift, and
whether a share stopping a thousandth of a pixel short is worth them is a
decision rather than a defect.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 16:00:14 -04:00
iris-aiandClaude Opus 5 bb5b79f2f6 Record the drift a partial repaint causes, and the rigs that reduced it
The oracle and the shrinker had separate generators and separate copies of
every scenario, so a failure one found could not be handed to the other: a
seed cannot be made smaller. Iris `98d4e98` splits growing a tree into
describing one and building it, and puts the cases in one place both rigs run.
Any failing seed now reduces with one command, which is written down here.

What it found first: marking one widget for redraw moves a span inside a
scroll 24px against a cold tree of the same state -- a whole child's height,
not a rounding. Five widgets, bisected to `95fb4f9`, where `Masked` began
reporting its box rather than its inner's size. Written out and ignored in
`tests/cases/unsettled.rs` until it is fixed.

Two claims in the verification list were withdrawn against a re-run rather
than an argument. The shrinker's "all five cases at 300 seeds of depth 5" does
not carry to a rig that runs fifteen cases over different trees and fails at
400. The 1000-seed depth-6 line does not hold at `5ed9e87` either, which fails
it on seed 40 after a resize -- so that run was never the clean baseline it
was quoted as.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 15:59:33 -04:00
iris-aiandClaude Opus 5 10e46522b4 Withdraw the fused multiply-add, and measure what the grid costs
The section claimed what was left of fixed point's cost was "an integer
multiply with a rounding against a float fused multiply-add". Both halves are
wrong and the evidence is in the binaries.

`5ed9e87` contains no FMA instruction, and rebuilding it with
`-C target-feature=+fma,+avx2` still produces none: Rust does not contract
`a + b * c`, so `LerpUtil::lerp` is `subss`, `mulss`, `addss` on any target.
The float side never had the instruction the grid was being compared against.

Nor is the multiply the cost. Replacing `shift_round` in `Fixed::mul` with a
truncating shift -- all twenty-seven work counters diffed identical -- takes
43M instructions off and leaves cycles inside the noise. What moves cycles is
whether `UiSpan::within` inlines: the rounding and both short-circuits removed
together are worth 118M instructions and 57M cycles where separately they are
worth 61M and 8M, and `<UiSpan>::within` stops being a symbol. None of that is
shippable, but it is where to push.

Three other entries corrected against measurement rather than argument. The
division is not 0.00% of cycles -- that was `apply_scalar`; `Holds::through`
runs 2.23M `i64` divisions a 500-frame `many` and holds the divider busy 21.3M
cycles. `d75a1e2` and `4cbb242`'s short-circuits no longer earn anything now
that `Fixed` wraps. Branchless `shift_round` still loses, for the opposite
reason to the one recorded: it is the rounding's size, not its branch.

And the measurement posture: cycles spread 1-3% across nine runs of one
binary rather than 0.23%, `instructions:u` returns a garbage value in roughly
a quarter of readings, and an instruction count moves 0.5% across a rebuild of
identical source. Quote deltas against a baseline built beside the thing
measured.

Checked: `/home/bob/repos/iris-float-cmp` carries `5ed9e87` with
`Edits::fixed_branches` applied, and is recorded under "Rigs and reproduction"
so the comparison can be re-run.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 15:07:02 -04:00
iris-aiandClaude Fable 5.1 663a95f4a2 Record what wrapping saved, and the rounding question left open
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 14:23:22 -04:00
iris-aiandClaude Opus 5 9ed90505b3 Withdraw the 3x, and record what a move actually costs
The performance section reported a cost the fixture had invented. `Branch`
picks which subtree to draw from a measured length, so seed 1 draws 88
widgets at `5ed9e87` and 115 at `bd6de71` -- the ms table was comparing
three and a half times the work. Measured on one tree, fixed point costs
about a fifth to a quarter.

Also corrects where the cost is. Cycles say the `i64` division is 0.00% of
`apply_scalar` and the multiply 1.5%; it is `saturating_add`, five
instructions with no `i32` vector form. Cycle counts are steady here to
0.23% -- it is wall time that varies 2x, not the counters -- and IPC is
unchanged across fixed point, so this is instruction count and not stalls.

Records the four wins taken (-7.4% cycles, -12% instructions), the four
things tried that made it worse, and the one lever left that needs a
decision rather than a measurement.

Also: the `Len`/`LayoutLen` split and `has_exact_size`; that `Scroll`
reports `LEFTOVER` rather than its content's size, which this said wrongly
in one place and rightly in another; and that the clipping `debug_assert`
holds mask-setters only, which is why a `LazySpan` does not trip it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 14:08:00 -04:00
iris-aiandClaude Opus 5 388a6a060d Bring the handoff to what the last session shipped
`OrthoSize` was deleted in `9d8415d` and the handoff still described it as
restored; the surviving rule is `Painter::ruled`, and the circularity lesson
is restated without the enum's names. `HOLDS_EPSILON_PX` went with
`39e4ca2`, the clipping `debug_assert` the alignment section asked for landed
as `95fb4f9` and is narrower than it asked, the glyph conversion queued under
"Next" landed as `11c55bc`, and `LAYOUT.md`'s stale sections are §4, §5 and
density rather than §2.

Also records why the grid costs 1.5x what floats did, from the disassembly
rather than from theory: `RegionRemap::apply_span` is 352 instructions with
two 64-bit `idiv`s in it, because a fixed-point rounding is code where a
float's is hardware, integer division has no vector form, and saturation
breaks the pairing the `f32` `Vec2` had.

And the finding that answers the queued clamp item's open half: a `Max` may
not take a `leftover`, because a cap must read the report a rule otherwise
makes moot, and a share puts the division into the same equation -- the
multiple-fixed-point failure seed 13 already punished once.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 13:21:37 -04:00
iris-aiandClaude Opus 5 f83be016ba Update the layout measurements to the current head
Three more roundings and divisions taken out after measuring each, one
optimisation tried and reverted with its number kept so it is not tried
again.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 04:14:18 -04:00
iris-aiandClaude Opus 5 6081726314 Record what fixed point cost, and what it was
Three times slower in layout, and two thirds of that was a stacked child
redrawing twice a frame rather than the arithmetic. The counters are what
found it, once they could say "another layer" at all.

The performance table is rewritten around the fixed-point commits and in
milliseconds a frame, since the fixture it used has changed and the old
instruction counts cannot be compared across it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 04:07:27 -04:00
iris-aiandClaude Opus 5 617331f913 Record what the last of the fixed-point imprecision is
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>
2026-09-16 03:42:56 -04:00
iris-aiandClaude Opus 5 ddf746d8a4 Record fixed-point layout, and what it left open
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>
2026-09-16 03:02:46 -04:00
iris-aiandClaude Opus 5 1a73aba1cd Record the leftover boundary, and close the reorder case
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>
2026-09-15 23:49:28 -04:00
iris-ai 76334cd5aa Update alignment handoff 2026-09-15 23:13:40 -04:00
iris-ai 085fc97334 Record why placement cannot be applied after a draw
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.
2026-09-15 21:26:04 -04:00
iris-ai cf7b33da39 Record why a move recomposes, and settle alignment's ownership
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.
2026-09-15 19:27:30 -04:00
iris-ai 8db0969d9d Update region-node handoff 2026-09-15 18:03:37 -04:00
iris-ai 7b8b43a7d2 Record explicit span orthogonal sizing 2026-09-15 16:41:03 -04:00
iris-ai 3a5d96a8ae Update Iris extraction handoff 2026-09-15 16:07:17 -04:00
iris-aiandClaude Opus 5 af751599f6 Make the handoff enough to start the leftover work from cold
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>
2026-09-15 14:32:47 -04:00
iris-aiandClaude Opus 5 df55e181d9 Record the rename, and what four ways of asking about leftover did
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 14:27:08 -04:00
iris-aiandClaude Opus 5 a128648bd2 Record the declared-length fix, and park the rest-with-no-leftover rule
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 13:52:04 -04:00
iris-aiandClaude Opus 5 324f0f02ef Sharpen the declared-length defect, and what a negative share does
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>
2026-09-15 12:44:55 -04:00
iris-aiandClaude Opus 5 e6b9cb29b7 Record the doubled declared fraction, and what a squeezed span does
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>
2026-09-15 12:32:12 -04:00
iris-aiandClaude Opus 5 89d20c31dc Re-measure #18 against the code it replaces, and retire the tabs divergence
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>
2026-09-15 02:56:11 -04:00
iris-aiandClaude Opus 5 155b7650a9 Name the handoff doc in AGENTS.md and generalise it to HANDOFF.md
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>
2026-09-15 02:33:53 -04:00