Record #16 and #17 merged, and #18 opened for the position chain

This commit is contained in:
iris-ai committed 2026-09-14 03:16:29 -04:00
1 parent 9f039d6440
commit 836c222f0c
1 file changed
+27 -58
+27 -58
View File
@@ -5,54 +5,25 @@ Not a decisions log; delete it when the extraction is done.
## Where things stand
Canonical `main` is **`43ce8c7`** (#12, pointer routing). Fourteen slices are
in.
Canonical `main` is **`ca2b4b2`** (#17, the headless rig). Sixteen slices are
in, and #16's size work and #17's rig both merged on 2026-09-14.
- **#16** `split/16-draw-size`, worktree `/home/bob/repos/iris-pr16`, head
`4654756`. A widget sizes itself while drawing; `SizeCtx`, the `desired_*`
methods and the size cache are gone, and `on_resize(axis)` drives the
retained path. Two review rounds answered: `Widget::draw` returns the `Size`
(no `Painter::set_size`), both `place` methods are folded back into
`widget_within`/`draw_inner`, `Painter::size_hint` records the size
dependency `place` used to, `()` sizes itself rest so it is a gap, and a
resize now moves the drawing instead of wiping the tree. `tabs` (all five
tabs, and a replay that adds two images), `view` and `minimal` render
byte-identical to `upstream/main`.
- **#17** `split/17-headless-rig`, worktree `/home/bob/repos/iris-pr17`, head
`deb9c1b`. The compositor script, `rig-input`'s `replay-touch` and the
`.touch` parser, so a rendering claim no longer has to be checked from
ai-app's checkout. One review round answered: machine-specific prose out of
the comments, and `sway`/`swaymsg`/`grim` detected up front. Small, and
disjoint from #16.
- **#18** `split/18-position-chain`, worktree `/home/bob/repos/iris-pr18`, head
`8223a55`. LAYOUT.md §2's O(1) subtree movement: every active widget owns a
slot in `UiRenderState::moves`, `prelude.wgsl` walks the chain, and
`try_reuse`'s translation case writes one slot instead of remapping a
subtree's primitives. Measured at 100 primitive region writes to 0 on a span
of 20 rows five primitives deep. `Vec2` is `repr(align(8))` so a GPU struct
holding one matches WGSL without saying so itself.
**Waiting on the owner:** whether size dependencies should be recorded per
axis. They could be, but `mark_readers`' only trigger is `needs_redraw`, which
carries no axis, so the precision would be unusable until the trigger changes:
redraw a child first, compare its new size to the old, and propagate only to
readers of the axes that moved. Offered on #16 as its own slice, ahead of the
position chain.
Not in it, both wanting the above first: `Painter::set_child_offset` for a
container that moves its children as a group (the scrolling case), and
`LazySpan`. Built-in alignment is after those -- the owner reordered it
behind the chain on 2026-09-14 as the more important of the two.
**What the resize change taught, and why the chain moved up.** `mov` cannot
stretch a drawing out of a box with no relative extent. `UiScalar::within` maps
a part's `(rel, abs)` through a matrix whose determinant is the box's own rel
extent, so at zero extent it is singular: every fraction of that box collapses
onto one stored value, and nothing recovers which one. `lerp_inv` used to return
a plausible number there instead of saying so; it and `outside` now return
`Option`, and `Remap` is the checked region-level operation, so a drawing that
cannot be taken out of its box is redrawn rather than left at its old size.
`Remap` is explicitly a holding pattern: the owner accepted it on 2026-09-14
with *"I don't like remap much but I'll let it in for now until we get proper
region chaining"*. Delete it in the chaining slice rather than build on it --
holding each part's own region against its box means there is no composition to
invert and nothing for `Remap` to check.
Counted draws on a resize, against the old wipe-everything: 101 widgets of
padded rows went 101 -> 0, `tabs` 73 -> 0, and the `text` example 49 -> 48. The
last is the shape that does not benefit -- both paragraphs rewrap to a different
height and the relayout runs to the root. Holding each part's own region against
its box and resolving late is what removes those, so the position chain now sits
directly after built-in alignment.
The invariant the chain rests on: `resolved = region + resolve(slot)`, so
anything that rewrites a region owes that slot a zero. `mov` does it for the
subtree it rewrites and `draw_inner` for the widget it draws.
Check for a review before starting anything, and read the newest
`submitted_at` rather than the first result:
@@ -127,8 +98,8 @@ Other standing instructions from the owner:
## The next slice
**Built-in alignment** — see the first entry below. #16 and #17 are both
waiting on review, and the alignment slice is disjoint from either.
**`set_child_offset` and `LazySpan`**, finishing LAYOUT.md §2 on top of #18,
then built-in alignment.
The archive is not a patch here: it writes `Widget::draw` against
`painter.set_size`, which #16 replaced with a returned `Size`, and it writes
@@ -137,11 +108,14 @@ Recreate on today's `Len` and let the dp slice follow.
Still in the target, roughly in dependency order:
- **Built-in alignment, and probably size**, directly after #16 and before the
position chain: the chain is an optimisation, this is behaviour. A child of a
span is handed the full extent on the ortho axis, so `.width(rel(0.5))`
inside a `Dir::DOWN` span changes what the child reports and not the box it
gets. **Do not "fix" that by reading the child's ortho `size_hint`**: a `Pad`
- **The rest of the position chain** (LAYOUT.md §2), on top of #18:
`set_child_offset` for a container that moves its children as a group, and
`LazySpan`.
- **Built-in alignment, and probably size**, after the chain rather than
before it: the owner reordered the two on 2026-09-14. Reproduced in the
harness -- a child of a span is handed the full extent on the ortho axis, so
`.width(rel(0.5))` inside a `Dir::DOWN` span changes what the child reports
and not the box it gets. **Do not "fix" that by reading the child's ortho `size_hint`**: a `Pad`
between the `SetSize` and the span has no hint of its own, so the declared
width silently goes back to filling. It works only when nothing is in the
way. Alignment has to belong to the widget rather than be discovered through
@@ -156,12 +130,7 @@ Still in the target, roughly in dependency order:
Size is the harder half: a declared size beside the one `draw` returns is two
sources of truth for one thing, so settle what each means before building it.
- **The position chain** (LAYOUT.md §2): `UiData::move_offsets`, per-primitive
slot ids and a bounded chain walk in WGSL, so moving a subtree writes one
slot instead of every descendant's primitives.
It also has to make `OnResize::Translate` possible, which is the reason that
variant does nothing today. `ActiveData::region` is both the box a widget was
- **`OnResize::Translate`**, which still does nothing. `ActiveData::region` is both the box a widget was
given and the box its primitives occupy, and `mov` remaps out of it; keeping
a drawing at its old size while the box grows leaves the two disagreeing and
the next move stretches it. Found by rendering `tabs` against `main`, not by