Move the settled layout vocabulary into LAYOUT.md and prune the handoff
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`.
This commit is contained in:
1 parent
265ce6f829
commit
61a2f601bb
4 files changed
+113
-98
No files matched your search
+38
-62
@@ -1,92 +1,68 @@
|
|||||||
# Handoff
|
# Handoff
|
||||||
|
|
||||||
Where the work in flight stands. The durable layout design and the
|
Where the work in flight stands. The settled layout design, the vocabulary
|
||||||
measurement method are in `docs/LAYOUT.md`; what the review of #19 found is
|
and the measurement method are in `docs/LAYOUT.md`; what the review of #19
|
||||||
in `docs/LAYOUT_LOG.md`.
|
found is in `docs/LAYOUT_LOG.md`.
|
||||||
|
|
||||||
## The Iris layout repair is submitted
|
## The Iris layout repair is submitted
|
||||||
|
|
||||||
**Iris PR #19** (`layout/one-ask`) replaces closed #18. The tip is `58ce74d`:
|
**Iris PR #19** (`layout/one-ask`) replaces closed #18. The tip is `58ce74d`,
|
||||||
`add6774` fixes collapsed-share placement, retained mask ownership and a
|
and past the reviewed `cadfba0` it is two rounds:
|
||||||
redraw-on-reparent defect, `84dad21` removes repeated work in the test
|
|
||||||
harness, and `5642f20` through `58ce74d` are the vocabulary and API work
|
|
||||||
below.
|
|
||||||
The core design remains sound; round-to-nearest is still unchanged.
|
|
||||||
|
|
||||||
Two checkouts share one Git storage. `/home/bob/repos/iris` is the active
|
- **The repair.** `add6774` fixes collapsed-share placement, retained mask
|
||||||
`layout/one-ask` worktree; `ai-app-2/iris` stays on `main` at the app's
|
ownership and a redraw-on-reparent defect; `84dad21` removes repeated work
|
||||||
`32f6ad8` pin, and is kept until the app's integration is ready.
|
in the test harness.
|
||||||
|
- **The vocabulary and the container API**, `5642f20` through `58ce74d`. What
|
||||||
|
they settled is described as design in `docs/LAYOUT.md` under "Three names,
|
||||||
|
and the one argument that says them" -- region, placement and rel_base, and
|
||||||
|
the single `impl Into<PlaceDesc>` argument that replaced `Place`, `Part`,
|
||||||
|
`widget_within` and the separate rel-base argument. **Bryan is still
|
||||||
|
refining this**, so treat the API as current rather than frozen.
|
||||||
|
|
||||||
Each round was checked with format, workspace clippy under `-D warnings`, the
|
The core design remains sound. Round-to-nearest is still unchanged.
|
||||||
workspace tests with and without `layout-diagnostics`, the release fast
|
|
||||||
oracle, and the three prescribed seed scans — 400 at depth 5, 1,000 at depth
|
|
||||||
6, 2,000 at depth 4. The cold dump covers 34,488 boxes: the fixes moved 650 of
|
|
||||||
them, all from the collapsed-share correction, and nothing since has moved
|
|
||||||
one.
|
|
||||||
|
|
||||||
## The vocabulary and the container API are settled
|
Two checkouts share one Git storage: `/home/bob/repos/iris` is the active
|
||||||
|
`layout/one-ask` worktree, and `ai-app-2/iris` stays on `main` at the app's
|
||||||
|
`32f6ad8` pin until the integration below is ready.
|
||||||
|
|
||||||
`5642f20` finished the rename decided on 2026-09-17, which `frame`'s return
|
### How each round was checked
|
||||||
as a length had left half-applied, and `aeb60e5` finished the word `frame`
|
|
||||||
itself. Three names, and only two of them are boxes:
|
|
||||||
|
|
||||||
- **region** -- the box a parent asks a widget in. It draws there, and a text
|
Format, workspace clippy under `-D warnings` with and without
|
||||||
wraps at it. Was `Painter::extent`, `ActiveData::part`, `DrawInfo::part`,
|
`layout-diagnostics`, the workspace tests both ways, and the three prescribed
|
||||||
`Placing::extent`, `LayoutHolds::extent`.
|
seed scans -- 400 at depth 5, 1,000 at depth 6, 2,000 at depth 4.
|
||||||
- **placement** -- the box its drawing ends up in, the size it reported,
|
|
||||||
once it has answered. Equal to the region wherever it fills one. Was
|
|
||||||
`ActiveData::extent`; `placed_extent` is now `placement`.
|
|
||||||
- **rel_base** -- not a box: the length a fraction it declares or reports is
|
|
||||||
a fraction of. Was `frame`, which said the wrong shape.
|
|
||||||
`Painter::frame_len()` is `Painter::rel_base()`, `LayoutHolds::frame_len`
|
|
||||||
is `rel_base`, and `frame_and_extent` is `rel_base_and_region`.
|
|
||||||
|
|
||||||
`aeb60e5` also cut what a container has to say, since correctness and cost
|
The gate that matters for anything shaped like a rename or a refactor is the
|
||||||
had been paid for and ergonomics had not:
|
**cold dump**: `layout_dump` over 400 depth-5 trees is 34,492 boxes, and the
|
||||||
|
compiler cannot catch two same-typed values being swapped. The repair moved
|
||||||
- **One argument says where a child goes.** `widget_at` and `place_at` take
|
650 of those boxes, all from the collapsed-share correction. Every commit
|
||||||
`impl Into<PlaceDesc>`, so a wrapper passes a `UiRegion` and says nothing
|
since has been byte-identical to `84dad21`, including the whole API rewrite.
|
||||||
else -- `Pad` and `Offset` are one call each. `Place` and `Part` are gone:
|
|
||||||
`Place` was a product written as a sum, and `Part`'s three cases named
|
|
||||||
operations the geometry already had (`UiSpan::within`, `UiSpan::shift`,
|
|
||||||
and `placement`'s own body).
|
|
||||||
- **`PlaceDescAxis` is named after the operation**: `within`, `shifted`,
|
|
||||||
`sized`, `WHOLE`, with `fills` and `rel_base` as builders. The rel base a
|
|
||||||
caller does not state follows the constructor -- `within` narrows it,
|
|
||||||
`shifted` passes it through, `sized` is it -- which removes the one rule a
|
|
||||||
caller could get wrong with nothing failing.
|
|
||||||
- **`PlaceDesc` is a proper pair**, with `x`/`y`, `axis`, `axis_mut` and
|
|
||||||
`from_axis` like every other pair here, so joint work is written once
|
|
||||||
rather than per axis.
|
|
||||||
- `Painter::place_at` returns the answer and draws the child where there is
|
|
||||||
no answer to re-express, so `Span`'s second pass is one call and its
|
|
||||||
`drawn_across` bookkeeping is gone.
|
|
||||||
- `widget_within` and `ActiveData::narrow_rel_base` are deleted; `asked` and
|
|
||||||
`placed` carry the rel base their ask stated.
|
|
||||||
|
|
||||||
Cold layout is byte-identical to `84dad21` across all of it, over 400
|
|
||||||
depth-5 trees.
|
|
||||||
|
|
||||||
## What is next, in order
|
## What is next, in order
|
||||||
|
|
||||||
1. **Bryan's review of #19.** Fixes are themselves unreviewed code: repeat
|
1. **Bryan's review of #19.** Fixes are themselves unreviewed code: repeat
|
||||||
`pre-submit-review` over each round's changes, and re-run the three
|
`pre-submit-review` over each round's changes, and re-run the three
|
||||||
fuzzers and dump diff for edits to `Span`, `Painter`, or `render_state`.
|
fuzzers and the dump diff for edits to `Span`, `Painter`, or
|
||||||
The ordinary oracle does not replace absolute geometry and
|
`render_state`. The ordinary oracle does not replace absolute geometry and
|
||||||
retained-primitive expectations.
|
retained-primitive expectations.
|
||||||
2. **Integrate the app's Iris capabilities before changing its pin.**
|
2. **Integrate the app's Iris capabilities before changing its pin.**
|
||||||
`32f6ad8` has 45 commits not reachable from the review branch; shared UI
|
`32f6ad8` has 45 commits not reachable from the review branch; shared UI
|
||||||
ownership, richer masks, Android support, and app-side performance work
|
ownership, richer masks, Android support, and app-side performance work
|
||||||
must survive the integration. The app then absorbs the branch's
|
must survive the integration. The app then absorbs the branch's
|
||||||
vocabulary and container API, all of it already upstream: `rest` →
|
vocabulary and container API, all of it already upstream: `rest` ->
|
||||||
`leftover`, `SizeRule` beside widgets, alignment properties, the removal
|
`leftover`, `SizeRule` beside widgets, alignment properties, the removal
|
||||||
of `OrthoSize`, the region/placement/rel_base names above, and
|
of `OrthoSize`, region/placement/rel_base, and `PlaceDesc`.
|
||||||
`widget_within`/`place_at`.
|
|
||||||
3. **Round-to-nearest**, CPU and shader together as one verified change.
|
3. **Round-to-nearest**, CPU and shader together as one verified change.
|
||||||
Bryan approved it on 2026-09-17 and neither half has landed; the
|
Bryan approved it on 2026-09-17 and neither half has landed; the
|
||||||
derivation, the form to use and what to re-check are in `docs/LAYOUT.md`
|
derivation, the form to use and what to re-check are in `docs/LAYOUT.md`
|
||||||
under "Rendering the grid (pending)".
|
under "Rendering the grid (pending)".
|
||||||
|
|
||||||
|
Wanted but not started, recorded in `iris/TODO`: transforms on a move entry,
|
||||||
|
so a whole subtree scales or rotates with one buffer write and no redraw.
|
||||||
|
Compose-style stretch at the end of a scroll area is the use that prompted
|
||||||
|
it. A move entry only translates today, and composing through one scales the
|
||||||
|
`rel` part while `px` passes through untouched, so fixed-size content and
|
||||||
|
glyphs do not follow a shortened entry.
|
||||||
|
|
||||||
## Smaller layout items, none urgent
|
## Smaller layout items, none urgent
|
||||||
|
|
||||||
- Nested spans pass `leftover` weight up, so three leftover children in one
|
- Nested spans pass `leftover` weight up, so three leftover children in one
|
||||||
|
|||||||
+60
-4
@@ -8,9 +8,9 @@ without reading that result does not make the parent's size depend on it.
|
|||||||
carries those commits whole plus the retained-layout repair built on them. §4 to §6 and the density section
|
carries those commits whole plus the retained-layout repair built on them. §4 to §6 and the density section
|
||||||
retain the rationale of the design but still name types that have since been
|
retain the rationale of the design but still name types that have since been
|
||||||
replaced; they are not an API reference. `docs/HANDOFF.md` is where the work
|
replaced; they are not an API reference. `docs/HANDOFF.md` is where the work
|
||||||
in flight stands. The sections from "Rel bases, decided boxes and padding"
|
in flight stands. The sections from "Three names, and the one argument that
|
||||||
onwards are the settled design, the findings that outlived the working log,
|
says them" onwards are the settled design, the findings that outlived the
|
||||||
and the measurement method.
|
working log, and the measurement method.
|
||||||
|
|
||||||
## Design
|
## Design
|
||||||
|
|
||||||
@@ -319,6 +319,56 @@ fragment stage cannot make. Rendering and hit-testing both traverse the full
|
|||||||
mask chain and use the same rounded-rectangle coverage; `iris/tests/mask_sdf.rs`
|
mask chain and use the same rounded-rectangle coverage; `iris/tests/mask_sdf.rs`
|
||||||
checks the WGSL implementation against the CPU SDF.
|
checks the WGSL implementation against the CPU SDF.
|
||||||
|
|
||||||
|
## Three names, and the one argument that says them
|
||||||
|
|
||||||
|
Settled with Bryan on 2026-09-17 and 2026-09-19. Layout has exactly three
|
||||||
|
things, and only two of them are boxes:
|
||||||
|
|
||||||
|
- **region** — the box a parent asks a widget in. It draws there, and a text
|
||||||
|
wraps at it. `ActiveData::region`, `Painter::region`.
|
||||||
|
- **placement** — the box its drawing ends up in: a box the size it reported,
|
||||||
|
positioned inside the region by its own alignment, and equal to the region
|
||||||
|
wherever it fills one. It is computed after `draw` returns, is not on
|
||||||
|
`Painter`, and no widget mentions it. `ActiveData::placement` is what
|
||||||
|
`window_region` resolves for hit testing.
|
||||||
|
- **rel_base** — not a box. The length a fraction the widget declares or
|
||||||
|
reports is a fraction of, per axis, as a length of the window. It is the
|
||||||
|
one value that never changes coordinate space: a region node re-expresses
|
||||||
|
the region locally and takes the rel base untouched.
|
||||||
|
|
||||||
|
`frame` and `extent` are retired and must not come back in this sense; the
|
||||||
|
word "frame" for one rendered frame is unaffected.
|
||||||
|
|
||||||
|
A container says all three in one argument. `Painter::widget_at(id, place)`
|
||||||
|
and `place_at(id, place)` take `impl Into<PlaceDesc>`, so a wrapper that only
|
||||||
|
hands over a box passes a `UiRegion` and says nothing else — which is all
|
||||||
|
`Pad`, `Offset` and `Painter::widget` do.
|
||||||
|
|
||||||
|
`PlaceDescAxis` is one axis, and its three constructors are named after the
|
||||||
|
operations the geometry already had, because two of them take the same span
|
||||||
|
and differ only in how it is applied:
|
||||||
|
|
||||||
|
- `within(span)` — `UiSpan::within`: composed into the caller's box, so it
|
||||||
|
moves and scales with it. What an inset speaks.
|
||||||
|
- `shifted(span)` — `UiSpan::shift`: window lengths from where the caller's
|
||||||
|
box starts. What a container dividing room speaks, and what makes a moved
|
||||||
|
box re-place every child by addition, exactly. Not directional: `Dir::Neg`
|
||||||
|
is handled by the span before the numbers get here.
|
||||||
|
- `sized(len)` — the body of `placement` with the length given from above
|
||||||
|
rather than reported. What a stack's sizing child decides for the rest.
|
||||||
|
|
||||||
|
What is optional is a builder, so a caller writes only what it decided:
|
||||||
|
`.fills()` says the region is the placement, and `.rel_base(len)` names the
|
||||||
|
child's rel base outright. **The rel base a caller does not name follows the
|
||||||
|
constructor** — `within` narrows it the way the box is narrowed, `shifted`
|
||||||
|
passes it through, `sized` is it. That rule is what makes the common case
|
||||||
|
right by default; stating it by hand was the one thing a container could get
|
||||||
|
wrong with nothing failing.
|
||||||
|
|
||||||
|
`PlaceDesc` is the pair, with `x` and `y` fields and the `axis`, `axis_mut`
|
||||||
|
and `from_axis` of every other pair here, so the joint work — resolving a
|
||||||
|
region, reading the fill flags — is written once rather than per axis.
|
||||||
|
|
||||||
## Rel bases, decided boxes and padding
|
## Rel bases, decided boxes and padding
|
||||||
|
|
||||||
Containers that only divide room are transparent to fractions. A child rel base
|
Containers that only divide room are transparent to fractions. A child rel base
|
||||||
@@ -381,7 +431,7 @@ composes a rel base back up a chain.
|
|||||||
|
|
||||||
A rel base decided for a child is therefore a length of the window too: a row's
|
A rel base decided for a child is therefore a length of the window too: a row's
|
||||||
slot, padding's rel base less its pixels, or the box a stack's sizing child
|
slot, padding's rel base less its pixels, or the box a stack's sizing child
|
||||||
decided (which arrives as `Part::Sized`). It is never a fraction of the
|
decided (which arrives as `PlaceDescAxis::sized`). It is never a fraction of the
|
||||||
parent's rel base, because a slot of a row is not a fraction of anything the row
|
parent's rel base, because a slot of a row is not a fraction of anything the row
|
||||||
can name -- a division of two lengths, which a `rel + px` length cannot hold.
|
can name -- a division of two lengths, which a `rel + px` length cannot hold.
|
||||||
A widget's own declaration *is* a fraction, of whichever of those reached it,
|
A widget's own declaration *is* a fraction, of whichever of those reached it,
|
||||||
@@ -587,6 +637,12 @@ must still agree exactly.
|
|||||||
- **Resolving a rule that is a fraction of the rel base from the record instead
|
- **Resolving a rule that is a fraction of the rel base from the record instead
|
||||||
of redrawing** recovers nothing: `reuse outside: a rel base` is 3 of
|
of redrawing** recovers nothing: `reuse outside: a rel base` is 3 of
|
||||||
264 cold draws at seed 1 and 1 of 758 at seed 13.
|
264 cold draws at seed 1 and 1 of 758 at seed 13.
|
||||||
|
- **A child rel base expressed as a fraction of the parent's region** (the
|
||||||
|
"extent-relative child frame" trial, 2026-09-17) was removed. The
|
||||||
|
size/resize cost it was aimed at came from conflating answer and drawing
|
||||||
|
dependencies, not from `Pad` and `Stack` reading their raw placement
|
||||||
|
conservatively. A decided rel base is a length of the window, not a
|
||||||
|
fraction of anything the parent can name.
|
||||||
- **A `git bisect`** once named a commit that could not be the cause; read
|
- **A `git bisect`** once named a commit that could not be the cause; read
|
||||||
the tree rather than the bisect when that happens.
|
the tree rather than the bisect when that happens.
|
||||||
|
|
||||||
|
|||||||
+5
-4
@@ -83,7 +83,7 @@ the tail is placed at `(215, 0)..(395, 100)`. Its slot was 210..400, a gap
|
|||||||
too long and a gap too early, and the declared 180 was then centred in it.
|
too long and a gap too early, and the declared 180 was then centred in it.
|
||||||
The row reports a total that ends at 400. A child drawn with `rel` lengths
|
The row reports a total that ends at 400. A child drawn with `rel` lengths
|
||||||
is stretched into the extra gap instead of being centred in it, because the
|
is stretched into the extra gap instead of being centred in it, because the
|
||||||
slot is a `Place::Fill`.
|
slot fills.
|
||||||
|
|
||||||
Recomputing the cursor in that branch fixes it, and the tail lands at
|
Recomputing the cursor in that branch fixes it, and the tail lands at
|
||||||
`(220, 0)..(400, 100)`:
|
`(220, 0)..(400, 100)`:
|
||||||
@@ -136,9 +136,10 @@ The descriptions above preserve the original failure at `cadfba0`.
|
|||||||
|
|
||||||
### Clarity, in the order worth doing
|
### Clarity, in the order worth doing
|
||||||
|
|
||||||
The first three -- the unswept `extent`, the two same-typed boxes on
|
Everything about naming is done: the unswept `extent`, the two same-typed
|
||||||
`ActiveData`, and `Painter`'s four holds accumulators -- were done in
|
boxes on `ActiveData` and `Painter`'s four holds accumulators in `5642f20`,
|
||||||
`5642f20`, and `Part::All` in `aeb60e5`. What is left:
|
`Part::All` in `aeb60e5`, and `Place`/`Part` themselves in `58ce74d`. The
|
||||||
|
settled vocabulary and the ask API are in `docs/LAYOUT.md`. What is left:
|
||||||
|
|
||||||
- `draw_inner` returns `(Size, LayoutHolds, LayoutHolds)`, two same-typed
|
- `draw_inner` returns `(Size, LayoutHolds, LayoutHolds)`, two same-typed
|
||||||
values ordered by convention. A named pair makes the swap unwriteable.
|
values ordered by convention. A named pair makes the swap unwriteable.
|
||||||
|
|||||||
+10
-28
@@ -864,34 +864,16 @@ such as corner radius and text's position within horizontal overflow use
|
|||||||
`Len`. Flexible padding participates in the same proportional allocation as a
|
`Len`. Flexible padding participates in the same proportional allocation as a
|
||||||
span rather than silently discarding its `rest` component.
|
span rather than silently discarding its `rest` component.
|
||||||
|
|
||||||
**The frame/extent experiment keeps measurement and geometry references separate**
|
**The one-ask layout design is settled, and lives in `docs/LAYOUT.md`**
|
||||||
(2026-09-17, prototype only; verification and costs in `HANDOFF.md`). The parent
|
(2026-09-17 to 2026-09-19). What this entry used to describe as the
|
||||||
chooses the reference for fractions; placing a widget's reported size does not
|
"frame/extent experiment" is Iris PR #19, and it has its own vocabulary now:
|
||||||
replace that reference. A measurement also records the available extent, so
|
the box a parent asks a widget in is its *region*, where the drawing ends up
|
||||||
reusing it checks both inputs. Retained primitive and mask coordinates name
|
is its *placement*, and the length a fraction resolves against is its *rel
|
||||||
whether they follow the frame or the extent. Numeric extent reads constrain
|
base*. A container says all three in one `PlaceDesc` argument. `Widget::draw`
|
||||||
layout validity; merely drawing in an extent does not require another widget
|
remains the only layout body. The invariants, the measured costs, the fuzzing
|
||||||
evaluation when it moves. Widgets also retain their original local frames,
|
method and the failed hypotheses are all in `docs/LAYOUT.md`; the app's pin at
|
||||||
so reuse composes positions in the same order as a fresh draw instead of
|
`32f6ad8` predates every part of it, so nothing here depends on it until the
|
||||||
recovering fractions by division. The experimental oracle requires exact
|
integration described in `docs/HANDOFF.md` lands.
|
||||||
pixel-region equality. `Widget::draw` remains the only layout body. The
|
|
||||||
prototype has significant container-update costs and is not yet adopted into
|
|
||||||
the pinned framework. A container's length query can evaluate the offered extent
|
|
||||||
without first placing the child's reported size; the parent's subsequent draw
|
|
||||||
assigns the final slot using the same `Widget::draw` body. A parent-selected slot
|
|
||||||
still honors a fixed child's alignment within that slot. Answer dependencies
|
|
||||||
are collected only from child sizes actually read; drawing dependencies
|
|
||||||
include all painted children. Root resize and local propagation check both
|
|
||||||
contracts. An unchanged answer with a wider validity range retains its old
|
|
||||||
contract: the parent's guarantee is still valid, and narrowing back must not
|
|
||||||
trigger layout. The same rule applies to retained drawing validity. A changed
|
|
||||||
declared size defers to its direct parent, then ordinary bottom-up propagation
|
|
||||||
stops when both contracts remain unchanged. Blanket ancestor invalidation is
|
|
||||||
unnecessary. The extent-relative child-frame API trial was removed: the major
|
|
||||||
size/resize cost came from conflating these dependencies, not from Pad and
|
|
||||||
Stack's conservative raw-placement reads. Cold layout and widespread content
|
|
||||||
changes still perform excess provisional work; that residual has not been
|
|
||||||
established as necessary for correctness.
|
|
||||||
|
|
||||||
**A `Span` leaves its children in the offered orthogonal region unless
|
**A `Span` leaves its children in the offered orthogonal region unless
|
||||||
explicitly compacted** (2026-09-12). It still reports the widest orthogonal
|
explicitly compacted** (2026-09-12). It still reports the widest orthogonal
|
||||||
|
|||||||
Reference in new issue
Block a user