Record the twelfth sweep, over the request arena

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
iris-aiandClaude Opus 5 committed 2026-09-20 21:35:36 -04:00
1 parent 338dd00f76
commit b788cd1382
2 files changed
+104 -1

No files matched your search

+79
View File
@@ -6,6 +6,85 @@ nothing here is rediscovered. Each entry says who found it and when.
it (settled design, the measurement method) belongs in `docs/LAYOUT.md`, and
the current plan is in `docs/HANDOFF.md`.
## Twelfth sweep: the request arena (2026-09-20)
Over `05e6ced`, which no earlier round reviewed -- one node type for a
request, an arena per owner, and `Widgets::edit_bound`. 316 inserted lines
over four files. Four findings, all in `f48e04e`.
- **The one path that copies a request into another was run by nothing.**
`SizeRequest::join` grafts the other side's nodes into this side's arena,
which happens only where both sides of a comparison are expressions --
`a.min(b).max(c.min(d))`, or a `clamp` whose ends are expressions.
`random.rs` builds only lengths and bounds, and every expression in the
three deferred corpora and in `cases/deferred.rs` compares an expression
against a plain length, so nothing reached it: measured, all 206 tests pass
with a `panic!` in that arm. It is where a missed renumbering would be
silent, because an operand copied without remapping still names a node that
exists -- just the wrong one. There is now a fixture at two window widths
with absolute geometry (`min(1 leftover, 40)` against
`max(min(2 leftover, 70), 10)`, which is 60 of 90 and 70 of 300), and a
fourth arm in `deferred_requests_agree_warm_and_cold` that puts an
expression on both sides across the whole corpus. Both were checked to
reach the arm by instrumenting it again. The path is correct as written:
this is a guard, not a fix.
- **The derived `Debug` the commit rejected was still what every `{:?}`
printed.** `SizeRequest` grew a `Display` because "a derived `Debug` of an
arena is not something a tree can be rebuilt from", and `describe` moved
onto it -- but `Debug` stayed derived, so the four `assert_eq!`s in
`cases/deferred.rs`, the only place a request is compared, print the arena
on failure, which is the case a reader has. Measured, `leftover(1).min(40)`
as derived `Debug` is `Expr(Expr { nodes: Nodes([Node { op: Min, a:
Linear(LayoutLen { px: 0, rel: 0, leftover: 1 }), b: Linear(LayoutLen { px:
40, rel: 0, leftover: 0 }), leftover: true }]), root: 0 })` -- 179
characters for what `Display` writes as `min(1 leftover;, 40 px;)`. That is
the class the eleventh sweep found one commit earlier and fixed at the call
site in hand. `Debug` forwards to `Display`, so the reason given for one
now governs both.
- **A method that asked nothing of its receiver.** `Nodes::linear(&self, at:
Operand)` never touched the arena, and `Nodes::leftover` beside it does, so
the pair reads as though both answers depend on it. It is `Operand`'s
question, the way `RequestedLen::linear` is `RequestedLen`'s; `combine`
opens `if let (Some(x), Some(y)) = (a.linear(), b.linear())`.
- **A closure parameter shadowing what it was called on.** `describe`'s
`|r| format!("{r}")` sits inside `let rule = |r: &SizeRule|`, so `r` means
two things four lines apart. It is `ToString::to_string`.
Four things the sweep **looked at and left**:
- **`RequestedLen`'s `leftover` repeats `Node`'s.** The same bit is stored on
the node and copied onto every handle made from it. Left because the handle
is the only form that leaves the arena and `Span` asks it where it has no
arena to ask -- `lens.iter().any(|len| len.has_leftover())` in
`position/span.rs` is widget code holding a slice of handles. Removing the
copy would push arena access into every widget that asks.
- **Nodes an arena can never reach.** `graft` copies a subtree and then
`combine` may fold the result away, which would leave the copy unreachable.
Worked through and it cannot happen: a fold discards an operand only where
both are `Operand::Linear` or the two are equal, and a grafted operand is
either a length or a fresh number that no existing node has, so it never
equals the side it is being combined with. Recorded so the next reader does
not derive it again.
- **`RequestArena::reset` reaches into `self.nodes.0`**, the one raw field
access outside `impl Nodes`. Left: a `clear` method is three lines to save
a `.0`, and `reset` must clear rather than replace, because the pass's
arena keeps its capacity across frames -- which is what the allocation rig
checks.
- **Three "compare, write, mark redraw" bodies in `Widgets`.** `set_len` and
`edit_bound` join `set_size_rule`, `set_alignment` and `set_region_node` in
writing that shape out. Unifying the first two needs a before-and-after
comparison of the whole rule, which is the clone this commit removed; the
duplication is the price of not having it.
Verified at `f48e04e`: format, workspace clippy under `-D warnings` with and
without `layout-diagnostics`, 207 ordinary and 211 diagnostic tests (206 and
210 before, plus the new fixture), the cold dump byte-identical to `05e6ced`
across all **34,986** boxes, and 400 depth-5 trees in each of the three
deferred corpora in 200.95s. The corpora were run because one of them
changed; the three seed scans were not, because nothing here can move a box
-- a `Debug` impl, a method moved between two types, and tests. `bounds_cost`
still prints `rule_bytes=40`.
## One node type for a request, and no refcount (2026-09-20)
`05e6ced`. `SizeRequest` was a second expression shape beside the one the